ALLCAPS — pneumococcal cps locus serotyping and novel-serotype detection
ALLCAPS takes a Streptococcus pneumoniae capsular biosynthetic locus (CBL, cps) and predicts
- whether the sequence is a cps locus at all,
- its serotype, and
- whether that serotype is novel — unlike anything seen in training.
Code, pipeline and issue tracker: https://github.com/bacpop/pneumococcal-serotyping
⚠️ License — please read before use
These weights are released under CC-BY-NC-4.0: non-commercial use only.
ALLCAPS is a classification head that runs on top of ProkBERT-mini-long, which is itself distributed under CC-BY-NC-4.0. ProkBERT is required at inference time — every query sequence is embedded by ProkBERT before ALLCAPS sees it — so the non-commercial restriction applies to the pipeline as a whole, and we pass it through rather than advertise a freedom we cannot grant.
Note the two licenses that apply to different parts of this project:
| Component | License |
|---|---|
ALLCAPS source code (the GitHub repository, and modeling_allcaps.py here) |
MIT |
| ALLCAPS weights (this repository) | CC-BY-NC-4.0 (inherited from ProkBERT) |
| ProkBERT-mini-long weights (downloaded separately at runtime) | CC-BY-NC-4.0 |
| ProkBERT source code | MIT |
If you need commercial use, you must take it up with the ProkBERT authors — our code license cannot lift their restriction. Attribution to both ALLCAPS and ProkBERT is required under CC-BY-NC; see Citation.
The
base_modelfield above records a dependency, not a fine-tune. ALLCAPS does not modify, contain or redistribute any ProkBERT weights — ProkBERT is loaded frozen, ineval()mode, and used purely as a feature extractor. This repository holds only ALLCAPS' own parameters.
Model description
Input DNA is split into 4 kbp chunks with 50% overlap and embedded with frozen ProkBERT-mini-long (384-d per chunk). Those chunk embeddings are then processed by ALLCAPS' own encoder:
chunk embeddings (L, 384)
→ + learned positional embeddings
→ TransformerEncoder (1 layer, nhead=4)
→ masked mean-pool (padding-aware)
→ projection MLP → L2-normalised 128-d locus embedding z
→ three linear heads:
cbl_classifier (2 classes: capsule y/n)
serotype_classifier (82 serotypes)
genogroup_classifier (53 genogroups — NOT TRAINED, see below)
Model class TransformerTriHeadLR. A single locus embedding z serves both classification and
novelty detection.
| Base encoder | neuralbioinfo/prokbert-mini-long (frozen, not included here) |
| Chunk size / stride | 4000 bp / 50% overlap |
| Max input length | 30,000 bp |
| Base embedding dim | 384 |
| Locus embedding dim | 128 (L2-normalised) |
| Trainable parameters | 2,013,833 |
| Serotype classes | 82 |
| Genogroup classes | 53 (head untrained — see below) |
⚠️ The genogroup head is not trained
The architecture has three heads; the released model trains two. The genogroup head was found to add nothing to serotype or novelty performance, so it was trained with loss weight 0. It still emits logits, and those logits are meaningless — the head sits at 0.8% accuracy over 53 classes, below the 1.9% you would get by guessing.
Do not read a genogroup off this model. If you want the genogroup of a call, derive it from the
predicted serotype, or use the nn_serotype the novelty detector returns (which comes from the
training data, not from this head).
Novel-serotype detection
After a query is classified as a cps locus, its pooled embedding z is compared by cosine
distance to every training cps embedding. The locus is called novel when the distance to
its nearest training neighbour (k = 1) exceeds
T = 0.014824147932735014
which is the 95th percentile of the training leave-one-out 1-NN distances — a threshold derived without ever looking at novel data. The index shipped here holds 54,102 training locus embeddings with their serotype labels, so the report also names the closest known serotype: a novel locus is placed in a neighbourhood rather than merely rejected.
An energy score (E = −T·logsumexp(logits/T)) is available as a reference baseline
(energy_summary.json carries its calibrated percentiles). Across 98 leave-one-serotype-out folds,
kNN beat energy on every fold-level metric, so kNN is the deployed detector.
Distances must be computed in float64. In float32,
1 − x·ycancels below machine epsilon for the many near-identical loci in this dataset, quantising ~73% of in-distribution distances toward zero and costing ~0.015 AUROC.modeling_allcaps.pyalready does this; if you reimplement the detector, don't lose it.
Files in this repository
| File | Contents |
|---|---|
model.safetensors |
The 2.0M ALLCAPS parameters |
config.json |
Architecture + the chunking parameters inference must match |
labels.json |
serotype_to_idx (82) and genogroup_to_idx (53) |
knn_index.npz |
Novelty index: 54,102 × 128 training embeddings, their sample keys and serotypes, and the leave-one-out distance distribution |
knn_config.json |
The deployed operating point: k=1, cosine, p95, and the resolved threshold |
energy_summary.json |
Calibrated percentiles for the energy baseline |
modeling_allcaps.py |
Self-contained inference — architecture, kNN detector, FASTA → calls. Loads model.safetensors, falling back to transformer_model.pth |
transformer_model.pth |
The same weights as a torch.save checkpoint — self-describing (model_config, serotype_to_idx), and the file the ALLCAPS CLI and the pipeline load |
assets/dexB_aliA_ATCC700669.fasta |
The flanking genes used to cut a cps locus out of an assembly |
eval/ |
Classification reports, confusion matrices, CV summary, and the 98-fold novelty report |
The knn_index.npz carries training contig identifiers (cbl|Public_ID#Contig_ID, the
pipeline's own record keys) and their serotypes. These are public GPS/ENA accessions.
How to use
The ALLCAPS CLI (recommended)
The CLI takes whole assemblies, cuts the cps locus out for you, and downloads
everything in this repository on first run (cached under ~/.cache/allcaps, override with
ALLCAPS_CACHE):
git clone https://github.com/bacpop/pneumococcal-serotyping && cd ALLCAPS
mamba env create -f environment-cpu.yml # or environment-gpu.yml on linux-64 + CUDA
conda activate allcaps && pip install .
ALLCAPS predict --input samples.txt --extract align --output results/
samples.txt lists one assembly FASTA per line (gzip is fine); bare paths work too —
ALLCAPS predict a.fasta b.fasta --extract align. The run writes results/predictions.tsv
and prints the same table, one row per contig, carrying serotype,
serotype_confidence, is_novel_knn, nn_serotype, nn_distance and a plain-language
note.
--extract align cuts between the dexB/aliA flanking genes with minimap2 — this is how
the model was trained, and what you want. --extract scan embeds rolling windows across the
whole assembly instead; it needs no flanking genes but is orders of magnitude slower.
To pin this repository explicitly, or to use a model you trained yourself:
ALLCAPS predict --input samples.txt --extract align --output results/ \
--hf-repo bacpop/ALLCAPS --revision main # or:
ALLCAPS predict --input samples.txt --extract align --output results/ \
--model transformer_model.pth \
--knn-index knn_index.npz \
--energy-summary energy_summary.json \
--offline
The three --model / --knn-index / --energy-summary files are exactly the ones in this
repository, and exactly what ALLCAPS train writes into its run directory.
Standalone, without the repository
modeling_allcaps.py here is self-contained — architecture, kNN detector and FASTA → calls
in one file, needing only torch, transformers, numpy, safetensors and biopython. It takes
already-cut loci, not whole genomes.
pip install torch 'transformers<5' numpy biopython safetensors
from huggingface_hub import snapshot_download
from modeling_allcaps import ALLCAPS # from this repository
path = snapshot_download("bacpop/ALLCAPS")
model = ALLCAPS.from_pretrained(path, device="cpu")
for call in model.predict_fasta("my_loci.fasta"):
print(call.record_id, call.serotype, call.confidence,
"NOVEL" if call.is_novel else call.nn_serotype)
Or from the command line:
python modeling_allcaps.py my_loci.fasta --model_dir . --output calls.csv
ProkBERT is fetched from the Hub automatically on first use.
Input must be cut loci, not whole genomes. Extract the region between the flanking
dexB/aliA genes first — assets/dexB_aliA_ATCC700669.fasta is the query file for that,
and allcaps.data_locus_cutter in the GitHub repository does the cutting. The ALLCAPS
CLI above does it for you.
The pipeline modules directly
Useful when you want the intermediate files. Run as modules from src/:
cd src
python -m allcaps.trihead.process_trihead_query \
--query my_loci.fasta --model_path transformer_model.pth \
--energy_summary energy_summary.json --output_dir out/ --device cpu
python -m allcaps.knn_ood predict \
--input_type query --embeddings out/query_embeddings.npz \
--knn_index knn_index.npz --threshold_percentile 95.0 --max_k 5 \
--output out/knn_query_distances.csv
| Output | Contents |
|---|---|
query_results.csv |
Serotype calls, confidence, is_novel_energy (the baseline) |
knn_query_distances.csv |
The deployed novelty call: is_novel_knn, distance, nearest known serotype |
ALLCAPS predict runs exactly these two stages and joins their output into one table.
modeling_allcaps.py and the pipeline agree to within 1e-15 on distances and exactly on
novelty flags. They can disagree on which neighbour is reported when several training loci
sit at identical distance (~0.4% of queries, all exact ties) — the tie-break order differs,
the distance does not.
Intended use and limitations
Intended for research and public-health genomic surveillance of S. pneumoniae: in-silico serotyping of assembled genomes, and triage of candidate novel capsular types for follow-up.
Not intended for clinical diagnosis or treatment decisions. This model is research software; it has not been validated as a diagnostic device, and the non-commercial license precludes that use in any case.
Limitations
- Trained on S. pneumoniae only — behaviour on other streptococci or other species is undefined, though the capsule y/n head is designed to reject non-cps input.
- Serotypes with fewer than 5 distinct samples were not kept as resolvable classes, so rare types may surface as "novel" rather than as themselves. 21 of the 98 serotype labels in the novelty index are represented by fewer than 20 contigs.
- Class support is very uneven: the largest serotype contributes 5,429 training contigs, the smallest 1.
- A cps locus is frequently split across two contigs in a draft assembly. Metrics here are per contig, not per sample.
- Novelty is a distance threshold, not proof. A "novel" call is a prompt to look, not a discovery.
- Training data reflects the geographic and temporal sampling of the GPS/Monocle collection.
- The genogroup head is untrained (see above).
Training data
S. pneumoniae assemblies from the GPS (Global Pneumococcal Sequencing) / Monocle collection.
| Training contigs | 119,909, of which 54,102 capsulated |
| Training assemblies with a cps locus | 34,125 |
| Held-out test contigs | 13,337 (6,014 capsulated) across 7,598 assemblies |
| Serotype labels present | 98 (82 resolvable as classes) |
| Collection version / date |
Train/test splitting is grouped by sample, never by contig, so sibling contigs of one assembly
never straddle the boundary; a runtime assertion enforces it. Serotypes with < 5 distinct samples
go entirely to train, so every test serotype is also represented in training.
Trained for 100 epochs, batch size 128, AdamW at lr 1e-3, with 5-fold stratified CV followed by a
refit on all data. Full hyperparameters: see TRAINING.md in the GitHub repository, or train your own with ALLCAPS train --input manifest.csv --output run/.
Evaluation
Capsule detection (training set, per contig, n = 106,909): precision, recall and F1 all 1.00; weighted F1 0.9997.
Closed-set serotyping
| Split | Accuracy | Macro F1 | Weighted F1 | n |
|---|---|---|---|---|
| 5-fold CV (mean ± sd) | 0.952 ± 0.003 | — | — | 23,982/fold |
| Held-out GPS test | 0.96 | 0.81 | 0.95 | 5,833 |
| UK benchmark (external) | 0.89 | 0.69 | 0.88 | 3,012 |
The macro/weighted gap is the long tail: rare serotypes carry most of the errors.
Novel-serotype detection, 98 leave-one-serotype-out folds — each fold retrains with one serotype removed, so that serotype is genuinely unseen:
| Detector | AUROC mean | AUROC median | TPR@5%FPR mean | TPR@5%FPR median | folds won |
|---|---|---|---|---|---|
| kNN (deployed) | 0.945 | 0.967 | 0.635 | 0.777 | 95/98 |
| energy (baseline) | 0.836 | 0.884 | 0.394 | 0.310 | 3/98 |
95% CI on mean AUROC: kNN [0.936, 0.955], energy [0.808, 0.864] — non-overlapping.
Threshold calibration. The p95 threshold is set on training leave-one-out distances, and it transfers: 5.00% of training loci are flagged (by construction) and 4.89% of held-out capsulated test loci — all of which are in-distribution, so that is the false-positive rate. Of held-out non-capsulated contigs, 99.9% are flagged, which is the correct behaviour: non-cps sequence is far from every cps embedding.
Per-fold and per-genogroup breakdowns are in eval/novel_detection_loo_report.md.
Citation
If you use ALLCAPS, please cite both ALLCAPS and ProkBERT.
@article{allcaps,
title = {ALLCAPS: a scalable and accurate deep-learning method for in silico pneumococcal serotyping},
author = {Tajmirriahi, Alireza and Mateeva, Teodora and Russell, Matthew and Lorenz, Oliver
and Kim, Soo-Jeong and Lo, Stephanie and Bentley, Stephen and Lees, John
and Horsfield, Sam},
year = {2026},
journal = {bioRxiv},
doi = {<!-- TODO: DOI on preprint -->}
}
@article{ligeti2024prokbert,
title = {ProkBERT family: genomic language models for microbiome applications},
author = {Ligeti, Bal{\'a}zs and Szepesi-Nagy, Istv{\'a}n and Bodn{\'a}r, Bal{\'a}zs
and Ligeti-Nagy, No{\'e}mi and Juh{\'a}sz, J{\'a}nos},
journal = {Frontiers in Microbiology},
volume = {14},
pages = {1331233},
year = {2024},
doi = {10.3389/fmicb.2023.1331233}
}
Acknowledgements
Built on ProkBERT by the Neural Bioinformatics Research Group, Pázmány Péter Catholic University. Training data from the Global Pneumococcal Sequencing project.
Contact
Alireza Tajmirriahi and Sam Horsfield — please open an issue on the repository.
- Downloads last month
- 14
Model tree for BacPop/ALLCAPS
Base model
neuralbioinfo/prokbert-mini-long