Download bert/model.py from Snapkitty/bert-agent: direct link, hf CLI and curl.
- Browser
- Download file 3.54 kB
-
https://huggingface.co/Snapkitty/bert-agent/resolve/main/bert/model.py
- Command line
-
hf download hf://Snapkitty/bert-agent/bert/model.py
-
curl -L -o model.py https://huggingface.co/Snapkitty/bert-agent/resolve/main/bert/model.py
3.54 kB
| """ | |
| bert/model.py | |
| DeBERTa-v3 Cross-Encoder for entailment verification. | |
| Architecture: Cross-Encoder (NOT Bi-Encoder). | |
| Both premise and hypothesis are concatenated and fed through the transformer | |
| together so self-attention can directly compare entities, negations, and dates | |
| across the premise-hypothesis boundary. This is critical for catching | |
| hallucinations like flipped dates or switched subjects. | |
| Labels: 0=Contradiction, 1=Neutral, 2=Entailment | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| import torch.nn as nn | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| from transformers import AutoModelForSequenceClassification, AutoTokenizer, PreTrainedModel | |
| class CrossEncoderConfig: | |
| backbone: str = "microsoft/deberta-v3-base" | |
| num_labels: int = 3 # 0=Contradiction 1=Neutral 2=Entailment | |
| dropout: float = 0.1 | |
| max_length: int = 512 | |
| # Class weights: penalise false-positive Entailment heavily. | |
| # Contradiction=2.0, Neutral=1.5, Entailment=1.0 | |
| class_weights: tuple = (2.0, 1.5, 1.0) | |
| class BertCrossEncoderVerifier(nn.Module): | |
| """ | |
| DeBERTa-v3 Cross-Encoder entailment verifier. | |
| DeBERTa is chosen over BERT/RoBERTa for its disentangled attention | |
| mechanism which handles positional reasoning significantly better — | |
| critical when LLM-generated claims reorder events from the source chunk. | |
| """ | |
| def __init__(self, config: CrossEncoderConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.model: PreTrainedModel = AutoModelForSequenceClassification.from_pretrained( | |
| config.backbone, | |
| num_labels=config.num_labels, | |
| hidden_dropout_prob=config.dropout, | |
| attention_probs_dropout_prob=config.dropout, | |
| ) | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| attention_mask: torch.Tensor, | |
| token_type_ids: Optional[torch.Tensor] = None, | |
| labels: Optional[torch.Tensor] = None, | |
| ) -> dict: | |
| outputs = self.model( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| token_type_ids=token_type_ids, | |
| labels=None, # compute loss manually with class weights | |
| ) | |
| logits = outputs.logits # (batch, 3) | |
| result = {"logits": logits} | |
| if labels is not None: | |
| weights = torch.tensor( | |
| self.config.class_weights, | |
| dtype=torch.float, | |
| device=logits.device, | |
| ) | |
| loss_fn = nn.CrossEntropyLoss(weight=weights) | |
| result["loss"] = loss_fn(logits, labels) | |
| return result | |
| def predict_entailment_score( | |
| self, | |
| input_ids: torch.Tensor, | |
| attention_mask: torch.Tensor, | |
| token_type_ids: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| """Return softmax probability of Entailment class (index 2).""" | |
| out = self.forward(input_ids, attention_mask, token_type_ids) | |
| probs = torch.softmax(out["logits"], dim=-1) | |
| return probs[:, 2] # entailment column | |
| def build_model(config: CrossEncoderConfig | None = None) -> BertCrossEncoderVerifier: | |
| if config is None: | |
| config = CrossEncoderConfig() | |
| return BertCrossEncoderVerifier(config) | |
| def load_tokenizer(backbone: str = "microsoft/deberta-v3-base") -> AutoTokenizer: | |
| return AutoTokenizer.from_pretrained(backbone) | |