Fine-tune¶
What it does¶
Updates a model’s parameters on labelled cells, following the recipe its authors published, then predicts labels for new cells. Unlike transfer, this changes the model — and so needs meaningfully more data, GPU time, and care.
Only methods whose official adaptation actually updates weights are offered here. Models
that are adapted by training a classifier on frozen embeddings — SCimilarity, GenePT, UCE
and scPRINT — are not listed under finetune; run transfer --classifier mlp instead,
which is the same post-hoc head for every method. The one deliberate exception is
scCello, whose official recipe is linear probing on the frozen encoder; it is kept here
because that is what its authors call fine-tuning.
Supported methods¶
scfoundry list methods --task finetune:
cellama cellfm cellplm geneformer langcell scbert sccello scfoundation scgpt
Cell2Sentence fine-tuning is not yet supported.
Inputs¶
Two files satisfying the standard input contract:
Reference — the training set. Must carry labels in
obs["cell_type"](or--label-key). Any discrete column works.Query — the cells to predict. Labels not required.
Warning
Input must be raw counts over the full transcriptome. Passing log-normalised values, or an object already subset to highly variable genes, does not raise an error — it produces a plausible-looking embedding that is silently wrong. See Input data format.
Every example on this page runs against the four demo files, downloaded once into a
demo/ directory of the workspace as shown in Demo datasets:
demo/colon_1000.h5ad 1,000 human colon cells, labelled, 7 batches
demo/colon_50.h5ad 50 human colon cells, labelled
demo/liver_1shot_support.h5ad one labelled cell per class (5 cells)
demo/liver_1shot_query.h5ad 75 liver cells to annotate
The colon pair is designed for this task: fine-tune on colon_1000.h5ad, predict the
held-out colon_50.h5ad.
Running it¶
Note
Run scfoundry commands from inside a workspace created by scfoundry init (any
subdirectory works — the workspace is found by walking upwards, like a git repository), or
pass --workspace DIR. Weights, the image cache, results and run logs all live there. See
The workspace.
scfoundry finetune --method scgpt \
--reference demo/colon_1000.h5ad \
--query demo/colon_50.h5ad
[PROCESS 4a/8c2e10] FINETUNE:finetune_by_scgpt (colon_1000)
[PROCESS b0/17d3f9] FINETUNE:predict_by_scgpt (colon_50)
[SUCCESS] completed=2 failed=0 cached=0
The fine-tuned weights are saved, so they can be reused later.
scfoundry finetune --method scgpt --reference demo/colon_1000.h5ad
results/finetune/finetuned_models/scgpt/colon_1000/
Fine-tuning is the expensive half. Split the stages when one trained model will be applied to several query sets.
scfoundry finetune --method scgpt \
--query demo/colon_50.h5ad \
--fitted results/finetune/finetuned_models/scgpt/colon_1000
--fitted is always the directory written by the fine-tuning stage, whatever the method.
Parameters¶
Option |
Type |
Default |
Description |
|---|---|---|---|
|
string |
required |
Which model to fine-tune. |
|
path |
none |
Labelled training set. Required to fine-tune. |
|
path |
none |
Cells to predict. Required to predict. |
|
path |
none |
Fine-tuned model directory. Required when |
|
string |
|
|
|
integer |
per method |
Training epochs. |
|
integer |
per method |
Training batch size. Lower it first when you hit out-of-memory. |
|
string |
per method |
Pretrained checkpoint to start from, under |
Two more training parameters have per-method defaults and are forwarded as pipeline
parameters: --finetune_eval_size (the fraction of the reference held out for validation)
and --predict_batch_size. scFoundation additionally exposes --scfoundation_lr,
--scfoundation_scheduler and --scfoundation_grad_clip; CELLama exposes
--cellama_finetuned_top_k.
Per-method training defaults¶
Epochs and batch sizes follow each model’s published fine-tuning recipe, which is why they vary so widely; the validation fraction is 0.2 for every method. These are the settings of the paper’s fine-tuning benchmark, so a run with no options reproduces its protocol. Changing them without a reason usually makes results worse rather than better.
Method |
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
not used |
|
not used |
|
|
|
not used (see below) |
not used |
Note
Two of these are not what they look like. scBERT’s 100 epochs is an upper bound — early
stopping with patience 10 governs in practice, matching the official recipe. And CELLama
is a two-stage method: its sentence backbone is fine-tuned on 10,000 generated cell
sentences with CELLama’s own training routine, which holds out a fixed 1,000 of them for
evaluation and never sees a cell-type label; the labels are then used by a post-hoc
classifier on the fine-tuned embeddings, which holds out 0.2 of the cells like every other
method. --finetune_eval_size therefore has nothing to change and is ignored.
Outputs¶
results/finetune/
├── finetuned_models/<method>/<reference>/
│ ├── ... the model's own files
│ ├── base_model/ CELLama only: the fine-tuned sentence backbone
│ └── posthoc_classifier/ CELLama only: the head trained on its embeddings
└── prediction/<method>/
├── <query>_predicted_labels.tsv
└── <query>_predicted_probs.tsv
For the commands above:
results/finetune/finetuned_models/scgpt/colon_1000/ args.json, best_model.pt, label_map.json, vocab.json
results/finetune/prediction/scgpt/colon_50_predicted_labels.tsv
results/finetune/prediction/scgpt/colon_50_predicted_probs.tsv
Model directories are copied out of the Nextflow work directory, not symlinked, so they survive the cleanup that follows a successful run.
Both TSVs are indexed by barcode. _predicted_labels.tsv has a single
predicted_label column; _predicted_probs.tsv has one column per class.
Reading results in Python¶
import anndata as ad
import pandas as pd
labels = pd.read_csv(
"results/finetune/prediction/scgpt/colon_50_predicted_labels.tsv",
sep="\t", index_col=0,
)
truth = ad.read_h5ad("demo/colon_50.h5ad").obs["cell_type"].reindex(labels.index)
print(f"accuracy: {(labels['predicted_label'] == truth).mean():.1%}")
A per-class breakdown is usually more informative than a single accuracy, especially with imbalanced cell types:
from sklearn.metrics import classification_report
print(classification_report(truth, labels["predicted_label"], zero_division=0))
Gotchas¶
Label sets must match. The classifier can only predict classes it saw during training. Cell types present in the query but not the reference are forced into some training class — quietly, with no warning. If you expect novel types, this task is the wrong tool.
Fine-tuning needs real data. The demo files exist to show the mechanics, not to produce a usable classifier. A thousand cells across seven classes will overfit almost any backbone.
Fine-tuned weights are large. A fine-tuned model directory is comparable in size to the original checkpoint — 205 MB for scGPT, 261 MB for CellPLM. Fine-tuning several models on several datasets adds up quickly.
Batch-size flags are inert for some methods. CellPLM ignores --batch-size and
--predict_batch_size; CELLama ignores --predict_batch_size. Passing them is accepted
but has no effect.
Note
Every launch gets its own run directory under runs/<task>/, and the workspace
nextflow.config sets cleanup = true, so the task work directory is deleted once the run
succeeds. After a failure it is kept: fix the cause, add --resume to the same command,
and Nextflow reuses every task that already completed. scfoundry runs lists the run
directories with their status; scfoundry runs --task <task> narrows the list.