lukeingawesome commited on
Commit
ac5c585
·
verified ·
1 Parent(s): 2550218

Upload modeling_chest2vec_labeler.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modeling_chest2vec_labeler.py +69 -20
modeling_chest2vec_labeler.py CHANGED
@@ -50,6 +50,7 @@ class Chest2VecLabelerConfig(PretrainedConfig):
50
  instruction: str = "Given the following chest CT report, extract the presence/absence of entities",
51
  max_len: int = 512,
52
  default_threshold: float = 0.5,
 
53
  **kwargs,
54
  ):
55
  super().__init__(**kwargs)
@@ -62,6 +63,7 @@ class Chest2VecLabelerConfig(PretrainedConfig):
62
  self.instruction = instruction
63
  self.max_len = max_len
64
  self.default_threshold = default_threshold
 
65
 
66
 
67
  @dataclass
@@ -201,35 +203,82 @@ class Chest2VecLabelerModel(PreTrainedModel):
201
  names = out["labels"]
202
  return [{names[j]: "positive" for j in range(len(names)) if row[j]} for row in out["positive"]]
203
 
204
- # ---- CheXbert / SRR-BERT-style report-comparison F1 ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  @torch.no_grad()
206
  def score_reports(self, gt_reports: List[str], pred_reports: List[str], tokenizer=None,
207
  threshold: Optional[float] = None, batch_size: int = 16,
208
- max_len: Optional[int] = None, device=None) -> Dict[str, Any]:
 
209
  """
210
- Label both GT and predicted reports, then compute label-level F1 (CheXbert-style).
 
211
 
212
- Treats the labels extracted from `gt_reports` as ground truth and those from
213
- `pred_reports` as the prediction. Returns micro / macro / weighted P,R,F1 over the
214
- 137 labels, plus per-label scores.
215
  """
216
  from sklearn.metrics import precision_recall_fscore_support
 
217
  if len(gt_reports) != len(pred_reports):
218
  raise ValueError("gt_reports and pred_reports must have the same length")
219
- kw = dict(tokenizer=tokenizer, threshold=threshold, batch_size=batch_size, max_len=max_len, device=device)
220
- y_true = self.predict(gt_reports, **kw)["positive"]
221
- y_pred = self.predict(pred_reports, **kw)["positive"]
222
- names = list(self.config.labels)
223
-
224
- res: Dict[str, Any] = {"n_reports": len(gt_reports),
225
- "threshold": self.config.default_threshold if threshold is None else threshold}
226
- for avg in ("micro", "macro", "weighted"):
227
- p, r, f, _ = precision_recall_fscore_support(y_true, y_pred, average=avg, zero_division=0)
228
- res[avg] = {"precision": float(p), "recall": float(r), "f1": float(f)}
229
- p, r, f, s = precision_recall_fscore_support(y_true, y_pred, average=None,
230
- labels=list(range(len(names))), zero_division=0)
231
- res["per_label"] = {names[j]: {"precision": float(p[j]), "recall": float(r[j]),
232
- "f1": float(f[j]), "support_gt": int(s[j])} for j in range(len(names))}
 
 
 
 
 
 
 
 
 
 
 
 
233
  return res
234
 
235
 
 
50
  instruction: str = "Given the following chest CT report, extract the presence/absence of entities",
51
  max_len: int = 512,
52
  default_threshold: float = 0.5,
53
+ label_hierarchy: Optional[dict] = None,
54
  **kwargs,
55
  ):
56
  super().__init__(**kwargs)
 
63
  self.instruction = instruction
64
  self.max_len = max_len
65
  self.default_threshold = default_threshold
66
+ self.label_hierarchy = label_hierarchy or {}
67
 
68
 
69
  @dataclass
 
203
  names = out["labels"]
204
  return [{names[j]: "positive" for j in range(len(names)) if row[j]} for row in out["positive"]]
205
 
