Voeg custom modeling-bestand toe (null-answer fix)
Browse files- modeling_nbclas_qa.py +79 -0
modeling_nbclas_qa.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Custom modeling-bestand voor NoaberAI/nbclas-mdeberta-qa.
|
| 2 |
+
|
| 3 |
+
Wordt automatisch gebruikt als iemand het model ophaalt met:
|
| 4 |
+
|
| 5 |
+
from transformers import AutoModel
|
| 6 |
+
model = AutoModel.from_pretrained("NoaberAI/nbclas-mdeberta-qa", trust_remote_code=True)
|
| 7 |
+
tokenizer = AutoTokenizer.from_pretrained("NoaberAI/nbclas-mdeberta-qa")
|
| 8 |
+
result = model.extract(tokenizer, "De put is verstopt.", "Wat is er kapot?")
|
| 9 |
+
# -> {"value": "put", "confidence": 0.91} of {"value": None, "confidence": ...}
|
| 10 |
+
# als er geen antwoord in de tekst staat.
|
| 11 |
+
|
| 12 |
+
Dit bakt de "geen antwoord"-kalibratie (NULL_BIAS) in het model zelf, zodat
|
| 13 |
+
gebruikers die dit model zonder de NBClas-GitHub-repo gebruiken niet dezelfde
|
| 14 |
+
bug tegenkomen die wij vonden: de standaard argmax-aanpak van een QA-model
|
| 15 |
+
geeft altijd een antwoord terug, ook als er geen antwoord in de tekst staat.
|
| 16 |
+
Zie https://github.com/Noaber-AI/NBClas voor de volledige uitleg en de
|
| 17 |
+
trainingspipeline.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
from transformers import DebertaV2ForQuestionAnswering, PretrainedConfig
|
| 22 |
+
|
| 23 |
+
# Bepaald met scripts/calibrate_extraction.py (CPU-only, geen training) op
|
| 24 |
+
# de validation-set van dit checkpoint (n=500, combinatie-accuracy 0.692).
|
| 25 |
+
# Opnieuw draaien na een volgende hertraining van dit model — deze waarde
|
| 26 |
+
# hoort bij dit specifieke checkpoint, niet bij de architectuur.
|
| 27 |
+
NULL_BIAS = 4.75
|
| 28 |
+
MAX_ANSWER_LENGTH = 30
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class NBClasQAModel(DebertaV2ForQuestionAnswering):
|
| 32 |
+
"""Zelfde architectuur als het onderliggende DebertaV2ForQuestionAnswering
|
| 33 |
+
(dus normale .forward() blijft gewoon werken), met een extra .extract()
|
| 34 |
+
gemakslaagje dat de gekalibreerde null-answer-logica toepast."""
|
| 35 |
+
|
| 36 |
+
def __init__(self, config: PretrainedConfig):
|
| 37 |
+
super().__init__(config)
|
| 38 |
+
self.null_bias = getattr(config, "null_bias", NULL_BIAS)
|
| 39 |
+
self.max_answer_length = getattr(config, "max_answer_length", MAX_ANSWER_LENGTH)
|
| 40 |
+
|
| 41 |
+
@torch.no_grad()
|
| 42 |
+
def extract(self, tokenizer, context: str, question: str) -> dict:
|
| 43 |
+
inputs = tokenizer(
|
| 44 |
+
question, context, return_tensors="pt", truncation="only_second"
|
| 45 |
+
).to(self.device)
|
| 46 |
+
|
| 47 |
+
outputs = self(**inputs)
|
| 48 |
+
start_logits = outputs.start_logits[0]
|
| 49 |
+
end_logits = outputs.end_logits[0]
|
| 50 |
+
|
| 51 |
+
sequence_ids = inputs.sequence_ids(0)
|
| 52 |
+
context_positions = [i for i, sid in enumerate(sequence_ids) if sid == 1]
|
| 53 |
+
if not context_positions:
|
| 54 |
+
return {"value": None, "confidence": 1.0}
|
| 55 |
+
context_start, context_end = context_positions[0], context_positions[-1]
|
| 56 |
+
|
| 57 |
+
null_score = float(start_logits[0] + end_logits[0])
|
| 58 |
+
|
| 59 |
+
start_vals = start_logits.tolist()
|
| 60 |
+
end_vals = end_logits.tolist()
|
| 61 |
+
best_score = float("-inf")
|
| 62 |
+
best_start, best_end = context_start, context_start
|
| 63 |
+
for s in range(context_start, context_end + 1):
|
| 64 |
+
for e in range(s, min(s + self.max_answer_length, context_end + 1)):
|
| 65 |
+
score = start_vals[s] + end_vals[e]
|
| 66 |
+
if score > best_score:
|
| 67 |
+
best_score = score
|
| 68 |
+
best_start, best_end = s, e
|
| 69 |
+
|
| 70 |
+
if null_score + self.null_bias >= best_score:
|
| 71 |
+
confidence = float(torch.sigmoid(torch.tensor(null_score + self.null_bias - best_score)))
|
| 72 |
+
return {"value": None, "confidence": confidence}
|
| 73 |
+
|
| 74 |
+
input_ids = inputs["input_ids"][0]
|
| 75 |
+
value = tokenizer.decode(
|
| 76 |
+
input_ids[best_start : best_end + 1], skip_special_tokens=True
|
| 77 |
+
).strip()
|
| 78 |
+
confidence = float(torch.sigmoid(torch.tensor(best_score - null_score - self.null_bias)))
|
| 79 |
+
return {"value": value, "confidence": confidence}
|