File size: 12,818 Bytes
ff4cd76
 
 
ee6da62
7c8f828
 
 
 
 
4bb2309
 
ee6da62
 
7d3c9bf
 
ee6da62
 
 
 
 
 
 
5e88b4f
 
 
 
 
 
 
 
ee6da62
 
 
7d3c9bf
 
 
 
 
 
 
 
 
ee6da62
 
7d3c9bf
ee6da62
7d3c9bf
 
 
ee6da62
7d3c9bf
 
 
 
 
 
 
 
 
 
 
 
ee6da62
 
 
 
 
 
 
7d3c9bf
 
 
ee6da62
 
8d43f08
 
 
 
 
 
 
 
 
ee6da62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d3c9bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee6da62
 
 
 
 
7d3c9bf
ee6da62
 
7d3c9bf
ee6da62
 
 
 
 
7d3c9bf
 
ee6da62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bcc72b5
7d3c9bf
 
 
 
 
 
ee6da62
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<h1 align="center">
  TD3B: Transition-Directed Discrete Diffusion for Allosteric Binder Generation
</h1>

<div align="center">
 <a href="https://arxiv.org/abs/2605.09810"><img src="https://img.shields.io/badge/Arxiv-2605.09810-red?style=for-the-badge&logo=Arxiv" alt="arXiv"/></a>

</div>