206
+ # ---- hierarchy roll-up (leaf -> upper -> anatomy), max over children ----
207
+ def aggregate_hierarchy(self, leaf_prob):
208
+ """Roll leaf positive-probabilities up to upper and anatomy levels (max over children).
209
+
210
+ Mirrors the training-time evaluation: each upper group's score is the max over its
211
+ child-leaf probabilities; each anatomy score is the max over its upper groups plus the
212
+ section's `*_others` leaf. Returns (upper_prob, upper_names, anatomy_prob, anatomy_names).
213
+ """
214
+ import numpy as np
215
+ leaf_prob = np.asarray(leaf_prob, dtype=np.float32)
216
+ H = self.config.label_hierarchy or {}
217
+ idx = {n: i for i, n in enumerate(self.config.labels)}
218
+ N = leaf_prob.shape[0]
219
+ u_names, u_cols, a_names, a_cols = [], [], [], []
220
+ for anat, groups in H.items():
221
+ a_names.append(anat)
222
+ ac = np.full(N, -1.0, dtype=np.float32)
223
+ for up, leaves in groups.items():
224
+ u_names.append(up)
225
+ cols = [idx[l] for l in leaves if l in idx]
226
+ uc = leaf_prob[:, cols].max(axis=1) if cols else np.zeros(N, dtype=np.float32)
227
+ u_cols.append(uc)
228
+ ac = np.maximum(ac, uc)
229
+ okey = f"{anat}_others"
230
+ if okey in idx:
231
+ ac = np.maximum(ac, leaf_prob[:, idx[okey]])
232
+ a_cols.append(np.maximum(ac, 0.0))
233
+ import numpy as _np
234
+ up = _np.column_stack(u_cols) if u_cols else _np.zeros((N, 0), dtype=_np.float32)
235
+ an = _np.column_stack(a_cols) if a_cols else _np.zeros((N, 0), dtype=_np.float32)
236
+ return up, u_names, an, a_names
237
+
238
+ # ---- CheXbert / SRR-BERT-style report-comparison F1 (leaf / upper / anatomy) ----
239
  @torch.no_grad()
240
  def score_reports(self, gt_reports: List[str], pred_reports: List[str], tokenizer=None,
241
  threshold: Optional[float] = None, batch_size: int = 16,
242
+ max_len: Optional[int] = None, device=None,
243
+ levels=("leaf", "upper", "anatomy")) -> Dict[str, Any]:
244
  """
245
+ Label both GT and predicted reports, then compute label-agreement F1 (CheXbert-style)
246
+ at the requested hierarchy levels.
247
 
248
+ `gt_reports` labels are treated as truth, `pred_reports` as prediction. For each level
249
+ in `levels` ("leaf" = 137 labels, "upper" = container groups, "anatomy" = sections),
250
+ returns micro / macro / weighted precision-recall-F1 plus per-label scores.
251
  """
252
  from sklearn.metrics import precision_recall_fscore_support
253
+ import numpy as np
254
  if len(gt_reports) != len(pred_reports):
255
  raise ValueError("gt_reports and pred_reports must have the same length")
256
+ thr = self.config.default_threshold if threshold is None else threshold
257
+ kw = dict(tokenizer=tokenizer, batch_size=batch_size, max_len=max_len, device=device)
258
+ gt_leaf = self.predict_proba(gt_reports, **kw).numpy()
259
+ pr_leaf = self.predict_proba(pred_reports, **kw).numpy()
260
+
261
+ level_inputs = {"leaf": (gt_leaf, pr_leaf, list(self.config.labels))}
262
+ if "upper" in levels or "anatomy" in levels:
263
+ gu, un, ga, an = self.aggregate_hierarchy(gt_leaf)
264
+ pu, _, pa, _ = self.aggregate_hierarchy(pr_leaf)
265
+ level_inputs["upper"] = (gu, pu, un)
266
+ level_inputs["anatomy"] = (ga, pa, an)
267
+
268
+ res: Dict[str, Any] = {"n_reports": len(gt_reports), "threshold": thr}
269
+ for lvl in levels:
270
+ gp, pp, names = level_inputs[lvl]
271
+ y_true = (gp >= thr).astype(int)
272
+ y_pred = (pp >= thr).astype(int)
273
+ block: Dict[str, Any] = {"n_labels": len(names)}
274
+ for avg in ("micro", "macro", "weighted"):
275
+ p, r, f, _ = precision_recall_fscore_support(y_true, y_pred, average=avg, zero_division=0)
276
+ block[avg] = {"precision": float(p), "recall": float(r), "f1": float(f)}
277
+ p, r, f, s = precision_recall_fscore_support(y_true, y_pred, average=None,
278
+ labels=list(range(len(names))), zero_division=0)
279
+ block["per_label"] = {names[j]: {"precision": float(p[j]), "recall": float(r[j]),
280
+ "f1": float(f[j]), "support_gt": int(s[j])} for j in range(len(names))}
281
+ res[lvl] = block
282
  return res
283
 
284