PowerMachine commited on
Commit
fff13a9
·
verified ·
1 Parent(s): 512d009

Delete models/nlp.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. models/nlp.py +0 -654
models/nlp.py DELETED
@@ -1,654 +0,0 @@
1
- """nlp.py — Natural Language Processing (NLP) para CNN-BiGRU.
2
-
3
- Módulo de processamento de linguagem natural que fornece tarefas clássicas
4
- de NLP em cima do backbone cooperativo CNN-BiGRU:
5
-
6
- - Sequence Classification (sentiment, topic, NLI)
7
- - Token Classification (NER, POS tagging)
8
- - Span Detection (QA extractive)
9
- - Sentence Pair Classification (paraphrase, entailment)
10
- - Embeddings para retrieval semântico
11
-
12
- ==============================================================================
13
- ANÁLISE MATEMÁTICA E LÓGICA
14
- ==============================================================================
15
-
16
- Dado o output do CooperativeCNNBiGRU (representações fused [B, 256] ou
17
- sequências temporais [B, T, 128] por stream), cada tarefa NLP aplica uma
18
- cabeça específica:
19
-
20
- 1. Sequence Classification:
21
- h_cls = SelfAttentionSummary(seq) [B, D]
22
- logits = Linear(D, num_classes) [B, C]
23
- loss = CrossEntropy(logits, y)
24
-
25
- 2. Token Classification:
26
- logits = Linear(D, num_labels) [B, T, L]
27
- loss = CrossEntropy(logits, y) (ignore padding)
28
-
29
- 3. Span Detection (QA):
30
- start_logits = Linear(D, 1) [B, T]
31
- end_logits = Linear(D, 1) [B, T]
32
- loss = CE(start_logits, start_pos) + CE(end_logits, end_pos)
33
-
34
- 4. Sentence Pair Classification:
35
- Usa o dual-stream A/B do CooperativeCNNBiGRU diretamente
36
- (já que o modelo é naturalmente dual-stream).
37
- logits = Linear(D_fused, num_classes)
38
-
39
- 5. Embeddings para retrieval:
40
- h = mean_pool(seq) [B, D]
41
- h = normalize(h, p=2) [B, D]
42
- similarity = cosine(h_query, h_doc)
43
- loss = InfoNCE (contrastive)
44
-
45
- ==============================================================================
46
- INTEGRAÇÃO
47
- ==============================================================================
48
-
49
- - CooperativeCNNBiGRU (cnn_bigru.models.cooperative_bigru): backbone
50
- - SelfAttentionSummary: já incluído no cooperative_bigru
51
- - BBPETokenizer: tokenização
52
-
53
- Autor: CNN-BiGRU Project
54
- """
55
- from __future__ import annotations
56
-
57
- import logging
58
- import math
59
- from dataclasses import dataclass, field
60
- from typing import Dict, List, Optional, Tuple, Union
61
-
62
- import torch
63
- import torch.nn as nn
64
- import torch.nn.functional as F
65
-
66
- logger = logging.getLogger(__name__)
67
-
68
-
69
- # ============================================================================
70
- # Configuração
71
- # ============================================================================
72
-
73
- @dataclass
74
- class NLPConfig:
75
- """Configuração do módulo NLP."""
76
- # Dimensões (precisam bater com o backbone)
77
- embed_dim: int = 64 # embedding de tokens
78
- cnn_filters: int = 64
79
- gru_hidden: int = 64 # 64 por direção -> 128 por stream
80
- feat_per_stream: int = 128 # = 2 * gru_hidden
81
- feat_fused: int = 256 # = 2 * feat_per_stream
82
- n_heads: int = 4
83
- dropout: float = 0.1
84
- pad_idx: int = 1
85
- # Tarefas
86
- num_classes_seq: int = 3 # sequence classification
87
- num_labels_tok: int = 7 # token classification (NER-style)
88
- # Pooling
89
- pooling: str = "self_attn" # "self_attn" | "mean" | "max" | "cls"
90
- # Loss
91
- label_smoothing: float = 0.0
92
- # Device
93
- device: str = "cpu"
94
-
95
-
96
- # ============================================================================
97
- # Sequence Classification Head
98
- # ============================================================================
99
-
100
- class SequenceClassificationHead(nn.Module):
101
- """Cabeça para classificação de sequência inteira.
102
-
103
- Aplica pooling (self-attention, mean, max ou cls) sobre a sequência
104
- temporal e projeta para num_classes logits.
105
-
106
- Args:
107
- embed_dim: dimensão de entrada (e.g., 256 para fused, 128 por stream)
108
- num_classes: número de classes de saída
109
- dropout: prob de dropout
110
- pooling: estratégia de pooling
111
- n_heads: cabeças para self-attention pooling
112
- """
113
-
114
- def __init__(
115
- self,
116
- embed_dim: int,
117
- num_classes: int,
118
- dropout: float = 0.1,
119
- pooling: str = "self_attn",
120
- n_heads: int = 4,
121
- ):
122
- super().__init__()
123
- self.embed_dim = embed_dim
124
- self.pooling = pooling
125
- self.num_classes = num_classes
126
-
127
- # Pooling
128
- if pooling == "self_attn":
129
- # Self-attention pooling simplificado (sem CLS token)
130
- self.attn_query = nn.Linear(embed_dim, 1)
131
- elif pooling == "cls":
132
- # CLS token learnable
133
- self.cls = nn.Parameter(torch.zeros(1, 1, embed_dim))
134
- nn.init.normal_(self.cls, std=0.02)
135
- # mean / max: não precisa de params
136
-
137
- self.dropout = nn.Dropout(dropout)
138
- self.classifier = nn.Linear(embed_dim, num_classes)
139
- nn.init.xavier_uniform_(self.classifier.weight)
140
- nn.init.zeros_(self.classifier.bias)
141
-
142
- def forward(
143
- self,
144
- seq: torch.Tensor,
145
- mask: Optional[torch.Tensor] = None,
146
- ) -> Tuple[torch.Tensor, torch.Tensor]:
147
- """
148
- Args:
149
- seq: [B, T, D] sequência de hidden states
150
- mask: [B, T] máscara de padding (1=real, 0=pad)
151
-
152
- Returns:
153
- logits: [B, C]
154
- pooled: [B, D] representação pooled
155
- """
156
- if seq.dim() == 2:
157
- # [B, D] — já é pooled (e.g., fused)
158
- pooled = seq
159
- else:
160
- B, T, D = seq.shape
161
-
162
- if self.pooling == "self_attn":
163
- # Attention pooling: softmax over T
164
- scores = self.attn_query(seq).squeeze(-1) # [B, T]
165
- if mask is not None:
166
- scores = scores.masked_fill(mask == 0, -1e9)
167
- # Lidar com linhas totalmente mascaradas
168
- all_neg = (scores <= -1e9).all(dim=-1, keepdim=True)
169
- scores = torch.where(all_neg, torch.zeros_like(scores), scores)
170
- weights = F.softmax(scores, dim=-1).unsqueeze(-1) # [B, T, 1]
171
- pooled = (seq * weights).sum(dim=1) # [B, D]
172
-
173
- elif self.pooling == "mean":
174
- if mask is not None:
175
- m = mask.unsqueeze(-1).float()
176
- pooled = (seq * m).sum(dim=1) / m.sum(dim=1).clamp(min=1.0)
177
- else:
178
- pooled = seq.mean(dim=1)
179
-
180
- elif self.pooling == "max":
181
- if mask is not None:
182
- seq_masked = seq.masked_fill(mask.unsqueeze(-1) == 0, -1e9)
183
- pooled, _ = seq_masked.max(dim=1)
184
- # Lidar com sequências totalmente mascaradas (retorna 0)
185
- all_neg = (pooled <= -1e9).all(dim=-1, keepdim=True)
186
- pooled = torch.where(all_neg, torch.zeros_like(pooled), pooled)
187
- else:
188
- pooled, _ = seq.max(dim=1)
189
-
190
- elif self.pooling == "cls":
191
- cls = self.cls.expand(B, -1, -1) # [B, 1, D]
192
- seq_ext = torch.cat([cls, seq], dim=1) # [B, T+1, D]
193
- pooled = seq_ext[:, 0, :] # pega CLS
194
-
195
- else:
196
- raise ValueError(f"pooling desconhecido: {self.pooling}")
197
-
198
- pooled = self.dropout(pooled)
199
- logits = self.classifier(pooled)
200
- return logits, pooled
201
-
202
-
203
- # ============================================================================
204
- # Token Classification Head
205
- # ============================================================================
206
-
207
- class TokenClassificationHead(nn.Module):
208
- """Cabeça para classificação de tokens (NER, POS tagging).
209
-
210
- Aplica Linear(D, num_labels) sobre cada posição da sequência.
211
-
212
- Args:
213
- embed_dim: dimensão de entrada
214
- num_labels: número de rótulos (e.g., 7 para BIO tagging)
215
- dropout: prob de dropout
216
- """
217
-
218
- def __init__(self, embed_dim: int, num_labels: int, dropout: float = 0.1):
219
- super().__init__()
220
- self.embed_dim = embed_dim
221
- self.num_labels = num_labels
222
- self.dropout = nn.Dropout(dropout)
223
- self.classifier = nn.Linear(embed_dim, num_labels)
224
- nn.init.xavier_uniform_(self.classifier.weight)
225
- nn.init.zeros_(self.classifier.bias)
226
-
227
- def forward(self, seq: torch.Tensor) -> torch.Tensor:
228
- """
229
- Args:
230
- seq: [B, T, D] sequência de hidden states
231
- Returns:
232
- logits: [B, T, L]
233
- """
234
- return self.classifier(self.dropout(seq))
235
-
236
-
237
- # ============================================================================
238
- # Span Detection Head (QA Extractive)
239
- # ============================================================================
240
-
241
- class SpanDetectionHead(nn.Module):
242
- """Cabeça para detecção de span (QA extractive).
243
-
244
- Prediz posições de início e fim do span de resposta.
245
-
246
- Args:
247
- embed_dim: dimensão de entrada
248
- dropout: prob de dropout
249
- """
250
-
251
- def __init__(self, embed_dim: int, dropout: float = 0.1):
252
- super().__init__()
253
- self.embed_dim = embed_dim
254
- self.dropout = nn.Dropout(dropout)
255
- self.start_classifier = nn.Linear(embed_dim, 1)
256
- self.end_classifier = nn.Linear(embed_dim, 1)
257
- for layer in (self.start_classifier, self.end_classifier):
258
- nn.init.xavier_uniform_(layer.weight)
259
- nn.init.zeros_(layer.bias)
260
-
261
- def forward(self, seq: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
262
- """
263
- Args:
264
- seq: [B, T, D]
265
- Returns:
266
- start_logits: [B, T]
267
- end_logits: [B, T]
268
- """
269
- seq = self.dropout(seq)
270
- start_logits = self.start_classifier(seq).squeeze(-1) # [B, T]
271
- end_logits = self.end_classifier(seq).squeeze(-1)
272
- return start_logits, end_logits
273
-
274
-
275
- # ============================================================================
276
- # Embedding Head (para retrieval semântico)
277
- # ============================================================================
278
-
279
- class EmbeddingHead(nn.Module):
280
- """Cabeça para gerar embeddings normalizados (retrieval semântico).
281
-
282
- Aplica mean-pool com máscara + LayerNorm + normalização L2.
283
-
284
- Args:
285
- embed_dim: dimensão de entrada e saída
286
- use_layer_norm: se True, aplica LayerNorm antes da normalização L2
287
- """
288
-
289
- def __init__(self, embed_dim: int, use_layer_norm: bool = True):
290
- super().__init__()
291
- self.embed_dim = embed_dim
292
- self.use_layer_norm = use_layer_norm
293
- if use_layer_norm:
294
- self.ln = nn.LayerNorm(embed_dim)
295
-
296
- def forward(
297
- self,
298
- seq: torch.Tensor,
299
- mask: Optional[torch.Tensor] = None,
300
- normalize: bool = True,
301
- ) -> torch.Tensor:
302
- """
303
- Args:
304
- seq: [B, T, D]
305
- mask: [B, T] (1=real, 0=pad)
306
- normalize: se True, normaliza com L2
307
- Returns:
308
- embedding: [B, D]
309
- """
310
- if mask is not None:
311
- m = mask.unsqueeze(-1).float()
312
- pooled = (seq * m).sum(dim=1) / m.sum(dim=1).clamp(min=1.0)
313
- else:
314
- pooled = seq.mean(dim=1)
315
-
316
- if self.use_layer_norm:
317
- pooled = self.ln(pooled)
318
-
319
- if normalize:
320
- pooled = F.normalize(pooled, p=2, dim=-1)
321
-
322
- return pooled
323
-
324
-
325
- # ============================================================================
326
- # NLP Module (wrapper integrando todas as cabeças)
327
- # ============================================================================
328
-
329
- class NLPModule(nn.Module):
330
- """Módulo NLP integrando múltiplas tarefas no backbone CNN-BiGRU.
331
-
332
- Args:
333
- config: configuração NLP
334
- backbone: modelo CooperativeCNNBiGRU (opcional — se None, cria um novo)
335
- """
336
-
337
- def __init__(
338
- self,
339
- config: NLPConfig,
340
- backbone: Optional[nn.Module] = None,
341
- ):
342
- super().__init__()
343
- self.config = config
344
-
345
- # Backbone (cria se não fornecido)
346
- if backbone is None:
347
- from .cooperative_bigru import CooperativeCNNBiGRU
348
- backbone = CooperativeCNNBiGRU(
349
- vocab_size=32000, # placeholder, sobrescrito via set_vocab_size
350
- embedding_dim=config.embed_dim,
351
- cnn_filters=config.cnn_filters,
352
- gru_hidden=config.gru_hidden,
353
- n_heads=config.n_heads,
354
- dropout=config.dropout,
355
- pad_idx=config.pad_idx,
356
- )
357
- self.backbone = backbone
358
-
359
- # Cabeças (uma por tarefa)
360
- self.seq_head = SequenceClassificationHead(
361
- embed_dim=config.feat_fused,
362
- num_classes=config.num_classes_seq,
363
- dropout=config.dropout,
364
- pooling=config.pooling,
365
- n_heads=config.n_heads,
366
- )
367
- self.tok_head = TokenClassificationHead(
368
- embed_dim=config.feat_per_stream, # por stream (usa seq_A ou seq_B)
369
- num_labels=config.num_labels_tok,
370
- dropout=config.dropout,
371
- )
372
- self.span_head = SpanDetectionHead(
373
- embed_dim=config.feat_per_stream,
374
- dropout=config.dropout,
375
- )
376
- self.embed_head = EmbeddingHead(
377
- embed_dim=config.feat_per_stream,
378
- use_layer_norm=True,
379
- )
380
-
381
- # ----------------------------------------------------------------------
382
- # Tarefas
383
- # ----------------------------------------------------------------------
384
-
385
- def sequence_classification(
386
- self,
387
- input_ids_a: torch.Tensor,
388
- input_ids_b: torch.Tensor,
389
- ) -> Dict[str, torch.Tensor]:
390
- """Classificação de sequência (usando representação fused).
391
-
392
- Args:
393
- input_ids_a, input_ids_b: [B, T]
394
-
395
- Returns:
396
- dict com logits [B, C] e pooled [B, D]
397
- """
398
- out = self.backbone(input_ids_a, input_ids_b, return_sequences=False)
399
- fused = out["fused"] # [B, 256]
400
- logits, pooled = self.seq_head(fused, mask=None)
401
- return {"logits": logits, "pooled": pooled, "fused": fused}
402
-
403
- def token_classification(
404
- self,
405
- input_ids_a: torch.Tensor,
406
- input_ids_b: torch.Tensor,
407
- stream: str = "A",
408
- ) -> torch.Tensor:
409
- """Classificação de tokens (NER/POS).
410
-
411
- Args:
412
- input_ids_a, input_ids_b: [B, T]
413
- stream: "A" ou "B" — qual stream usar para token classification
414
-
415
- Returns:
416
- logits: [B, T, L]
417
- """
418
- out = self.backbone(input_ids_a, input_ids_b, return_sequences=True)
419
- seq = out.get("seq_A") if stream == "A" else out.get("seq_B")
420
- if seq is None:
421
- # Fallback: usar fused (broadcast para T)
422
- fused = out["fused"] # [B, 256]
423
- T = input_ids_a.size(1)
424
- seq = fused.unsqueeze(1).expand(-1, T, -1) # [B, T, 256]
425
- # Projeta para feat_per_stream se necessário
426
- if fused.size(-1) != self.config.feat_per_stream:
427
- if not hasattr(self, "_proj_tok"):
428
- self._proj_tok = nn.Linear(
429
- fused.size(-1), self.config.feat_per_stream, bias=False
430
- ).to(fused.device)
431
- seq = self._proj_tok(seq)
432
- return self.tok_head(seq)
433
-
434
- def span_detection(
435
- self,
436
- input_ids_a: torch.Tensor,
437
- input_ids_b: torch.Tensor,
438
- stream: str = "A",
439
- ) -> Tuple[torch.Tensor, torch.Tensor]:
440
- """Detecção de span (QA extractive).
441
-
442
- Returns:
443
- (start_logits, end_logits) — ambos [B, T]
444
- """
445
- out = self.backbone(input_ids_a, input_ids_b, return_sequences=True)
446
- seq = out.get("seq_A") if stream == "A" else out.get("seq_B")
447
- if seq is None:
448
- fused = out["fused"]
449
- T = input_ids_a.size(1)
450
- seq = fused.unsqueeze(1).expand(-1, T, -1)
451
- if fused.size(-1) != self.config.feat_per_stream:
452
- if not hasattr(self, "_proj_span"):
453
- self._proj_span = nn.Linear(
454
- fused.size(-1), self.config.feat_per_stream, bias=False
455
- ).to(fused.device)
456
- seq = self._proj_span(seq)
457
- return self.span_head(seq)
458
-
459
- def embed(
460
- self,
461
- input_ids_a: torch.Tensor,
462
- input_ids_b: torch.Tensor,
463
- stream: str = "A",
464
- normalize: bool = True,
465
- ) -> torch.Tensor:
466
- """Gera embedding normalizado para retrieval semântico.
467
-
468
- Returns:
469
- embedding: [B, D]
470
- """
471
- out = self.backbone(input_ids_a, input_ids_b, return_sequences=True)
472
- seq = out.get("seq_A") if stream == "A" else out.get("seq_B")
473
- mask = out.get("mask_A") if stream == "A" else out.get("mask_B")
474
- if seq is None:
475
- # Fallback: fused
476
- return self.embed_head(
477
- out["fused"].unsqueeze(1), mask=None, normalize=normalize
478
- )
479
- if seq.size(-1) != self.config.feat_per_stream:
480
- if not hasattr(self, "_proj_emb"):
481
- self._proj_emb = nn.Linear(
482
- seq.size(-1), self.config.feat_per_stream, bias=False
483
- ).to(seq.device)
484
- seq = self._proj_emb(seq)
485
- return self.embed_head(seq, mask=mask, normalize=normalize)
486
-
487
- # ----------------------------------------------------------------------
488
- # Loss functions para cada tarefa
489
- # ----------------------------------------------------------------------
490
-
491
- def sequence_classification_loss(
492
- self,
493
- input_ids_a: torch.Tensor,
494
- input_ids_b: torch.Tensor,
495
- labels: torch.Tensor,
496
- ) -> torch.Tensor:
497
- """Computa CE loss para sequence classification."""
498
- out = self.sequence_classification(input_ids_a, input_ids_b)
499
- return F.cross_entropy(
500
- out["logits"], labels,
501
- label_smoothing=self.config.label_smoothing,
502
- )
503
-
504
- def token_classification_loss(
505
- self,
506
- input_ids_a: torch.Tensor,
507
- input_ids_b: torch.Tensor,
508
- labels: torch.Tensor,
509
- stream: str = "A",
510
- ) -> torch.Tensor:
511
- """Computa CE loss para token classification.
512
-
513
- Args:
514
- labels: [B, T] rótulos por token (use -100 ou pad_idx para ignorar)
515
- """
516
- logits = self.token_classification(input_ids_a, input_ids_b, stream=stream)
517
- # Ignora posições com label = -100 ou mask = 0
518
- mask = (input_ids_a != self.config.pad_idx).float()
519
- ignore_mask = (labels == -100) | (labels == self.config.pad_idx)
520
- labels_clamped = labels.clone()
521
- labels_clamped[ignore_mask] = -100
522
- return F.cross_entropy(
523
- logits.view(-1, self.config.num_labels_tok),
524
- labels_clamped.view(-1),
525
- ignore_index=-100,
526
- )
527
-
528
- def span_detection_loss(
529
- self,
530
- input_ids_a: torch.Tensor,
531
- input_ids_b: torch.Tensor,
532
- start_positions: torch.Tensor,
533
- end_positions: torch.Tensor,
534
- stream: str = "A",
535
- ) -> torch.Tensor:
536
- """Computa loss para span detection.
537
-
538
- Args:
539
- start_positions, end_positions: [B] índices do span (long)
540
- """
541
- start_logits, end_logits = self.span_detection(
542
- input_ids_a, input_ids_b, stream=stream
543
- )
544
- # Ignora posições fora do span (mask)
545
- loss_start = F.cross_entropy(start_logits, start_positions, ignore_index=-100)
546
- loss_end = F.cross_entropy(end_logits, end_positions, ignore_index=-100)
547
- return (loss_start + loss_end) / 2.0
548
-
549
- def contrastive_loss(
550
- self,
551
- query_ids_a: torch.Tensor,
552
- query_ids_b: torch.Tensor,
553
- pos_ids_a: torch.Tensor,
554
- pos_ids_b: torch.Tensor,
555
- neg_ids_a: torch.Tensor,
556
- neg_ids_b: torch.Tensor,
557
- temperature: float = 0.07,
558
- ) -> torch.Tensor:
559
- """InfoNCE loss para retrieval semântico.
560
-
561
- Args:
562
- query, pos, neg: tensores [B, T] para query, positivo e negativo
563
- """
564
- q = self.embed(query_ids_a, query_ids_b, normalize=True)
565
- p = self.embed(pos_ids_a, pos_ids_b, normalize=True)
566
- n = self.embed(neg_ids_a, neg_ids_b, normalize=True)
567
-
568
- # Similaridade coseno (já normalizado -> produto escalar)
569
- sim_pos = (q * p).sum(dim=-1, keepdim=True) / temperature # [B, 1]
570
- sim_neg = (q * n).sum(dim=-1, keepdim=True) / temperature # [B, 1]
571
-
572
- # InfoNCE: maximizar sim_pos - sim_neg
573
- logits = torch.cat([sim_pos, sim_neg], dim=-1) # [B, 2]
574
- labels = torch.zeros(q.size(0), dtype=torch.long, device=q.device) # 0 = pos
575
- return F.cross_entropy(logits, labels)
576
-
577
-
578
- # ============================================================================
579
- # Self-test
580
- # ============================================================================
581
-
582
- def _self_test():
583
- """Teste rápido do módulo NLP."""
584
- torch.manual_seed(42)
585
- from .cooperative_bigru import CooperativeCNNBiGRU
586
- config = NLPConfig(
587
- embed_dim=32,
588
- cnn_filters=32,
589
- gru_hidden=32,
590
- feat_per_stream=64,
591
- feat_fused=128,
592
- n_heads=4,
593
- dropout=0.1,
594
- pad_idx=1,
595
- num_classes_seq=3,
596
- num_labels_tok=5,
597
- pooling="self_attn",
598
- )
599
- backbone = CooperativeCNNBiGRU(
600
- vocab_size=100,
601
- embedding_dim=config.embed_dim,
602
- cnn_filters=config.cnn_filters,
603
- gru_hidden=config.gru_hidden,
604
- n_heads=config.n_heads,
605
- dropout=config.dropout,
606
- pad_idx=config.pad_idx,
607
- )
608
- nlp = NLPModule(config, backbone=backbone)
609
-
610
- B, T = 2, 8
611
- ids_a = torch.randint(2, 100, (B, T))
612
- ids_b = torch.randint(2, 100, (B, T))
613
-
614
- # Sequence classification
615
- out = nlp.sequence_classification(ids_a, ids_b)
616
- print(f"Seq cls: logits {out['logits'].shape}, pooled {out['pooled'].shape}")
617
- labels = torch.randint(0, 3, (B,))
618
- loss_seq = nlp.sequence_classification_loss(ids_a, ids_b, labels)
619
- print(f" loss={loss_seq.item():.4f}")
620
-
621
- # Token classification
622
- tok_logits = nlp.token_classification(ids_a, ids_b)
623
- print(f"Tok cls: {tok_logits.shape}")
624
- tok_labels = torch.randint(0, 5, (B, T))
625
- loss_tok = nlp.token_classification_loss(ids_a, ids_b, tok_labels)
626
- print(f" loss={loss_tok.item():.4f}")
627
-
628
- # Span detection
629
- start_logits, end_logits = nlp.span_detection(ids_a, ids_b)
630
- print(f"Span: start {start_logits.shape}, end {end_logits.shape}")
631
- sp = torch.randint(0, T, (B,))
632
- ep = torch.randint(0, T, (B,))
633
- loss_span = nlp.span_detection_loss(ids_a, ids_b, sp, ep)
634
- print(f" loss={loss_span.item():.4f}")
635
-
636
- # Embedding
637
- emb = nlp.embed(ids_a, ids_b)
638
- print(f"Embed: {emb.shape}, norm: {emb.norm(dim=-1).mean().item():.4f}")
639
-
640
- print("NLP self-test OK")
641
-
642
-
643
- if __name__ == "__main__":
644
- _self_test()
645
-
646
-
647
- __all__ = [
648
- "NLPConfig",
649
- "SequenceClassificationHead",
650
- "TokenClassificationHead",
651
- "SpanDetectionHead",
652
- "EmbeddingHead",
653
- "NLPModule",
654
- ]