![Screenshot 2026-05-10 at 6.52.29β€―PM](https://cdn-uploads.huggingface.co/production/uploads/64cd5b3f0494187a9e8b7c69/38-6PQ83pPraF7KAs3g3E.png)

TD3B is a sequence-based generative framework that designs peptide binders with specified agonist or antagonist behavior. It combines a Direction Oracle, a soft binding-affinity gate, and amortized fine-tuning of a pre-trained discrete diffusion model (MDLM).

> **Development release.** This repository (`ChatterjeeLab/TD3B-dev`) carries the full code. The heavy artifacts β€” trained checkpoints, training/test data, and generated binders β€” are distributed as a single archive on Google Drive (see [Data and Checkpoints](#data-and-checkpoints)). For the clean code-only release see [`ChatterjeeLab/TD3B`](https://huggingface.co/ChatterjeeLab/TD3B).

## Installation

```bash
conda env create -f env.yml
conda activate td3b
pip install -e .
```

## Demo

An interactive inference demo is provided in [`notebooks/TD3B_Inference_Demo.ipynb`](notebooks/TD3B_Inference_Demo.ipynb) (configured for a Colab T4 GPU).

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/)

To run it: download the notebook from this repository, open [Google Colab](https://colab.research.google.com/), upload it via **File β†’ Upload notebook**, and select a GPU runtime (**Runtime β†’ Change runtime type β†’ T4 GPU**).

## Data and Checkpoints

The checkpoints, datasets, and generated binders are bundled in a single archive on Google Drive:

**Download:** [`td3b_dev_artifacts.zip`](https://drive.google.com/file/d/1EU0-pcF-UoEb0OEMuO4qhodZ5RjMU4n9/view?usp=sharing) (~3.4 GB)

Unzip it at the repository root to restore the full layout:

```bash
unzip td3b_dev_artifacts.zip -d .
```

```
TD3B-dev/
β”œβ”€β”€ checkpoints/
β”‚   β”œβ”€β”€ pretrained.ckpt              # Pre-trained MDLM weights (1.4 GB)
β”‚   β”œβ”€β”€ td3b.ckpt                    # Fine-tuned TD3B model (231 MB)
β”‚   └── direction_oracle.pt          # Direction Oracle weights (2.85 GB)
β”œβ”€β”€ scoring/functions/classifiers/
β”‚   β”œβ”€β”€ binding-affinity.pt          # (from archive)
β”‚   β”œβ”€β”€ permeability-xgboost.json    # (from archive)
β”‚   β”œβ”€β”€ hemolysis-xgboost.json       # (in repo)
β”‚   β”œβ”€β”€ nonfouling-xgboost.json      # (in repo)
β”‚   └── solubility-xgboost.json      # (in repo)
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ train.csv                    # Training set (target-binder pairs)
β”‚   β”œβ”€β”€ test.csv                     # Test set
β”‚   └── td3b_data_new.csv            # Full labeled target/ligand pairs (agonist/antagonist)
└── generated_binders/
    β”œβ”€β”€ agonist/                     # 1106 generated agonist binders (.pdb + .trb), 249 targets
    └── antagonist.tar.gz            # Generated antagonist binders
```

## Code Structure

```
TD3B/
β”œβ”€β”€ inference.py                 # Generate binders (main inference entry point)
β”œβ”€β”€ finetune_on_target.py        # Finetune on your own target(s) + generate (Function A)
β”œβ”€β”€ generate_valid.py            # Validity-boosting sampling CLI (Function B)
β”œβ”€β”€ sampling_strategies.py       # Validity-boosting sampler library (Function B)
β”œβ”€β”€ finetune_multi_target.py     # Multi-target TD3B training
β”œβ”€β”€ launch_multi_target.sh       # Training launcher script
β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ diffusion.py             # MDLM backbone (TR2-D2)
β”‚   β”œβ”€β”€ roformer.py              # RoFormer wrapper
β”‚   └── noise_schedule.py        # Noise schedules
β”œβ”€β”€ training/
β”‚   β”œβ”€β”€ finetune_utils.py        # Training utilities
β”‚   └── distributed_utils.py     # Distributed training helpers
β”œβ”€β”€ mcts/
β”‚   └── peptide_mcts.py          # MCTS tree search
β”œβ”€β”€ td3b/
β”‚   β”œβ”€β”€ direction_oracle.py      # Direction Oracle (f_Ο†)
β”‚   β”œβ”€β”€ td3b_scoring.py          # Gated reward R = g_ψ Β· Οƒ(d*Β·(f_Ο†βˆ’0.5)/Ο„)
β”‚   β”œβ”€β”€ td3b_losses.py           # L_WDCE + λ·L_ctr + Ξ²Β·L_KL
β”‚   β”œβ”€β”€ td3b_mcts.py             # TD3B-extended MCTS
β”‚   β”œβ”€β”€ td3b_finetune.py         # Training loop
β”‚   └── data_utils.py            # Data loading utilities
β”œβ”€β”€ scoring/                     # Affinity predictor (g_ψ) and property classifiers
β”œβ”€β”€ baselines/                   # CG, SMC, TDS, PepTune, Unguided baselines
β”œβ”€β”€ tokenizer/                   # SMILES tokenizer (vocab + splits)
β”œβ”€β”€ configs/                     # Model and training configs
└── utils/                       # Misc utilities
```

## Inference

Generate agonist/antagonist binders for target proteins:

```bash
python inference.py \
    --ckpt_path checkpoints/td3b.ckpt \
    --val_csv data/test.csv \
    --save_path results/ \
    --seed 42 \
    --num_pool 32 \
    --val_samples_per_target 8 \
    --resample_alpha 0.1
```

This generates 32 candidates per (target, direction), scores them with the Direction Oracle and affinity predictor, applies Algorithm 2 weighted resampling, and saves only valid peptide samples.

> **Generation length matters.** The length (in SMILES tokens) comes from `--seq_length` if given, else it is
> derived from each row's reference binder (correctly tokenized). This released checkpoint reliably produces
> **valid** SMILES only at **short lengths (≲100 tokens)**; long binders (e.g. 25–30-residue peptides β‰ˆ 150–210
> tokens) yield few or no valid samples regardless of step count. For non-empty output on such targets, pass a
> shorter `--seq_length` and a validity-boosting sampler (see below), e.g.:
> ```bash
> python inference.py --ckpt_path checkpoints/td3b.ckpt --val_csv data/test.csv \
>     --seq_length 50 --sampler best_of_n --best_of_n 6 --num_pool 32 --val_samples_per_target 4
> ```
> On a real run this yields valid, oracle-scored binders in both directions (antagonist direction accuracy β‰ˆ 1.0).

Output: `results/td3b_results_seed42.csv` with columns: target, target_uid, sequence, direction_name, target_direction, is_valid, affinity, gated_reward, direction_oracle, direction_accuracy.

## Finetune on your own target(s)

`finetune_on_target.py` takes protein target(s) you supply, finetunes the pretrained TD3B policy on **only those target(s)**, then generates directional (agonist/antagonist) binders for them. It reuses the existing machinery: the finetune half subprocess-invokes `finetune_multi_target.py` (with `K` set to the number of targets), and the generation half runs in-process with the same reward, oracle, and Algorithm 2 resampling as `inference.py`.

```bash
python finetune_on_target.py \
    --target_seq MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQ \
    --target_seq GSHMKELVLALYDYQEKSPREVTMKKGDILTLL \
    --direction both \
    --validity_reward on \
    --num_epochs 20 \
    --num_pool 32 \
    --gen_samples_per_target 8 \
    --device cuda:0 --seed 42
```

Provide targets by repeating `--target_seq` and/or via `--targets_csv` (a CSV with a `Target_Sequence` column; optional `Ligand_Sequence`/`label` rows seed the per-direction length prior):

```bash
python finetune_on_target.py --targets_csv my_targets.csv --direction agonist --binder_length 20
```

Key flags:
- `--target_seq SEQ` (repeatable) and/or `--targets_csv` β€” provide at least one target.
- `--direction {agonist,antagonist,both}` (default `both`) β€” which binders to **generate**. Finetuning always searches both directions.
- `--validity_reward {on,off}` (default `on`) β€” validity gate applied to **both** halves. `on` keeps the `is_peptide` filter (invalid MCTS children are zero-rewarded during finetuning; only valid peptides are eligible for resampling during generation). `off` drops the gate on both halves (invalid samples are retained, each row keeps `is_valid`); the reward itself is always affinity Γ— direction β€” validity is only ever a filter, never part of the reward formula. `--finetune_validity_hook {on,off}` overrides just the finetune-side gate.
- `--binder_length` (default 20) β€” placeholder binder length (residues) used to seed the generation-length prior for any target with no known binder.
- `--skip_finetune` (generate directly from `--td3b_checkpoint`) / `--skip_generate` (finetune only).
- Training knobs: `--num_epochs` (20), `--num_iter` (10) / `--num_children` (16) (MCTS), `--learning_rate` (3e-4), `--seq_length` (200). Generation knobs: `--num_pool` (32), `--gen_samples_per_target` (8), `--resample_alpha` (0.1), `--total_num_steps` (128).
- Paths default to the repo root; override `--pretrained_checkpoint`, `--direction_oracle_ckpt`, `--output_dir`, `--device` (`auto`/`cpu`/`cuda:N`), `--seed` as needed.

Output: `results/finetune_on_target/binders_<direction>_validity-<on|off>_seed<seed>.csv` with columns target, sequence, direction_name, target_direction, is_valid, affinity, gated_reward, direction_oracle, direction_accuracy, gen_length. The finetuned checkpoint is written under `results/<run_name>_<timestamp>/` (used automatically for the generation half).

## Higher-validity sampling for long binders

As the target length grows, the fraction of decoded SMILES that pass the RDKit peptide check drops. `generate_valid.py` (backed by `sampling_strategies.py`) applies **sampling-time** strategies β€” no retraining β€” to raise the valid-peptide yield, especially at large length, and reports the valid fraction. The strategies only change token selection (temperature / top-k / top-p) and add a remask self-correction loop and best-of-N rejection on top of the model's existing diffusion primitives.

```bash
python generate_valid.py \
    --ckpt_path checkpoints/td3b.ckpt \
    --length 400 --num_samples 64 \
    --strategy nucleus_remask \
    --device cuda:0 --seed 42 \
    --save_path results/valid_len400.csv
```

Key flags:
- `--strategy` (default `nucleus_remask`) β€” one of `baseline`, `more_steps`, `top_p` (a.k.a. `nucleus`), `top_k`, `low_temp`, `remask`, `best_of_n`, `nucleus_remask`. On random-init benchmarks the self-correcting strategies (`remask`, `best_of_n`, `nucleus_remask`) give the largest relative validity gains at length β‰₯ 200; `nucleus_remask` is the recommended default.
- `--length` (default 200, in tokens) and `--num_samples` (default 64).
- `--ckpt_path` β€” TD3B checkpoint. If omitted or missing, a **random-init** model is used (exercises the sampling machinery only; yields are meaningless β€” pass a real checkpoint for real numbers).
- Per-strategy overrides (else the preset default is used): `--top_p`, `--top_k`, `--temperature`, `--steps_per_token`, `--remask_rounds`, `--remask_frac`, `--remask_steps`, `--best_of_n`, `--num_steps`.

Output: prints the valid yield (valid / `num_samples`) with per-round counts, and writes the valid sequences to `--save_path` (default `results/valid_<strategy>_len<L>.csv`) with columns idx, sequence, n_chars.

## Training

### Multi-target TD3B

1. Edit `launch_multi_target.sh` only if needed β€” `BASE_PATH` auto-detects the repo root (this script's own directory), and the checkpoint, data, and oracle paths derive from it:

```bash
BASE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"  # auto-detects the repo root
PRETRAINED_CHECKPOINT="${BASE_PATH}/checkpoints/pretrained.ckpt"
TRAIN_CSV="${BASE_PATH}/data/train.csv"
ORACLE_CKPT="${BASE_PATH}/checkpoints/direction_oracle.pt"
```

`finetune_multi_target.py --base_path` also defaults to the repo root, so you only need to override these when your checkpoints or data live elsewhere.

2. Launch training:

```bash
bash launch_multi_target.sh
```

Key hyperparameters (in `launch_multi_target.sh`):
- `CONTRASTIVE_WEIGHT=0.1` β€” Ξ» for L_ctr
- `KL_BETA=0.1` β€” Ξ² for L_KL
- `SIGMOID_TEMPERATURE=0.1` β€” Ο„ for gated reward
- `NUM_ITER=20` β€” MCTS iterations per round
- `NUM_CHILDREN=16` β€” Children per MCTS expansion

### Baselines

Run baseline methods (CG, SMC, TDS, PepTune, Unguided):

```bash
cd baselines/
bash run.sh --baseline cg --device cuda:0
bash run.sh --baseline smc --device cuda:0
bash run.sh --baseline tds --device cuda:0
```

## Citation

```bibtex
@inproceedings{
cao2026tdb,
title={{TD}3B: Transition-Directed Discrete Diffusion for Allosteric Binder Generation},
author={Hanqun Cao and Aastha Pal and Sophia Tang and Yinuo Zhang and Jingjie Zhang and Pheng-Ann Heng and Pranam Chatterjee},
booktitle={Learning Meaningful Representations of Life (LMRL) Workshop at ICLR 2026},
year={2026},
url={https://openreview.net/forum?id=uMUicLylVp}
}
```