Radgraph-IT / modeling_radgraph.py
roccoangelella's picture
Add transformers-compatible wrapper (AutoModel/AutoTokenizer via trust_remote_code)
0c48771 verified
Raw
History Blame Contribute Delete
4.62 kB
"""transformers-compatible wrapper around `DyGIEModel` (training_v2/src/dygie/model.py).
`RadgraphModel.model` is a plain `DyGIEModel`, so its state_dict keys are `model.<...>`,
matching the `model.` prefix applied when this repo's checkpoint was converted from the
original `best.pt` (`{"epoch": ..., "model_state_dict": ...}`, see observability.py's
`HFCheckpointUploader` / trainer.py in the training repo) to `model.safetensors`.
Two ways to run inference:
- `model.predict(text)` -- raw report text in, decoded entities/relations out. Applies the
exact word-level preprocessing training used (regex cleanup + wordpunct tokenization,
ported from radgraph/radgraph/utils.py's `radgraph_xl_preprocess_report`, verified against
the gold tokenization by check_tokenization_raw_it.py in the training repo).
- `model.predict_words(words)` -- skip preprocessing if you already have the exact
word-tokenized list (e.g. re-running on the original training data).
"""
import re
from typing import List
import torch
from transformers import PreTrainedModel
from nltk.tokenize import wordpunct_tokenize
from .configuration_radgraph import RadgraphConfig
from .document import Document
from .model import DyGIEModel
from .predictor import predict_document
from .vocab import Vocabulary
def _preprocess_report(text: str) -> str:
"""Exact port of radgraph/radgraph/utils.py:34-48 (inference-time preprocessing)."""
text = text.replace("\\n", " ")
text = text.replace("\\f", " ")
text = text.replace("\\u2122", " ")
text = text.replace("\n", " ")
text = text.replace("\\\"", "``")
text_sub = re.sub(r"\s+", " ", text)
t = " ".join(wordpunct_tokenize(text_sub))
t = t.replace(").", ") .")
t = t.replace("%.", "% .")
t = t.replace(".'", ". '")
t = t.replace("%,", "% ,")
t = t.replace("%)", "% )")
return t
def _build_vocab(config: RadgraphConfig) -> Vocabulary:
vocab = Vocabulary()
ner_ns = f"{config.dataset}__ner_labels"
rel_ns = f"{config.dataset}__relation_labels"
vocab.add_namespace(ner_ns)
vocab.add_namespace(rel_ns)
for token, _ in sorted(config.ner_labels.items(), key=lambda kv: kv[1]):
if token != "":
vocab.add_token(token, ner_ns)
for token, _ in sorted(config.relation_labels.items(), key=lambda kv: kv[1]):
if token != "":
vocab.add_token(token, rel_ns)
return vocab
class RadgraphModel(PreTrainedModel):
config_class = RadgraphConfig
def __init__(self, config: RadgraphConfig):
super().__init__(config)
self.vocab = _build_vocab(config)
self.model = DyGIEModel(
vocab=self.vocab,
encoder_name=config.encoder_name,
max_length=config.max_length,
max_span_width=config.max_span_width,
feature_size=config.feature_size,
feedforward_params=config.feedforward_params,
loss_weights=config.loss_weights,
relation_spans_per_word=config.relation_spans_per_word,
train_encoder=config.train_encoder,
span_pooling=config.span_pooling,
transformer_params=config.transformer_params,
relation_context=config.relation_context,
relation_feedforward_params=config.relation_feedforward_params,
)
# Required by transformers' from_pretrained machinery (sets up all_tied_weights_keys
# etc.) -- harmless here since DyGIEModel has no tied weights and the checkpoint's
# state dict gets loaded over whatever this touches anyway.
self.post_init()
def forward(self, words: List[str], doc_key: str = "doc"):
"""Low-level forward pass: pre-tokenized words in, raw model output dict out
(see DyGIEModel.forward / predict_document)."""
doc = Document.from_json({"doc_key": doc_key, "dataset": self.config.dataset, "sentences": [words]})
return predict_document(self.model, self.vocab, doc, self.config.max_span_width)
@torch.no_grad()
def predict_words(self, words: List[str], doc_key: str = "doc") -> dict:
result = self.forward(words, doc_key=doc_key)
return {
"doc_key": result["doc_key"],
"words": words,
"entities": [e.to_json() for e in result["predicted_ner"]],
"relations": [r.to_json() for r in result["predicted_relations"]],
}
@torch.no_grad()
def predict(self, text: str, doc_key: str = "doc") -> dict:
words = _preprocess_report(text).split()
return self.predict_words(words, doc_key=doc_key)