Skill Tracer
Skills › Scientific Agent Skills › waypoint-bio › python-api.md

python-api.md

244 lines · skills/waypoint-bio/references/python-api.md at 5f2bc03

Open on GitHub
1# Using Waypoint from Python
2
3The CLI covers the standard paths. Drop to Python when you need a custom training loop, a different
4head, or embeddings inside a larger pipeline. Hub examples are source-checked templates;
5the review ran tiny random CPU models and local synthetic taxa, without gated weights or data.
6Use the compatibility stack in `SKILL.md`; see `upstream-review.md` for release limitations.
7

Using Waypoint from Python

The CLI covers the standard paths. Drop to Python when you need a custom training loop, a different head, or embeddings inside a larger pipeline. Hub examples are source-checked templates; the review ran tiny random CPU models and local synthetic taxa, without gated weights or data. Use the compatibility stack in SKILL.md; see upstream-review.md for release limitations.

Raw lines 1 to 7
8## Package surface
9
10`waypoint_bio` lazily re-exports:
12```python
13from waypoint_bio import (
14 TaxonomicTokenizer, # the tokenizer class
15 load_tokenizer, # load one from a Hub id or local dir
16 MicrobiomePretrainingDataset, # causal-LM dataset
17 MicrobiomeBenchmarkDataset, # supervised dataset with targets/covariates
18 load_waypoint_dataframe, # read waypoint-format parquet/csv/tsv
19 load_abundance_matrix, # read a sample x taxa matrix
20 matrix_to_waypoint_df, # matrix -> waypoint format
22```
24Imports are deferred, so `import waypoint_bio` does not pull in torch.

Package surface

waypoint_bio lazily re-exports:

from waypoint_bio import (
    TaxonomicTokenizer,             # the tokenizer class
    load_tokenizer,                 # load one from a Hub id or local dir
    MicrobiomePretrainingDataset,   # causal-LM dataset
    MicrobiomeBenchmarkDataset,     # supervised dataset with targets/covariates
    load_waypoint_dataframe,        # read waypoint-format parquet/csv/tsv
    load_abundance_matrix,          # read a sample x taxa matrix
    matrix_to_waypoint_df,          # matrix -> waypoint format
)

Imports are deferred, so import waypoint_bio does not pull in torch.

Raw lines 8 to 25
26## Loading a checkpoint directly with transformers
28The tokenizer is custom and ships as remote code, so `trust_remote_code=True` is required for it.
29The model itself is a stock GPT-2 and does not need it.
31```python
32from transformers import AutoTokenizer, AutoModel
34model_id = "outpost-bio/Waypoint-45m"
35revision = "1664ab5ec88b4bdf571f2512968f99dc222ce0ac" # Hub metadata checked 2026-10-01
36tok = AutoTokenizer.from_pretrained(model_id, revision=revision, trust_remote_code=True)
37model = AutoModel.from_pretrained(model_id, revision=revision) # requires approved access
38```
40Review the repository's `tokenization_taxonomic.py` before executing it. The upstream
41`load_tokenizer(model_path)` and `try_load_token_std_means(model_path)` do not accept a
42`revision`, and CLI commands do not expose one. To keep tokenizer, weights and statistics
43at the same revision, download a snapshot after access approval and use its local path:
45```python
46from huggingface_hub import snapshot_download
48checkpoint = snapshot_download(repo_id=model_id, revision=revision)
49# Pass checkpoint to --model, load_tokenizer and try_load_token_std_means.
50```
52This downloads model artifacts; it was not executed during the bounded review. A public Hub
53model card or metadata response does not grant access to its gated files.
55`AutoModelForCausalLM` also works if you want the LM head for likelihood scoring or generation —
56generation samples taxa, which is occasionally useful for probing what the model learned about
57co-occurrence, but is not a validated use.

Loading a checkpoint directly with transformers

The tokenizer is custom and ships as remote code, so trust_remote_code=True is required for it. The model itself is a stock GPT-2 and does not need it.

from transformers import AutoTokenizer, AutoModel

model_id = "outpost-bio/Waypoint-45m"
revision = "1664ab5ec88b4bdf571f2512968f99dc222ce0ac"  # Hub metadata checked 2026-10-01
tok = AutoTokenizer.from_pretrained(model_id, revision=revision, trust_remote_code=True)
model = AutoModel.from_pretrained(model_id, revision=revision)  # requires approved access

