Skip to content

Commit c6ad2d7

Browse files
Add human review workflow for model predictions
Signed-off-by: Kaushik <kaushikrjpm10@gmail.com>
1 parent f506f93 commit c6ad2d7

4 files changed

Lines changed: 1085 additions & 0 deletions

File tree

etc/scripts/dataset_pipeline/add_ml_phrases.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Run the trained phrase tagger over license rules and mark its predictions.
2+
from dataclasses import dataclass
23
import json
34
import os
45
import sys
@@ -22,6 +23,7 @@
2223
from licensedcode.tokenize import required_phrase_splitter
2324

2425
from train_model import extract_spans
26+
from train_model import first_subword_positions
2527
from train_model import ID2LABEL
2628
from train_model import load_final_model
2729

@@ -31,6 +33,25 @@
3133
MAX_RULE_TEXT = 4000
3234

3335

36+
@dataclass(frozen=True)
37+
class PhrasePrediction:
38+
"""One predicted required phrase."""
39+
40+
text: str
41+
start_word: int
42+
end_word: int
43+
confidence: float
44+
45+
46+
@dataclass(frozen=True)
47+
class PredictionResult:
48+
"""Predictions and tokenization details for one rule."""
49+
50+
words: tuple[str, ...]
51+
phrases: tuple[PhrasePrediction, ...]
52+
truncated: bool
53+
54+
3455
def load_model(model, hf_token=None):
3556
"""Load and validate a local or Hugging Face Final_Model."""
3657
model_dir = Path(model)
@@ -111,6 +132,79 @@ def encode_words(tokenizer, words, max_length):
111132
return encoding, complete_words < len(words)
112133

113134

135+
def span_confidence(crf, word_emissions, tags, mask, free, span):
136+
"""Return the CRF probability mass agreeing with one decoded span."""
137+
start, end = span
138+
pinned = word_emissions.clone()
139+
floor = float(word_emissions.min()) - 10000.0
140+
141+
for position in range(start, end + 1):
142+
label = int(tags[0, position])
143+
keep = float(pinned[0, position, label])
144+
pinned[0, position] = floor
145+
pinned[0, position, label] = keep
146+
147+
constrained = crf(pinned, tags, mask=mask, reduction="none")
148+
confidence = float((free - constrained).detach().exp())
149+
return min(max(confidence, 0.0), 1.0)
150+
151+
152+
def predict_rule(tagger, tokenizer, max_length, text):
153+
"""Return phrase predictions for rule text without changing a rule."""
154+
import torch
155+
156+
words = words_from_text(text)
157+
if not words:
158+
return PredictionResult(words=(), phrases=(), truncated=False)
159+
160+
encoding, truncated = encode_words(tokenizer, words, max_length)
161+
word_ids = encoding.word_ids()
162+
positions = first_subword_positions(word_ids)
163+
device = next(tagger.parameters()).device
164+
input_ids = torch.tensor([encoding["input_ids"]], dtype=torch.long, device=device)
165+
attention_mask = torch.tensor(
166+
[encoding["attention_mask"]],
167+
dtype=torch.long,
168+
device=device,
169+
)
170+
171+
with torch.inference_mode():
172+
emissions = tagger.emissions(input_ids, attention_mask)
173+
word_emissions = emissions[:, positions].float()
174+
mask = torch.ones(
175+
word_emissions.shape[:2],
176+
dtype=torch.bool,
177+
device=word_emissions.device,
178+
)
179+
decoded = tagger.crf.decode(word_emissions, mask=mask)[0]
180+
tags = torch.tensor([decoded], device=word_emissions.device)
181+
free = tagger.crf(word_emissions, tags, mask=mask, reduction="none")
182+
labels = [ID2LABEL[int(label)] for label in decoded]
183+
predictions = [
184+
PhrasePrediction(
185+
text=" ".join(words[start : end + 1]),
186+
start_word=start,
187+
end_word=end,
188+
confidence=span_confidence(
189+
tagger.crf,
190+
word_emissions,
191+
tags,
192+
mask,
193+
free,
194+
(start, end),
195+
),
196+
)
197+
for start, end in extract_spans(labels)
198+
]
199+
200+
predictions.sort(key=lambda prediction: (prediction.start_word, prediction.end_word))
201+
return PredictionResult(
202+
words=tuple(words),
203+
phrases=tuple(predictions),
204+
truncated=truncated,
205+
)
206+
207+
114208
def phrases_from_tags(tags, words):
115209
"""Return unique predicted phrase texts, longest first."""
116210
phrases = {

0 commit comments

Comments
 (0)