import torch import torch.nn as nn from transformers import PreTrainedModel, DistilBertConfig, DistilBertModel from .configuration_dualmedbert import DualMedBertConfig class LoRALinear(nn.Module): def __init__(self, linear: nn.Linear, rank: int = 8, alpha: int = 32): super().__init__() self.linear = linear self.linear.weight.requires_grad_(False) if self.linear.bias is not None: self.linear.bias.requires_grad_(False) d_in = linear.in_features d_out = linear.out_features self.lora_A = nn.Parameter(torch.randn(d_in, rank) * 0.01) self.lora_B = nn.Parameter(torch.zeros(rank, d_out)) self.scale = alpha / rank def forward(self, x): return self.linear(x) + (x @ self.lora_A @ self.lora_B) * self.scale def inject_lora(model, rank: int = 8, alpha: int = 32): for p in model.parameters(): p.requires_grad_(False) for layer in getattr(model, "transformer").layer: layer.attention.q_lin = LoRALinear(layer.attention.q_lin, rank, alpha) layer.attention.v_lin = LoRALinear(layer.attention.v_lin, rank, alpha) return model class SelfAttnPool(nn.Module): def __init__(self, hidden: int = 768, heads: int = 4): super().__init__() self.heads = heads self.query = nn.Linear(hidden, heads, bias=False) self.proj = nn.Linear(hidden * heads, hidden) def forward(self, h: torch.Tensor, mask: torch.Tensor) -> torch.Tensor: scores = self.query(h) mask_3d = mask.unsqueeze(-1).expand_as(scores) scores = scores.masked_fill(mask_3d == 0, torch.finfo(scores.dtype).min) weights = torch.softmax(scores, dim=1) contexts = torch.matmul(weights.transpose(1, 2), h) flat = contexts.reshape(h.size(0), -1) return self.proj(flat) class DualMedBertForSequenceClassification(PreTrainedModel): config_class = DualMedBertConfig _tied_weights_keys = [] @property def all_tied_weights_keys(self): return {} def __init__(self, config): super().__init__(config) self.num_classes = config.num_classes base_config = DistilBertConfig() base = DistilBertModel(base_config) self.backbone = inject_lora(base, rank=config.lora_rank, alpha=config.lora_alpha) self.attn_pool = SelfAttnPool(768, heads=4) self.fuse = nn.Linear(768 * 2, 768) self.relu = nn.ReLU() self.fuse_drop = nn.Dropout(0.3) self.dropout = nn.Dropout(0.2) self.head = nn.Linear(768, self.num_classes) def _backbone_pass(self, input_ids, attention_mask): return self.backbone(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state def forward(self, input_ids, attention_mask=None, **kwargs): if attention_mask is None: attention_mask = torch.ones_like(input_ids) h = self._backbone_pass(input_ids, attention_mask) cls = h[:, 0, :] pool = self.attn_pool(h, attention_mask) x = self.fuse(torch.cat([cls, pool], dim=-1)) x = self.relu(x) x = self.fuse_drop(x) x = self.dropout(x) logits = self.head(x) from transformers.modeling_outputs import SequenceClassifierOutput return SequenceClassifierOutput( loss=None, logits=logits, hidden_states=(h,), attentions=None )