Review the repository's tokenization_taxonomic.py before executing it. The upstream load_tokenizer(model_path) and try_load_token_std_means(model_path) do not accept a revision, and CLI commands do not expose one. To keep tokenizer, weights and statistics at the same revision, download a snapshot after access approval and use its local path:

from huggingface_hub import snapshot_download

checkpoint = snapshot_download(repo_id=model_id, revision=revision)
# Pass checkpoint to --model, load_tokenizer and try_load_token_std_means.

This downloads model artifacts; it was not executed during the bounded review. A public Hub model card or metadata response does not grant access to its gated files.

AutoModelForCausalLM also works if you want the LM head for likelihood scoring or generation — generation samples taxa, which is occasionally useful for probing what the model learned about co-occurrence, but is not a validated use.

Raw lines 26 to 58
59## Tokenizing by hand
61```python
62from waypoint_bio import load_tokenizer
64tok = load_tokenizer("outpost-bio/Waypoint-6m")
66lineage = "k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Lactobacillaceae; g__Lactobacillus"
67print(tok.tokenize(lineage)) # ['g__Lactobacillus']
68print(tok.convert_tokens_to_ids(["g__Lactobacillus"]))
70# One sample = newline-separated lineages
71sample = "\n".join([lineage, "k__Bacteria; p__Bacteroidota; g__Bacteroides"])
72print(tok(sample)["input_ids"])
73```
75Checking whether a taxon is in vocabulary:
77```python
78vocab = tok.get_vocab()
79"g__Lactobacillus" in vocab # inspect the exact checkpoint vocabulary
80tok.convert_tokens_to_ids("g__Nonesuch") == tok.unk_token_id
81```
83`tok._extract(lineage)` applies rank extraction and higher-rank fallback, returning a token
84string or `None`. It is private and source-checked only for the reviewed versions; the bundled
85coverage helper instead uses `convert_tokens_to_ids`. `tok(sample)` alone does not reproduce
86the dataset encoding: it does not sort by abundance or explicitly add BOS/EOS. Use the dataset
87or `tokenize_for_embedding` for the model's sample representation.

Tokenizing by hand

from waypoint_bio import load_tokenizer

tok = load_tokenizer("outpost-bio/Waypoint-6m")

lineage = "k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Lactobacillaceae; g__Lactobacillus"
print(tok.tokenize(lineage))                    # ['g__Lactobacillus']
print(tok.convert_tokens_to_ids(["g__Lactobacillus"]))

# One sample = newline-separated lineages
sample = "\n".join([lineage, "k__Bacteria; p__Bacteroidota; g__Bacteroides"])
print(tok(sample)["input_ids"])

Checking whether a taxon is in vocabulary:

vocab = tok.get_vocab()
"g__Lactobacillus" in vocab          # inspect the exact checkpoint vocabulary
tok.convert_tokens_to_ids("g__Nonesuch") == tok.unk_token_id

tok._extract(lineage) applies rank extraction and higher-rank fallback, returning a token string or None. It is private and source-checked only for the reviewed versions; the bundled coverage helper instead uses convert_tokens_to_ids. tok(sample) alone does not reproduce the dataset encoding: it does not sort by abundance or explicitly add BOS/EOS. Use the dataset or tokenize_for_embedding for the model's sample representation.

Raw lines 59 to 88
89## Building a dataset
91```python
92import pandas as pd
93from waypoint_bio import MicrobiomePretrainingDataset, load_tokenizer, load_waypoint_dataframe
94from waypoint_bio.dataset import try_load_token_std_means
96df = load_waypoint_dataframe("dataset.parquet")
97tok = load_tokenizer("outpost-bio/Waypoint-6m")
98stats = try_load_token_std_means("outpost-bio/Waypoint-6m") # None if absent
100ds = MicrobiomePretrainingDataset(df, tok, max_length=512, token_std_means=stats)
101ds[0]["input_ids"].shape # torch.Size([512])
102```
104Each item is `[BOS] + ordered token ids + [EOS]`, right-padded. With no statistics it uses raw
105abundance order. `MicrobiomePretrainingDataset` drops rows with no known taxa, so its row count
106can differ from `df`. Its raw `labels` include pad IDs; use `DataCollatorForLanguageModeling`
107with `mlm=False` to mask pad labels, as the CLI does.
109Computing the ordering statistics for a corpus of your own:
111```python
112from waypoint_bio.dataset import compute_token_std_means
114train_df = df # replace with the training subset only when holding out validation/test data
115stats = compute_token_std_means(train_df, tok, show_progress=True)
116stats.to_parquet("token_std_means.parquet") # index name "token", columns mean/std
117```
119For a newly trained model, store these statistics beside its checkpoint. For an existing
120pretrained model, retain its original statistics: replacing them changes token ordering and
121the input distribution. Upstream catches every Hub statistics-download exception and falls
122back to raw abundance order, including authorization/network errors; require the expected
123local file for reproducible comparisons.

