joranvnbeekAI commited on
Commit
dc7304b
·
verified ·
1 Parent(s): 81215d9

Voeg custom modeling-bestand toe (null-answer fix)

Browse files
Files changed (1) hide show
  1. 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}