Feature Extraction
Transformers
Safetensors
Italian
radgraph_it
radiology
information-extraction
named-entity-recognition
relation-extraction
medical
radgraph
custom_code
Instructions to use radgraphIT/Radgraph-IT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use radgraphIT/Radgraph-IT with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="radgraphIT/Radgraph-IT", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("radgraphIT/Radgraph-IT", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 4,997 Bytes
0c48771 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | """NER head: score every candidate span, cross-entropy against the gold label (or null).
Port of `radgraph.dygie.models.ner.NERTagger`. Simplified for batch_size=1 (dataset.py): no
span mask, no per-sentence looping -- one document's worth of spans at a time.
`transformer_params` (medbit_transformer arm, see notes/v2_plan.md item 1) swaps the plain
FeedForward scorer for a TransformerBlock: every candidate span attends to every other
candidate span in the document before classification, instead of each span being scored in
isolation. NER scores every candidate span unpruned, so this is full O(n^2) self-attention
over the whole doc's span pool (accepted cost, not capped -- see v2_plan.md item 1).
"""
from typing import Dict, List
import torch
from torch import nn
from .document import PredictedNER, Sentence
from .feedforward import FeedForward
from .metrics import NERMetrics
from .transformer_block import TransformerBlock
from .vocab import Vocabulary
class NERHead(nn.Module):
def __init__(self, vocab: Vocabulary, span_emb_dim: int, feedforward_params: dict,
transformer_params: dict = None):
super().__init__()
self.vocab = vocab
self.namespaces = [ns for ns in vocab.get_namespaces() if ns.endswith("ner_labels")]
self.n_labels = {ns: vocab.get_vocab_size(ns) for ns in self.namespaces}
self.use_transformer = transformer_params is not None
self.scorers = nn.ModuleDict()
self.classifiers = nn.ModuleDict()
self.metrics: Dict[str, NERMetrics] = {}
for ns in self.namespaces:
if self.use_transformer:
body = TransformerBlock(span_emb_dim, **transformer_params)
else:
body = FeedForward(span_emb_dim, feedforward_params["hidden_dims"],
feedforward_params["dropout"])
self.scorers[ns] = body
self.classifiers[ns] = nn.Linear(body.output_dim, self.n_labels[ns] - 1)
self.metrics[ns] = NERMetrics(self.n_labels[ns])
self._loss = nn.CrossEntropyLoss(reduction="sum")
def forward(self, dataset: str, spans: List[tuple], span_embeddings: torch.Tensor,
sentence: Sentence, ner_label_ids: torch.Tensor = None) -> dict:
ns = f"{dataset}__ner_labels"
if self.use_transformer:
starts = torch.tensor([s[0] for s in spans], dtype=torch.long,
device=span_embeddings.device)
hidden = self.scorers[ns](span_embeddings, starts)
else:
hidden = self.scorers[ns](span_embeddings)
scores = self.classifiers[ns](hidden) # (num_spans, n_labels - 1)
dummy = scores.new_zeros(scores.size(0), 1) # null-label score, fixed at 0
scores = torch.cat([dummy, scores], dim=-1) # (num_spans, n_labels)
predicted = scores.argmax(dim=-1)
predictions = self.decode(ns, scores.detach(), spans, sentence)
output = {"namespace": ns, "scores": scores, "predicted": predicted, "predictions": predictions}
if ner_label_ids is not None:
self.metrics[ns](predicted, ner_label_ids)
output["loss"] = self._loss(scores, ner_label_ids)
return output
def decode(self, namespace: str, scores: torch.Tensor, spans: List[tuple],
sentence: Sentence) -> List[PredictedNER]:
softmax_scores = torch.softmax(scores, dim=-1)
raw, predicted = scores.max(dim=-1)
soft, _ = softmax_scores.max(dim=-1)
predictions = []
for i in (predicted != 0).nonzero(as_tuple=True)[0].tolist():
label = self.vocab.get_token_from_index(int(predicted[i]), namespace)
start, end = spans[i]
entry = [start, end, label, float(raw[i]), float(soft[i])]
predictions.append(PredictedNER(entry, sentence, sentence_offsets=True))
return predictions
def get_metrics(self, reset: bool = False) -> Dict[str, float]:
res = {}
for ns, metric in self.metrics.items():
prefix = ns.replace("_labels", "")
per_class = metric.get_per_class() # {class idx -> F1}, read BEFORE the reset below
for idx, class_f1 in per_class.items():
res[f"ner_perclass/{self.vocab.get_token_from_index(idx, ns)}"] = class_f1
res["ner_macro_f1"] = sum(per_class.values()) / len(per_class) if per_class else 0.0
precision, recall, f1 = metric.get_metric(reset)
res[f"{prefix}_precision"] = precision
res[f"{prefix}_recall"] = recall
res[f"{prefix}_f1"] = f1
for name in ("precision", "recall", "f1"):
values = [v for k, v in res.items()
if k.endswith(f"_{name}") and "perclass/" not in k and "_macro_" not in k]
res[f"MEAN__ner_{name}"] = sum(values) / len(values) if values else 0.0
return res
|