Building a dataset

import pandas as pd
from waypoint_bio import MicrobiomePretrainingDataset, load_tokenizer, load_waypoint_dataframe
from waypoint_bio.dataset import try_load_token_std_means

df = load_waypoint_dataframe("dataset.parquet")
tok = load_tokenizer("outpost-bio/Waypoint-6m")
stats = try_load_token_std_means("outpost-bio/Waypoint-6m")   # None if absent

ds = MicrobiomePretrainingDataset(df, tok, max_length=512, token_std_means=stats)
ds[0]["input_ids"].shape        # torch.Size([512])

Each item is [BOS] + ordered token ids + [EOS], right-padded. With no statistics it uses raw abundance order. MicrobiomePretrainingDataset drops rows with no known taxa, so its row count can differ from df. Its raw labels include pad IDs; use DataCollatorForLanguageModeling with mlm=False to mask pad labels, as the CLI does.

Computing the ordering statistics for a corpus of your own:

from waypoint_bio.dataset import compute_token_std_means

train_df = df  # replace with the training subset only when holding out validation/test data
stats = compute_token_std_means(train_df, tok, show_progress=True)
stats.to_parquet("token_std_means.parquet")   # index name "token", columns mean/std

For a newly trained model, store these statistics beside its checkpoint. For an existing pretrained model, retain its original statistics: replacing them changes token ordering and the input distribution. Upstream catches every Hub statistics-download exception and falls back to raw abundance order, including authorization/network errors; require the expected local file for reproducible comparisons.

Raw lines 89 to 124
125## Embeddings without the CLI
127```python
128import torch
129from transformers import AutoModel
130from waypoint_bio.dataset import load_waypoint_dataframe, try_load_token_std_means
131from waypoint_bio.embed import tokenize_for_embedding
132from waypoint_bio.models import _pool
133from waypoint_bio.tokenizer import load_tokenizer
135model_path = checkpoint # reviewed local snapshot from above
136df = load_waypoint_dataframe("dataset.parquet")
137tok = load_tokenizer(model_path)
138model = AutoModel.from_pretrained(model_path).eval()
140samples = tokenize_for_embedding(df, tok, max_length=512,
141 token_std_means=try_load_token_std_means(model_path))
143# This small example materializes the whole batch; use a DataLoader for large datasets.
144input_ids = torch.stack([s["input_ids"] for s in samples])
145attn = torch.stack([s["attention_mask"] for s in samples])
147with torch.no_grad():
148 hidden = model(input_ids=input_ids, attention_mask=attn).last_hidden_state
149 emb = _pool(hidden, attn, "last_token") # [n_samples, hidden_size]
150```
152`tokenize_for_embedding` preserves one output row per input row even when a row has no
153in-vocabulary taxa, so `emb` stays aligned with `df.index`. Those rows encode as `[BOS][EOS]` and
154their embeddings should be discarded, not interpreted.

Embeddings without the CLI

import torch
from transformers import AutoModel
from waypoint_bio.dataset import load_waypoint_dataframe, try_load_token_std_means
from waypoint_bio.embed import tokenize_for_embedding
from waypoint_bio.models import _pool
from waypoint_bio.tokenizer import load_tokenizer

model_path = checkpoint  # reviewed local snapshot from above
df = load_waypoint_dataframe("dataset.parquet")
tok = load_tokenizer(model_path)
model = AutoModel.from_pretrained(model_path).eval()

samples = tokenize_for_embedding(df, tok, max_length=512,
                                 token_std_means=try_load_token_std_means(model_path))

