FRET-FACS / models /README.md
neuwirtt
Initial release: FRET-FACS pipeline, weights, and datasets
6e4d123
|
Raw
History Blame Contribute Delete
14.8 kB
# FRET binary classifiers: model training scripts
This folder contains **command-line training pipelines** for a binary FRET task (positive class **`highFRET`**, negative **`lowFRET`**). Each script fits a classifier on a train/validation split, applies **temperature scaling** and an **F1-optimal threshold** on validation, evaluates on a held-out test set (metrics are **printed to the terminal**), and writes artifacts under `--output`.
**Default (no extra flag):** outputs are **minimal** — serialized model weights (and scalers / PCA arrays for ESM scripts), **`random_seed.txt`** when applicable, **`*_validation_temperature.csv`**, and **`*_validation_diagnostics_summary.csv`** (threshold for downstream evaluation scripts). **Plots (PNG)** and detailed **evaluation CSVs** are **off** unless you pass **`--save-diagnostics`**.
Install dependencies from the repo (see **Requirements** below), then run a script with `python` from your environment.
---
## Requirements
Install packages listed in [`requirements_models_training.txt`](requirements_models_training.txt) (numpy, pandas, scikit-learn, matplotlib, h5py, tqdm, psutil, optuna, joblib, torch, **fair-esm**, tensorflow, biopython).
A **GPU helps** TensorFlow/PyTorch but is not strictly required if datasets are modest in size.
---
## Input data: FASTA and CSV
### FASTA
- Use standard FASTA records; scripts read sequences with **`esm.data.read_fasta`**.
- **The FASTA header (text after `>`)** must equal the identifier used to look up rows in your CSV (**no extra spaces** vs the CSV unless your IDs genuinely include them).
Example:
```fasta
>variant_A001
MSKGEELFTGVVPILVELDGDV...
```
### CSV column names (**important**)
The four scripts **do not all use the same CSV schema**:
| Script | ID column | Label column | Allowed label strings |
|--------|-----------|--------------|------------------------|
| `nn_one_hot.py` | **`sequence_id`** | **`per_token_model_class`** | `highFRET`, `lowFRET` |
| `rf_one_hot.py` | **`variant`** | **`label`** | `highFRET`, `lowFRET` |
| `nn_mean_pertoken_esm.py` | **`variant`** | **`label`** | `highFRET`, `lowFRET` |
| `rf_mean_pertoken_esm.py` | **`variant`** | **`label`** | `highFRET`, `lowFRET` |
If you run **`nn_one_hot.py`** alongside the others, prepare a CSV with **`sequence_id`** and **`per_token_model_class`**, **or** add/duplicate columns so that file matches the script you call.
Rows with labels other than those two strings are skipped.
### ESM embedding files (`*.pt`)
The two **ESM** scripts expect **one mean file and one per-token file per variant**, named after the **`variant`** value:
- **Mean embeddings directory** (`--emb-mean`): for each variant ID `V`, a file **`V.pt`** loaded with PyTorch (`torch.load`). Inside the checkpoint:
- `mean_representations[emb_layer]` — vector used as the pooled representation.
- **Per-token embeddings directory** (`--emb-token`): **`V.pt`** with:
- `representations[emb_layer]` — token-level tensor (flattened internally for modeling).
Layer index **`--emb-layer`** defaults to **`33`** (typical **ESM-2** last-layer convention in **fair-esm**); change it only if your `.pt` files were extracted with another layer indexing scheme.
---
## Train / validation / test split (high level)
- **One-hot scripts** (`nn_one_hot.py`, `rf_one_hot.py`): stratified split **80%** train+val / **20%** test; from train, another **20%** is held out as **validation** (calibration / threshold tuning).
- **ESM scripts** (`nn_mean_pertoken_esm.py`, `rf_mean_pertoken_esm.py`): stratified **80%** train / **20%** test; each model internally uses a validation slice from train for tuning (details in script).
Unless you pass **`--seed`**, a seed is sampled and saved to **`random_seed.txt`** in the output directory so runs can be reproduced.
---
## 1. `nn_one_hot.py` — neural network on one-hot sequences
**Idea:** Each sequence is encoded as **L × 20** one-hot positions (canonical amino acids **ACDEFGHIKLMNPQRSTVWY**; unknown residues skipped; truncate/pad to `--target-length`).
**Model:** Keras **MLP** (flatten → dense layers → 2-class softmax). Optional **Optuna** search over width, learning rate, batch size, and epochs.
**Pipeline highlights:** validation **temperature scaling**, **F1-optimal threshold** on calibrated probabilities, then **test** metrics printed to the console; **`--save-diagnostics`** restores full ROC/PR/calibration CSVs and PNGs.
### Required arguments
```bash
python models/nn_one_hot.py \
--fasta /path/to/sequences.fasta \
--csv /path/to/labels.csv \
--output /path/to/run_output/
```
### Useful options
| Flag | Default | Meaning |
|------|---------|--------|
| `--target-length` | 120 | Max sequence length for encoding; may be **auto-increased** to `max(seq_len) + 4` if needed |
| `--n-trials` | 50 | Optuna trials (when enabled) |
| `--no-optuna` | — | Fixed default MLP (64→32 units, 50 epochs, batch 32) |
| `--no-weights-export` | — | Skip extra weight export formats; still saves `one_hot_model_weights.h5` |
| `--seed` | random | Integer seed for NumPy/TensorFlow |
| `--save-diagnostics` | off | PNG plots plus full evaluation CSVs (ROC/PR, calibration files, Optuna tables, training history, `model_parameters.json`) |
### Outputs (selection)
- **Always:** **`one_hot_model_weights.h5`** (and optional **`model_weights/`** exports unless `--no-weights-export`), **`one_hot_nn_validation_temperature.csv`**, **`one_hot_nn_validation_validation_diagnostics_summary.csv`**, **`random_seed.txt`** if `--seed` was omitted.
- **With `--save-diagnostics`:** validation/test diagnostic CSV and PNG prefixes **`one_hot_nn_*`**, **`one_hot_training_history.csv`**, Optuna CSVs when Optuna ran, **`model_parameters.json`**.
---
## 2. `rf_one_hot.py` — random forest on one-hot sequences
**Idea:** Same one-hot tensor as above, **flattened** to a 2D feature matrix for **sklearn `RandomForestClassifier`**.
### Required arguments
```bash
python models/rf_one_hot.py \
--fasta /path/to/sequences.fasta \
--csv /path/to/labels_with_variant_label.csv \
--output /path/to/run_output/
```
### Useful options
| Flag | Default | Meaning |
|------|---------|--------|
| `--target-length` | 120 | Same semantics as NN script (`max(seq_len)+4` bump possible) |
| `--n-trials` | 50 | Optuna trials over RF hyperparameters |
| `--no-optuna` | — | Default RandomForest hyperparameters |
| `--seed` | random | Random state for splitting and forests |
| `--save-diagnostics` | off | PNG plots, test/validation CSVs, **`model_parameters.json`**, Optuna CSVs when used |
### Outputs (selection)
- **Always:** **`one_hot_rf_model.joblib`**, **`one_hot_rf_validation_temperature.csv`**, **`one_hot_rf_validation_validation_diagnostics_summary.csv`**, **`random_seed.txt`** if `--seed` was omitted.
- **With `--save-diagnostics`:** **`one_hot_rf_*`** diagnostics (calibration, ROC, PR, etc.), **`model_parameters.json`**, Optuna CSVs when Optuna ran.
---
## 3. `nn_mean_pertoken_esm.py` — Keras L1 logistic regression on ESM embeddings
**Idea:** Trains **two** separate binary heads: one on **mean** embeddings (**1280**-dim typical for ESM-2), one on **flattened per-token** embeddings (optionally reduced with **PCA**). Each head is essentially **multinomial logistic regression** implemented as a single **Dense(2, softmax)** Keras layer with **L1** regularization and **Adam**; **Optuna** tunes learning rate and L1 strength (**20 trials** per model in code).
Designed for **large** per-token tensors: streaming batch loads, optional **HDF5 disk cache**, **`--train-separately`** to peak less memory, **`--max-samples`** for debugging.
### Required arguments
```bash
python models/nn_mean_pertoken_esm.py \
--fasta /path/to/sequences.fasta \
--csv /path/to/labels.csv \
--emb-mean /path/to/mean_emb_dir/ \
--emb-token /path/to/per_token_emb_dir/ \
--output /path/to/run_output/
```
### Useful options
| Flag | Default | Meaning |
|------|---------|--------|
| `--emb-layer` | 33 | ESM representation layer index inside `.pt` |
| `--batch-size` | 500 | Embeddings streaming batch |
| `--max-samples` | all | Cap number of variants (testing) |
| `--use-pca-early` | off | PCA per-token rows during loading |
| `--pca-components` | 1280 | PCA size for per-token when early PCA is used |
| `--use-disk-cache` | off | Write batches to HDF5 (+ temp pickle for IDs) |
| `--cache-dir` | temp | Where to write cache files |
| `--train-separately` | off | Train mean model, free memory, then per-token |
| `--seed` | random | Reproducibility |
| `--save-diagnostics` | off | **`neural_net_comparison_results.csv`**, **`model_parameters.json`**, training history / hyperparameter CSVs, per-model diagnostic CSVs + PNGs on validation and test |
### Outputs (selection)
- **Always:** per-head **`*_embeddings_model_weights.h5`**, **`*_embeddings_scaler.npy`** (and **`*_embeddings_pca.npy`** when used), **`mean_embeddings_validation_temperature.csv`** / **`per_token_embeddings_validation_temperature.csv`**, matching **`mean_embeddings_validation_validation_diagnostics_summary.csv`** and **`per_token_embeddings_validation_validation_diagnostics_summary.csv`**, **`random_seed.txt`** when applicable.
- **With `--save-diagnostics`:** **`neural_net_comparison_results.csv`**, **`model_parameters.json`**, **`mean_embeddings_training_history.csv`**, **`per_token_embeddings_training_history.csv`**, **`*_embeddings_hyperparameters.csv`**, and expanded **`mean_embeddings_*` / `per_token_embeddings_*`** diagnostic files.
---
## 4. `rf_mean_pertoken_esm.py` — random forests on ESM embeddings
**Idea:** Same embedding loading alignment as script (3): **two** RandomForest models (**mean** and **per-token**), optional **PCA** on per-token side, **`StandardScaler`**, Optuna tuning of RF hyperparameters (`--n-trials`, `--no-optuna`).
### Required arguments
```bash
python models/rf_mean_pertoken_esm.py \
--fasta /path/to/sequences.fasta \
--csv /path/to/labels.csv \
--emb-mean /path/to/mean_emb_dir/ \
--emb-token /path/to/per_token_emb_dir/ \
--output /path/to/run_output/
```
CLI flags **`--emb-layer`**, **`--batch-size`**, **`--max-samples`**, **`--use-pca-early`**, **`--pca-components`**, **`--use-disk-cache`**, **`--cache-dir`**, **`--train-separately`**, **`--seed`**, **`--save-diagnostics`**, **`--n-trials`**, **`--no-optuna`** mirror the NN ESM script where applicable.
### Outputs (selection)
- **Always:** **`mean_embeddings_rf_model.joblib`** & **`per_token_embeddings_rf_model.joblib`** (plus **`*_embeddings_scaler.joblib`**, **`_*_embeddings_pca.joblib`** when PCA is used), **`mean_embeddings_validation_temperature.csv`**, **`per_token_embeddings_validation_temperature.csv`**, **`mean_embeddings_validation_validation_diagnostics_summary.csv`**, **`per_token_embeddings_validation_validation_diagnostics_summary.csv`**, **`random_seed.txt`** when applicable.
- **With `--save-diagnostics`:** **`rf_esm_comparison_results.csv`**, **`model_parameters.json`**, **`mean_embeddings_hyperparameters.csv`** / **`per_token_embeddings_hyperparameters.csv`**, ROC/PR/confusion/feature-importance CSVs + PNGs.
---
## 5. `lr_sequence_composition_baseline.py` — logistic regression on composition only (Figure 3 baseline)
**Idea:** No embeddings and no one-hot sequence tensor — only **eight** scalar features per variant computed from the amino-acid sequence:
| Category | Features |
|----------|----------|
| Hydrophobicity | `gravy_mean` (mean Kyte-Doolittle index) |
| Isoelectric point | `isoelectric_point` (Biopython pI) |
| Charge composition | `frac_positive`, `frac_negative`, `frac_charged`, `net_charge_ph7` |
| Sequence complexity | `shannon_entropy_aa`, `complexity_normalized` (entropy / log₂20) |
**Model:** `sklearn.linear_model.LogisticRegression` (L2) on **StandardScaler**-normalized features.
**Split / evaluation:** Same as ESM scripts — stratified **80%** train / **20%** test; validation slice from train for **temperature scaling** and **F1-optimal threshold**; **test ROC-AUC** is the headline metric. The script prints an explicit note if test AUC **≥ 0.85** (composition may explain much of the ESM signal).
### Required arguments
```bash
python models/lr_sequence_composition_baseline.py \
--fasta /path/to/sequences.fasta \
--csv /path/to/labels.csv \
--output /path/to/composition_baseline/
```
Use **`--csv-schema esm`** (default: `variant` + **`label`**, same as ESM/RF embedding scripts). The script **always prefers the `label` column** when it exists (not `per_token_model_class`). Override with `--csv-label-col` / `--csv-id-col` if needed.
### Useful options
| Flag | Default | Meaning |
|------|---------|--------|
| `--c` | 1.0 | L2 inverse regularization for logistic regression |
| `--ablation` | off | Per-group test AUC (hydrophobicity, pI, charge, complexity alone) |
| `--save-features` | off | Write `composition_features_per_variant.csv` |
| `--save-diagnostics` | off | ROC CSV/PNG and related exports |
| `--seed` | random | Reproducible split |
| `--perm-n-repeats` | 30 | Shuffle repeats for permutation importance on the test set |
### Outputs (selection)
- **Always:** `composition_baseline_summary.csv`, `composition_lr_model.joblib` (inference bundle for `evaluation/evaluate_lr_sequence_composition.py`), `composition_lr_auc_score.csv`, `composition_lr_coefficients.csv`, `composition_lr_permutation_importance.csv` (ranked test-set permutation importance), `composition_lr_validation_diagnostics_summary.csv`, `composition_lr_validation_temperature.csv`, `composition_baseline_metadata.json`
- **With `--ablation`:** `composition_baseline_ablation.csv`
- **With `--save-features` or `--save-diagnostics`:** `composition_features_per_variant.csv`
Requires **Biopython** (see `requirements_models_training.txt`).
---
## Quick reference: which script should I run?
| You have… | Consider |
|-----------|----------|
| Only FASTA + CSV (**no embeddings**) | **`lr_sequence_composition_baseline.py`** (composition baseline for Figure 3), **`nn_one_hot.py`**, or **`rf_one_hot.py`** — check CSV column requirements. |
| FASTA + CSV + **`variant`-keyed `.pt` mean & per-token** | **`nn_mean_pertoken_esm.py`** (linear / L1 Keras heads) vs **`rf_mean_pertoken_esm.py`** (nonlinear forests). |
| Prefer interpretable linear weights / smaller models | NN ESM script’s logistic heads. |
| Prefer fewer assumptions about geometry of features | RF ESM or RF one-hot. |
---
## Help
Each script exposes defaults via argparse:
```bash
python models/nn_one_hot.py -h
python models/rf_one_hot.py -h
python models/nn_mean_pertoken_esm.py -h
python models/rf_mean_pertoken_esm.py -h
python models/lr_sequence_composition_baseline.py -h
```