A high-performance downstream regression and ranking head for predicting Green Fluorescent Protein (avGFP) log-fluorescence fitness from ESM-2 embeddings.
This repository contains the architecture, feature caching scripts, training logic, and baseline benchmarks for predicting protein fitness using the Meta AI ESM-2 (650M) protein language model.
Traditional single-token representations ([CLS] or mean pooling) often dilute important local mutation signals in Deep Mutational Scanning (DMS) datasets.
This project implements BioYeti Multi-Vector Hybrid Pooling (CLS + Mean + Max), concatenating representation vectors into a unified 3840-dimensional embedding space per protein sequence. A non-linear regression head trained on top of these cached features achieves state-of-the-art ranking (Spearman ρ = 0.4787) and linear correlation (Pearson r = 0.6523), outperforming standard tree-based and linear baselines.
All evaluations were conducted on the validation split (N = 5,366) of the InstaDeepAI/true-cds-protein-tasks (avGFP task) dataset.
| Model / Strategy | Pearson r ↑ | Spearman ρ ↑ | RMSE ↓ |
|---|---|---|---|
| ESM2-650M + BioYeti Multi-Pool Head (Ours) | 0.6523 | 0.4787 | 0.6432 |
| ESM2-650M + LightGBM | 0.6134 | 0.4055 | 0.6834 |
| ESM2-650M + Ridge Regression (α = 10.0) | 0.5387 | 0.4008 | 0.7986 |
| ESM2-650M + SGD Linear | 0.4886 | 0.3761 | 1.0104 |
| Mean-Pooling Only (Baseline) | 0.3068 | 0.2214 | 0.9102 |
A prebuilt BioYeti head is attached to every release:
bioyeti_head.safetensors— the trained head weights in HuggingFace safetensors format.config.json— the model card (backbone, embedding dim, training setup, validation metrics).
Latest release: https://github.com/Monster-ML/BioYeti/releases/latest
from safetensors.torch import load_file
state = load_file("models/bioyeti_head.safetensors") # dict {tensor_name: tensor}
print(list(state.keys()))The release workflow (.github/workflows/release.yml) builds the weights on a free CPU runner using a small ESM-2 backbone (8M), so the artifact is reproducible without a GPU. For the full 650M-model weights that match the table above, train locally on a GPU and publish them:
python src/extract.py
python src/train.py # saves models/bioyeti_head.pt
python src/export_model.py --checkpoint models/bioyeti_head.pt- Source: Hugging Face Datasets Hub (
InstaDeepAI/true-cds-protein-tasks, subsetfluorescence). - Origin: Experimental Deep Mutational Scanning (DMS) of Aequorea victoria GFP (avGFP) fitness landscape (Sarkisyan et al.).
- Task: Sequence-level log-fluorescence intensity regression.
- Train split: 21,464 sequences.
- Validation split: 5,366 sequences.
- Test split: 27,217 sequences.
- Backbone:
facebook/esm2_t33_650M_UR50D— frozen transformer weights, 33 layers, hidden dim d = 1280. - Multi-Vector Pooling: the three representations are concatenated along the feature axis into one 3840-dim vector:
h_hybrid = [ h_CLS ∥ h_Mean ∥ h_Max ] ∈ ℝ³⁸⁴⁰ (d_ESM2 = 1280 → 1280 × 3 = 3840)
- h_CLS — sequence context token representation (learned sequence-level summary).
- h_Mean — mask-weighted global baseline sequence composition.
- h_Max — isolates non-zero localized mutation spikes across amino-acid substitutions (padding flushed to −∞ so it never wins the max).
Input (3840-dim concatenated vector)
│
├── Linear Layer (3840 → 512)
├── BatchNorm1d
├── GELU Activation
├── Dropout (p = 0.3)
│
├── Linear Layer (512 → 128)
├── BatchNorm1d
├── GELU Activation
├── Dropout (p = 0.2)
│
└── Linear Layer (128 → 1) → Scalar Log-Fluorescence Output
The model optimizes both absolute-value calibration and relative rank ordering simultaneously:
L_composite = L_MSE + λ · L_rank (λ = rank_weight)
where L_MSE fits prediction magnitudes and L_rank is a pairwise margin / hinge term over all pairs in the batch that drives Spearman ρ.
.
├── README.md # System instructions and documentation
├── LICENSE # MIT license
├── requirements.txt # Python runtime dependencies
├── requirements-dev.txt # Dev tooling (ruff)
├── requirements-notebooks.txt# Optional deps for the notebooks
├── .gitignore # Git exclusions (caches, weights, data)
├── pyproject.toml # Ruff / formatting configuration
├── package.json # Husky + lint-staged git hooks (npm)
├── .github/
│ ├── workflows/
│ │ ├── ci.yml # Lint + format + notebook validation
│ │ └── release.yml # Builds .safetensors and creates a release
│ └── dependabot.yml # Automated dependency updates
├── src/
│ ├── __init__.py
│ ├── extract.py # Pre-extracts 3840-dim embeddings from ESM-2
│ ├── models.py # BioYeti Neural Head & Composite Loss definition
│ ├── train.py # Training execution script (saves checkpoint)
│ └── export_model.py # Converts a checkpoint to .safetensors
├── scripts/
│ └── build_release_model.py# CI: trains + exports the release model
├── docs/
│ └── RESEARCH.md # Curated research papers & reading path
├── notebooks/
│ ├── 01_dataset_eda.ipynb # Explore the avGFP DMS dataset
│ └── 02_pooling_showdown.ipynb # CLS vs Mean vs Max vs Hybrid (cookbook)
├── examples/
│ └── cookbook_protein_fitness.md # Recipes for new datasets, tuning, eval
├── benchmark.py # Comparative evaluation script vs. ML baselines
└── results/
└── benchmark_report.csv # Auto-generated performance metric comparison
src/extract.py— Runsfacebook/esm2_t33_650M_UR50Dover the dataset and caches the hybrid 3840-dim embeddings ascached_train_features.pt/cached_val_features.pt(TensorDatasets). See the three pooling operations (CLS / mean / max) implemented here.src/models.py— DefinesBioYetiMultiPoolHead, the MLP regression head (3840 → 512 → 128 → 1), andCompositeFitnessLoss, which blends MSE with a pairwise margin-ranking term.src/train.py— Loads the frozen backbone, pre-extracts embeddings in fp16, trains the head with AdamW + a cosine-warmup schedule, and reports Pearson r / Spearman ρ on the validation split. Saves a checkpoint tomodels/bioyeti_head.pt.src/export_model.py— Converts a trained checkpoint into HuggingFace.safetensorsfor distribution.scripts/build_release_model.py— What the release workflow runs: trains a demo head on a small ESM-2 backbone on CPU and exports.safetensors+config.json.benchmark.py— Trains cheap baselines (Ridge, SGD Linear, LightGBM) on the cached features and writes the head-to-head comparison toresults/benchmark_report.csv.
CI runs ruff (lint + format) on every push and PR; a local husky pre-commit hook does the same on staged files.
# Python tooling
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Git hooks (optional, needs Node.js)
npm install # installs husky + lint-staged, activates the pre-commit hookThe pre-commit hook runs ruff check --fix and ruff format on staged .py files. To bypass it in a pinch: git commit --no-verify.
.github/workflows/ci.yml— On push/PR:ruff check,ruff format --check, and notebook JSON validation..github/workflows/release.yml— On av*tag: installs CPU-only torch, trains the demo head, exports.safetensors, and uploads it to a GitHub release..github/dependabot.yml— Weekly dependency bump PRs for pip, npm, and GitHub Actions.
Cut a new release by tagging:
git tag v0.1.1
git push origin v0.1.1docs/RESEARCH.md— A curated, annotated list of the papers this project builds on: the ESM/ProtTrans language models, the Sarkisyan et al. GFP landscape, FLIP/ProteinGym benchmarks, zero-shot mutation-effect methods (EVE, DeepSequence), and ranking losses — plus a recommended reading order.
notebooks/01_dataset_eda.ipynb— EDA on the avGFP DMS dataset: split sizes, label distribution, sequence lengths, amino-acid composition.notebooks/02_pooling_showdown.ipynb— Cookbook-style ablation ofCLSvsMeanvsMaxvsHybridpooling on a small 8M-parameter ESM-2 (~2 min on CPU). Reproduces why hybrid pooling wins before you spend GPU time.
pip install -r requirements-notebooks.txt
jupyter labexamples/cookbook_protein_fitness.md— Copy-pasteable recipes: applying the pipeline to a new DMS dataset or raw CSV, tuning the composite loss, fighting VRAM limits, ProteinGym-style evaluation, exporting embeddings, and rebuilding the benchmark table.
git clone https://github.com/Monster-ML/BioYeti.git
cd BioYeti
pip install -r requirements.txtRuns facebook/esm2_t33_650M_UR50D over the Hugging Face dataset and caches the 3840-dim tensors locally (~20 s on GPU):
python src/extract.pyTrains the multi-layer perceptron head on top of the cached representations:
python src/train.pyRuns fast evaluation against Ridge Regression, SGD Regressor, and LightGBM:
python benchmark.py- ESM-2 Backbone: Lin et al., Language models enable zero-shot prediction of the effects of mutations on protein function. bioRxiv (2022).
- Dataset: Sarkisyan et al., Local fitness landscape of the green fluorescent protein. Nature (2016). Curated under
InstaDeepAI/true-cds-protein-tasks. - BioYeti Head Architecture: Custom multi-vector pooling strategy designed for mutational fitness prediction.