# This small example materializes the whole batch; use a DataLoader for large datasets.
input_ids = torch.stack([s["input_ids"] for s in samples])
attn = torch.stack([s["attention_mask"] for s in samples])

with torch.no_grad():
    hidden = model(input_ids=input_ids, attention_mask=attn).last_hidden_state
    emb = _pool(hidden, attn, "last_token")     # [n_samples, hidden_size]

tokenize_for_embedding preserves one output row per input row even when a row has no in-vocabulary taxa, so emb stays aligned with df.index. Those rows encode as [BOS][EOS] and their embeddings should be discarded, not interpreted.

Raw lines 125 to 155
156## Custom heads
158`waypoint_bio.models` provides the two heads used by `finetune` and `benchmark`:
160```python
161from waypoint_bio.models import ClassificationModel, RegressionModel
163head = ClassificationModel(
164 base_model=model,
165 tokenizer=tok,
166 label_dims=[3], # one entry per target column
167 pooling_strategy="last_token",
168 covariate_dim=0, # width of the one-hot covariate block
169 class_weights=None, # list[torch.Tensor], one per target
171```
173Both pool `last_hidden_state`, concatenate the one-hot covariate block if present, and apply one
174`nn.Linear` per target column. Multi-target classification masks label `-100` per target, so targets
175with missing values in some rows are handled without dropping the row.
177Pooling strategies: `mean` (mask-weighted average), `last_token` (last non-padding position — the
178default for supervised heads; pretraining has no sample-pooling objective), `first_token` / `cls_token` (position 0, the BOS
179token; weak in a causal LM).

Custom heads

waypoint_bio.models provides the two heads used by finetune and benchmark:

from waypoint_bio.models import ClassificationModel, RegressionModel

head = ClassificationModel(
    base_model=model,
    tokenizer=tok,
    label_dims=[3],                 # one entry per target column
    pooling_strategy="last_token",
    covariate_dim=0,                # width of the one-hot covariate block
    class_weights=None,             # list[torch.Tensor], one per target
)

Both pool last_hidden_state, concatenate the one-hot covariate block if present, and apply one nn.Linear per target column. Multi-target classification masks label -100 per target, so targets with missing values in some rows are handled without dropping the row.

Pooling strategies: mean (mask-weighted average), last_token (last non-padding position — the default for supervised heads; pretraining has no sample-pooling objective), first_token / cls_token (position 0, the BOS token; weak in a causal LM).

Raw lines 156 to 180
181## Loading Atlas and Compass
183```python
184from datasets import load_dataset
186atlas = load_dataset("outpost-bio/Atlas", split="pretrain",
187 revision="9dad400edb9e4f1e483a2f9021208c16f694aec2") # 485,377 rows
188atlas_bench = load_dataset("outpost-bio/Atlas", split="benchmark",
189 revision="9dad400edb9e4f1e483a2f9021208c16f694aec2") # 53,931 held out
191compass = load_dataset("outpost-bio/Compass", "mastrorilli",
192 revision="370f857265cab6756bb06c72451c083b13e2418f")
193compass["train"], compass["validation"], compass["test"]
194```
196Atlas is ~5.6 GB. Stream it if you are only inspecting:
198```python
199atlas = load_dataset("outpost-bio/Atlas", split="pretrain", streaming=True,
200 revision="9dad400edb9e4f1e483a2f9021208c16f694aec2")
201first = next(iter(atlas))
202```
204Atlas rows carry `Taxa`, `Relative Abundances`, `Run Accession`, `Data Type`, `Sequencing Method`,
205`Pipeline Version`, `Study Accession`. Filtering by `Data Type` or `Sequencing Method` before
206pretraining is a reasonable way to build a modality-specific model; filtering by `Study Accession` is
207how you would hold out whole studies.
209Provenance: scraped from MGnify across pipeline versions v1.0–v5.0 and four modalities (16S amplicon,
210whole-genome shotgun, metagenomic assembly, and metatranscriptomic), then filtered to a minimum
211relative abundance of 1e-4 and a minimum of 10 taxa per sample. The pretrain/benchmark split is
212random with `seed=42` — it is *not* a study-level holdout, so the Atlas `benchmark` split can share
213studies with `pretrain`; verify overlap for your intended evaluation.

