Skip to content

Commit 144c06a

Browse files
authored
dl4h final project kobeguo2 - CaliForest (#999)
* dl4h final project kobeguo2 - CaliForest * Update CaliForest to require explicit fit before inference * Remove unused logit_scale from CaliForest
1 parent 5f63039 commit 144c06a

6 files changed

Lines changed: 582 additions & 0 deletions

File tree

docs/api/models.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,4 @@ API Reference
205205
models/pyhealth.models.TextEmbedding
206206
models/pyhealth.models.BIOT
207207
models/pyhealth.models.unified_multimodal_embedding_docs
208+
models/pyhealth.models.califorest
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pyhealth.models.califorest
2+
==========================
3+
4+
.. automodule:: pyhealth.models.califorest
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

examples/mimic4_califorest.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from __future__ import annotations
2+
3+
import os
4+
5+
import numpy as np
6+
import torch
7+
from sklearn.ensemble import RandomForestClassifier
8+
from sklearn.metrics import brier_score_loss, roc_auc_score
9+
10+
from pyhealth.datasets import (
11+
MIMIC4EHRDataset,
12+
create_sample_dataset,
13+
get_dataloader,
14+
)
15+
from pyhealth.models import CaliForest
16+
from pyhealth.tasks import InHospitalMortalityMIMIC4
17+
18+
19+
# Set your MIMIC-IV dataset path via environment variable before running:
20+
# export MIMIC4_ROOT=/your/path/to/mimiciv/3.1
21+
ROOT = os.getenv("MIMIC4_ROOT")
22+
23+
24+
def evaluate(y_true: np.ndarray, y_prob: np.ndarray) -> dict[str, float]:
25+
"""Compute AUROC and Brier score."""
26+
y_true = np.asarray(y_true).reshape(-1)
27+
y_prob = np.asarray(y_prob).reshape(-1)
28+
return {
29+
"auroc": float(roc_auc_score(y_true, y_prob)),
30+
"brier": float(brier_score_loss(y_true, y_prob)),
31+
}
32+
33+
34+
def run_califorest(
35+
X_train: np.ndarray,
36+
y_train: np.ndarray,
37+
X_test: np.ndarray,
38+
y_test: np.ndarray,
39+
calibration: str,
40+
) -> dict[str, float]:
41+
"""Train and evaluate CaliForest on tabularized features."""
42+
train_samples = []
43+
for i in range(len(X_train)):
44+
train_samples.append(
45+
{
46+
"patient_id": f"train-{i}",
47+
"visit_id": f"train-{i}",
48+
"features": X_train[i].tolist(),
49+
"label": int(y_train[i]),
50+
}
51+
)
52+
53+
test_samples = []
54+
for i in range(len(X_test)):
55+
test_samples.append(
56+
{
57+
"patient_id": f"test-{i}",
58+
"visit_id": f"test-{i}",
59+
"features": X_test[i].tolist(),
60+
"label": int(y_test[i]),
61+
}
62+
)
63+
64+
train_dataset = create_sample_dataset(
65+
samples=train_samples,
66+
input_schema={"features": "tensor"},
67+
output_schema={"label": "binary"},
68+
dataset_name=f"mimic4_train_tabular_{calibration}",
69+
)
70+
test_dataset = create_sample_dataset(
71+
samples=test_samples,
72+
input_schema={"features": "tensor"},
73+
output_schema={"label": "binary"},
74+
dataset_name=f"mimic4_test_tabular_{calibration}",
75+
)
76+
77+
train_loader = get_dataloader(
78+
train_dataset, batch_size=len(train_dataset), shuffle=False
79+
)
80+
test_loader = get_dataloader(
81+
test_dataset, batch_size=len(test_dataset), shuffle=False
82+
)
83+
84+
test_batch = next(iter(test_loader))
85+
86+
model = CaliForest(
87+
dataset=train_dataset,
88+
n_estimators=100,
89+
calibration=calibration,
90+
random_state=42,
91+
)
92+
model.fit(train_loader)
93+
94+
with torch.no_grad():
95+
ret = model(**test_batch)
96+
97+
cali_probs = ret["y_prob"].detach().cpu().numpy().reshape(-1)
98+
return evaluate(y_test, cali_probs)
99+
100+
101+
def main():
102+
if not ROOT:
103+
raise ValueError(
104+
"MIMIC4_ROOT is not set. Example:\n"
105+
"export MIMIC4_ROOT=/your/path/to/mimiciv/3.1"
106+
)
107+
108+
print("=" * 80)
109+
print("Loading MIMIC-IV EHR dataset")
110+
print("=" * 80)
111+
112+
dataset = MIMIC4EHRDataset(
113+
root=ROOT,
114+
tables=["diagnoses_icd", "procedures_icd", "labevents"],
115+
)
116+
117+
task = InHospitalMortalityMIMIC4()
118+
sample_dataset = dataset.set_task(task)
119+
120+
print(f"Total samples: {len(sample_dataset)}")
121+
122+
subset_size = 2000
123+
raw_subset_samples = [sample_dataset[i] for i in range(subset_size)]
124+
125+
clean_subset_samples = []
126+
for sample in raw_subset_samples:
127+
clean_subset_samples.append(
128+
{
129+
"patient_id": str(sample["patient_id"]),
130+
"visit_id": str(sample["admission_id"]),
131+
"labs": sample["labs"].tolist(),
132+
"mortality": int(sample["mortality"].item()),
133+
}
134+
)
135+
136+
subset_dataset = create_sample_dataset(
137+
samples=clean_subset_samples,
138+
input_schema={"labs": "tensor"},
139+
output_schema={"mortality": "binary"},
140+
dataset_name="mimic4_mortality_subset",
141+
)
142+
143+
loader = get_dataloader(subset_dataset, batch_size=subset_size, shuffle=False)
144+
batch = next(iter(loader))
145+
146+
X = batch["labs"].detach().cpu().numpy()
147+
y = batch["mortality"].detach().cpu().numpy().reshape(-1)
148+
149+
X = X.reshape(X.shape[0], -1)
150+
151+
print("Flattened feature matrix:", X.shape)
152+
print("Labels:", y.shape)
153+
154+
split = int(0.8 * len(X))
155+
X_train, X_test = X[:split], X[split:]
156+
y_train, y_test = y[:split], y[split:]
157+
158+
print("=" * 80)
159+
print("Baseline Random Forest")
160+
print("=" * 80)
161+
162+
rf = RandomForestClassifier(
163+
n_estimators=100,
164+
random_state=42,
165+
bootstrap=True,
166+
)
167+
rf.fit(X_train, y_train)
168+
rf_probs = rf.predict_proba(X_test)[:, 1]
169+
rf_metrics = evaluate(y_test, rf_probs)
170+
print("RF metrics:", rf_metrics)
171+
172+
print("=" * 80)
173+
print("CaliForest (isotonic calibration)")
174+
print("=" * 80)
175+
isotonic_metrics = run_califorest(
176+
X_train, y_train, X_test, y_test, calibration="isotonic"
177+
)
178+
print("CaliForest isotonic metrics:", isotonic_metrics)
179+
180+
print("=" * 80)
181+
print("CaliForest (logistic calibration)")
182+
print("=" * 80)
183+
logistic_metrics = run_califorest(
184+
X_train, y_train, X_test, y_test, calibration="logistic"
185+
)
186+
print("CaliForest logistic metrics:", logistic_metrics)
187+
188+
189+
if __name__ == "__main__":
190+
main()

pyhealth/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@
4545
from .sdoh import SdohClassifier
4646
from .medlink import MedLink
4747
from .unified_embedding import UnifiedMultimodalEmbeddingModel, SinusoidalTimeEmbedding
48+
from .califorest import CaliForest

0 commit comments

Comments
 (0)