Skip to content

Commit 62ce633

Browse files
lagillenwaterclaude
andcommitted
Publish results and model descriptions
Adds Mermaid style evaluation flowchart, results, the head-invariance table/figure, and the data figures. docs/models.md describes each model operationally (input, normalization, scoring contract, coverage), cross-linking the adapter contract and the companion manuscript. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fde67c7 commit 62ce633

2 files changed

Lines changed: 122 additions & 5 deletions

File tree

README.md

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
Foundation-model evaluation harness for patient-derived tumor organoid (PDTO) drug-response prediction. Realizing the benefits of foundation models requires careful evaluations that map the boundaries of generalization.
44

5-
The harness produces a registry-backed report comparing three models against three out-of-distribution split strategies on two PDTO datasets, with negative/positive controls, bootstrap confidence intervals, and a pretraining-leakage exposure profile per result.
6-
7-
See [docs/fm-pdo-evaluator-plan.md](docs/fm-pdo-evaluator-plan.md) for the 3-week plan.
5+
The harness trains a fixed downstream head on cell-line drug screens (GDSC2) and tests it, frozen, on sarcoma organoids (Soragni 2024), comparing foundation-model embeddings (Stack) against simple expression baselines under negative and positive controls. It is the companion code for *Prospective Evaluation of Foundation Model Performance in Precision Medicine* (greenelab/fm-pm-eval-manuscript).
86

97
## Quickstart
108

