| from typing import Any, Dict, List |
|
|
| import torch |
| from transformers import BertTokenizerFast |
|
|
| from model import BertForTokenAndSequenceJointClassification, TOKEN_TAGS, SEQUENCE_TAGS |
|
|
| MAX_LENGTH = 512 |
| |
| IGNORED_TAG_IDS = {0, 1} |
| |
| |
| |
| |
| TOKENIZER_BASE = "bert-base-cased" |
|
|
|
|
| class EndpointHandler: |
| def __init__(self, path: str = ""): |
| self.tokenizer = BertTokenizerFast.from_pretrained(TOKENIZER_BASE) |
| self.model = BertForTokenAndSequenceJointClassification.from_pretrained(path) |
| self.model.eval() |
|
|
| def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]: |
| text = data.get("inputs") |
| if not isinstance(text, str) or not text: |
| return {"error": "expected data.inputs to be a non-empty string"} |
|
|
| encoding = self.tokenizer( |
| text, |
| return_offsets_mapping=True, |
| return_tensors="pt", |
| truncation=True, |
| max_length=MAX_LENGTH, |
| ) |
| offset_mapping = encoding.pop("offset_mapping")[0].tolist() |
|
|
| with torch.no_grad(): |
| outputs = self.model(**encoding) |
|
|
| |
| token_probs = torch.softmax(outputs.token_logits[0], dim=-1) |
| token_tag_ids = token_probs.argmax(dim=-1).tolist() |
| token_tag_probs = token_probs.max(dim=-1).values.tolist() |
|
|
| sequence_probs = torch.softmax(outputs.sequence_logits[0], dim=-1).tolist() |
| sequence_tag_id = int(torch.argmax(outputs.sequence_logits[0]).item()) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| spans: List[Dict[str, Any]] = [] |
| current = None |
| for i, (tag_id, prob, (start, end)) in enumerate(zip(token_tag_ids, token_tag_probs, offset_mapping)): |
| is_technique = tag_id not in IGNORED_TAG_IDS and end > start |
| if ( |
| is_technique |
| and current is not None |
| and current["tag_id"] == tag_id |
| and i == current["last_index"] + 1 |
| ): |
| current["end"] = end |
| current["probs"].append(prob) |
| current["last_index"] = i |
| else: |
| if current is not None: |
| spans.append(current) |
| current = ( |
| {"tag_id": tag_id, "start": start, "end": end, "probs": [prob], "last_index": i} |
| if is_technique |
| else None |
| ) |
| if current is not None: |
| spans.append(current) |
|
|
| out_spans = [ |
| { |
| "technique": TOKEN_TAGS[s["tag_id"]], |
| "start": s["start"], |
| "end": s["end"], |
| "text": text[s["start"] : s["end"]], |
| "confidence": sum(s["probs"]) / len(s["probs"]), |
| } |
| for s in spans |
| ] |
|
|
| return { |
| "spans": out_spans, |
| "sequence_label": SEQUENCE_TAGS[sequence_tag_id], |
| "sequence_probs": {SEQUENCE_TAGS[i]: p for i, p in enumerate(sequence_probs)}, |
| "truncated": len(encoding["input_ids"][0]) >= MAX_LENGTH, |
| } |
|
|