Add ovarian Xenium full-slide InterScale model (no held-out split; same config as heldout_split)
5fca8d3 verified |
Download ovarian_xenium/no_heldout_split/README.md from theislab/InterScale: direct link, hf CLI and curl.
- Browser
- Download file 6.52 kB
-
https://huggingface.co/theislab/InterScale/resolve/main/ovarian_xenium/no_heldout_split/README.md
- Command line
-
hf download hf://theislab/InterScale/ovarian_xenium/no_heldout_split/README.md
-
curl -L -o README.md https://huggingface.co/theislab/InterScale/resolve/main/ovarian_xenium/no_heldout_split/README.md
6.52 kB
| license: bsd-3-clause | |
| library_name: interscale | |
| tags: | |
| - spatial-transcriptomics | |
| - xenium | |
| - cell-cell-communication | |
| - graph-neural-network | |
| - transformer | |
| - single-cell | |
| # InterScale — 10x Xenium human ovarian adenocarcinoma (full-slide model, no held-out split) | |
| Pre-trained [InterScale](https://github.com/theislab/interscale) model (Drummer, Jiménez *et al.*, | |
| bioRxiv 2026) for the 10x Xenium human ovarian adenocarcinoma section of the cell–cell communication | |
| chapter of the single-cell best-practices book, trained on **every window of the section**. It is | |
| the sibling of [`../heldout_split`](../heldout_split), which uses the same architecture and | |
| hyperparameters but holds out 30 % of the windows. Use this one to *read out* the tissue — attention | |
| flows, gene programmes — where holding out windows would only discard cells; use `heldout_split` | |
| when you need generalisation numbers. Trained in `notebooks/psls_ccc/interscale_ovarian_train_full_slide.ipynb`, | |
| interpreted in `interscale_ovarian_full_slide_interpretation.ipynb`. | |
| InterScale combines a **local** GCN over the spatial neighbour graph with a **global** transformer | |
| over all cells of a tissue window. Each component reconstructs masked expression through its own | |
| decoder (`dual_decoder: true`). The transformer's cell-by-cell attention is what the net-flow | |
| analysis reads as directed communication between cell types. | |
| ## Files | |
| | file | contents | | |
| | --- | --- | | |
| | `model.pt` | trained weights, InterScale save format (`{"state_dict": ...}`), 20 MB | | |
| | `config.yaml` | full resolved config the weights were trained with; only the two local paths are blanked | | |
| | `genes.tsv` | the 3,000 input genes **in model order**, with their Moran's I | | |
| | `metrics.json` | in-sample scores of this exact file on all windows (there is no held-out data) | | |
| ## Loading | |
| Needs `interscale` from `main` at or after commit `c98a307`, the version it was trained with. | |
| ```python | |
| from huggingface_hub import snapshot_download | |
| import interscale | |
| from interscale.config import load_config | |
| d = snapshot_download("theislab/InterScale", allow_patterns="ovarian_xenium/no_heldout_split/*") | |
| d = f"{d}/ovarian_xenium/no_heldout_split" | |
| cfg = load_config(f"{d}/config.yaml") | |
| # `adata` must be prepared as described under "Input" below | |
| interscale.model.CombinedModel._setup_anndata( | |
| adata=adata, | |
| prediction_task="regression", | |
| layer_key="log1p_norm", | |
| sample_key_list=["sliding_window"], | |
| split_key=None, | |
| ) | |
| model = interscale.model.CombinedModel.load( | |
| d, adata, cfg, model_name="", local_component=True, global_component=True | |
| ) | |
| model.module.eval() # a loaded model starts in training mode, where dropout makes every run differ | |
| result = model.get_model_output(adata) # .obsm["_attn_matrix"], ["_local_emb"], ["_global_emb"], | |
| # .layers["_y_pred_local"], ["_y_pred_global"] | |
| ``` | |
| `load` raises if the config does not describe the checkpoint, so a model that loads is the trained | |
| model. It will not silently keep random weights. The recipe above was checked by reloading this | |
| folder and comparing all weights bit for bit. | |
| ## Input | |
| The model expects the tutorial's preprocessing. Section 2 of `interscale_ovarian.ipynb` does all of | |
| it: | |
| * **Expression:** `adata.layers["log1p_norm"]` = `log1p(normalize_total(counts))`. | |
| * **Genes:** exactly the 3,000 genes of `genes.tsv`, **in that order** | |
| (`adata = adata[:, genes].copy()`). They are the most spatially variable genes of the 4,447-gene | |
| panel by Moran's I (radius-30 µm graph, whole slide). | |
| * **Windows:** `adata.obs["sliding_window"]`, 600 µm non-overlapping tiles of the slide made with | |
| `interscale.pp.sliding_window`, with windows of fewer than 50 cells dropped. The model builds a | |
| radius-30 µm neighbour graph inside each window. | |
| * **Window size limit:** the largest window must have at most **3,167 cells**, the config's | |
| `max_seq_len`. For larger windows, raise `model.global_component.parameters.max_seq_len` in the | |
| config. A too-small value silently subsamples windows. | |
| * `adata.obsm["spatial"]` in µm. | |
| ## Training | |
| | | | | |
| | --- | --- | | |
| | data | one section, 400,600 cells, 199 windows (mean 2,013 cells) | | |
| | split | **none** — all 199 windows are training data (`train_size` 1.0, `val_size` 0.0) | | |
| | objective | node-level reconstruction, **cell masking at 30 %**, SmoothL1 on the masked cells | | |
| | architecture | 2-layer GCN (hidden 512, dropout 0.1) → 2-layer transformer (4 heads, FF 512, dropout 0) → linear decoders, latent width 256 | | |
| | optimisation | lr 0.003, weight decay 0.001, cosine schedule with 10-epoch warm-up, batch 2 windows, **fixed 250 epochs, no early stopping** (250 ≈ where the held-out-split model stopped on its own: 242; 148–307 over 3 seeds) | | |
| | hardware | 1× NVIDIA H100 80 GB | | |
| Hyperparameters are those of `heldout_split` (the best of a 48-trial random sweep), unchanged. | |
| Lightning needs a validation loader, so it received copies of the training windows with their own | |
| masks; that is a convergence monitor, not a held-out score. | |
| ## Evaluation | |
| There is no held-out data, so every number below is **in-sample**. They are shown next to the | |
| `heldout_split` model scored on the **same 199 windows with the same fixed masks** (seed 2026), as | |
| the median over genes of the per-gene Pearson r: | |
| | condition | this model, local / global | `heldout_split`, local / global | | |
| | --- | --- | --- | | |
| | 30 % of cells fully masked (the training task) | 0.182 / 0.204 | 0.178 / 0.184 | | |
| | 50 % of (cell, gene) entries masked | 0.180 / 0.115 | 0.178 / 0.184 | | |
| On the task it was trained for, the model is slightly better, as expected from more training data | |
| and a longer schedule on the cells it is scored on. Its **global decoder generalises worse to | |
| gene-level masking** (0.115 vs 0.184): trained longer on whole-cell masking only, it has specialised | |
| more narrowly. The attention and gene-rank read-outs use the unmasked pass and are not directly | |
| affected, but prefer `heldout_split` if you reconstruct partially observed cells. | |
| ## Limitations | |
| * **No generalisation estimate.** Every cell was seen in training; use `heldout_split` for | |
| out-of-sample numbers. | |
| * **One tissue section.** Like `heldout_split`, the model describes this section, not ovarian cancer | |
| in general. | |
| * Attention-based net flow is a model read-out, not a measurement of ligand–receptor signalling. | |
| ## Citation | |
| Drummer, F., Jiménez, S. *et al.* InterScale. bioRxiv (2026). Code: | |
| https://github.com/theislab/interscale | |