Back to Claude Scientific Skills

Using Waypoint from Python

skills/waypoint-bio/references/python-api.md

2.64.08.2 KB
Original Source

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.

Package surface

waypoint_bio lazily re-exports:

python
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.

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.

python
from transformers import AutoTokenizer, AutoModel

tok = AutoTokenizer.from_pretrained("outpost-bio/Waypoint-45m", trust_remote_code=True)
model = AutoModel.from_pretrained("outpost-bio/Waypoint-45m")   # gated: needs HF_TOKEN

trust_remote_code=True executes the tokenizer code stored in the repo. Pin a revision when that matters to you, so the code cannot change under a later run:

python
tok = AutoTokenizer.from_pretrained(
    "outpost-bio/Waypoint-45m", trust_remote_code=True, revision="1664ab5"
)

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.

Tokenizing by hand

python
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:

python
vocab = tok.get_vocab()
"g__Lactobacillus" in vocab          # True for anything seen in Atlas
tok.convert_tokens_to_ids("g__Nonesuch") == tok.unk_token_id

tok._extract(lineage) applies the rank extraction and higher-rank fallback and returns the token string, or None. It is private but stable across 1.0.x and is what the datasets and scripts/vocab_coverage.py use.

Building a dataset

python
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] + z-score-ordered token ids + [EOS], right-padded.

Computing the ordering statistics for a corpus of your own:

python
from waypoint_bio.dataset import compute_token_std_means

stats = compute_token_std_means(df, tok, show_progress=True)
stats.to_parquet("token_std_means.parquet")   # index name "token", columns mean/std

Drop that file next to a checkpoint and embed, finetune, and benchmark will pick it up.

Embeddings without the CLI

python
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_id = "outpost-bio/Waypoint-45m"
df = load_waypoint_dataframe("dataset.parquet")
tok = load_tokenizer(model_id)
model = AutoModel.from_pretrained(model_id).eval()

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

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.

Custom heads

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

python
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 and what the checkpoints were tuned for), first_token / cls_token (position 0, the BOS token; weak in a causal LM).

Loading Atlas and Compass

python
from datasets import load_dataset

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

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

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

python
atlas = load_dataset("outpost-bio/Atlas", split="pretrain", streaming=True)
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 shares studies with pretrain.

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. 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.
python
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.