UMCU commited on
Commit
a9416ca
·
verified ·
1 Parent(s): b2d49ac

Update modeling.py

Browse files
Files changed (1) hide show
  1. modeling.py +4 -773
modeling.py CHANGED
@@ -3,7 +3,6 @@ from typing import Dict, List, Optional, Tuple, Union
3
 
4
  import torch
5
  import torch.nn as nn
6
- from torchcrf import CRF
7
  from transformers import PretrainedConfig, PreTrainedModel
8
  from transformers.modeling_outputs import TokenClassifierOutput
9
 
@@ -16,8 +15,6 @@ except Exception:
16
  EuroBertModel = None
17
  print("COULD NOT IMPORT EUROBERT MODEL")
18
 
19
- # Large negative number for masking impossible transitions
20
- LARGE_NEGATIVE_NUMBER = -1e9
21
  NUM_PER_LAYER = 16
22
 
23
 
@@ -64,727 +61,11 @@ def _build_backbone_from_config(config):
64
  return backbone, backbone_name
65
 
66
 
67
- class MultiHeadCRFConfig(PretrainedConfig):
68
- """
69
- Configuration class for Multi-Head CRF models.
70
- """
71
-
72
- model_type = "multihead-crf-tagger"
73
-
74
- def __init__(
75
- self,
76
- entity_types: Optional[List[str]] = None,
77
- number_of_layers_per_head: int = 1,
78
- crf_reduction: str = "mean",
79
- freeze_backbone: bool = False,
80
- num_frozen_encoders: int = 0,
81
- classifier_dropout: float = 0.1,
82
- classifier_hidden_layers: Optional[Tuple] = None,
83
- class_weights: Optional[List[float]] = None,
84
- backbone_model_name: Optional[str] = None,
85
- **kwargs,
86
- ):
87
- self.entity_types = entity_types or []
88
- self.number_of_layers_per_head = number_of_layers_per_head
89
- self.crf_reduction = crf_reduction
90
- self.freeze_backbone = freeze_backbone
91
- self.num_frozen_encoders = num_frozen_encoders
92
- self.classifier_dropout = classifier_dropout
93
- self.classifier_hidden_layers = classifier_hidden_layers
94
- self.class_weights = class_weights
95
- self.backbone_model_name = backbone_model_name
96
- super().__init__(**kwargs)
97
-
98
-
99
- class MultiHeadCRF(nn.Module):
100
- """
101
- Custom CRF implementation with BIO transition masking.
102
- """
103
-
104
- def __init__(self, num_tags: int, batch_first: bool = True) -> None:
105
- if num_tags <= 0:
106
- raise ValueError(f"invalid number of tags: {num_tags}")
107
- super().__init__()
108
- self.num_tags = num_tags
109
- self.batch_first = batch_first
110
- self.start_transitions = nn.Parameter(torch.empty(num_tags))
111
- self.end_transitions = nn.Parameter(torch.empty(num_tags))
112
- self.transitions = nn.Parameter(torch.empty(num_tags, num_tags))
113
-
114
- self.reset_parameters()
115
- self.mask_impossible_transitions()
116
-
117
- def reset_parameters(self) -> None:
118
- nn.init.uniform_(self.start_transitions, -0.1, 0.1)
119
- nn.init.uniform_(self.end_transitions, -0.1, 0.1)
120
- nn.init.uniform_(self.transitions, -0.1, 0.1)
121
-
122
- def mask_impossible_transitions(self) -> None:
123
- with torch.no_grad():
124
- if self.num_tags > 2:
125
- self.start_transitions[2] = LARGE_NEGATIVE_NUMBER
126
- self.transitions[0][2] = LARGE_NEGATIVE_NUMBER
127
-
128
- if self.num_tags > 3:
129
- self.start_transitions[3] = LARGE_NEGATIVE_NUMBER
130
- for i in range(3):
131
- self.transitions[i][3] = LARGE_NEGATIVE_NUMBER
132
- for i in range(3):
133
- self.transitions[3][i] = LARGE_NEGATIVE_NUMBER
134
-
135
- def __repr__(self) -> str:
136
- return f"{self.__class__.__name__}(num_tags={self.num_tags})"
137
-
138
- def forward(
139
- self,
140
- emissions: torch.Tensor,
141
- tags: torch.Tensor,
142
- mask: Optional[torch.Tensor] = None,
143
- reduction: str = "mean",
144
- ) -> torch.Tensor:
145
- self._validate(emissions, tags=tags, mask=mask)
146
- if reduction not in ("none", "sum", "mean", "token_mean"):
147
- raise ValueError(f"invalid reduction: {reduction}")
148
- if mask is None:
149
- mask = torch.ones_like(tags, dtype=torch.uint8)
150
-
151
- device = emissions.device
152
- tags = tags.to(device)
153
- mask = mask.to(device)
154
-
155
- if self.batch_first:
156
- emissions = emissions.transpose(0, 1)
157
- tags = tags.transpose(0, 1)
158
- mask = mask.transpose(0, 1)
159
-
160
- numerator = self._compute_score(emissions, tags, mask)
161
- denominator = self._compute_normalizer(emissions, mask)
162
- llh = numerator - denominator
163
- nllh = -llh
164
-
165
- if reduction == "none":
166
- return nllh
167
- if reduction == "sum":
168
- return nllh.sum()
169
- if reduction == "mean":
170
- return nllh.mean()
171
- return nllh.sum() / mask.type_as(emissions).sum()
172
-
173
- def decode(
174
- self, emissions: torch.Tensor, mask: Optional[torch.Tensor] = None
175
- ) -> List[List[int]]:
176
- self._validate(emissions, mask=mask)
177
- if mask is None:
178
- mask = emissions.new_ones(emissions.shape[:2], dtype=torch.uint8)
179
-
180
- if self.batch_first:
181
- emissions = emissions.transpose(0, 1)
182
- mask = mask.transpose(0, 1)
183
-
184
- return self._viterbi_decode(emissions, mask)
185
-
186
- def _validate(
187
- self,
188
- emissions: torch.Tensor,
189
- tags: Optional[torch.Tensor] = None,
190
- mask: Optional[torch.Tensor] = None,
191
- ) -> None:
192
- if emissions.dim() != 3:
193
- raise ValueError(
194
- f"emissions must have dimension of 3, got {emissions.dim()}"
195
- )
196
- if emissions.size(2) != self.num_tags:
197
- raise ValueError(
198
- f"expected last dimension of emissions is {self.num_tags}, "
199
- f"got {emissions.size(2)}"
200
- )
201
-
202
- if tags is not None and emissions.shape[:2] != tags.shape:
203
- raise ValueError(
204
- "the first two dimensions of emissions and tags must match, "
205
- f"got {tuple(emissions.shape[:2])} and {tuple(tags.shape)}"
206
- )
207
-
208
- if mask is not None:
209
- if emissions.shape[:2] != mask.shape:
210
- raise ValueError(
211
- "the first two dimensions of emissions and mask must match, "
212
- f"got {tuple(emissions.shape[:2])} and {tuple(mask.shape)}"
213
- )
214
- no_empty_seq = not self.batch_first and mask[0].all()
215
- no_empty_seq_bf = self.batch_first and mask[:, 0].all()
216
- if not no_empty_seq and not no_empty_seq_bf:
217
- raise ValueError("mask of the first timestep must all be on")
218
-
219
- def _compute_score(
220
- self, emissions: torch.Tensor, tags: torch.Tensor, mask: torch.Tensor
221
- ) -> torch.Tensor:
222
- assert emissions.dim() == 3 and tags.dim() == 2
223
- assert emissions.shape[:2] == tags.shape
224
- assert emissions.size(2) == self.num_tags
225
- assert mask.shape == tags.shape
226
- assert mask[0].all()
227
-
228
- device = emissions.device
229
- tags = tags.to(device)
230
- mask = mask.to(device)
231
-
232
- seq_length, batch_size = tags.shape
233
- mask = mask.type_as(emissions)
234
-
235
- batch_indices = torch.arange(batch_size, device=device)
236
- score = self.start_transitions[tags[0]]
237
- score += emissions[0, batch_indices, tags[0]]
238
-
239
- for i in range(1, seq_length):
240
- score += self.transitions[tags[i - 1], tags[i]] * mask[i]
241
- score += emissions[i, batch_indices, tags[i]] * mask[i]
242
-
243
- seq_ends = mask.long().sum(dim=0) - 1
244
- last_tags = tags[seq_ends, batch_indices]
245
- score += self.end_transitions[last_tags]
246
-
247
- return score
248
-
249
- def _compute_normalizer(
250
- self, emissions: torch.Tensor, mask: torch.Tensor
251
- ) -> torch.Tensor:
252
- assert emissions.dim() == 3 and mask.dim() == 2
253
- assert emissions.shape[:2] == mask.shape
254
- assert emissions.size(2) == self.num_tags
255
- assert mask[0].all()
256
-
257
- seq_length = emissions.size(0)
258
- score = self.start_transitions + emissions[0]
259
-
260
- for i in range(1, seq_length):
261
- broadcast_score = score.unsqueeze(2)
262
- broadcast_emissions = emissions[i].unsqueeze(1)
263
- next_score = broadcast_score + self.transitions + broadcast_emissions
264
- next_score = torch.logsumexp(next_score, dim=1)
265
- score = torch.where(mask[i].unsqueeze(1).bool(), next_score, score)
266
-
267
- score += self.end_transitions
268
- return torch.logsumexp(score, dim=1)
269
-
270
- def _viterbi_decode(
271
- self, emissions: torch.Tensor, mask: torch.Tensor
272
- ) -> List[List[int]]:
273
- assert emissions.dim() == 3 and mask.dim() == 2
274
- assert emissions.shape[:2] == mask.shape
275
- assert emissions.size(2) == self.num_tags
276
- assert mask[0].all()
277
-
278
- seq_length, batch_size = mask.shape
279
- score = self.start_transitions + emissions[0]
280
- history = []
281
-
282
- for i in range(1, seq_length):
283
- broadcast_score = score.unsqueeze(2)
284
- broadcast_emission = emissions[i].unsqueeze(1)
285
- next_score = broadcast_score + self.transitions + broadcast_emission
286
- next_score, indices = next_score.max(dim=1)
287
- score = torch.where(mask[i].unsqueeze(1).bool(), next_score, score)
288
- history.append(indices)
289
-
290
- score += self.end_transitions
291
-
292
- seq_ends = mask.long().sum(dim=0) - 1
293
- best_tags_list = []
294
-
295
- for idx in range(batch_size):
296
- _, best_last_tag = score[idx].max(dim=0)
297
- best_tags = [best_last_tag.item()]
298
-
299
- for hist in reversed(history[: seq_ends[idx]]):
300
- best_last_tag = hist[idx][best_tags[-1]]
301
- best_tags.append(best_last_tag.item())
302
-
303
- best_tags.reverse()
304
- best_tags_list.append(best_tags)
305
-
306
- return best_tags_list
307
-
308
-
309
- class TokenClassificationModelCRF(PreTrainedModel):
310
- """
311
- Custom token classification model with CRF layer and configurable classifier head.
312
- """
313
-
314
- def __init__(
315
- self,
316
- config,
317
- base_model=None,
318
- freeze_backbone=False,
319
- classifier_hidden_layers=None,
320
- classifier_dropout=0.1,
321
- ):
322
- super().__init__(config)
323
- self.config = config
324
- self.num_labels = config.num_labels
325
-
326
- if base_model is None:
327
- self.roberta, backbone_name = _build_backbone_from_config(config)
328
- else:
329
- if hasattr(base_model, "roberta"):
330
- self.roberta = base_model.roberta
331
- else:
332
- self.roberta = base_model
333
- backbone_name = (
334
- getattr(getattr(self.roberta, "config", None), "_name_or_path", None)
335
- or getattr(config, "backbone_model_name", None)
336
- or getattr(config, "_name_or_path", None)
337
- )
338
- if getattr(config, "backbone_model_name", None) is None:
339
- config.backbone_model_name = backbone_name
340
-
341
- self.lm_output_size = self.roberta.config.hidden_size
342
-
343
- self.config.freeze_backbone = freeze_backbone
344
- self.config.classifier_hidden_layers = classifier_hidden_layers
345
- self.config.classifier_dropout = classifier_dropout
346
-
347
- if freeze_backbone:
348
- print("+" * 30, "\n\n", "Freezing backbone...", "+" * 30, "\n\n")
349
- for param in self.roberta.parameters():
350
- param.requires_grad = False
351
- self.roberta.eval()
352
- else:
353
- print("+" * 30, "\n\n", "NOT Freezing backbone...", "+" * 30, "\n\n")
354
- self.roberta.train(True)
355
-
356
- self.dropout = nn.Dropout(getattr(config, "hidden_dropout_prob", 0.1))
357
- self.crf = CRF(self.num_labels, batch_first=True)
358
-
359
- self._build_classifier_head(classifier_hidden_layers, classifier_dropout)
360
- self.post_init()
361
-
362
- def _build_classifier_head(self, hidden_layers, dropout_rate):
363
- layers = []
364
- input_size = self.lm_output_size
365
-
366
- if not hidden_layers:
367
- self.classifier = nn.Sequential(
368
- nn.Dropout(dropout_rate), nn.Linear(input_size, self.num_labels)
369
- )
370
- return
371
-
372
- for hidden_size in hidden_layers:
373
- layers.append(nn.Linear(input_size, hidden_size))
374
- layers.append(nn.ReLU())
375
- layers.append(nn.Dropout(dropout_rate))
376
- input_size = hidden_size
377
-
378
- layers.append(nn.Linear(input_size, self.num_labels))
379
- self.classifier = nn.Sequential(*layers)
380
-
381
- def forward(
382
- self,
383
- input_ids: Optional[torch.LongTensor] = None,
384
- attention_mask: Optional[torch.FloatTensor] = None,
385
- token_type_ids: Optional[torch.LongTensor] = None,
386
- position_ids: Optional[torch.LongTensor] = None,
387
- head_mask: Optional[torch.FloatTensor] = None,
388
- inputs_embeds: Optional[torch.FloatTensor] = None,
389
- labels: Optional[torch.LongTensor] = None,
390
- output_attentions: Optional[bool] = None,
391
- output_hidden_states: Optional[bool] = None,
392
- return_dict: Optional[bool] = None,
393
- **kwargs,
394
- ) -> Union[Tuple[torch.Tensor], TokenClassifierOutput]:
395
- return_dict = (
396
- return_dict if return_dict is not None else self.config.use_return_dict
397
- )
398
-
399
- try:
400
- outputs = self.roberta(
401
- input_ids,
402
- attention_mask=attention_mask,
403
- token_type_ids=token_type_ids,
404
- position_ids=position_ids,
405
- head_mask=head_mask,
406
- inputs_embeds=inputs_embeds,
407
- output_attentions=output_attentions,
408
- output_hidden_states=output_hidden_states,
409
- return_dict=return_dict,
410
- )
411
- except TypeError:
412
- outputs = self.roberta(
413
- input_ids,
414
- attention_mask=attention_mask,
415
- position_ids=position_ids,
416
- inputs_embeds=inputs_embeds,
417
- output_attentions=output_attentions,
418
- output_hidden_states=output_hidden_states,
419
- return_dict=return_dict,
420
- )
421
-
422
- sequence_output = self.dropout(outputs.last_hidden_state)
423
- logits = self.classifier(sequence_output)
424
-
425
- loss = None
426
- if labels is not None:
427
- labels_long = labels.long()
428
- if attention_mask is not None:
429
- mask = attention_mask.bool()
430
- loss = -self.crf(logits, labels_long, mask=mask, reduction="mean")
431
- else:
432
- if not getattr(self, "_warned_no_attention_mask", False):
433
- print(
434
- "WARNING: attention_mask is None; CRF loss will include padding tokens."
435
- )
436
- self._warned_no_attention_mask = True
437
- loss = -self.crf(logits, labels_long, reduction="mean")
438
-
439
- if not return_dict:
440
- output = (logits,) + outputs[2:]
441
- return ((loss,) + output) if loss is not None else output
442
-
443
- return TokenClassifierOutput(
444
- loss=loss,
445
- logits=logits,
446
- hidden_states=outputs.hidden_states,
447
- attentions=outputs.attentions,
448
- )
449
-
450
- @property
451
- def device_info(self):
452
- return next(self.parameters()).device
453
-
454
- def get_input_embeddings(self):
455
- return self.roberta.get_input_embeddings()
456
-
457
- def set_input_embeddings(self, value):
458
- self.roberta.set_input_embeddings(value)
459
-
460
- @classmethod
461
- def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
462
- config = kwargs.pop("config", None)
463
- if config is None:
464
- from transformers import AutoConfig
465
-
466
- config = AutoConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
467
-
468
- freeze_backbone = getattr(config, "freeze_backbone", False)
469
- classifier_hidden_layers = getattr(config, "classifier_hidden_layers", None)
470
- classifier_dropout = getattr(config, "classifier_dropout", 0.1)
471
-
472
- model = cls(
473
- config=config,
474
- freeze_backbone=freeze_backbone,
475
- classifier_hidden_layers=classifier_hidden_layers,
476
- classifier_dropout=classifier_dropout,
477
- )
478
-
479
- try:
480
- state_dict = torch.load(
481
- f"{pretrained_model_name_or_path}/pytorch_model.bin", map_location="cpu"
482
- )
483
- model.load_state_dict(state_dict)
484
- except Exception:
485
- print(
486
- "Warning: Could not load pre-trained weights. Using randomly initialized model."
487
- )
488
-
489
- return model
490
-
491
-
492
- class TokenClassificationModelMultiHeadCRF(PreTrainedModel):
493
- """
494
- Multi-Head CRF model for token classification with multiple entity types.
495
- """
496
-
497
- config_class = MultiHeadCRFConfig
498
- base_model_prefix = "roberta"
499
- _keys_to_ignore_on_load_unexpected = [r"pooler"]
500
-
501
- def __init__(self, config, base_model=None, freeze_backbone=None):
502
- super().__init__(config)
503
- self.config = config
504
-
505
- self.entity_types = getattr(config, "entity_types", [])
506
- if not self.entity_types:
507
- raise ValueError("entity_types must be provided in config")
508
-
509
- self.num_labels = config.num_labels
510
- self.number_of_layers_per_head = getattr(config, "number_of_layers_per_head", 1)
511
- self.crf_reduction = getattr(config, "crf_reduction", "mean")
512
- freeze_backbone = (
513
- freeze_backbone
514
- if freeze_backbone is not None
515
- else getattr(config, "freeze_backbone", False)
516
- )
517
- self.num_frozen_encoders = getattr(config, "num_frozen_encoders", 0)
518
- classifier_dropout = getattr(config, "classifier_dropout", 0.1)
519
-
520
- if base_model is None:
521
- self.roberta, backbone_name = _build_backbone_from_config(config)
522
- else:
523
- if hasattr(base_model, "roberta"):
524
- self.roberta = base_model.roberta
525
- else:
526
- self.roberta = base_model
527
- backbone_name = (
528
- getattr(getattr(self.roberta, "config", None), "_name_or_path", None)
529
- or getattr(config, "backbone_model_name", None)
530
- or getattr(config, "_name_or_path", None)
531
- )
532
- if getattr(config, "backbone_model_name", None) is None:
533
- config.backbone_model_name = backbone_name
534
-
535
- self.hidden_size = self.roberta.config.hidden_size
536
- self.dropout = nn.Dropout(getattr(config, "hidden_dropout_prob", 0.1))
537
-
538
- print(f"Creating Multi-Head CRF with entity types: {sorted(self.entity_types)}")
539
-
540
- for entity_type in self.entity_types:
541
- for i in range(self.number_of_layers_per_head):
542
- setattr(
543
- self,
544
- f"{entity_type}_dense_{i}",
545
- nn.Linear(self.hidden_size, self.hidden_size),
546
- )
547
- setattr(
548
- self,
549
- f"{entity_type}_dense_activation_{i}",
550
- nn.GELU(approximate="none"),
551
- )
552
- setattr(
553
- self, f"{entity_type}_dropout_{i}", nn.Dropout(classifier_dropout)
554
- )
555
-
556
- setattr(
557
- self,
558
- f"{entity_type}_classifier",
559
- nn.Linear(self.hidden_size, self.num_labels),
560
- )
561
- setattr(
562
- self,
563
- f"{entity_type}_crf",
564
- MultiHeadCRF(num_tags=self.num_labels, batch_first=True),
565
- )
566
-
567
- if freeze_backbone:
568
- self._freeze_backbone()
569
-
570
- self.post_init()
571
-
572
- def _freeze_backbone(self):
573
- print("+" * 30, "\n\n", "Freezing backbone...", "+" * 30, "\n\n")
574
-
575
- for param in self.roberta.embeddings.parameters():
576
- param.requires_grad = False
577
-
578
- if self.num_frozen_encoders > 0:
579
- for _, param in islice(
580
- self.roberta.encoder.named_parameters(),
581
- self.num_frozen_encoders * NUM_PER_LAYER,
582
- ):
583
- param.requires_grad = False
584
-
585
- def reset_head_parameters(self):
586
- for entity_type in self.entity_types:
587
- for i in range(self.number_of_layers_per_head):
588
- getattr(self, f"{entity_type}_dense_{i}").reset_parameters()
589
- getattr(self, f"{entity_type}_classifier").reset_parameters()
590
- getattr(self, f"{entity_type}_crf").reset_parameters()
591
- getattr(self, f"{entity_type}_crf").mask_impossible_transitions()
592
-
593
- def forward(
594
- self,
595
- input_ids: Optional[torch.LongTensor] = None,
596
- attention_mask: Optional[torch.FloatTensor] = None,
597
- token_type_ids: Optional[torch.LongTensor] = None,
598
- position_ids: Optional[torch.LongTensor] = None,
599
- head_mask: Optional[torch.FloatTensor] = None,
600
- inputs_embeds: Optional[torch.FloatTensor] = None,
601
- labels: Optional[Dict[str, torch.LongTensor]] = None,
602
- output_attentions: Optional[bool] = None,
603
- output_hidden_states: Optional[bool] = None,
604
- return_dict: Optional[bool] = None,
605
- **kwargs,
606
- ):
607
- return_dict = (
608
- return_dict if return_dict is not None else self.config.use_return_dict
609
- )
610
-
611
- try:
612
- outputs = self.roberta(
613
- input_ids,
614
- attention_mask=attention_mask,
615
- token_type_ids=token_type_ids,
616
- position_ids=position_ids,
617
- head_mask=head_mask,
618
- inputs_embeds=inputs_embeds,
619
- output_attentions=output_attentions,
620
- output_hidden_states=output_hidden_states,
621
- return_dict=return_dict,
622
- )
623
- except TypeError:
624
- outputs = self.roberta(
625
- input_ids,
626
- attention_mask=attention_mask,
627
- position_ids=position_ids,
628
- inputs_embeds=inputs_embeds,
629
- output_attentions=output_attentions,
630
- output_hidden_states=output_hidden_states,
631
- return_dict=return_dict,
632
- )
633
-
634
- sequence_output = outputs[0]
635
- sequence_output = self.dropout(sequence_output)
636
-
637
- logits = {}
638
- for entity_type in self.entity_types:
639
- head_output = sequence_output
640
- for i in range(self.number_of_layers_per_head):
641
- head_output = getattr(self, f"{entity_type}_dense_{i}")(head_output)
642
- head_output = getattr(self, f"{entity_type}_dense_activation_{i}")(
643
- head_output
644
- )
645
- head_output = getattr(self, f"{entity_type}_dropout_{i}")(head_output)
646
- logits[entity_type] = getattr(self, f"{entity_type}_classifier")(
647
- head_output
648
- )
649
-
650
- if labels is not None:
651
- losses = {}
652
- mask = attention_mask.bool() if attention_mask is not None else None
653
-
654
- for entity_type in self.entity_types:
655
- if entity_type in labels:
656
- entity_labels = (
657
- labels[entity_type].long().to(logits[entity_type].device)
658
- )
659
- crf = getattr(self, f"{entity_type}_crf")
660
- if mask is not None:
661
- losses[entity_type] = crf(
662
- logits[entity_type],
663
- entity_labels,
664
- mask=mask,
665
- reduction=self.crf_reduction,
666
- )
667
- else:
668
- if not getattr(self, "_warned_no_attention_mask", False):
669
- print(
670
- "WARNING: attention_mask is None; CRF loss will include padding tokens."
671
- )
672
- self._warned_no_attention_mask = True
673
- losses[entity_type] = crf(
674
- logits[entity_type],
675
- entity_labels,
676
- reduction=self.crf_reduction,
677
- )
678
-
679
- total_loss = sum(losses.values())
680
- return total_loss, logits
681
-
682
- predictions = {}
683
- mask = attention_mask.bool() if attention_mask is not None else None
684
-
685
- for entity_type in self.entity_types:
686
- crf = getattr(self, f"{entity_type}_crf")
687
- if mask is not None:
688
- decoded = crf.decode(logits[entity_type], mask=mask)
689
- else:
690
- decoded = crf.decode(logits[entity_type])
691
- predictions[entity_type] = torch.tensor(decoded)
692
-
693
- return [predictions[ent] for ent in sorted(self.entity_types)]
694
-
695
- def get_input_embeddings(self):
696
- return self.roberta.get_input_embeddings()
697
-
698
- def set_input_embeddings(self, value):
699
- self.roberta.set_input_embeddings(value)
700
-
701
- @classmethod
702
- def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
703
- import json
704
- import os
705
-
706
- config = kwargs.pop("config", None)
707
-
708
- if config is None:
709
- config_file = os.path.join(pretrained_model_name_or_path, "config.json")
710
- if os.path.exists(config_file):
711
- with open(config_file, "r") as f:
712
- config_dict = json.load(f)
713
- config = MultiHeadCRFConfig(**config_dict)
714
- else:
715
- from transformers import AutoConfig
716
-
717
- config = AutoConfig.from_pretrained(
718
- pretrained_model_name_or_path,
719
- trust_remote_code=kwargs.get("trust_remote_code", True),
720
- )
721
-
722
- roberta_defaults = {
723
- "layer_norm_eps": 1e-5,
724
- "hidden_size": 768,
725
- "num_hidden_layers": 12,
726
- "num_attention_heads": 12,
727
- "intermediate_size": 3072,
728
- "hidden_act": "gelu",
729
- "hidden_dropout_prob": 0.1,
730
- "attention_probs_dropout_prob": 0.1,
731
- "max_position_embeddings": 514,
732
- "type_vocab_size": 1,
733
- "initializer_range": 0.02,
734
- "vocab_size": 52000,
735
- "pad_token_id": 1,
736
- "bos_token_id": 0,
737
- "eos_token_id": 2,
738
- "position_embedding_type": "absolute",
739
- "use_cache": True,
740
- "is_decoder": False,
741
- "add_cross_attention": False,
742
- "chunk_size_feed_forward": 0,
743
- "output_hidden_states": False,
744
- "output_attentions": False,
745
- "torchscript": False,
746
- "tie_word_embeddings": True,
747
- "return_dict": True,
748
- "gradient_checkpointing": False,
749
- "pruned_heads": {},
750
- "problem_type": None,
751
- "embedding_size": None,
752
- }
753
-
754
- for key, default_value in roberta_defaults.items():
755
- if not hasattr(config, key) or getattr(config, key) is None:
756
- setattr(config, key, default_value)
757
-
758
- freeze_backbone = getattr(config, "freeze_backbone", False)
759
- model = cls(config=config, freeze_backbone=freeze_backbone)
760
-
761
- weight_file = os.path.join(pretrained_model_name_or_path, "pytorch_model.bin")
762
- safetensors_file = os.path.join(
763
- pretrained_model_name_or_path, "model.safetensors"
764
- )
765
-
766
- try:
767
- if os.path.exists(safetensors_file):
768
- from safetensors.torch import load_file
769
-
770
- state_dict = load_file(safetensors_file)
771
- model.load_state_dict(state_dict)
772
- elif os.path.exists(weight_file):
773
- state_dict = torch.load(weight_file, map_location="cpu")
774
- model.load_state_dict(state_dict)
775
- else:
776
- print(
777
- "Warning: No pre-trained weights found. Using randomly initialized model."
778
- )
779
- except Exception as e:
780
- print(f"Warning: Could not load pre-trained weights: {e}")
781
-
782
- return model
783
 
