Update README.md
Browse files
README.md
CHANGED
|
@@ -12,22 +12,55 @@ pipeline_tag: other
|
|
| 12 |
|
| 13 |
# AssayFormer
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
The amortized gene ranker from **AssayLoop**: *Biology-in-the-loop: Amortized
|
| 16 |
Adaptive Hit Discovery in CRISPR Screens*. Given a screen description and the
|
| 17 |
-
labels revealed so far, it scores every gene in a 24,217-gene vocabulary and
|
| 18 |
-
proposes the next batch to assay.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
- **Code:** https://github.com/genentech/assayloop
|
| 24 |
-
- **Benchmark data:** [Genentech/assaybench](https://huggingface.co/datasets/Genentech/assaybench)
|
| 25 |
-
- **Benchmark package:** [`assaybench`](https://pypi.org/project/assaybench/) on PyPI
|
| 26 |
|
| 27 |
## Results
|
| 28 |
|
| 29 |
-
Twenty held-out genome-wide screens, ten rounds of one hundred genes, against a
|
| 30 |
-
21,147-gene universe (`--min-screen-freq 2`).
|
| 31 |
|
| 32 |
| metric | value |
|
| 33 |
|---|---|
|
|
@@ -38,8 +71,6 @@ Twenty held-out genome-wide screens, ten rounds of one hundred genes, against a
|
|
| 38 |
| DepMap common-essential fraction | 0.407 |
|
| 39 |
| Effective pathways EP-B / EP-S / EP-D | 19.3 / 46.7 / 65.0 |
|
| 40 |
|
| 41 |
-
EP is the effective number of Reactome level-2 groups, rarefied to 30 / 200 /
|
| 42 |
-
6000 annotated genes; a uniform draw scores 21.8 / 56.4 / 82.1.
|
| 43 |
|
| 44 |
## Files
|
| 45 |
|
|
@@ -49,10 +80,7 @@ EP is the effective number of Reactome level-2 groups, rarefied to 30 / 200 /
|
|
| 49 |
| `config.json` | architecture and RL hyperparameters |
|
| 50 |
| `vocab.json` | the 24,217-entry gene vocabulary (index 0 is `<unk>`) |
|
| 51 |
|
| 52 |
-
`model_last.pt` is the checkpoint the paper reports
|
| 53 |
-
one. Held-out EF dips and recovers during GRPO, so the run also kept a
|
| 54 |
-
best-on-val snapshot; every number above, every figure, and the handoff
|
| 55 |
-
experiments use the final epoch. If you want the gene embedding table on its
|
| 56 |
own it is `state_dict["gene_emb.weight"]`, shape (24217, 10).
|
| 57 |
|
| 58 |
## Architecture
|
|
@@ -69,85 +97,13 @@ gene embedding.
|
|
| 69 |
| context | up to 1024 observations |
|
| 70 |
|
| 71 |
**The description input is a precomputed embedding, not text.** The model
|
| 72 |
-
expects a 1536-d vector from OpenAI `text-embedding-3-small`
|
| 73 |
-
encoder in the checkpoint. The `bert_model` field in `config.json` is inert for
|
| 74 |
-
this `encoder_type` — it applies only to the `modernbert_text` variant. The
|
| 75 |
repository ships the screen-description embeddings for the benchmark screens, so
|
| 76 |
you only need an embedding backend for screens of your own.
|
| 77 |
|
| 78 |
-
## Usage
|
| 79 |
-
|
| 80 |
-
```python
|
| 81 |
-
from huggingface_hub import snapshot_download
|
| 82 |
-
from assayloop.models.amortized_ranker import AmortizedRankerModel
|
| 83 |
-
|
| 84 |
-
ckpt = snapshot_download("Genentech/assayformer")
|
| 85 |
-
model = AmortizedRankerModel(checkpoint=ckpt, ckpt_file="model_last.pt")
|
| 86 |
-
```
|
| 87 |
-
|
| 88 |
-
Note the explicit `ckpt_file`: the loader defaults to `model.pt`, which is not
|
| 89 |
-
in this repository.
|
| 90 |
-
|
| 91 |
-
Then run it on a held-out screen and score the trajectory:
|
| 92 |
-
|
| 93 |
-
```python
|
| 94 |
-
from assaybench import SequentialLoop, enrichment_factor
|
| 95 |
-
from assayloop.acquisitions.greedy_from_model import GreedyFromModel
|
| 96 |
-
from assayloop.tasks.gene_batch import AssayBenchGeneBatchTask
|
| 97 |
-
from assayloop.tasks.screen_sets import load_screens
|
| 98 |
-
|
| 99 |
-
screen = load_screens(target_set="public")[0] # the paper's 20-screen test set
|
| 100 |
-
task = AssayBenchGeneBatchTask(screen)
|
| 101 |
-
run = SequentialLoop(task, model, GreedyFromModel(), metrics=[], batch_size=100).run(n_steps=10)
|
| 102 |
-
|
| 103 |
-
picked = [g for step in run.history for g in step.acquired_batch]
|
| 104 |
-
hits = [g for g, h in zip(screen.genes, screen.hits) if h]
|
| 105 |
-
print(enrichment_factor(picked, screen.genes, hits, budget=1000)) # 6.96 on U_1733_merged
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
No API key is needed: the screen-description embeddings for the benchmark
|
| 109 |
-
screens ship with the repository, so `task_context` resolves offline.
|
| 110 |
-
|
| 111 |
-
That prints one number for one screen, which is not the 4.83 in the table
|
| 112 |
-
above. Two differences: the table is the mean over all twenty screens, and it
|
| 113 |
-
scores against the full-genome candidate pool — the union of the twenty
|
| 114 |
-
libraries, restricted to genes measured in at least two of them — which the
|
| 115 |
-
task takes as `universe_genes=`. Picking from a 21,147-gene pool rather than
|
| 116 |
-
one screen's own library is harder, and it is the number the paper reports.
|
| 117 |
-
`python -m assayloop.scripts.full_genome_table --min-screen-freq 2` reproduces
|
| 118 |
-
the table row for row.
|
| 119 |
-
|
| 120 |
-
## Training
|
| 121 |
-
|
| 122 |
-
Supervised on 1,349 screens (hits objective, leave-one-out, 40 epochs), then
|
| 123 |
-
GRPO for 100 epochs on the `public_train` split with `public_validation` held
|
| 124 |
-
out: group size 8, 10 steps of 100 genes, lr 1e-5, entropy coefficient 0.01, no
|
| 125 |
-
KL penalty, context reset every 25 epochs, seed 19. The reward is the
|
| 126 |
-
context-delta form; `config.json` has the rest.
|
| 127 |
-
|
| 128 |
-
## Gene embeddings and BPMF
|
| 129 |
-
|
| 130 |
-
The gene embedding table was initialised from the posterior mean of a K=10
|
| 131 |
-
Bayesian PMF fit and then updated by supervised training and GRPO. Every row
|
| 132 |
-
moved, but not far: median cosine to the BPMF initialisation is 0.9999 and the
|
| 133 |
-
median relative displacement is 1.6%. Treat the table as approximately the BPMF
|
| 134 |
-
gene factors rather than as independently learned representations. The BPMF fit
|
| 135 |
-
itself — the screen loadings, the posterior samples, the predicted
|
| 136 |
-
probabilities — is not part of this release.
|
| 137 |
-
|
| 138 |
-
## Limitations
|
| 139 |
-
|
| 140 |
-
- Trained on BioGRID ORCS screens; the split is by year, and the vocabulary is
|
| 141 |
-
fixed at training time. Genes outside it map to `<unk>`.
|
| 142 |
-
- EF is computed against each screen's own library. A gene the screen never
|
| 143 |
-
assayed can never be a hit, and 10% of this model's picks fall there.
|
| 144 |
-
- 40.7% of its picks are DepMap common-essential — a real fraction of the hit
|
| 145 |
-
rate comes from genes that are essential in most cell lines.
|
| 146 |
|
| 147 |
## Citation
|
| 148 |
|
| 149 |
-
The arXiv identifier is not assigned yet; this entry will be updated when it is.
|
| 150 |
-
|
| 151 |
```bibtex
|
| 152 |
@article{assayloop2026,
|
| 153 |
title = {Biology-in-the-loop: Amortized Adaptive Hit Discovery in CRISPR Screens},
|
|
|
|
| 12 |
|
| 13 |
# AssayFormer
|
| 14 |
|
| 15 |
+
|
| 16 |
+
[Website](https://genentech.github.io/AssayLoop) | [Models and Data](https://huggingface.co/collections/Genentech/assaybench) | [Paper](README_DETAILED.md) | [](https://pypi.org/project/assaybench/)
|
| 17 |
+
|
| 18 |
+

|
| 21 |
+
|
| 22 |
+
|
| 23 |
The amortized gene ranker from **AssayLoop**: *Biology-in-the-loop: Amortized
|
| 24 |
Adaptive Hit Discovery in CRISPR Screens*. Given a screen description and the
|
| 25 |
+
hit labels revealed so far, it scores every gene in a 24,217-gene vocabulary and
|
| 26 |
+
proposes the next batch to assay. In the paper, a 21,147 gene subset was used.
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
## Usage
|
| 30 |
+
|
| 31 |
+
```python
|
| 32 |
+
from huggingface_hub import snapshot_download
|
| 33 |
+
from assayloop.models.amortized_ranker import AmortizedRankerModel
|
| 34 |
+
|
| 35 |
+
ckpt = snapshot_download("Genentech/assayformer")
|
| 36 |
+
model = AmortizedRankerModel(checkpoint=ckpt, ckpt_file="model_last.pt")
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
Note the explicit `ckpt_file`: the loader defaults to `model.pt`, which is not
|
| 40 |
+
in this repository.
|
| 41 |
+
|
| 42 |
+
Then run it on a held-out screen and score the trajectory:
|
| 43 |
+
|
| 44 |
+
```python
|
| 45 |
+
from assaybench import SequentialLoop, enrichment_factor
|
| 46 |
+
from assayloop.acquisitions.greedy_from_model import GreedyFromModel
|
| 47 |
+
from assayloop.tasks import AssayBenchGeneBatchTask, gene_universe, load_screens
|
| 48 |
+
|
| 49 |
+
screens = load_screens(target_set="public") # the paper's 20-screen test set
|
| 50 |
+
universe = gene_universe(screens) # the f2 pool, 21,147 genes
|
| 51 |
|
| 52 |
+
screen = screens[0]
|
| 53 |
+
task = AssayBenchGeneBatchTask(screen, universe_genes=universe)
|
| 54 |
+
run = SequentialLoop(task, model, GreedyFromModel(), metrics=[], batch_size=100).run(n_steps=10)
|
| 55 |
+
|
| 56 |
+
picked = [g for step in run.history for g in step.acquired_batch]
|
| 57 |
+
hits = [g for g, h in zip(screen.genes, screen.hits) if h]
|
| 58 |
+
print(enrichment_factor(picked, screen.genes, hits, universe=universe, budget=1000)) # 7.61 on U_1733_merged
|
| 59 |
+
```
|
| 60 |
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
## Results
|
| 63 |
|
|
|
|
|
|
|
| 64 |
|
| 65 |
| metric | value |
|
| 66 |
|---|---|
|
|
|
|
| 71 |
| DepMap common-essential fraction | 0.407 |
|
| 72 |
| Effective pathways EP-B / EP-S / EP-D | 19.3 / 46.7 / 65.0 |
|
| 73 |
|
|
|
|
|
|
|
| 74 |
|
| 75 |
## Files
|
| 76 |
|
|
|
|
| 80 |
| `config.json` | architecture and RL hyperparameters |
|
| 81 |
| `vocab.json` | the 24,217-entry gene vocabulary (index 0 is `<unk>`) |
|
| 82 |
|
| 83 |
+
`model_last.pt` is the checkpoint the paper reports. If you want the gene embedding table on its
|
|
|
|
|
|
|
|
|
|
| 84 |
own it is `state_dict["gene_emb.weight"]`, shape (24217, 10).
|
| 85 |
|
| 86 |
## Architecture
|
|
|
|
| 97 |
| context | up to 1024 observations |
|
| 98 |
|
| 99 |
**The description input is a precomputed embedding, not text.** The model
|
| 100 |
+
expects a 1536-d vector from OpenAI `text-embedding-3-small`. The
|
|
|
|
|
|
|
| 101 |
repository ships the screen-description embeddings for the benchmark screens, so
|
| 102 |
you only need an embedding backend for screens of your own.
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
## Citation
|
| 106 |
|
|
|
|
|
|
|
| 107 |
```bibtex
|
| 108 |
@article{assayloop2026,
|
| 109 |
title = {Biology-in-the-loop: Amortized Adaptive Hit Discovery in CRISPR Screens},
|