Skip to content

Commit 0efed1d

Browse files
authored
Merge pull request #14 from nancytanaka1/chore/tidy-up-post-public
docs+refactor: reframe Mullenbach 2018 from benchmark to inspiration; deprecate compare_to_mullenbach
2 parents 3949a06 + 3fd0521 commit 0efed1d

9 files changed

Lines changed: 249 additions & 173 deletions

File tree

DECISIONS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Format: `[Decision]: [What was chosen] — [Why] — [Alternatives considered]`
1313
- **2026-04-20** — Chunk-and-max-pool over full notes; Longformer deferred
1414
- **2026-04-21** — Defer date un-shifting to Silver; EDA chart flagged as diagnostic-only
1515
- **2026-04-23** — TF-IDF + LR baseline ships on the F1 story; P@k floor lowered to informational
16+
- **2026-04-26** — Reframing Mullenbach 2018 from benchmark to inspiration
1617

1718
---
1819

@@ -136,3 +137,30 @@ would be portfolio-irrelevant. Real F1 numbers will come from
136137
**Conclusion** — loop validated end-to-end. Tokenization, chunking, fp16 training,
137138
MLflow logging, and TrainingArguments wiring all confirmed working. Ready to scale
138139
on Databricks GPU.
140+
141+
142+
## 2026-04-26 — Reframing Mullenbach 2018 from benchmark to inspiration
143+
144+
Earlier drafts of the README presented numerical deltas between this work's
145+
results on MIMIC-IV/ICD-10 top-50 and Mullenbach et al. 2018's results on
146+
MIMIC-III/ICD-9 top-50 (Table 5). On review, the comparison is
147+
methodologically invalid: different dataset, different coding system,
148+
different cohort, different label space. Numerical proximity ("Micro F1
149+
within 0.003") is confounded by all four factors and does not constitute
150+
benchmark equivalence.
151+
152+
Decision: drop all numerical comparisons to Mullenbach 2018 from the README,
153+
the metric targets, and the MLflow logged metrics. Retain the methodological
154+
inheritance (multi-label framing, patient-level splits, P@k as
155+
coder-assist-relevant, top-50 label cardinality) and cite the paper as the
156+
intellectual antecedent.
157+
158+
A future apples-to-apples reproduction on MIMIC-III/ICD-9 — using
159+
Mullenbach's published cohort logic and label set — would make benchmark
160+
comparison meaningful. Tracked as future work; out of scope for the current
161+
job-search-window deliverable.
162+
163+
Affected:
164+
- README.md (headline, §1, §6, §14)
165+
- src/mimic_icd_coder/evaluate.py (compare_to_mullenbach function deprecated)
166+
- MLflow logged metrics (delta_vs_caml_* removed from baseline runs going forward)

README.md

Lines changed: 138 additions & 122 deletions
Large diffs are not rendered by default.

reports/EDA_Report.docx

-118 KB
Binary file not shown.

reports/baseline_error_analysis.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
**Evaluation data:** held-out patient-level test split (n=12,091 admissions, 6,567 patients, seed 42)
77
> **TL;DR** — 30-second skim.
88
>
9-
> - **Baseline beats Mullenbach 2018 CAML on F1.** Micro F1 0.617 (+0.003 vs. CAML), Macro F1 0.584 (+0.052). Trails on P@5 (0.526 vs. 0.609) — a deliberate trade from `class_weight="balanced"` + F1-optimal thresholds.
9+
> - **Baseline ships strong absolute numbers.** Micro F1 0.617, Macro F1 0.584 on held-out patient-level test split (n=12,091, 6,567 patients). P@5 = 0.526 — a deliberate trade from `class_weight="balanced"` + F1-optimal per-label thresholds.
1010
> - **Signature failure pattern:** Z87.891 (personal history of nicotine dependence) is systematically over-fired in 9 of the top 10 confusion pairs at 36–49% FP rates on unrelated conditions. Root cause is calibration, not representation — a transformer encoder alone will not fix this.
1111
> - **Pre-registered transformer predictions:** F17.210 (current vs. former smoker), K59.00 (buried constipation meds), and Z23 (buried immunization) should exit the worst-10 after Bio_ClinicalBERT fine-tuning. Specific F1 targets and falsification conditions are at the bottom of this document.
1212

reports/eval_report.qmd

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ format:
1515

1616
*To be filled after baseline training completes.*
1717

18-
One paragraph: model choice, cohort, headline metrics, delta versus Mullenbach et al. 2018.
18+
One paragraph: model choice, cohort, absolute headline metrics, key MLOps disciplines (patient-level splits, per-label thresholds, reproducibility, calibration analysis).
1919

2020
# Research Question
2121

22-
Given a free-text discharge summary, predict the set of ICD-10 codes assigned to that admission. Benchmark against Mullenbach et al. 2018 CAML (MIMIC-III top-50 ICD-9).
22+
Given a free-text discharge summary, predict the set of ICD-10 codes assigned to that admission. The multi-label framing, patient-level evaluation discipline, and top-50 cardinality are inherited from Mullenbach et al. 2018 (CAML, MIMIC-III/ICD-9 top-50); this work targets MIMIC-IV/ICD-10 and does not claim apples-to-apples benchmark equivalence — see [`DECISIONS.md`](../DECISIONS.md) 2026-04-26.
2323

2424
# Data
2525

2626
Cohort construction, composition, and limitations are documented in [`reports/data_card.md`](data_card.md). Headline numbers:
2727

2828
- MIMIC-IV-Note v2.2 + MIMIC-IV v3.1 Hosp, joined on `hadm_id`.
2929
- Top-50 ICD-10 codes covering 91.04% of admissions; long tail spans 19,440 distinct codes.
30-
- Modelable cohort: **122,288 admissions**.
30+
- Modelable cohort: **122,283 admissions**.
3131
- Splits: patient-level 80/10/10 by `subject_id`, seed = 42.
3232

3333
# Methods
@@ -47,14 +47,16 @@ Full rationale in [`DECISIONS.md`](../DECISIONS.md).
4747

4848
## Headline Metrics (Test Split)
4949

50-
| Metric | Baseline (TF-IDF + LR) | Chunked Bio_ClinicalBERT | Mullenbach 2018 CAML (MIMIC-III top-50) | Target | Floor | Target Δ vs. CAML |
51-
|---|---|---|---|---|---|---|
52-
| Micro F1 | **0.617** | TBD | 0.614 | ≥ 0.70 | 0.55 | +0.086 |
53-
| Macro F1 | **0.584** | TBD | 0.532 | ≥ 0.55 | 0.40 | +0.018 |
54-
| P@5 | 0.526 | TBD | 0.609 | ≥ 0.70 || +0.091 |
55-
| P@8 | 0.433 | TBD | n/a (not reported for top-50) | ≥ 0.65 |||
50+
Test-split results on MIMIC-IV-Note v2.2 + MIMIC-IV v3.1 Hosp, top-50 ICD-10 codes. Patient-level held-out test split, n=12,091 admissions, seed=42.
5651

57-
CAML baseline values are from Mullenbach et al. 2018, Table 5 ("Results on MIMIC-III, 50 labels"), CAML row. P@8 is not reported in Table 5 (the top-50 setting); the paper's P@8 values (0.709 for MIMIC-III full, 0.523 for MIMIC-II full) are from different evaluation settings and are not used here. See [`src/mimic_icd_coder/evaluate.py::MULLENBACH_CAML_TOP50`](../src/mimic_icd_coder/evaluate.py) for the citation.
52+
| Metric | Baseline (TF-IDF + LR) | Chunked Bio_ClinicalBERT | Target | Floor |
53+
|---|---|---|---|---|
54+
| Micro F1 | **0.617** | TBD | ≥ 0.70 | 0.55 |
55+
| Macro F1 | **0.584** | TBD | ≥ 0.55 | 0.40 |
56+
| P@5 | 0.526 | TBD | ≥ 0.70 ||
57+
| P@8 | 0.433 | TBD | ≥ 0.65 ||
58+
59+
Targets are absolute, not benchmark-relative. They reflect the operational threshold for "the transformer branch is delivering value over the baseline." Numerical comparison to Mullenbach et al. 2018's published numbers is not methodologically valid because this work targets MIMIC-IV/ICD-10 (different dataset, different coding system, different cohort, different label space) — see [`DECISIONS.md`](../DECISIONS.md) 2026-04-26.
5860

5961
## Per-Label Error Analysis
6062

@@ -74,23 +76,26 @@ Macro F1 reported by:
7476

7577
Temporal fairness deferred pending Silver-stage date un-shifting. Race-stratified analysis is deferred pending Silver/Gold integration of the demographic columns — race is already ingested to Bronze (per `feat/ingest-demographic`), but the fairness slicing pass has not been implemented. See [`DECISIONS.md`](../DECISIONS.md) 2026-04-21.
7678

77-
# Comparison to Mullenbach 2018
79+
# Methodological inheritance from Mullenbach 2018
80+
81+
Mullenbach et al. 2018 (CAML) established the multi-label ICD-coding benchmark on MIMIC-III/ICD-9 top-50. This work inherits its methodological framing — multi-label classification with sigmoid + per-label decisions, patient-level evaluation, P@k as a coder-assist-relevant metric, top-50 label cardinality as a tractable problem size. These are methodological inheritances, not benchmark equivalences.
7882

79-
Metric deltas are reported with the following caveats:
83+
Numerical comparison between this work's results and Mullenbach's published numbers is **not methodologically valid**. The work differs from Mullenbach 2018 along all four axes that matter:
8084

81-
- Cohort differences — size (47K MIMIC-III vs. 122K here), era (pre-2015 vs. 2008–2019), code system (ICD-9 vs. ICD-10).
82-
- ICD-10 sub-code fragmentation redistributes common conditions (CHF, CKD, COPD, T2DM) across several codes, so top-50 label spaces are not identical.
83-
- CAML trained on full-length notes with custom attention; this work uses chunked Bio_ClinicalBERT.
85+
- **Different dataset.** Mullenbach used MIMIC-III v1.4; this work uses MIMIC-IV v3.1 + MIMIC-IV-Note v2.2.
86+
- **Different coding system.** Mullenbach used ICD-9-CM; this work uses ICD-10-CM. Label spaces are non-overlapping; the top-50 codes in each are different sets covering different clinical concepts.
87+
- **Different cohort.** Different size (47K MIMIC-III vs. 122K here), different era (pre-2015 vs. 2008–2019), different documentation conventions.
88+
- **Different model architecture.** CAML used full-length notes with custom label-attention; this work uses chunked Bio_ClinicalBERT.
8489

85-
Deltas are informative but not strictly apples-to-apples.
90+
A future apples-to-apples reproduction on MIMIC-III/ICD-9 — using Mullenbach's published cohort logic and label set — would make benchmark comparison meaningful. Tracked as future work in [`DECISIONS.md`](../DECISIONS.md) 2026-04-26; out of scope for the current deliverable.
8691

8792
# Limitations
8893

8994
See [`reports/data_card.md`](data_card.md). Key points relevant to eval interpretation:
9095

9196
- Cohort is 48% of all ICD-10 admissions in v3.1 Hosp, limited by MIMIC-IV-Note v2.2 sampling.
9297
- Temporal and race-stratified fairness evaluation are deferred, not absent.
93-
- Top-50 K is comparability-driven, not coverage-optimal; raising K would improve coverage but weaken the CAML comparison.
98+
- Top-50 K is methodologically inherited from Mullenbach 2018 as a tractable problem size, not coverage-optimal. Raising K would improve coverage but the choice here keeps the methodological framing aligned with prior published work even though direct numerical comparison is invalid (see *Methodological inheritance from Mullenbach 2018* above).
9499

95100
# References
96101

reports/model_card.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,40 @@ Template: Mitchell et al. 2019.
1818

1919
## Intended Use
2020

21-
- **Primary use:** Production-grade benchmark for clinical NLP ICD-10 auto-coding on MIMIC-IV, deployed as an Azure Databricks Model Serving endpoint for reproducible inference. Demonstrates the full MLOps lifecycle — feature engineering, model validation against a published benchmark (Mullenbach et al. 2018 CAML), Unity Catalog Model Registry, drift monitoring.
21+
- **Primary use:** Reproducible end-to-end clinical NLP + MLOps reference build for ICD-10 auto-coding on MIMIC-IV, deployed as an Azure Databricks Model Serving endpoint for reproducible inference. Demonstrates the full MLOps lifecycle — feature engineering, multi-label model training and evaluation discipline, Unity Catalog Model Registry, drift monitoring. Methodologically inspired by Mullenbach et al. 2018 (CAML) on MIMIC-III/ICD-9; this work targets MIMIC-IV/ICD-10 and does not claim apples-to-apples benchmark equivalence (see [`DECISIONS.md`](../DECISIONS.md) 2026-04-26).
2222
- **Out of scope:** Clinical decision support, real-world coding workflows, any production health-system deployment. The MIMIC-IV license restricts use to research and benchmarking on credentialed PhysioNet data only.
2323
- **Users:** Data scientists and ML engineers evaluating production clinical NLP patterns; research teams benchmarking against this implementation on MIMIC-IV top-50 ICD-10.
2424

2525
## Training Data
2626

2727
Cohort definition, composition, and limitations are documented in [`reports/data_card.md`](data_card.md). Model-specific details:
2828

29-
- Modelable cohort: **122,288 admissions**.
29+
- Modelable cohort: **122,283 admissions**.
3030
- Splits: patient-level 80/10/10 by `subject_id`, seed = 42.
3131
- Labels: top-50 ICD-10 codes, covering 91.04% of cohort admissions.
3232

3333
## Evaluation Data
3434

3535
- **Test split:** Held-out 10% of patients, no overlap with train or val.
3636
- **Metrics:** Micro F1, Macro F1, P@5, P@8, per-label F1.
37-
- **Reference baseline:** Mullenbach et al. 2018 CAML on MIMIC-III top-50 ICD-9 — Table 5 of the paper. Micro F1 = 0.614, Macro F1 = 0.532, P@5 = 0.609. P@8 is not reported for the top-50 setting.
37+
- **Methodological inspiration:** Mullenbach et al. 2018 (CAML) established the multi-label ICD-coding benchmark on MIMIC-III/ICD-9 top-50 — its multi-label framing, patient-level evaluation discipline, and top-50 cardinality are inherited here. Numerical comparison is *not* methodologically valid because this work targets MIMIC-IV/ICD-10 (different dataset, different coding system, different cohort, different label space). See [`DECISIONS.md`](../DECISIONS.md) 2026-04-26.
3838

39-
Full evaluation methodology and comparison caveats are in [`reports/eval_report.qmd`](eval_report.qmd).
39+
Full evaluation methodology is in [`reports/eval_report.qmd`](eval_report.qmd).
4040

4141
## Performance
4242

43-
| Metric | Target | Floor | Mullenbach 2018 CAML (MIMIC-III top-50) | Target Δ vs. CAML | Result |
44-
|---|---|---|---|---|---|
45-
| Micro F1 | ≥ 0.70 | 0.55 | 0.614 | +0.086 | **0.617** (baseline) / TBD (transformer) |
46-
| Macro F1 | ≥ 0.55 | 0.40 | 0.532 | +0.018 | **0.584** (baseline) / TBD (transformer) |
47-
| P@5 | ≥ 0.70 || 0.609 | +0.091 | 0.526 (baseline) / TBD (transformer) |
48-
| P@8 | ≥ 0.65 || n/a (Mullenbach Table 5 reports P@5 only) || 0.433 (baseline) / TBD (transformer) |
43+
Test-split results on MIMIC-IV-Note v2.2 + MIMIC-IV v3.1 Hosp, top-50 ICD-10 codes. Patient-level held-out test split, n=12,091 admissions, seed=42.
4944

50-
Baseline = TF-IDF (1–2 gram, min_df=5, max_features=200k) + One-vs-Rest Logistic Regression (`class_weight="balanced"`, liblinear solver, max_iter=1000) with per-label F1-optimal thresholds. Evaluated on held-out patient-level test split, n=12,091 admissions, seed=42. MLflow run `4e577699a67a4027bc27628e9b237ac5`. Baseline beats Mullenbach CAML on both F1 metrics; P@k is intentionally de-emphasized for the baseline and is the transformer branch's primary gate — see [`DECISIONS.md`](../DECISIONS.md) 2026-04-23.
45+
| Metric | Target | Floor | Result |
46+
|---|---|---|---|
47+
| Micro F1 | ≥ 0.70 | 0.55 | **0.617** (baseline) / TBD (transformer) |
48+
| Macro F1 | ≥ 0.55 | 0.40 | **0.584** (baseline) / TBD (transformer) |
49+
| P@5 | ≥ 0.70 || 0.526 (baseline) / TBD (transformer) |
50+
| P@8 | ≥ 0.65 || 0.433 (baseline) / TBD (transformer) |
5151

52-
CAML values from Mullenbach et al. 2018 Table 5 (MIMIC-III, 50 labels). See [`src/mimic_icd_coder/evaluate.py::MULLENBACH_CAML_TOP50`](../src/mimic_icd_coder/evaluate.py) for the citation.
52+
Targets are absolute, not benchmark-relative. They reflect the operational threshold for "the transformer branch is delivering value over the baseline" rather than a literature comparison.
53+
54+
Baseline = TF-IDF (1–2 gram, min_df=5, max_features=200k) + One-vs-Rest Logistic Regression (`class_weight="balanced"`, liblinear solver, max_iter=1000) with per-label F1-optimal thresholds. MLflow run `4e577699a67a4027bc27628e9b237ac5`. P@k is intentionally de-emphasized for the baseline and is the transformer branch's primary gate — see [`DECISIONS.md`](../DECISIONS.md) 2026-04-23.
5355

5456
## Fairness Analysis
5557

src/mimic_icd_coder/evaluate.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -175,19 +175,36 @@ def evaluate_multilabel(
175175
return result
176176

177177

178-
def compare_to_mullenbach(result: EvalResult) -> dict[str, float]:
179-
"""Return absolute deltas from the Mullenbach 2018 CAML top-50 baseline.
178+
def compare_to_mullenbach(result: EvalResult) -> dict[str, float]: # noqa: ARG001
179+
"""**Deprecated.** Returns numeric deltas from Mullenbach 2018 CAML top-50.
180+
181+
.. deprecated:: 2026-04-26
182+
See ``DECISIONS.md`` 2026-04-26 — "Reframing Mullenbach 2018 from
183+
benchmark to inspiration." Mullenbach's published numbers are on
184+
MIMIC-III/ICD-9 top-50; this project trains on MIMIC-IV/ICD-10
185+
top-50. Different dataset, different coding system, different
186+
cohort, different label space. Numerical proximity does not
187+
constitute benchmark equivalence and the deltas are
188+
methodologically invalid as a comparison.
189+
190+
Retained as a no-op-with-warning so any downstream caller (e.g.
191+
``pipeline.run_evaluate_test``) surfaces the issue at runtime
192+
instead of silently logging meaningless ``mullenbach_*_delta``
193+
metrics. Will be removed once
194+
``pipeline.run_evaluate_test`` stops calling it.
180195
181-
Positive = we beat the baseline.
196+
Returns:
197+
Empty dict. Issues a ``DeprecationWarning``.
182198
"""
183-
deltas = {
184-
"micro_f1_delta": result.micro_f1 - MULLENBACH_CAML_TOP50["micro_f1"],
185-
"macro_f1_delta": result.macro_f1 - MULLENBACH_CAML_TOP50["macro_f1"],
186-
}
187-
if 5 in result.precision_at_k and "p_at_5" in MULLENBACH_CAML_TOP50:
188-
deltas["p_at_5_delta"] = result.precision_at_k[5] - MULLENBACH_CAML_TOP50["p_at_5"]
189-
# Mullenbach Table 5 does not report P@8 for the 50-label setting — no
190-
# apples-to-apples baseline exists, so we deliberately skip the delta.
191-
if 8 in result.precision_at_k and "p_at_8" in MULLENBACH_CAML_TOP50:
192-
deltas["p_at_8_delta"] = result.precision_at_k[8] - MULLENBACH_CAML_TOP50["p_at_8"]
193-
return deltas
199+
import warnings
200+
201+
warnings.warn(
202+
"compare_to_mullenbach is deprecated as of 2026-04-26. The numeric "
203+
"deltas are methodologically invalid (different dataset, different "
204+
"coding system, different cohort vs. Mullenbach 2018). See "
205+
"DECISIONS.md 2026-04-26. This function now returns an empty dict; "
206+
"remove the call site to silence this warning.",
207+
DeprecationWarning,
208+
stacklevel=2,
209+
)
210+
return {}

src/mimic_icd_coder/pipeline.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,15 @@ def run_evaluate_test(cfg: AppConfig, paths: Paths) -> dict[str, float]:
419419
scoreboard.
420420
421421
Returns:
422-
A flat dictionary of test metrics. Also includes ``mullenbach_*_delta``
423-
keys — how far our numbers are from the published CAML baseline.
422+
A flat dictionary of test metrics: micro/macro F1, AUC, AUPRC, P@k.
423+
424+
Note:
425+
``mullenbach_*_delta`` keys were previously included here. Removed
426+
per ``DECISIONS.md`` 2026-04-26 — Mullenbach 2018's published numbers
427+
are MIMIC-III/ICD-9 top-50; this work trains on MIMIC-IV/ICD-10
428+
top-50, so the numerical deltas are methodologically invalid.
424429
"""
425-
from mimic_icd_coder.evaluate import compare_to_mullenbach, evaluate_multilabel
430+
from mimic_icd_coder.evaluate import evaluate_multilabel
426431
from mimic_icd_coder.models.baseline import BaselineModel
427432

428433
silver = pd.read_parquet(paths.silver / "notes.parquet")
@@ -442,7 +447,5 @@ def run_evaluate_test(cfg: AppConfig, paths: Paths) -> dict[str, float]:
442447
top_k_list=cfg.evaluation.top_k_metrics,
443448
)
444449
metrics = result.to_dict()
445-
deltas = compare_to_mullenbach(result)
446450
logger.info("test.metrics", **metrics)
447-
logger.info("test.vs_mullenbach", **deltas)
448-
return {**metrics, **{f"mullenbach_{k}": v for k, v in deltas.items()}}
451+
return metrics

tests/test_pipeline_integration.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ def test_pipeline_baseline_beats_random(synthetic_workspace: tuple[AppConfig, Pa
145145

146146
test_metrics = run_evaluate_test(cfg, paths)
147147
assert "micro_f1" in test_metrics
148-
assert "mullenbach_micro_f1_delta" in test_metrics
148+
# mullenbach_*_delta keys were removed per DECISIONS.md 2026-04-26
149+
# (different dataset / coding system / cohort vs. Mullenbach 2018);
150+
# asserting on test_metrics["micro_f1"] alone is the right contract now.
151+
assert not any(
152+
k.startswith("mullenbach_") for k in test_metrics
153+
), "mullenbach_* keys must not be present after the 2026-04-26 reframe"
149154
# Test metrics on a tiny synthetic corpus may trail val; sanity bound only
150155
assert test_metrics["micro_f1"] > 0.1

0 commit comments

Comments
 (0)