784
 
785
  class MultiHeadConfig(PretrainedConfig):
786
  """
787
- Configuration class for Multi-Head models (without CRF).
788
  """
789
 
790
  model_type = "multihead-tagger"
@@ -814,7 +95,7 @@ class MultiHeadConfig(PretrainedConfig):
814
 
815
  class TokenClassificationModelMultiHead(PreTrainedModel):
816
  """
817
- Multi-Head model for token classification with multiple entity types (no CRF).
818
  """
819
 
820
  config_class = MultiHeadConfig
@@ -1104,7 +385,7 @@ class TokenClassificationModelMultiHead(PreTrainedModel):
1104
 
1105
  class TokenClassificationModel(PreTrainedModel):
1106
  """
1107
- Custom token classification model with configurable classifier head (no CRF).
1108
  """
1109
 
1110
  def __init__(self, config, base_model=None):
@@ -1272,46 +553,6 @@ def load_custom_cardioner_multiclass_model(model_path: str, device: str = "auto"
1272
  return model, tokenizer, model.config
1273
 
1274
 
1275
- def load_custom_multihead_crf_model(model_path: str, device: str = "auto"):
1276
- import json
1277
- import os
1278
-
1279
- from transformers import AutoTokenizer
1280
-
1281
- required_files = ["config.json", "modeling.py"]
1282
- missing_files = [
1283
- f for f in required_files if not os.path.exists(os.path.join(model_path, f))
1284
- ]
1285
-
1286
- if missing_files:
1287
- raise FileNotFoundError(
1288
- f"Missing required files in {model_path}: {missing_files}"
1289
- )
1290
-
1291
- print(f"Loading Multi-Head CRF model from: {model_path}")
1292
-
1293
- tokenizer = AutoTokenizer.from_pretrained(model_path)
1294
-
1295
- with open(os.path.join(model_path, "config.json"), "r") as f:
1296
- config_dict = json.load(f)
1297
-
1298
- config = MultiHeadCRFConfig(**config_dict)
1299
-
1300
- model = TokenClassificationModelMultiHeadCRF.from_pretrained(
1301
- model_path, config=config
1302
- )
1303
-
1304
- if device == "auto":
1305
- device = "cuda" if torch.cuda.is_available() else "cpu"
1306
-
1307
- model = model.to(device)
1308
-
1309
- print(f"Model loaded successfully on {device}")
1310
- print(f"Model type: {type(model).__name__}")
1311
- print(f"Entity types: {model.entity_types}")
1312
- print(f"Number of labels per head: {model.num_labels}")
1313
-
1314
- return model, tokenizer, model.config
1315
 
1316
 
1317
  def validate_custom_multiclass_model_directory(model_path: str) -> dict:
@@ -1376,9 +617,6 @@ def validate_custom_multiclass_model_directory(model_path: str) -> dict:
1376
  validation_results["model_info"]["freeze_backbone"] = config.get(
1377
  "freeze_backbone", None
1378
  )
1379
- validation_results["model_info"]["use_crf"] = (
1380
- "TokenClassificationModelCRF" in str(config.get("architectures", []))
1381
- )
1382
 
1383
  if not config.get("auto_map"):
1384
  validation_results["warnings"].append(
@@ -1397,7 +635,6 @@ def validate_custom_multiclass_model_directory(model_path: str) -> dict:
1397
 
1398
  required_classes = [
1399
  "TokenClassificationModel",
1400
- "TokenClassificationModelCRF",
1401
  ]
1402
  missing_classes = [cls for cls in required_classes if cls not in content]
1403
 
@@ -1415,12 +652,6 @@ def validate_custom_multiclass_model_directory(model_path: str) -> dict:
1415
  return validation_results
1416
 
1417
 
1418
- try:
1419
- from transformers import AutoConfig
1420
-
1421
- AutoConfig.register("multihead-crf-tagger", MultiHeadCRFConfig)
1422
- except Exception:
1423
- pass
1424
 
1425
 
1426
  def patch_legacy_model(
@@ -1492,4 +723,4 @@ def patch_multiple_models(
1492
  f"Successfully {'would patch' if dry_run else 'patched'}: {success}/{len(model_paths)}"
1493
  )
1494
 
1495
- return results
 
3
 
4
  import torch
5
  import torch.nn as nn
 
6
  from transformers import PretrainedConfig, PreTrainedModel
7
  from transformers.modeling_outputs import TokenClassifierOutput
8
 
 
15
  EuroBertModel = None
16
  print("COULD NOT IMPORT EUROBERT MODEL")
17
 
 
 
18
  NUM_PER_LAYER = 16
19
 
20
 
 
61
  return backbone, backbone_name
62
 
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
 
66
  class MultiHeadConfig(PretrainedConfig):
67
  """
68
+ Configuration class for Multi-Head models.
69
  """
70
 
71
  model_type = "multihead-tagger"
 
95
 
96
  class TokenClassificationModelMultiHead(PreTrainedModel):
97
  """
98
+ Multi-Head model for token classification with multiple entity types.
99
  """
100
 
101
  config_class = MultiHeadConfig
 
385
 
386
  class TokenClassificationModel(PreTrainedModel):
387
  """
388
+ Custom token classification model with configurable classifier head.
389
  """
390
 
391
  def __init__(self, config, base_model=None):
 
553
  return model, tokenizer, model.config
554
 
555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
 
557
 
558
  def validate_custom_multiclass_model_directory(model_path: str) -> dict:
 
617
  validation_results["model_info"]["freeze_backbone"] = config.get(
618
  "freeze_backbone", None
619
  )
 
 
 
620
 
621
  if not config.get("auto_map"):
622
  validation_results["warnings"].append(
 
635
 
636
  required_classes = [
637
  "TokenClassificationModel",
 
638
  ]
639
  missing_classes = [cls for cls in required_classes if cls not in content]
640
 
 
652
  return validation_results
653
 
654
 
 
 
 
 
 
 
655
 
656
 
657
  def patch_legacy_model(
 
723
  f"Successfully {'would patch' if dry_run else 'patched'}: {success}/{len(model_paths)}"
724
  )
725
 
726
+ return results