ESMC integration
Browse files- AGENTS.md +55 -0
- app.py +1 -1
- header.md +3 -3
- instructions.md +4 -2
- model.py +3 -31
- requirements.txt +1 -2
AGENTS.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ZSP — Agent Guide
|
| 2 |
+
|
| 3 |
+
**What it is.** Gradio web app that scores protein sequence variants using ESM2, E1, and ESMC language models. Implements ESM-Scan methodology ([doi:10.1002/pro.5221](https://doi.org/10.1002/pro.5221)).
|
| 4 |
+
|
| 5 |
+
## Structure
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
app.py — Gradio UI, entry point
|
| 9 |
+
model.py — ModelFactory + wrappers for ESM/E1/ESMC models
|
| 10 |
+
data.py — Input parsing, scoring modes, output rendering (heatmap/table)
|
| 11 |
+
requirements.txt — deps (gradio, torch, esm[Biohub], pandas, seaborn, E1)
|
| 12 |
+
header.md — UI header text
|
| 13 |
+
instructions.md — user instructions tab content
|
| 14 |
+
out.* — generated outputs (gitignored)
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## Run
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
source .venv/bin/activate # or however you activate
|
| 21 |
+
python app.py
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Deploys as a HuggingFace Space (`sdk: gradio`, `app_file: app.py`). Python 3.12.
|
| 25 |
+
|
| 26 |
+
## Scoring Modes
|
| 27 |
+
|
| 28 |
+
Parsed from the substitutions input in `data.py`:
|
| 29 |
+
|
| 30 |
+
| Mode | Trigger | What it does |
|
| 31 |
+
|------|---------|-------------|
|
| 32 |
+
| **TMS** (triple mutational scan) | No match to other patterns | All-positions × all-amino-acids sweep → heatmap image |
|
| 33 |
+
| **DMS** (deep mutational scan) | All tokens are integers | Specified positions × all alternatives → sorted table |
|
| 34 |
+
| **MUT** (single mutations) | Tokens match `[A-Z]\d+[A-Z]` | Explicit substitution list → scored table |
|
| 35 |
+
| **MUT-seq-vs-seq** | Single token same length as sequence | Align two sequences, score differences → scored table |
|
| 36 |
+
|
| 37 |
+
Scoring strategy flag: `"masked-marginals"` (higher accuracy, slower) vs `"wt-marginals"`.
|
| 38 |
+
|
| 39 |
+
## Key Design Notes
|
| 40 |
+
|
| 41 |
+
- Models loaded lazily via `ModelFactory`; ESM and ESMC use shared `ESMModel` wrapper, E1 uses its own package (`E1@git+...`)
|
| 42 |
+
- ESMC requires `esm@git+Biohub/esm` — registers custom `esmc` architecture with transformers fork
|
| 43 |
+
- GPU auto-detected; falls back to CPU
|
| 44 |
+
- E1 model chunks batches at 60 to avoid OOM
|
| 45 |
+
- TMS mode produces a seaborn heatmap PNG; other modes produce styled HTML tables
|
| 46 |
+
- Output files always written to `out.csv` / `out.png` in CWD
|
| 47 |
+
- Progress tracking wired into Gradio's `Progress` object
|
| 48 |
+
|
| 49 |
+
## Edit Rules
|
| 50 |
+
|
| 51 |
+
- Match existing style: one-line docstrings on classes/methods, minimal comments
|
| 52 |
+
- No new dependencies unless absolutely necessary — stdlib or already-installed first
|
| 53 |
+
- Torch/transformers signatures: grep before guessing versions
|
| 54 |
+
- Output format changes affect both CSV and image/table paths — update both
|
| 55 |
+
- ModelFactory registry is class-level `_models` dict; adding a model type means registering there
|
app.py
CHANGED
|
@@ -47,7 +47,7 @@ with Blocks() as demo:
|
|
| 47 |
, label="Substitutions"
|
| 48 |
, placeholder="Substitutions here…"
|
| 49 |
, value='' )
|
| 50 |
-
model_name = Dropdown(MODELS, label="Model", value="
|
| 51 |
acc_box = Checkbox(value=True, label="Use higher accuracy scoring", interactive=True)
|
| 52 |
run_btn = Button(value="Run", variant="primary")
|
| 53 |
dl_btn = DownloadButton(label="Download raw data", visible=False)
|
|
|
|
| 47 |
, label="Substitutions"
|
| 48 |
, placeholder="Substitutions here…"
|
| 49 |
, value='' )
|
| 50 |
+
model_name = Dropdown(MODELS, label="Model", value="biohub/ESMC-300M")
|
| 51 |
acc_box = Checkbox(value=True, label="Use higher accuracy scoring", interactive=True)
|
| 52 |
run_btn = Button(value="Run", variant="primary")
|
| 53 |
dl_btn = DownloadButton(label="Download raw data", visible=False)
|
header.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
Calculate the fitness of single amino acid substitutions on proteins, using
|
| 2 |
|
| 3 |
-
**UPDATE:**
|
| 4 |
-
[
|
| 5 |
|
| 6 |
**WARNING:**
|
| 7 |
Due to high server traffic, the tool might become slow or unresponsive.
|
|
|
|
| 1 |
+
Calculate the fitness of single amino acid substitutions on proteins, using zero-shot protein language models from the [ESM](https://huggingface.co/facebook) and [ESMC](https://huggingface.co/collections/biohub/esmc-model-family) families.
|
| 2 |
|
| 3 |
+
**UPDATE:**
|
| 4 |
+
[Biohub](https://biohub.ai)'s [ESMC model family](https://huggingface.co/collections/biohub/esmc-model-family) (trained on UniRef + MGnify + JGI) is now available alongside ESM2 models.
|
| 5 |
|
| 6 |
**WARNING:**
|
| 7 |
Due to high server traffic, the tool might become slow or unresponsive.
|
instructions.md
CHANGED
|
@@ -16,8 +16,10 @@ Note: While jolly characters (e.g., `-X.B`) can be included, they currently cann
|
|
| 16 |
- **Same-Length Sequence**: Analyze differing amino acid substitutions one by one within sequences of equal length.
|
| 17 |
- **Different Inputs**: For any other input format, a deep mutational scan of the full sequence will be performed.
|
| 18 |
|
| 19 |
-
**Model Selection**: Choose a model for calculations from those available on Hugging Face Model Hub.
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
**Accuracy Option**: The **Use higher accuracy** option applies a masked-marginals scoring strategy, which considers sequence context during inference.
|
| 23 |
While this method is slower, it enhances accuracy.
|
|
|
|
| 16 |
- **Same-Length Sequence**: Analyze differing amino acid substitutions one by one within sequences of equal length.
|
| 17 |
- **Different Inputs**: For any other input format, a deep mutational scan of the full sequence will be performed.
|
| 18 |
|
| 19 |
+
**Model Selection**: Choose a model for calculations from those available on Hugging Face Model Hub. Two model families are supported:
|
| 20 |
+
|
| 21 |
+
- **Facebook ESM2** — trained on UniRef50. `esm2_t33_650M_UR50D` offers an optimal balance between cost and accuracy [*](https://doi.org/10.1126/science.ade2574).
|
| 22 |
+
- **Biohub ESMC** — trained on UniRef + MGnify + JGI (broader metagenomic coverage). Pre-LN architecture with rotary embeddings, max 2048 token context. Rankings correlate strongly with ESM2 (Spearman ρ ≥ 0.93) while absolute scores may differ due to training data differences.
|
| 23 |
|
| 24 |
**Accuracy Option**: The **Use higher accuracy** option applies a masked-marginals scoring strategy, which considers sequence context during inference.
|
| 25 |
While this method is slower, it enhances accuracy.
|
model.py
CHANGED
|
@@ -4,11 +4,9 @@ from typing import Any
|
|
| 4 |
from transformers import AutoTokenizer, AutoModelForMaskedLM
|
| 5 |
from transformers.tokenization_utils_base import BatchEncoding
|
| 6 |
from transformers.modeling_outputs import MaskedLMOutput
|
| 7 |
-
from E1.modeling import E1ForMaskedLM
|
| 8 |
-
from E1.scorer import E1Scorer, EncoderScoreMethod
|
| 9 |
|
| 10 |
class ESMModel:
|
| 11 |
-
"Wrapper for ESM models"
|
| 12 |
def __init__(self, model_name:str):
|
| 13 |
"Load selected model and tokenizer"
|
| 14 |
self.model_name = model_name
|
|
@@ -75,29 +73,6 @@ class ESMModel:
|
|
| 75 |
lambda row: label_row(row['0'], token_probs)
|
| 76 |
, axis=1 )
|
| 77 |
|
| 78 |
-
class E1Model:
|
| 79 |
-
"Wrapper for E1 models"
|
| 80 |
-
def __init__(self, model_name:str):
|
| 81 |
-
self.model_name = model_name
|
| 82 |
-
self.scoring_strategy = EncoderScoreMethod.MASKED_MARGINAL
|
| 83 |
-
if model_name:
|
| 84 |
-
self.model = E1ForMaskedLM.from_pretrained(model_name, dtype=torch.float)
|
| 85 |
-
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 86 |
-
self.model = self.model.to(self.device)
|
| 87 |
-
self.scorer = E1Scorer(self.model, self.scoring_strategy)
|
| 88 |
-
self.alphabet = self.scorer.vocab
|
| 89 |
-
|
| 90 |
-
def run_model(self, data):
|
| 91 |
-
"Run model on data"
|
| 92 |
-
if not data.scoring_strategy.startswith("masked-marginals"):
|
| 93 |
-
self.scorer = E1Scorer(self.model, EncoderScoreMethod.WILDTYPE_MARGINAL)
|
| 94 |
-
batch_size = 60 ## chunking to avoid OOM
|
| 95 |
-
out = []
|
| 96 |
-
for chunk in data.progress.tqdm([data.trg[i:i+batch_size] for i in range(0, len(data.trg), batch_size)], desc="Calculating"):
|
| 97 |
-
scores = self.scorer.score(parent_sequence=data.seq, sequences=chunk)
|
| 98 |
-
out.extend(s['score'] for s in scores)
|
| 99 |
-
data.out[self.model_name] = out
|
| 100 |
-
|
| 101 |
class ModelFactory:
|
| 102 |
"Factory for models"
|
| 103 |
_models = { **{ m.id:ESMModel for m in HfApi().list_models(
|
|
@@ -106,11 +81,8 @@ class ModelFactory:
|
|
| 106 |
, filter="fill-mask"
|
| 107 |
, sort="lastModified"
|
| 108 |
, direction=-1 ) }
|
| 109 |
-
, **{ m.id:
|
| 110 |
-
author="
|
| 111 |
-
, model_name="E1"
|
| 112 |
-
, sort="lastModified"
|
| 113 |
-
, direction=-1 ) } }
|
| 114 |
|
| 115 |
@classmethod
|
| 116 |
def register(cls, model_name, model_cls):
|
|
|
|
| 4 |
from transformers import AutoTokenizer, AutoModelForMaskedLM
|
| 5 |
from transformers.tokenization_utils_base import BatchEncoding
|
| 6 |
from transformers.modeling_outputs import MaskedLMOutput
|
|
|
|
|
|
|
| 7 |
|
| 8 |
class ESMModel:
|
| 9 |
+
"Wrapper for ESM/ESMC models"
|
| 10 |
def __init__(self, model_name:str):
|
| 11 |
"Load selected model and tokenizer"
|
| 12 |
self.model_name = model_name
|
|
|
|
| 73 |
lambda row: label_row(row['0'], token_probs)
|
| 74 |
, axis=1 )
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
class ModelFactory:
|
| 77 |
"Factory for models"
|
| 78 |
_models = { **{ m.id:ESMModel for m in HfApi().list_models(
|
|
|
|
| 81 |
, filter="fill-mask"
|
| 82 |
, sort="lastModified"
|
| 83 |
, direction=-1 ) }
|
| 84 |
+
, **{ m.id:ESMModel for m in HfApi().list_models(
|
| 85 |
+
author="biohub", model_name="esmc", filter="fill-mask" ) if "-step" not in m.id and "-2024-12" not in m.id } }
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
@classmethod
|
| 88 |
def register(cls, model_name, model_cls):
|
requirements.txt
CHANGED
|
@@ -2,5 +2,4 @@ gradio
|
|
| 2 |
pandas
|
| 3 |
seaborn
|
| 4 |
torch
|
| 5 |
-
|
| 6 |
-
E1@git+https://github.com/Profluent-AI/E1.git@main#egg=E1
|
|
|
|
| 2 |
pandas
|
| 3 |
seaborn
|
| 4 |
torch
|
| 5 |
+
esm@git+https://github.com/Biohub/esm.git@main
|
|
|