haiderAI's picture
Upload inference.py
ed1e572 verified
Raw
History Blame Contribute Delete
5.57 kB
"""Standalone inference script for ModernBERT-large Medical Dataset NER model.
Chunks long inputs at 6000 chars (matching training) and merges per-chunk entities.
"""
import json
import torch
import torch.nn as nn
from transformers import AutoModel, AutoTokenizer
class ModernBERTTokenClassifier(nn.Module):
def __init__(self, model_name, num_labels=3, dropout=0.1, freeze_layers=0,
use_gradient_checkpointing=False):
super().__init__()
self.encoder = AutoModel.from_pretrained(model_name)
self.dropout = nn.Dropout(dropout)
self.linear = nn.Linear(self.encoder.config.hidden_size, num_labels)
self.num_labels = num_labels
def forward(self, input_ids, attention_mask, labels=None):
outputs = self.encoder(input_ids=input_ids, attention_mask=attention_mask)
seq_out = self.dropout(outputs.last_hidden_state)
logits = self.linear(seq_out)
if labels is not None:
loss_fn = nn.CrossEntropyLoss(ignore_index=-100)
loss = loss_fn(logits.view(-1, self.num_labels), labels.view(-1))
return loss
return torch.argmax(logits, dim=-1)
def load_model(model_dir, device="cpu"):
with open(f"{model_dir}/config.json", "r") as f:
config = json.load(f)
model = ModernBERTTokenClassifier(
config["base_model"],
config["num_labels"],
config["hyperparameters"].get("dropout", 0.1),
config["hyperparameters"].get("freeze_layers", 0),
)
model.load_state_dict(
torch.load(f"{model_dir}/best_model.pt", map_location=device)
)
model.to(device).eval()
tokenizer = AutoTokenizer.from_pretrained(config["base_model"])
id2label = {int(v): k for k, v in config["label2id"].items()}
return model, tokenizer, id2label, config
def _predict_chunk(text, offset_base, model, tokenizer, id2label, device,
max_length=8192):
enc = tokenizer(
text, return_offsets_mapping=True, add_special_tokens=True,
truncation=True, max_length=max_length,
return_attention_mask=True, return_tensors="pt"
)
ids = enc["input_ids"].to(device)
attn = enc["attention_mask"].to(device)
offsets = enc["offset_mapping"][0].tolist()
wids = enc.word_ids(0)
with torch.no_grad():
preds = model(input_ids=ids, attention_mask=attn)[0].cpu().tolist()
word_tags = {}
seen = set()
for i, wid in enumerate(wids):
if wid is not None and wid not in seen:
seen.add(wid)
word_tags[wid] = id2label[preds[i]]
word_spans = {}
for i, wid in enumerate(wids):
if wid is not None:
s, e = offsets[i]
if e == 0: continue
word_spans[wid] = (s, max(word_spans.get(wid, (s, 0))[1], e))
entities, curr_s, curr_e = [], None, None
for wid in sorted(word_spans.keys()):
tag = word_tags.get(wid, "O")
s, e = word_spans[wid]
if tag.startswith("B-"):
if curr_s is not None:
entities.append({
"text": text[curr_s:curr_e],
"start": curr_s + offset_base,
"end": curr_e + offset_base,
"label": "Dataset",
})
curr_s, curr_e = s, e
elif tag.startswith("I-") and curr_s is not None:
curr_e = e
else:
if curr_s is not None:
entities.append({
"text": text[curr_s:curr_e],
"start": curr_s + offset_base,
"end": curr_e + offset_base,
"label": "Dataset",
})
curr_s, curr_e = None, None
if curr_s is not None:
entities.append({
"text": text[curr_s:curr_e],
"start": curr_s + offset_base,
"end": curr_e + offset_base,
"label": "Dataset",
})
return entities
def predict(text, model, tokenizer, id2label, device="cpu",
chunk_size=6000, chunk_overlap=500, max_length=8192):
"""Predict entities on arbitrarily long text by chunking at the character level."""
if len(text) <= chunk_size:
return _predict_chunk(text, 0, model, tokenizer, id2label, device, max_length)
results = []
seen_spans = set()
start = 0
while start < len(text):
end = min(start + chunk_size, len(text))
chunk = text[start:end]
for ent in _predict_chunk(chunk, start, model, tokenizer, id2label, device, max_length):
key = (ent["start"], ent["end"], ent["text"])
if key not in seen_spans:
seen_spans.add(key)
results.append(ent)
if end == len(text):
break
start = end - chunk_overlap
return results
if __name__ == "__main__":
import sys
d = sys.argv[1] if len(sys.argv) > 1 else "."
model, tok, id2l, cfg = load_model(d)
print("Loaded ModernBERT model from {}".format(d))
print(" Model: {}".format(cfg["base_model"]))
print(" Max seq length: {}".format(cfg.get("max_seq_length", 8192)))
print(" Labels: {}".format(cfg.get("bio_labels", list(id2l.values()))))
txt = "We evaluated our method on the MIMIC-III dataset."
print("\nSample text: {}".format(txt))
ents = predict(txt, model, tok, id2l)
print("Entities found: {}".format(len(ents)))
for ent in ents:
print(" [{}:{}] {} ({})".format(
ent["start"], ent["end"], ent["text"], ent["label"]
))