@@ -19,11 +17,76 @@ uv sync --extra dev
1917
uv run pytest
2018
```
2119

20+
## Evaluation design
21+
22+
A model is scored as a **representation** (PCA/NMF of expression, or a Stack embedding) fed to a swappable **head** (linear ridge or RBF kernel ridge). The head is trained on GDSC2 cell lines and frozen, then asked to predict held-out Soragni organoids. Negative (within-drug permutation) and positive (planted interaction) controls bracket every result. See [docs/models.md](docs/models.md) for each model and [docs/adapter_contract.md](docs/adapter_contract.md) for the encoder interface.
23+
24+
```mermaid
25+
flowchart LR
26+
G[GDSC2 sarcoma<br/>cell lines] --> R
27+
S[Soragni PDTOs<br/>organoids] --> R
28+
subgraph R[Representation]
29+
E[log1p CPM<br/>expression PCA/NMF]
30+
K[Stack-Large<br/>embedding]
31+
end
32+
R --> H
33+
subgraph H[Head]
34+
L[linear ridge<br/>SimpleProbe]
35+
N[RBF kernel ridge<br/>KernelProbe]
36+
B[bilinear<br/>z, g, z⊗g]
37+
end
38+
H --> T[train on GDSC2,<br/>predict frozen on Soragni]
39+
T --> M[metrics:<br/>interaction rho,<br/>within-drug rho,<br/>regret@k]
40+
M --> C{controls}
41+
C --> NEG[negative:<br/>within-drug permutation]
42+
C --> POS[positive:<br/>planted interaction]
43+
```
44+
2245
## Datasets
2346

24-
- **Soragni 2024** sarcoma PDTOs ([Synapse PDTOSarcoma](https://www.synapse.org/PDTOSarcoma))
25-
- **Yang 2024** primary liver cancer PDOs ([Cancer Cell](https://www.cell.com/cancer-cell/fulltext/S1535-6108(24)00089-8))
47+
- **Soragni 2024** sarcoma PDTOs ([Synapse PDTOSarcoma](https://www.synapse.org/PDTOSarcoma)) — 17 matched organoids, 21 drugs shared with GDSC2 by PubChem CID
48+
- **GDSC2** sarcoma cell lines (DepMap RNA-seq + GDSC2 screen) — 28 lines, the powered training cohort
49+
- **Yang 2024** primary liver cancer PDOs ([Cancer Cell](https://www.cell.com/cancer-cell/fulltext/S1535-6108(24)00089-8)) — deferred to v2
50+
51+
The cohorts and the shared drug panel (the raw inputs, no model):
52+
53+
![Cohort composition](docs/figures/cohort_composition.png)
54+
![Response distributions](docs/figures/response_distributions.png)
55+
![Soragni organoid x drug viability](docs/figures/soragni_response_heatmap.png)
56+
![Shared drug panel](docs/figures/shared_panel.png)
57+
58+
Regenerate with `uv run python scripts/plot_data.py`.
59+
60+
## Results
2661

62+
**A Stack-Large embedding gives no advantage over PCA of expression for sarcoma drug response.** In-distribution (within GDSC2 sarcoma), the embedding signal beyond the drug mean is, if anything, weaker than expression PCA (interaction rho: expression 0.224 > Stack 0.199 > subtype 0.138). Out of distribution (GDSC2 → Soragni, frozen), the transcriptome models collapse toward the drug-mean prior, and the predictive signal lives in the functional organoid screens rather than the transcriptome. Positive controls confirm each apparatus has full power when signal is planted in its own representation, so the null is biological, not a dead pipeline.
63+
64+
### Head-invariance
65+
66+
The headline comparison rides on a *linear* head, so the obvious objection is that a foundation-model embedding might only pay off under a *nonlinear* head. Running the same frozen GDSC2 → Soragni transfer through linear ridge, RBF kernel ridge, and the bilinear model shows the result holds across head families — the nonlinear head does **not** rescue Stack:
67+
68+
| head | representation | interaction rho | p |
69+
|---|---|--:|--:|
70+
| linear | expression PCA | +0.130 | 0.105 |
71+
| linear | expression NMF | +0.184 | 0.035 |
72+
| linear | Stack | +0.130 | 0.085 |
73+
| kernel | expression PCA | **+0.225** | 0.005 |
74+
| kernel | Stack | +0.076 | 0.225 |
75+
| bilinear | 16 shared drugs | −0.019 | 0.420 |
76+
77+
Under the linear head expression and Stack tie; under the kernel head **expression rises to 0.225 while Stack stays at 0.076** — the nonlinear capacity benefits expression, not the embedding. Stack ≤ expression is head-invariant.
78+
79+
![Head-invariance](docs/figures/head_invariance.png)
80+
81+
Regenerate the table and figure:
82+
83+
```bash
84+
uv run python scripts/transfer_gdsc_soragni.py --all-heads \
85+
--stack-gdsc stack_gdsc.csv --stack-soragni stack_soragni.csv \
86+
--out results/head_invariance.csv
87+
uv run python scripts/transfer_pharmaformer_lite.py --out results/head_invariance.csv
88+
uv run python scripts/plot_data.py
89+
```
2790

2891
## Affiliation
2992

docs/models.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Models under evaluation
2+
3+
Operational description of every model/representation the harness scores, kept next to
4+
the code so it stays in sync. For the encoder interface see [adapter_contract.md](adapter_contract.md);
5+
for how the comparison is read see the Results section of the [README](../README.md). The
6+
scholarly description of each model (architecture, citations, rationale) lives in the
7+
companion manuscript (greenelab/fm-pm-eval-manuscript), which cross-references this file.
8+
9+
## Two layers: representation and head
10+
11+
A prediction is `response(sample, drug) = a_drug + f(representation(sample))`. Two choices
12+
are independent:
13+
14+
- **Representation** — the per-sample vector a model produces: PCA/NMF of expression, or a
15+
foundation-model embedding (Stack). Swapping the representation is how we ask "what does the
16+
encoder capture."
17+
- **Head** — the function `f` fit on top: a linear ridge slope (`SimpleProbe`) or an RBF
18+
kernel ridge (`KernelProbe`). Swapping the head is how we ask whether a finding is
19+
*head-invariant* (a linear head could under-read a representation that only pays off
20+
nonlinearly).
21+
22+
Both heads share the per-drug mean and the PCA/NMF reduction
23+
([`probe/base.py`](../src/fmharness/probe/base.py)) and the same `fit(emb, drugs, y)` /
24+
`predict_parts(emb, drugs) -> (base, residual)` contract, so any representation can be scored
25+
under either head apples-to-apples. The bilinear and biomarker models below are standalone
26+
(they do not use this head interface).
27+
28+
## Catalogue
29+
30+
| Model | Representation | Head / form | Input + normalization | Where |
31+
|---|---|---|---|---|
32+
| Expression PCA | top-k PCA of log1p CPM expression | linear ridge (`SimpleProbe`) | bulk RNA-seq, CPM then log1p; per-drug ridge over k PCs | [`probe/simple.py`](../src/fmharness/probe/simple.py) |
33+
| Expression NMF | non-negative gene programs of log1p CPM | linear ridge | same; NMF requires non-negative input | `probe/simple.py` (`reducer="nmf"`) |
34+
| Stack-Large | 1600-dim Stack embedding (PCA-reduced) | linear ridge | CPM expression fed to Stack as pseudo-cells; embedding CSV indexed by sample id | [`stack_panel.py`](../src/fmharness/stack_panel.py), `probe/simple.py` |
35+
| Kernel (any representation) | same PCA/NMF reduction, score-standardized | RBF kernel ridge (`KernelProbe`) | identical to the linear head's input; `(alpha, gamma)` by per-drug leave-one-out | [`probe/kernel.py`](../src/fmharness/probe/kernel.py) |
36+
| Bilinear (PharmaFormer-lite) | expression PCA `z` + Morgan-fingerprint PCA `g` | ridge on `[z, g, z⊗g]` | log1p CPM expression + 1024-bit Morgan fingerprint; predicts unseen drugs via chemistry | [`scripts/transfer_pharmaformer_lite.py`](../scripts/transfer_pharmaformer_lite.py), [`bilinear.py`](../src/fmharness/bilinear.py) |
37+
| Biomarker | pre-specified genomic rules (e.g. PIK3CA→everolimus) | rule offsets on the GDSC2 drug-mean prior | WES SNV/CNV calls + drug identity | [`scripts/biomarker_anchored.py`](../scripts/biomarker_anchored.py) |
38+
| Drug-mean baseline | none | `a_drug` only (`n_components=0`) | response labels only | `probe/simple.py` |
39+
40+
## Shared guarantees
41+
42+
- **Graceful degradation.** Every learned head shrinks its embedding term toward 0 when the
43+
representation is uninformative (ridge penalty / large kernel alpha), so an uninformative
44+
model reduces to the drug-mean baseline rather than injecting noise — this is what makes a
45+
null result a real null, not a weak model.
46+
- **`base + residual` split.** `predict_parts` returns the per-drug mean and the embedding
47+
residual separately so the metrics (`interaction_rho`, `within_drug_rho` in
48+
[`evaluation.py`](../src/fmharness/evaluation.py)) score the embedding part alone, avoiding
49+
the leave-one-out drug-base artifact.
50+
- **Coverage.** The per-drug transfers (expression, Stack) score only drugs shared between
51+
GDSC2 and Soragni (~15–21 by PubChem CID); the bilinear scores the full fingerprinted panel.
52+
Uncovered drugs fall back to the GDSC2 drug-mean.
53+
- **Determinism.** All heads are closed-form (RidgeCV / KernelRidge) and seeded; repeated fits
54+
are bit-identical.

0 commit comments

Comments
 (0)