Loading Atlas and Compass

from datasets import load_dataset

atlas = load_dataset("outpost-bio/Atlas", split="pretrain",
                     revision="9dad400edb9e4f1e483a2f9021208c16f694aec2")       # 485,377 rows
atlas_bench = load_dataset("outpost-bio/Atlas", split="benchmark",
                           revision="9dad400edb9e4f1e483a2f9021208c16f694aec2")  # 53,931 held out

compass = load_dataset("outpost-bio/Compass", "mastrorilli",
                       revision="370f857265cab6756bb06c72451c083b13e2418f")
compass["train"], compass["validation"], compass["test"]

Atlas is ~5.6 GB. Stream it if you are only inspecting:

atlas = load_dataset("outpost-bio/Atlas", split="pretrain", streaming=True,
                     revision="9dad400edb9e4f1e483a2f9021208c16f694aec2")
first = next(iter(atlas))

Atlas rows carry Taxa, Relative Abundances, Run Accession, Data Type, Sequencing Method, Pipeline Version, Study Accession. Filtering by Data Type or Sequencing Method before pretraining is a reasonable way to build a modality-specific model; filtering by Study Accession is how you would hold out whole studies.

Provenance: scraped from MGnify across pipeline versions v1.0–v5.0 and four modalities (16S amplicon, whole-genome shotgun, metagenomic assembly, and metatranscriptomic), then filtered to a minimum relative abundance of 1e-4 and a minimum of 10 taxa per sample. The pretrain/benchmark split is random with seed=42 — it is not a study-level holdout, so the Atlas benchmark split can share studies with pretrain; verify overlap for your intended evaluation.

Raw lines 181 to 214
215## Fine-tuning programmatically
217There is no stable public function for the whole loop; `waypoint_bio.finetune` is written as a CLI
218module. Two workable options:
2201. Once tokenizer save/reload is verified, call the CLI with `subprocess` and read `finetune_results.json` — what the upstream webinar
221 notebooks do.
2222. Assemble it yourself from `MicrobiomeBenchmarkDataset` + `ClassificationModel`/`RegressionModel`
223 and a `transformers.Trainer`, mirroring `benchmark.py`. Reuse `waypoint_bio.scoring.score_task`
224 and `predictions_to_arrays` so your metrics match the published definitions.
226```python
227import json, subprocess
229subprocess.run([
230 "waypoint", "finetune",
231 "--model", "outpost-bio/Waypoint-45m",
232 "--data", "dataset.parquet",
233 "--output_dir", "outputs/ft",
234 "--task_type", "classification",
235 "--target", "Group",
236], check=True)
238results = json.loads(open("outputs/ft/finetune_results.json").read())
239print(results["test_score"], results["test_metrics"])
240```
242The upstream repo's `examples/webinar/` carries two worked notebooks — a regression walkthrough on
243Compass task 6 and a classification walkthrough on task 8 that also plots PCA / t-SNE projections of
244the embeddings against a logistic-regression baseline. Shared helpers live in `webinar_utils.py`.

Fine-tuning programmatically

There is no stable public function for the whole loop; waypoint_bio.finetune is written as a CLI module. Two workable options:

  1. Once tokenizer save/reload is verified, call the CLI with subprocess and read finetune_results.json — what the upstream webinar notebooks do.
  2. Assemble it yourself from MicrobiomeBenchmarkDataset + ClassificationModel/RegressionModel and a transformers.Trainer, mirroring benchmark.py. Reuse waypoint_bio.scoring.score_task and predictions_to_arrays so your metrics match the published definitions.
import json, subprocess

subprocess.run([
    "waypoint", "finetune",
    "--model", "outpost-bio/Waypoint-45m",
    "--data", "dataset.parquet",
    "--output_dir", "outputs/ft",
    "--task_type", "classification",
    "--target", "Group",
], check=True)

results = json.loads(open("outputs/ft/finetune_results.json").read())
print(results["test_score"], results["test_metrics"])

The upstream repo's examples/webinar/ carries two worked notebooks — a regression walkthrough on Compass task 6 and a classification walkthrough on task 8 that also plots PCA / t-SNE projections of the embeddings against a logistic-regression baseline. Shared helpers live in webinar_utils.py.

Raw lines 215 to 244