Ace3Z commited on
Commit
6a04155
·
verified ·
1 Parent(s): bd9b916

New model card: GrapHist v2 only, shorter, matches the new layout

Browse files
Files changed (1) hide show
  1. README.md +90 -139
README.md CHANGED
@@ -9,206 +9,154 @@ tags:
9
  - pytorch-geometric
10
  - graph-representation-learning
11
  - edge-features
12
- - vicreg
13
  ---
14
 
15
- # GrapHist v2 + VICReg: Edge-Informed Graph Self-Supervised Learning for Histopathology
16
 
17
- GrapHist v2 extends [GrapHist](https://huggingface.co/ogutsevda/graphist)
18
- ([arXiv:2603.00143](https://arxiv.org/abs/2603.00143)) with **edge-informed message
19
- passing**: cell graphs carry a 75-dimensional feature vector per edge describing the
20
- inter-cellular region, and the encoder becomes an **ACM-GINEConv** that injects those
21
- edge features into aggregation.
 
 
22
 
23
- The un-regularised v2 encoder **dimensionally collapsed** (`pca_1 ≈ 0.5`, effective
24
- dim ≈ 2). Adding a **VICReg** variance/covariance regulariser fixes it: this checkpoint
25
- ends collapse-free at `pca_1 = 0.17`, **effective dim 11.9**, and improves over vanilla
26
- GrapHist on transfer subtyping, cell-level phenotyping and survival.
27
 
28
- Self-supervision follows GraphMAE (masked node-feature reconstruction, scaled cosine
29
- error) plus the VICReg term on the pooled graph embedding.
30
 
31
- ## Repository Structure
32
 
33
- ```
34
- graphist_V2.pt # final v2+VICReg checkpoint - 126,597,010 B, md5 81a2e6b91cefff0bbbc13c6fd318ee78
35
- models/
36
- ├── __init__.py # build_model(args) factory
37
- ├── edcoder.py # PreModel encoder-decoder wrapper
38
- ├── acm_gineconv.py # ACM-GINEConv backbone (v2, edge-informed)
39
- ├── acm_gin.py # ACM-GIN backbone (v1) - REQUIRED IMPORT, see note
40
- └── utils.py # activation helpers
41
- graphist_utils.py # NormalizeData + AddVirtualNode transforms, GraphDataset,
42
- # filter_valid_graph_paths, checkpoint helpers - REQUIRED
43
- rebase_graph_paths.py # REQUIRED before using data/v2_graphs/ - see data/README.md
44
- checkpoints/
45
- └── graphist_v1.pt # vanilla GrapHist v1 (ACM-GIN, 1-dim edges) - AdapterGNN starting point
46
- data/ # all datasets and embeddings - see data/README.md for the full
47
- # artifact -> purpose -> branch -> paper-table map
48
- ├── v2_graphs/ # 75-dim edge cell graphs (BACH, BRACS, BreakHis)
49
- ├── v2_embeddings/ # slide (4 cohorts) + cell (22 tars) embeddings from graphist_V2.pt
50
- ├── v1_graphs/ # NuCLS + PanNuke 1-dim edge graphs (AdapterGNN, homophily)
51
- ├── v1_graphs_upstream/ # byte-identical mirrors of the upstream v1 releases:
52
- │ # BRACS + BreakHis tars, and tcga_brca/ (254 files, 271 GB)
53
- ├── homophily_tiles/ # 109,904 CellViT++ pseudo-labelled 224px tile graphs
54
- ├── baseline_embeddings/# DINOv2, MAE, GrapHist-v1 TCGA-BRCA features (Table 3 baselines)
55
- ├── homophily_bach_cellvit/ # 400 per-image CellViT pseudo-label CSVs (BACH fat-tail)
56
- ├── preproc_final/ # Table C.8 preprocessing-runtime run (19,200 BACH tiles)
57
- └── survival/ # GDC TCGA-BRCA clinical.tsv - required for Table 3
58
- README.md
59
- ```
60
-
61
- **Which files do I need?** For inference only: `graphist_V2.pt`, `models/`,
62
- `graphist_utils.py`. Everything under `data/` is there to reproduce a specific published
63
- number — [`data/README.md`](data/README.md) says which one, for which branch.
64
-
65
- ⚠️ **If you use `data/v2_graphs/`, you must run `rebase_graph_paths.py` first**, or the
66
- loader drops every graph — non-fatally, with a single `WARNING:` line, then an unhelpful
67
- `IndexError`. `data/README.md` has the exact commands and the expected counts to check
68
- against.
69
-
70
- > **`acm_gin.py` is required even though v2 never uses it.** `models/edcoder.py` imports
71
- > `ACM_GIN_model` and `ACM_GINEConv_model` at module top level; omitting it is an `ImportError`.
72
-
73
- > **`graphist_utils.py` is required.** Reproducing the published embeddings needs
74
- > `Compose([ToUndirected(), NormalizeData(...), AddVirtualNode(...)])`, and neither
75
- > transform lives in `models/`.
76
 
77
- ## Which checkpoint this is
 
 
 
78
 
79
- The run wrote four checkpoint files. **All four contain identical weights** — `max |ΔW| = 0`
80
- across 332/332 tensors, same `run_id` and same `best_loss`; they differ only in optimizer
81
- state and in the `epoch` field (99 for three, 100 for `_final`), which is why their byte sizes
82
- differ. So the old "which of the four?" question is moot: any of them yields the same encoder.
83
 
84
- This file is the best-validation-loss one (`gineconv_vicreg_full_checkpoint.pt`), and the one
85
- that produced every reported v2+VICReg number:
86
-
87
- | Field | Value |
88
- |---|---|
89
- | `epoch` | 99 |
90
- | `best_loss` | 0.019939 |
91
- | `run_id` | `1udpmgxw` |
92
- | md5 | `81a2e6b91cefff0bbbc13c6fd318ee78` |
93
-
94
- Parameters: **encoder 7,980,649 (7.98 M)**; **9.29 M on the inference path**, because
95
- `embed()` ends with `rep = self.encoder_to_decoder(enc_rep)` (`edcoder.py:324`), adding
96
- 1,310,720; **10,528,542 (10.53 M)** for the full pretraining model (encoder +
97
- `encoder_to_decoder` + decoder 1,237,077 + mask token 96, counting each shared module once).
98
- A naive `state_dict` sum reports 19.35 M because `acm_gineconv.py` registers each channel
99
- MLP twice — 156 of the 332 keys are aliases of the same tensors (332 keys, 176 unique
100
- storages).
101
-
102
- ## Requirements
103
 
104
  ```
105
- torch >= 2.2
106
- torch_geometric >= 2.5
107
- numpy, pandas
 
 
 
 
 
 
108
  ```
109
- Verified on torch 2.10.0+cu128 / PyG 2.7.0. CPU-only inference works.
110
 
111
- ## Usage
 
 
 
 
 
112
 
113
- ### 1. Download
114
 
115
  ```python
116
  from huggingface_hub import snapshot_download
117
  path = snapshot_download(repo_id="Ace3Z/graphist-v2")
118
  ```
119
 
120
- ### 2. Load
 
121
 
122
- ⚠️ **Three arguments are load-critical** — omit any one and you get either an
123
- `AttributeError` or a `load_state_dict` shape/key mismatch. Two are read with a `getattr`
124
- default that does **not** match this checkpoint; the third is a plain attribute read:
125
-
126
- | Argument | Must be | If omitted | Failure |
127
- |---|---|---|---|
128
- | `edge_distance_in_proj` | `False` | `getattr` default `True` (`models/__init__.py:29`) | `edge_input_proj` built as (512, **75**); checkpoint has (512, **74**) |
129
- | `encoder_norm` | `"layer"` | `getattr` default `"none"` (`models/__init__.py:22`) | `encoder.layer_norms.*` missing from the model |
130
- | `concat_hidden` | `True` | **`AttributeError`** — `models/__init__.py:40` is `args.concat_hidden`, a plain attribute read with no default | if instead set to `False`: `encoder_to_decoder` built as (512, 512); checkpoint has (512, **2560**) |
131
-
132
- (The `generate_embs.py` CLI defaults `--concat_hidden` to `True`, and `False` appears only as
133
- a `PreModel.__init__` signature default that the `Args` flow below never reaches — so
134
- `concat_hidden` has no effective "silent wrong default", it simply must be present.)
135
 
136
  ```python
137
  import sys, torch
138
- sys.path.insert(0, path)
139
  from models import build_model
140
 
141
  class Args:
142
- # --- architecture ---
143
- encoder = "acm_gineconv"
144
- decoder = "acm_gineconv"
145
- num_features = 96 # node features
146
- num_edge_features = 75 # edge features (projection sees 74; distance excluded)
147
  num_hidden = 512
148
  num_layers = 5
149
- concat_hidden = True # load-critical
150
- encoder_norm = "layer" # load-critical
 
151
  input_norm = "none"
152
- edge_distance_in_proj = False # load-critical
153
  batchnorm = False
154
  activation = "prelu"
155
  norm = None
156
  residual = False
157
- # --- SSL objective ---
158
  loss_fn = "sce"
159
  alpha_l = 3
160
  mask_rate = 0.5
161
  replace_rate = 0.1
162
  drop_edge_rate = 0.0
163
- # --- VICReg (training only) ---
164
- vicreg_var_weight = 0.05
165
  vicreg_cov_weight = 0.002
166
  vicreg_gamma = 1.0
167
- # --- unused, vestigial from the GAT lineage, but read by build_model ---
168
- num_heads = 4
169
  num_out_heads = 1
170
  in_drop = 0.2
171
  attn_drop = 0.1
172
  negative_slope = 0.2
173
 
174
  model = build_model(Args())
175
- ckpt = torch.load(f"{path}/graphist_V2.pt", map_location="cpu", weights_only=False)
176
  model.load_state_dict(ckpt["model_state_dict"], strict=True) # note the key name
177
  model.eval()
178
- ```
179
 
180
- The checkpoint is a training dict — `{epoch, model_state_dict, optimizer_state_dict,
181
- best_loss, run_id}`. The weights are under **`model_state_dict`**, not `model`.
182
-
183
- ### 3. Inference
184
-
185
- ```python
186
- # x: [num_nodes, 96] edge_index: [2, num_edges] edge_attr: [num_edges, 75]
187
  # batch: [num_nodes] graph assignment (zeros for a single graph)
188
  with torch.no_grad():
189
- node_emb = model.embed(x, edge_index, edge_attr, batch) # -> [num_nodes, 512]
190
  ```
191
 
192
- Region/slide-level embeddings are the mean over node embeddings; slide-level is the mean
193
- over its tiles. For results matching the paper, apply the same transform pipeline used at
194
- training time (`NormalizeData` with the dataset's `normalization.json`, then
195
- `AddVirtualNode`) — see `graphist_utils.py`.
196
 
197
- ## Verification
 
 
 
 
 
 
198
 
199
- This repository was validated end-to-end before publishing: `build_model(Args())` +
200
- `load_state_dict(..., strict=True)` returns **0 missing / 0 unexpected keys** using only
201
- the files shipped here, and `embed()` returns a finite `[n, 512]` tensor.
 
 
202
 
203
- ## Acknowledgements
 
204
 
205
- Built on [GrapHist](https://huggingface.co/ogutsevda/graphist) (Ogut et al.), GraphMAE
206
- (Hou et al., 2022), ACM (Luan et al., 2022), GINEConv (Hu et al., 2020) and VICReg
207
- (Bardes et al., 2022). Developed at [LTS4, EPFL](https://www.epfl.ch/labs/lts4/).
 
 
 
208
 
209
- ## Citation
210
 
211
- The v2 preprint is not yet available. Please cite the original GrapHist paper:
 
 
 
 
212
 
213
  ```bibtex
214
  @article{ogut2026graphist,
@@ -222,3 +170,6 @@ The v2 preprint is not yet available. Please cite the original GrapHist paper:
222
  primaryClass = {cs.CV}
223
  }
224
  ```
 
 
 
 
9
  - pytorch-geometric
10
  - graph-representation-learning
11
  - edge-features
 
12
  ---
13
 
14
+ # GrapHist v2 — Edge-Informed Graph Self-Supervised Learning for Histopathology
15
 
16
+ GrapHist v2 encodes a histopathology tile as a **cell graph** — one node per nucleus, edges
17
+ from a Delaunay triangulation pruned at 100 µm — and embeds it with a self-supervised
18
+ **ACM-GINEConv** encoder. It extends [GrapHist](https://huggingface.co/ogutsevda/graphist)
19
+ ([arXiv:2603.00143](https://arxiv.org/abs/2603.00143)) by making the **edges carry
20
+ information**: each edge holds a 75-dimensional descriptor of the tissue between two cells,
21
+ and the encoder injects those features into message passing instead of treating edges as mere
22
+ connectivity.
23
 
24
+ **Trained on 11,149,499 cell graphs from TCGA-BRCA**, 100 epochs, batch 2048, no labels. The
25
+ objective is GraphMAE-style masked node-feature reconstruction (mask 50 %, scaled cosine
26
+ error, α = 3) plus a **VICReg** variance/covariance term on the pooled graph embedding, which
27
+ keeps all embedding dimensions in use (final `pca_1` 0.17, effective dimension 11.9).
28
 
29
+ 7.98 M-parameter encoder · 512-dim embeddings · 0.093 ms/patch inference.
 
30
 
31
+ ## Results
32
 
33
+ Slide-level MIL, transfer to unseen cohorts (test macro-F1 %, best of three MIL heads):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ | | BRACS | BreakHis | BACH |
36
+ |---|:--:|:--:|:--:|
37
+ | GrapHist (v1) | 60.30 | 89.37 | **69.16** |
38
+ | **GrapHist v2** | **69.00** | **95.53** | 68.98 |
39
 
40
+ Survival on TCGA-BRCA (Cox PH, C-index): **0.793**, vs 0.763 for v1, 0.724 MAE, 0.632 DINOv2.
41
+ Cell-type identification and patch-level subtyping are in [`DATA.md`](DATA.md), which maps
42
+ every artifact here to the result it reproduces.
 
43
 
44
+ ## What's in this repo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  ```
47
+ graphist_v2.pt the model — 126,597,010 B, md5 81a2e6b91cefff0bbbc13c6fd318ee78
48
+ modeling/ code to load and run it, plus helper scripts
49
+ graphs/ cell graphs: TCGA-BRCA (11.1 M, pre-training) · BACH · BRACS ·
50
+ BreakHis · SPIDER-breast
51
+ embeddings/ embeddings from this model: slide-level (4 cohorts), cell-level (22 sets)
52
+ baselines/ DINOv2, MAE and GrapHist v1 features for the comparison rows
53
+ labels/ TCGA-BRCA clinical table and slide labels
54
+ studies/ side analyses: homophily, AdapterGNN, batch effect, preprocessing runtime
55
+ upstream_v1/ mirror of the original GrapHist v1 releases
56
  ```
 
57
 
58
+ For inference you need only `graphist_v2.pt` + `modeling/`. Everything else exists to reproduce
59
+ a specific published number — see [`DATA.md`](DATA.md).
60
+
61
+ > ⚠️ **If you use `graphs/`, run `modeling/rebase_graph_paths.py` first.** The label CSVs store
62
+ > bare filenames and the loader resolves them against the working directory, so without this
63
+ > step every graph is silently dropped. `DATA.md` has the commands and the expected counts.
64
 
65
+ ## Usage
66
 
67
  ```python
68
  from huggingface_hub import snapshot_download
69
  path = snapshot_download(repo_id="Ace3Z/graphist-v2")
70
  ```
71
 
72
+ Three arguments are **load-critical** — `build_model` reads two with defaults that do not match
73
+ this checkpoint, and the third has no default at all:
74
 
75
+ | Argument | Must be | If wrong |
76
+ |---|---|---|
77
+ | `edge_distance_in_proj` | `False` | `edge_input_proj` built as (512, **75**); checkpoint has (512, **74**) |
78
+ | `encoder_norm` | `"layer"` | `encoder.layer_norms.*` missing from the model |
79
+ | `concat_hidden` | `True` | `AttributeError` if omitted; (512, 512) instead of (512, **2560**) if `False` |
 
 
 
 
 
 
 
 
80
 
81
  ```python
82
  import sys, torch
83
+ sys.path.insert(0, f"{path}/modeling")
84
  from models import build_model
85
 
86
  class Args:
87
+ encoder = decoder = "acm_gineconv"
88
+ num_features = 96 # per-cell features
89
+ num_edge_features = 75 # per-edge features (projection sees 74; distance excluded)
 
 
90
  num_hidden = 512
91
  num_layers = 5
92
+ concat_hidden = True # load-critical
93
+ encoder_norm = "layer" # load-critical
94
+ edge_distance_in_proj = False # load-critical
95
  input_norm = "none"
 
96
  batchnorm = False
97
  activation = "prelu"
98
  norm = None
99
  residual = False
 
100
  loss_fn = "sce"
101
  alpha_l = 3
102
  mask_rate = 0.5
103
  replace_rate = 0.1
104
  drop_edge_rate = 0.0
105
+ vicreg_var_weight = 0.05 # training only
 
106
  vicreg_cov_weight = 0.002
107
  vicreg_gamma = 1.0
108
+ num_heads = 4 # vestigial, but read by build_model
 
109
  num_out_heads = 1
110
  in_drop = 0.2
111
  attn_drop = 0.1
112
  negative_slope = 0.2
113
 
114
  model = build_model(Args())
115
+ ckpt = torch.load(f"{path}/graphist_v2.pt", map_location="cpu", weights_only=False)
116
  model.load_state_dict(ckpt["model_state_dict"], strict=True) # note the key name
117
  model.eval()
 
118
 
119
+ # x: [num_nodes, 96] edge_index: [2, num_edges] edge_attr: [num_edges, 75]
 
 
 
 
 
 
120
  # batch: [num_nodes] graph assignment (zeros for a single graph)
121
  with torch.no_grad():
122
+ node_emb = model.embed(x, edge_index, edge_attr, batch) # -> [num_nodes, 512]
123
  ```
124
 
125
+ Region- and slide-level embeddings are means over node embeddings. To match the published
126
+ numbers, apply the transforms used at training time — `NormalizeData` with the dataset's
127
+ `normalization.json`, then `AddVirtualNode` (both in `modeling/graphist_utils.py`).
 
128
 
129
+ Requires `torch >= 2.2`, `torch_geometric >= 2.5`, `numpy`, `pandas`. Verified on torch
130
+ 2.10.0+cu128 / PyG 2.7.0; CPU-only inference works.
131
+
132
+ > `modeling/models/acm_gin.py` is required even though this model never uses it —
133
+ > `edcoder.py` imports it at module level.
134
+
135
+ ## Graph format
136
 
137
+ | Tensor | Shape | Meaning |
138
+ |---|---|---|
139
+ | `x` | `[num_nodes, 96]` | per-cell morphology, texture and colour features |
140
+ | `edge_index` | `[2, num_edges]` | Delaunay edges, pruned at 100 µm |
141
+ | `edge_attr` | `[num_edges, 75]` | column 0 = centroid distance (µm); 1–74 describe the inter-cellular region |
142
 
143
+ The v1 releases use a 1-dimensional `edge_attr` (distance only) and are **not** interchangeable
144
+ with this model in either direction.
145
 
146
+ ## Verification
147
+
148
+ `build_model` + `load_state_dict(strict=True)` returns 0 missing / 0 unexpected keys using only
149
+ the files shipped here. All 397 BACH slide embeddings regenerate from this checkpoint at
150
+ minimum cosine similarity **0.9999999997** against the published set (a different checkpoint
151
+ gives ≈ 0), confirming these weights produced the released embeddings.
152
 
153
+ ## Licence and citation
154
 
155
+ Released `cc-by-nc-sa-4.0`. Derived from public cohorts — BACH/ICIAR 2018, BreakHis, BRACS,
156
+ TCGA-BRCA, PanNuke, NuCLS and [SPIDER-breast](https://huggingface.co/datasets/histai/SPIDER-breast)
157
+ (`cc-by-nc-4.0`, research use only). These are derived cell-level features, not images; the
158
+ source cohorts keep their own terms, and `labels/tcga_brca_clinical.tsv` is the open-access GDC
159
+ clinical export. Cite the original cohort papers alongside GrapHist.
160
 
161
  ```bibtex
162
  @article{ogut2026graphist,
 
170
  primaryClass = {cs.CV}
171
  }
172
  ```
173
+
174
+ Built on GraphMAE (Hou et al., 2022), ACM (Luan et al., 2022), GINEConv (Hu et al., 2020) and
175
+ VICReg (Bardes et al., 2022). Developed at [LTS4, EPFL](https://www.epfl.ch/labs/lts4/).