Official-pack-compatible Virtual Cell Challenge pipeline with metadata inspection, leakage-safe AnnData/HDF5 loading, Cell-Eval-aligned evaluation, hardened submission validation, and AMD/Hugging Face reproducibility workflows.
Python
0
6 commits
updated Jun 8, 2026
Predicting cellular responses to CRISPRi genetic perturbations in human H1 embryonic stem cells for the Virtual Cell Challenge 2025.
Combines causal GRN propagation, variance-weighted training, and corrected evaluation metrics — informed by ICML 2025 findings on mode collapse in perturbation prediction.
| Component | Module | What It Does |
|---|---|---|
| PerturbationTransformer | src/models/transformer.py | Encoder-only transformer with gene value + identity + perturbation embeddings. Delta learning. |
| BiologicalGNN | src/models/gnn.py | GAT on gene regulatory network. Perturbation injected at the target gene node via register_buffer mapping, then propagated through the graph. |
| HybridModel | src/models/hybrid.py | Gates transformer and GNN branches with learned sigmoid gate per gene. |
| Ensemble | src/models/ensemble.py | Weighted averaging with metric-aware weight optimization (DES, PDS, MAE). |
| Baselines | src/models/baseline.py | CellMean, PerturbationSpecific, KNN, Ridge — honest baselines evaluated on held-out perturbations only. |
The GNN supports two edge sources (--edge_source):
coexpression — Pearson correlation from control cells (correlation, not causation)causal — RENGE-style directed inference: when CRISPRi knockdown of gene A causes gene B to change significantly (Wilcoxon p<0.05, |log₂FC|>0.5), that's a directed edge A→B. This is causal signal extracted directly from perturbation data.$$\mathcal{L} = \alpha \cdot \mathcal{L}{\text{recon}} + \beta \cdot \mathcal{L}{\text{disc}} + \gamma \cdot \mathcal{L}_{\text{DE}}$$
| Component | Targets | Options |
|---|---|---|
| Reconstruction | MAE metric | --reconstruction mae (0.5 MAE + 0.5 MSE) or --reconstruction wmse (variance-weighted MSE — prevents mode collapse) |
| Discrimination | PDS metric | --discrimination contrastive (NT-Xent) or triplet (semi-hard negative mining) |
| DE-weighted | DES metric | Top-K DEGs upweighted 5× |
WMSE (from ICML 2025): weights each gene by its perturbation-induced variance, so predicting zero-delta for strongly affected genes is heavily penalized.
| Metric | Description | Implementation |
|---|---|---|
| MAE | Mean Absolute Error | Standard |
| DES | Differential Expression Score | Wilcoxon rank-sum + p<0.05 + |log₂FC|>0.5 threshold, Jaccard overlap |
| PDS | Perturbation Discrimination Score | KNN alignment of predicted vs true centroids |
| WMSE | Variance-Weighted MSE | Penalizes mode collapse — genes with large true deltas dominate |
| R²_w(Δ) | Weighted R² on deltas | Measures explained perturbation variance. Positive = better than mean prediction. |
WMSE and R²_w(Δ) are computed automatically by evaluate_perturbations().
virtual-cell-challenge/
├── src/
│ ├── models/
│ │ ├── transformer.py # PerturbationTransformer
│ │ ├── gnn.py # BiologicalGNN (register_buffer gene targeting)
│ │ ├── hybrid.py # Gated transformer+GNN hybrid
│ │ ├── ensemble.py # Metric-aware weighted ensemble
│ │ ├── foundation_adapter.py # Backbone adapter (scratch / embeddings)
│ │ ├── pretrained_embeddings.py # Gene embedding loader with coverage guard
│ │ └── baseline.py # Statistical baselines
│ ├── training/
│ │ ├── loss.py # CompositeLoss, WMSELoss, ContrastiveLoss, TripletLoss
│ │ ├── train.py # Training loop (AMP, augmentation, W&B)
│ │ ├── finetune.py # 3-phase progressive fine-tuning
│ │ └── hyperparam_search.py # Optuna HPO (on real data)
│ ├── evaluation/
│ │ └── metrics.py # MAE, WMSE, R²_w(Δ), DES, PDS
│ ├── data/
│ │ ├── dataset.py # PerturbationDataset (global pert_to_id)
│ │ ├── augmentation.py # Noise, Mixup, interpolation (Beta(5,1))
│ │ ├── preprocessing.py # scanpy QC → normalize → log1p → HVG subset
│ │ ├── biological_knowledge.py # GRN edges: STRING, KEGG, co-expression, causal
│ │ └── mock_data.py # Synthetic data for testing
│ └── utils/
│ ├── config.py # VCCConfig (single source of truth)
│ └── seed.py # Reproducibility
├── scripts/
│ ├── generate_submission.py # Submission builder (seeded noise, checkpoint context)
│ ├── run_full_pipeline.py # End-to-end pipeline
│ ├── download_data.py # Dataset fetcher
│ └── verify_submission_pipeline.py
├── notebooks/
│ ├── 01_eda.py # Exploratory Data Analysis
│ └── 02_pipeline_demo.py # Pipeline demonstration
├── docs/ # Research notes and analysis
│ ├── final.md # VCC 2025 winner strategy analysis
│ ├── fix1.md # Bug identification document 1
│ ├── fix2.md # Bug identification document 2
│ ├── logic.md # Technical + literature verification of all fixes
│ ├── achieve.md # Publication roadmap
│ ├── verdict.md # Honest assessment of codebase vs winners
│ └── masterplan.md # Project plan
└── requirements.txt
pip install -r requirements.txt
Required: torch, anndata, scanpy, scikit-learn, scipy, numpy, pandas
Optional: torch_geometric, wandb, optuna
# Transformer with WMSE loss (prevents mode collapse)
python -m src.training.train --model transformer --reconstruction wmse \
--epochs 50 --data_path data/raw/train.h5ad
# GNN with causal edges + WMSE
python -m src.training.train --model gnn --edge_source causal --reconstruction wmse \
--epochs 50 --data_path data/raw/train.h5ad
# GNN with co-expression edges + standard loss + triplet discrimination
python -m src.training.train --model gnn --edge_source coexpression \
--discrimination triplet --use_embeddings --augment 3 \
--data_path data/raw/train.h5ad
| Flag | Default | Description |
|---|---|---|
--model | transformer | transformer or gnn |
--reconstruction | mae | mae (MAE+MSE blend) or wmse (variance-weighted) |
--edge_source | coexpression | coexpression or causal (GNN only) |
--discrimination | contrastive | contrastive or triplet |
--alpha | 1.0 | Reconstruction loss weight |
--beta | 0.1 | Discrimination loss weight |
--gamma | 0.05 | DE loss weight |
--epochs | 50 | Training epochs |
--lr | 1e-4 | Learning rate |
--batch_size | 32 | Batch size |
--augment | 1 | Augmentation factor (1 = none) |
--amp | off | Mixed precision training |
--use_embeddings | off | Enable discrimination loss |
python scripts/generate_submission.py --model gnn \
--checkpoint checkpoints/best_gnn.pt \
--data_path data/raw/train.h5ad \
--test_perts_path data/test_perturbations.txt \
--output submission.h5ad --n_cells 100 --n_control 500
The checkpoint contains pert_to_id, gene_names, and edge_index — no need to reconstruct them from data.
Global perturbation vocabulary: A single pert_to_id mapping is built from all perturbations before train/val split, ensuring embedding IDs are consistent across splits.
Unknown perturbation handling: padding_idx = n_perturbations reserves a zeroed, frozen embedding for any perturbation not seen during training.
DES with log₂FC conversion: Scanpy log1p uses natural log. LFC values are divided by ln(2) before applying the |log₂FC| > 0.5 threshold to match competition specification.
No batch correction on adata.X: Harmony only corrects PCA embeddings by design. regress_out and ComBat introduce artifacts (Luecken et al. 2024). Batch is better handled as a model covariate.
Semi-hard negative mining: Triplet loss uses semi-hard negatives (farther than positive but within margin) when available, falling back to hard negatives. Prevents training instability from hardest mining in early epochs.
Research notes and analysis are in docs/:
| Document | Content |
|---|---|
| final.md | VCC 2025 winner strategy analysis |
| fix1.md | 17 identified bugs with fixes |
| fix2.md | 23 identified bugs with fixes |
| logic.md | Technical + literature verification of all 23 bugs |
| achieve.md | Publication roadmap — tier-1 paper strategy |
| verdict.md | Honest assessment of codebase vs competition winners |
MIT
Official-pack-compatible Virtual Cell Challenge pipeline with metadata inspection, leakage-safe AnnData/HDF5 loading, Cell-Eval-aligned evaluation, hardened submission validation, and AMD/Hugging Face reproducibility workflows.
Python
0
6 commits
updated Jun 8, 2026
Predicting cellular responses to CRISPRi genetic perturbations in human H1 embryonic stem cells for the Virtual Cell Challenge 2025.
Combines causal GRN propagation, variance-weighted training, and corrected evaluation metrics — informed by ICML 2025 findings on mode collapse in perturbation prediction.
| Component | Module | What It Does |
|---|---|---|
| PerturbationTransformer | src/models/transformer.py | Encoder-only transformer with gene value + identity + perturbation embeddings. Delta learning. |
| BiologicalGNN | src/models/gnn.py | GAT on gene regulatory network. Perturbation injected at the target gene node via register_buffer mapping, then propagated through the graph. |
| HybridModel | src/models/hybrid.py | Gates transformer and GNN branches with learned sigmoid gate per gene. |
| Ensemble | src/models/ensemble.py | Weighted averaging with metric-aware weight optimization (DES, PDS, MAE). |
| Baselines | src/models/baseline.py | CellMean, PerturbationSpecific, KNN, Ridge — honest baselines evaluated on held-out perturbations only. |
The GNN supports two edge sources (--edge_source):
coexpression — Pearson correlation from control cells (correlation, not causation)causal — RENGE-style directed inference: when CRISPRi knockdown of gene A causes gene B to change significantly (Wilcoxon p<0.05, |log₂FC|>0.5), that's a directed edge A→B. This is causal signal extracted directly from perturbation data.$$\mathcal{L} = \alpha \cdot \mathcal{L}{\text{recon}} + \beta \cdot \mathcal{L}{\text{disc}} + \gamma \cdot \mathcal{L}_{\text{DE}}$$
| Component | Targets | Options |
|---|---|---|
| Reconstruction | MAE metric | --reconstruction mae (0.5 MAE + 0.5 MSE) or --reconstruction wmse (variance-weighted MSE — prevents mode collapse) |
| Discrimination | PDS metric | --discrimination contrastive (NT-Xent) or triplet (semi-hard negative mining) |
| DE-weighted | DES metric | Top-K DEGs upweighted 5× |
WMSE (from ICML 2025): weights each gene by its perturbation-induced variance, so predicting zero-delta for strongly affected genes is heavily penalized.
| Metric | Description | Implementation |
|---|---|---|
| MAE | Mean Absolute Error | Standard |
| DES | Differential Expression Score | Wilcoxon rank-sum + p<0.05 + |log₂FC|>0.5 threshold, Jaccard overlap |
| PDS | Perturbation Discrimination Score | KNN alignment of predicted vs true centroids |
| WMSE | Variance-Weighted MSE | Penalizes mode collapse — genes with large true deltas dominate |
| R²_w(Δ) | Weighted R² on deltas | Measures explained perturbation variance. Positive = better than mean prediction. |
WMSE and R²_w(Δ) are computed automatically by evaluate_perturbations().
virtual-cell-challenge/
├── src/
│ ├── models/
│ │ ├── transformer.py # PerturbationTransformer
│ │ ├── gnn.py # BiologicalGNN (register_buffer gene targeting)
│ │ ├── hybrid.py # Gated transformer+GNN hybrid
│ │ ├── ensemble.py # Metric-aware weighted ensemble
│ │ ├── foundation_adapter.py # Backbone adapter (scratch / embeddings)
│ │ ├── pretrained_embeddings.py # Gene embedding loader with coverage guard
│ │ └── baseline.py # Statistical baselines
│ ├── training/
│ │ ├── loss.py # CompositeLoss, WMSELoss, ContrastiveLoss, TripletLoss
│ │ ├── train.py # Training loop (AMP, augmentation, W&B)
│ │ ├── finetune.py # 3-phase progressive fine-tuning
│ │ └── hyperparam_search.py # Optuna HPO (on real data)
│ ├── evaluation/
│ │ └── metrics.py # MAE, WMSE, R²_w(Δ), DES, PDS
│ ├── data/
│ │ ├── dataset.py # PerturbationDataset (global pert_to_id)
│ │ ├── augmentation.py # Noise, Mixup, interpolation (Beta(5,1))
│ │ ├── preprocessing.py # scanpy QC → normalize → log1p → HVG subset
│ │ ├── biological_knowledge.py # GRN edges: STRING, KEGG, co-expression, causal
│ │ └── mock_data.py # Synthetic data for testing
│ └── utils/
│ ├── config.py # VCCConfig (single source of truth)
│ └── seed.py # Reproducibility
├── scripts/
│ ├── generate_submission.py # Submission builder (seeded noise, checkpoint context)
│ ├── run_full_pipeline.py # End-to-end pipeline
│ ├── download_data.py # Dataset fetcher
│ └── verify_submission_pipeline.py
├── notebooks/
│ ├── 01_eda.py # Exploratory Data Analysis
│ └── 02_pipeline_demo.py # Pipeline demonstration
├── docs/ # Research notes and analysis
│ ├── final.md # VCC 2025 winner strategy analysis
│ ├── fix1.md # Bug identification document 1
│ ├── fix2.md # Bug identification document 2
│ ├── logic.md # Technical + literature verification of all fixes
│ ├── achieve.md # Publication roadmap
│ ├── verdict.md # Honest assessment of codebase vs winners
│ └── masterplan.md # Project plan
└── requirements.txt
pip install -r requirements.txt
Required: torch, anndata, scanpy, scikit-learn, scipy, numpy, pandas
Optional: torch_geometric, wandb, optuna
# Transformer with WMSE loss (prevents mode collapse)
python -m src.training.train --model transformer --reconstruction wmse \
--epochs 50 --data_path data/raw/train.h5ad
# GNN with causal edges + WMSE
python -m src.training.train --model gnn --edge_source causal --reconstruction wmse \
--epochs 50 --data_path data/raw/train.h5ad
# GNN with co-expression edges + standard loss + triplet discrimination
python -m src.training.train --model gnn --edge_source coexpression \
--discrimination triplet --use_embeddings --augment 3 \
--data_path data/raw/train.h5ad
| Flag | Default | Description |
|---|---|---|
--model | transformer | transformer or gnn |
--reconstruction | mae | mae (MAE+MSE blend) or wmse (variance-weighted) |
--edge_source | coexpression | coexpression or causal (GNN only) |
--discrimination | contrastive | contrastive or triplet |
--alpha | 1.0 | Reconstruction loss weight |
--beta | 0.1 | Discrimination loss weight |
--gamma | 0.05 | DE loss weight |
--epochs | 50 | Training epochs |
--lr | 1e-4 | Learning rate |
--batch_size | 32 | Batch size |
--augment | 1 | Augmentation factor (1 = none) |
--amp | off | Mixed precision training |
--use_embeddings | off | Enable discrimination loss |
python scripts/generate_submission.py --model gnn \
--checkpoint checkpoints/best_gnn.pt \
--data_path data/raw/train.h5ad \
--test_perts_path data/test_perturbations.txt \
--output submission.h5ad --n_cells 100 --n_control 500
The checkpoint contains pert_to_id, gene_names, and edge_index — no need to reconstruct them from data.
Global perturbation vocabulary: A single pert_to_id mapping is built from all perturbations before train/val split, ensuring embedding IDs are consistent across splits.
Unknown perturbation handling: padding_idx = n_perturbations reserves a zeroed, frozen embedding for any perturbation not seen during training.
DES with log₂FC conversion: Scanpy log1p uses natural log. LFC values are divided by ln(2) before applying the |log₂FC| > 0.5 threshold to match competition specification.
No batch correction on adata.X: Harmony only corrects PCA embeddings by design. regress_out and ComBat introduce artifacts (Luecken et al. 2024). Batch is better handled as a model covariate.
Semi-hard negative mining: Triplet loss uses semi-hard negatives (farther than positive but within margin) when available, falling back to hard negatives. Prevents training instability from hardest mining in early epochs.
Research notes and analysis are in docs/:
| Document | Content |
|---|---|
| final.md | VCC 2025 winner strategy analysis |
| fix1.md | 17 identified bugs with fixes |
| fix2.md | 23 identified bugs with fixes |
| logic.md | Technical + literature verification of all 23 bugs |
| achieve.md | Publication roadmap — tier-1 paper strategy |
| verdict.md | Honest assessment of codebase vs competition winners |
MIT