Tasks/Life Sciences/Single-Cell Omics

Cross-dataset single-cell type annotation — maximize held-out macro-F1 at inference time

Annotate cells across datasets with a frozen foundation model

singlecell_celltype_annotation_cellxgene Life Sciences Single-Cell Omics
instruction.mdthis is what the agent is given

You inherit a cross-dataset cell-type annotation problem. You are given a labelled reference set (single cells with cell-type labels) and an unlabelled query set whose cells were assayed in a different experiment — different lab, different protocol, different batch. Using a shipped single-cell foundation model at inference time only, you must assign a cell type to every query cell. Your submitted annotate() is re-run unchanged on a sealed query drawn from an experiment you never see, and scored by macro-F1 against hidden ground-truth labels.

The difficulty is the cross-experiment distribution shift: a label-transfer pipeline that looks healthy when reference and query come from the same protocol degrades sharply once the query comes from an unseen one. Closing that gap without touching the model's weights is the research problem.

Hard Constraints

  • Single GPU, 24 h wall clock.
  • No fine-tuning and no re-training of the foundation model — forward passes to extract embeddings only. Do not train a new learned model on the query.
  • Co-annotation regime: you may use the reference labels and the query expression matrix only. You must not read, reconstruct, or infer the query ground-truth labels from any source.
  • Do not submit precomputed labels. annotate() must compute its output from the arrays it is handed, at grading time, on data it has not seen.
  • Only files under environment/methods/main/ are graded; do not modify selfcheck.py or data/.

What You Have

  • data/visible/ — a reference set plus a query from a different dataset, with labels used only by selfcheck.py for local scoring.
  • methods/main/solver.py — the starting method: default embeddings plus a plain nearest-neighbour label transfer. It is deliberately weak and it is what you replace.
  • The foundation-model weights, provided by the image; the path arrives in item["scgpt_model_dir"].
  • python environment/selfcheck.py — free and unlimited: runs your current annotate() on the visible query and prints macro-F1.

What You Submit

Edit environment/methods/main/solver.py, keeping this exact signature:

annotate(item: dict) -> dict
    # item: {
    #   "reference_h5ad": <labelled reference, .X = raw counts, .obs["cell_type"] = labels>,
    #   "query_h5ad":     <unlabelled query,   .X = raw counts, no labels>,
    #   "scgpt_model_dir": <foundation-model weight directory>,
    # }
    # returns: {"labels": [<predicted cell_type string per query cell>, ...]}   # query .obs order

You may add sibling modules under main/ and import them. There is no submit step and no per-attempt feedback: whatever sits in methods/main/ at the end is what gets re-run and graded.

How It Is Judged

A sealed verifier copies your methods/main/ into a clean sandbox with no network and no access to labels, calls annotate() once on the held-out query, and computes macro-F1 (over the cell types present in the ground truth) between your returned labels and the sealed truth — higher is better. The verifier recomputes the metric itself from your labels; it does not read any score you report. You receive no feedback from this run.

Metric

macro-F1 on the sealed cross-experiment query · higher is better

sklearn macro-F1 over the cell types present in the hidden truth, on one held-out query dataset

anchorvisible setheld-outreward
Bshipped 10-NN label transfer0.34200.13270.00
Rtuned label transfer (reference)0.36700.29040.30
Umacro-F1 = 1.0, metric maximum1.00001.00001.00
normalisation
m <= B0
B < m <= R0.3 * (m - B) / (R - B)
R < m <= U0.3 + 0.7 * (m - R) / (U - R)
m > U1.0 (cap)

m = this run's held-out metric  ·  B = shipped 10-NN label transfer  ·  R = tuned label transfer (reference)  ·  U = macro-F1 = 1.0, metric maximum

Linear inside each segment, clamped to 1.0 above U. Items are averaged first, then mapped once; the sealed set holds one item.

Rollouts

459 minwall clock
$134.69spend
205.3Mtokens
18versions, 18 kept
0.375 0.450 0.525 0.600 0.675 $0 $30 $60 $90 $120 cumulative spend on the run visible-query macro-F1 (selfcheck), higher is better tuned label transfer · visible · 0.3670 v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17
keptrevertedno scoreturning point
  1. v0Shipped baseline: scGPT embeddings plus uniform Euclidean 10-NN label transfer0.3424 min · $0.36
  2. v1Distance-weighted 30-NN with sqrt inverse reference-class-frequency votingWeight neighbours by distance and damp abundant reference classes, since macro-F1 pays per class, not per cell.0.42450 min · $11.11
  3. v2Hybrid: scGPT votes, marker centroids, joint SVD transfer, rank calibrationStop trusting one embedding: add reference marker centroids and a joint reference-query SVD, then combine by rank.0.56955 min · $12.24
  4. v3Frozen-scGPT rank-vote weight cut from 0.05 to 0.025 after a cached sweep0.57490 min · $23.49
  5. v41.5% lexical-ancestor near-tie boost when parent has 5x descendant support0.586100 min · $25.47
  6. v5Denoise local-SVD votes with 5% evidence from three nearest query neighbours0.592154 min · $40.88
  7. v6Tie-aware ranking in the sparse scGPT component: ties get their average rank0.592163 min · $44.98
  8. v70.5% rank term from class detection-rate centroids on detection markers0.593193 min · $54.39
  9. v89.1% of pooled-centroid rank moved to dataset/donor pseudobulks and markers0.595242 min · $64.00
  10. v98% of the local joint-SVD vote made invariant by dropping assay/donor PCs0.596280 min · $74.91
  11. v100.75% of the robust-SVD vote drops dataset- and assay-linked coordinates0.597302 min · $82.16
  12. v110.5% of the robust-SVD vote drops the query/reference-separating axis0.6352 min · $97.51
  13. v1214% of the pooled marker centroid replaced by a query-assay-matched one0.602366 min · $101.39
  14. v132% of the exact-assay centroid reserved for a 10x-family fallback centroid0.602379 min · $105.29
  15. v14One-rank explicit-marker sibling tie-break from runtime labels and gene names0.61411 min · $117.63
  16. v15Sibling tie-break strength raised from 1.0 to 1.2 rank units0.616416 min · $119.91
  17. v16Five exact-assay canonical marker gates: club, Treg, mast, LNEC, ciliatedHand-written multi-gene marker panels overrule the ensemble for five named classes it kept confusing.0.64446 min · $129.24
  18. v17Quantitative resolver: winner marker must beat the rival by 1.5 log units0.657455 min · $132.65

18 snapshots, $134.69 over 7.7 h; the first landed at 3.8 min, the last at 455. Nothing was ever reverted.

On the hidden set

held-out metricreward
shipped 10-NN label transfer0.13270.00
tuned label transfer (reference)0.29040.30
macro-F1 = 1.0, metric maximum1.00001.00
this run0.30890.3183