bsvae 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bsvae-0.3.2/PKG-INFO +224 -0
- bsvae-0.3.2/README.md +192 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/pyproject.toml +1 -1
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/cli/networks.py +59 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/cli/sweep_k.py +1 -1
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/cli/train.py +76 -5
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/gmm_prior.py +5 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/gmvae.py +20 -4
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/losses.py +265 -85
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/networks/extract_networks.py +13 -7
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/networks/module_extraction.py +4 -8
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/simulation/scenario.py +61 -6
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/modelIO.py +5 -4
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/training.py +12 -3
- bsvae-0.3.1/PKG-INFO +0 -204
- bsvae-0.3.1/README.md +0 -172
- {bsvae-0.3.1 → bsvae-0.3.2}/LICENSE +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/cli/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/cli/simulate.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/hyperparam.ini +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/latent/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/latent/latent_analysis.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/latent/latent_export.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/main.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/decoder.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/encoder.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/models/vae.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/networks/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/networks/cli.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/networks/utils.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/simulation/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/simulation/generate.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/simulation/metrics.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/__init__.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/datasets.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/evaluate.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/helpers.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/hierarchy.py +0 -0
- {bsvae-0.3.1 → bsvae-0.3.2}/src/bsvae/utils/initialization.py +0 -0
bsvae-0.3.2/PKG-INFO
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bsvae
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Gaussian Mixture VAE for multi-modal biological module discovery in omics data.
|
|
5
|
+
License: GPL-3.0-only
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: variational-autoencoder,gaussian-mixture-model,pytorch,bioinformatics,gene-expression,module-discovery,omics
|
|
8
|
+
Author: Kynon J Benjamin
|
|
9
|
+
Author-email: kj.benjamin90@gmail.com
|
|
10
|
+
Requires-Python: >=3.11,<3.14
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Requires-Dist: faiss-cpu (>=1.7)
|
|
20
|
+
Requires-Dist: h5py (>=3.0)
|
|
21
|
+
Requires-Dist: numpy (<2.3)
|
|
22
|
+
Requires-Dist: pandas (>=2.3.2,<3.0.0)
|
|
23
|
+
Requires-Dist: scikit-learn (>=1.7.2,<2.0.0)
|
|
24
|
+
Requires-Dist: scipy (>=1.16.2,<2.0.0)
|
|
25
|
+
Requires-Dist: torch (>=2.8.0,<3.0.0)
|
|
26
|
+
Requires-Dist: tqdm (>=4.67.1,<5.0.0)
|
|
27
|
+
Project-URL: Bug Tracker, https://github.com/heart-gen/BSVAE/issues
|
|
28
|
+
Project-URL: homepage, https://bsvae.readthedocs.io/en/latest/
|
|
29
|
+
Project-URL: repository, https://github.com/heart-gen/BSVAE
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# BSVAE: Gaussian Mixture VAE for Module Discovery
|
|
33
|
+
|
|
34
|
+
[](https://pytorch.org/)
|
|
35
|
+
[](https://python.org/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://bsvae.readthedocs.io/)
|
|
38
|
+
|
|
39
|
+
BSVAE is a PyTorch package for feature-level module discovery in omics data. The main model, `GMMModuleVAE`, learns latent structure across features such as genes, transcripts, or proteins, then supports downstream network extraction, module assignment, latent analysis, and simulation-based benchmarking.
|
|
40
|
+
|
|
41
|
+
## What The Package Provides
|
|
42
|
+
|
|
43
|
+
- `bsvae-train` for training a two-phase GMM-VAE
|
|
44
|
+
- `bsvae-sweep-k` for selecting the number of modules (`K`) with held-out validation and optional stability replicates
|
|
45
|
+
- `bsvae-networks` for post-training network extraction, module extraction, latent export, and latent analysis
|
|
46
|
+
- `bsvae-simulate` for synthetic data generation, scenario-grid simulation, and module-recovery benchmarking
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Install from PyPI:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install bsvae
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Install from source:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/heart-gen/BSVAE.git
|
|
60
|
+
cd BSVAE
|
|
61
|
+
pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Optional dependency:
|
|
65
|
+
|
|
66
|
+
- `anndata` is only needed for `.h5ad` input or `.h5ad` latent export helpers
|
|
67
|
+
|
|
68
|
+
## Input Data Contract
|
|
69
|
+
|
|
70
|
+
Training and most downstream commands expect an expression matrix in `features x samples` orientation:
|
|
71
|
+
|
|
72
|
+
- rows are features such as genes, transcripts, or proteins
|
|
73
|
+
- columns are sample IDs
|
|
74
|
+
- the first column is the feature index when using CSV/TSV
|
|
75
|
+
|
|
76
|
+
Supported loaders:
|
|
77
|
+
|
|
78
|
+
- `.csv` / `.csv.gz`
|
|
79
|
+
- `.tsv` / `.tsv.gz`
|
|
80
|
+
- `.h5` / `.hdf5`
|
|
81
|
+
- `.h5ad` with optional `anndata`
|
|
82
|
+
|
|
83
|
+
## CLI Entry Points
|
|
84
|
+
|
|
85
|
+
- `bsvae-train`
|
|
86
|
+
- `bsvae-sweep-k`
|
|
87
|
+
- `bsvae-networks`
|
|
88
|
+
- `bsvae-simulate`
|
|
89
|
+
|
|
90
|
+
## Quickstart
|
|
91
|
+
|
|
92
|
+
Train a small model:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
bsvae-train pilot_run \
|
|
96
|
+
--dataset data/expression.csv \
|
|
97
|
+
--epochs 50 \
|
|
98
|
+
--n-modules 12 \
|
|
99
|
+
--latent-dim 16
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Recommended production flow: select `K` first, then use the retrained final model.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
bsvae-sweep-k sweep1 \
|
|
106
|
+
--dataset data/expression.csv \
|
|
107
|
+
--k-grid 8,12,16,24,32 \
|
|
108
|
+
--sweep-epochs 60 \
|
|
109
|
+
--stability-reps 5 \
|
|
110
|
+
--val-frac 0.1
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This writes sweep outputs under `results/sweep1/sweep_k/` and retrains the selected model under `results/sweep1/final_k<K>/`.
|
|
114
|
+
|
|
115
|
+
Extract module assignments from the final model:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
bsvae-networks extract-modules \
|
|
119
|
+
--model-path results/sweep1/final_k16 \
|
|
120
|
+
--dataset data/expression.csv \
|
|
121
|
+
--output-dir results/sweep1/final_k16/modules \
|
|
122
|
+
--expr data/expression.csv \
|
|
123
|
+
--soft-eigengenes
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Export latents:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
bsvae-networks export-latents \
|
|
130
|
+
--model-path results/sweep1/final_k16 \
|
|
131
|
+
--dataset data/expression.csv \
|
|
132
|
+
--output results/sweep1/final_k16/latents
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This command writes `results/sweep1/final_k16/latents.npz` containing `mu`, `logvar`, `gamma`, and `feature_ids`.
|
|
136
|
+
|
|
137
|
+
Extract feature-feature networks:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
bsvae-networks extract-networks \
|
|
141
|
+
--model-path results/sweep1/final_k16 \
|
|
142
|
+
--dataset data/expression.csv \
|
|
143
|
+
--output-dir results/sweep1/final_k16/networks \
|
|
144
|
+
--methods mu_cosine gamma_knn \
|
|
145
|
+
--top-k 50
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Run latent-space analysis:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bsvae-networks latent-analysis \
|
|
152
|
+
--model-path results/sweep1/final_k16 \
|
|
153
|
+
--dataset data/expression.csv \
|
|
154
|
+
--output-dir results/sweep1/final_k16/latent_analysis \
|
|
155
|
+
--kmeans-k 16 \
|
|
156
|
+
--umap
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Training Outputs
|
|
160
|
+
|
|
161
|
+
`bsvae-train` writes to `results/<experiment>/` by default:
|
|
162
|
+
|
|
163
|
+
- `model.pt`
|
|
164
|
+
- `specs.json`
|
|
165
|
+
- `train_losses.csv`
|
|
166
|
+
- `model-<epoch>.pt` checkpoints when `--checkpoint-every` is greater than zero
|
|
167
|
+
|
|
168
|
+
`bsvae-sweep-k` writes:
|
|
169
|
+
|
|
170
|
+
- `results/<name>/sweep_k/sweep_results.csv`
|
|
171
|
+
- `results/<name>/sweep_k/sweep_summary.json`
|
|
172
|
+
- `results/<name>/sweep_k/k<K>/rep_<rep>/...` per-sweep run outputs
|
|
173
|
+
- `results/<name>/final_k<K>/...` for the final retrained model when `--train-final` is enabled
|
|
174
|
+
|
|
175
|
+
## Simulation and Benchmarking
|
|
176
|
+
|
|
177
|
+
Generate one synthetic dataset:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
bsvae-simulate generate \
|
|
181
|
+
--output data/sim_expr.csv \
|
|
182
|
+
--save-ground-truth data/sim_truth.csv
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Generate a scenario grid:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
bsvae-simulate init-config --output sim.yaml
|
|
189
|
+
|
|
190
|
+
bsvae-simulate generate-grid \
|
|
191
|
+
--config sim.yaml \
|
|
192
|
+
--outdir results/sim_pub_v1 \
|
|
193
|
+
--reps 30 \
|
|
194
|
+
--base-seed 13
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Validate the generated grid:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
bsvae-simulate validate-grid --grid-dir results/sim_pub_v1
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Each scenario replicate writes method-ready files under `results/sim_pub_v1/scenarios/<scenario_id>/rep_<rep>/`, including:
|
|
204
|
+
|
|
205
|
+
- `expr/features_x_samples.tsv.gz`
|
|
206
|
+
- `expr/samples_x_features.tsv.gz`
|
|
207
|
+
- `truth/modules_hard.csv`
|
|
208
|
+
- `method_inputs.json`
|
|
209
|
+
|
|
210
|
+
## Documentation
|
|
211
|
+
|
|
212
|
+
See the docs for the full workflow and command reference:
|
|
213
|
+
|
|
214
|
+
- `docs/index.md`
|
|
215
|
+
- `docs/quickstart.md`
|
|
216
|
+
- `docs/tutorial.md`
|
|
217
|
+
- `docs/cli.md`
|
|
218
|
+
- `docs/networks.md`
|
|
219
|
+
- `docs/hyperparameters.md`
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
This project is licensed under the [GNU General Public License v3.0](LICENSE).
|
|
224
|
+
|
bsvae-0.3.2/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# BSVAE: Gaussian Mixture VAE for Module Discovery
|
|
2
|
+
|
|
3
|
+
[](https://pytorch.org/)
|
|
4
|
+
[](https://python.org/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://bsvae.readthedocs.io/)
|
|
7
|
+
|
|
8
|
+
BSVAE is a PyTorch package for feature-level module discovery in omics data. The main model, `GMMModuleVAE`, learns latent structure across features such as genes, transcripts, or proteins, then supports downstream network extraction, module assignment, latent analysis, and simulation-based benchmarking.
|
|
9
|
+
|
|
10
|
+
## What The Package Provides
|
|
11
|
+
|
|
12
|
+
- `bsvae-train` for training a two-phase GMM-VAE
|
|
13
|
+
- `bsvae-sweep-k` for selecting the number of modules (`K`) with held-out validation and optional stability replicates
|
|
14
|
+
- `bsvae-networks` for post-training network extraction, module extraction, latent export, and latent analysis
|
|
15
|
+
- `bsvae-simulate` for synthetic data generation, scenario-grid simulation, and module-recovery benchmarking
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Install from PyPI:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install bsvae
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Install from source:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/heart-gen/BSVAE.git
|
|
29
|
+
cd BSVAE
|
|
30
|
+
pip install -e .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Optional dependency:
|
|
34
|
+
|
|
35
|
+
- `anndata` is only needed for `.h5ad` input or `.h5ad` latent export helpers
|
|
36
|
+
|
|
37
|
+
## Input Data Contract
|
|
38
|
+
|
|
39
|
+
Training and most downstream commands expect an expression matrix in `features x samples` orientation:
|
|
40
|
+
|
|
41
|
+
- rows are features such as genes, transcripts, or proteins
|
|
42
|
+
- columns are sample IDs
|
|
43
|
+
- the first column is the feature index when using CSV/TSV
|
|
44
|
+
|
|
45
|
+
Supported loaders:
|
|
46
|
+
|
|
47
|
+
- `.csv` / `.csv.gz`
|
|
48
|
+
- `.tsv` / `.tsv.gz`
|
|
49
|
+
- `.h5` / `.hdf5`
|
|
50
|
+
- `.h5ad` with optional `anndata`
|
|
51
|
+
|
|
52
|
+
## CLI Entry Points
|
|
53
|
+
|
|
54
|
+
- `bsvae-train`
|
|
55
|
+
- `bsvae-sweep-k`
|
|
56
|
+
- `bsvae-networks`
|
|
57
|
+
- `bsvae-simulate`
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
Train a small model:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bsvae-train pilot_run \
|
|
65
|
+
--dataset data/expression.csv \
|
|
66
|
+
--epochs 50 \
|
|
67
|
+
--n-modules 12 \
|
|
68
|
+
--latent-dim 16
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Recommended production flow: select `K` first, then use the retrained final model.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bsvae-sweep-k sweep1 \
|
|
75
|
+
--dataset data/expression.csv \
|
|
76
|
+
--k-grid 8,12,16,24,32 \
|
|
77
|
+
--sweep-epochs 60 \
|
|
78
|
+
--stability-reps 5 \
|
|
79
|
+
--val-frac 0.1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This writes sweep outputs under `results/sweep1/sweep_k/` and retrains the selected model under `results/sweep1/final_k<K>/`.
|
|
83
|
+
|
|
84
|
+
Extract module assignments from the final model:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bsvae-networks extract-modules \
|
|
88
|
+
--model-path results/sweep1/final_k16 \
|
|
89
|
+
--dataset data/expression.csv \
|
|
90
|
+
--output-dir results/sweep1/final_k16/modules \
|
|
91
|
+
--expr data/expression.csv \
|
|
92
|
+
--soft-eigengenes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Export latents:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
bsvae-networks export-latents \
|
|
99
|
+
--model-path results/sweep1/final_k16 \
|
|
100
|
+
--dataset data/expression.csv \
|
|
101
|
+
--output results/sweep1/final_k16/latents
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This command writes `results/sweep1/final_k16/latents.npz` containing `mu`, `logvar`, `gamma`, and `feature_ids`.
|
|
105
|
+
|
|
106
|
+
Extract feature-feature networks:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bsvae-networks extract-networks \
|
|
110
|
+
--model-path results/sweep1/final_k16 \
|
|
111
|
+
--dataset data/expression.csv \
|
|
112
|
+
--output-dir results/sweep1/final_k16/networks \
|
|
113
|
+
--methods mu_cosine gamma_knn \
|
|
114
|
+
--top-k 50
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Run latent-space analysis:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bsvae-networks latent-analysis \
|
|
121
|
+
--model-path results/sweep1/final_k16 \
|
|
122
|
+
--dataset data/expression.csv \
|
|
123
|
+
--output-dir results/sweep1/final_k16/latent_analysis \
|
|
124
|
+
--kmeans-k 16 \
|
|
125
|
+
--umap
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Training Outputs
|
|
129
|
+
|
|
130
|
+
`bsvae-train` writes to `results/<experiment>/` by default:
|
|
131
|
+
|
|
132
|
+
- `model.pt`
|
|
133
|
+
- `specs.json`
|
|
134
|
+
- `train_losses.csv`
|
|
135
|
+
- `model-<epoch>.pt` checkpoints when `--checkpoint-every` is greater than zero
|
|
136
|
+
|
|
137
|
+
`bsvae-sweep-k` writes:
|
|
138
|
+
|
|
139
|
+
- `results/<name>/sweep_k/sweep_results.csv`
|
|
140
|
+
- `results/<name>/sweep_k/sweep_summary.json`
|
|
141
|
+
- `results/<name>/sweep_k/k<K>/rep_<rep>/...` per-sweep run outputs
|
|
142
|
+
- `results/<name>/final_k<K>/...` for the final retrained model when `--train-final` is enabled
|
|
143
|
+
|
|
144
|
+
## Simulation and Benchmarking
|
|
145
|
+
|
|
146
|
+
Generate one synthetic dataset:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bsvae-simulate generate \
|
|
150
|
+
--output data/sim_expr.csv \
|
|
151
|
+
--save-ground-truth data/sim_truth.csv
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Generate a scenario grid:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
bsvae-simulate init-config --output sim.yaml
|
|
158
|
+
|
|
159
|
+
bsvae-simulate generate-grid \
|
|
160
|
+
--config sim.yaml \
|
|
161
|
+
--outdir results/sim_pub_v1 \
|
|
162
|
+
--reps 30 \
|
|
163
|
+
--base-seed 13
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Validate the generated grid:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bsvae-simulate validate-grid --grid-dir results/sim_pub_v1
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Each scenario replicate writes method-ready files under `results/sim_pub_v1/scenarios/<scenario_id>/rep_<rep>/`, including:
|
|
173
|
+
|
|
174
|
+
- `expr/features_x_samples.tsv.gz`
|
|
175
|
+
- `expr/samples_x_features.tsv.gz`
|
|
176
|
+
- `truth/modules_hard.csv`
|
|
177
|
+
- `method_inputs.json`
|
|
178
|
+
|
|
179
|
+
## Documentation
|
|
180
|
+
|
|
181
|
+
See the docs for the full workflow and command reference:
|
|
182
|
+
|
|
183
|
+
- `docs/index.md`
|
|
184
|
+
- `docs/quickstart.md`
|
|
185
|
+
- `docs/tutorial.md`
|
|
186
|
+
- `docs/cli.md`
|
|
187
|
+
- `docs/networks.md`
|
|
188
|
+
- `docs/hyperparameters.md`
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
This project is licensed under the [GNU General Public License v3.0](LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "bsvae"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2" # dynamically resolved by poetry-dynamic-versioning
|
|
4
4
|
description = "Gaussian Mixture VAE for multi-modal biological module discovery in omics data."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = ["Kynon J Benjamin <kj.benjamin90@gmail.com>"]
|
|
@@ -64,6 +64,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
64
64
|
mp.add_argument("--use-leiden", action="store_true",
|
|
65
65
|
help="Also run Leiden clustering as fallback comparison.")
|
|
66
66
|
mp.add_argument("--leiden-resolution", type=float, default=1.0)
|
|
67
|
+
mp.add_argument("--tx2gene", type=str, default=None,
|
|
68
|
+
help="TSV (transcript_id, gene_id) for isoform-to-gene aggregation.")
|
|
69
|
+
mp.add_argument("--aggregate-to-gene", action="store_true", default=False,
|
|
70
|
+
help="Aggregate isoform-level γ to gene level by averaging across "
|
|
71
|
+
"isoforms per gene. Saves gamma_gene.npz and "
|
|
72
|
+
"hard_assignments_gene.npz. Requires --tx2gene.")
|
|
67
73
|
|
|
68
74
|
# ------------------------------------------------------------------
|
|
69
75
|
# export-latents
|
|
@@ -120,6 +126,35 @@ def handle_extract_networks(args, logger: logging.Logger) -> None:
|
|
|
120
126
|
logger.info("Network extraction complete → %s", args.output_dir)
|
|
121
127
|
|
|
122
128
|
|
|
129
|
+
def _aggregate_gamma_to_gene(
|
|
130
|
+
gamma: "np.ndarray",
|
|
131
|
+
feature_ids: List[str],
|
|
132
|
+
tx2gene_path: str,
|
|
133
|
+
) -> "tuple[np.ndarray, list[str]]":
|
|
134
|
+
"""Average isoform-level γ (F_tx, K) to gene level, renormalise rows to sum 1."""
|
|
135
|
+
import numpy as np
|
|
136
|
+
from collections import defaultdict
|
|
137
|
+
from bsvae.utils.hierarchy import load_tx2gene
|
|
138
|
+
|
|
139
|
+
tx2gene = load_tx2gene(path=tx2gene_path, feature_ids=feature_ids)
|
|
140
|
+
# Map every feature to its gene_id; fall back to itself for unmapped features
|
|
141
|
+
fid_to_gene = {fid: tx2gene.get(fid, fid) for fid in feature_ids}
|
|
142
|
+
|
|
143
|
+
gene_to_idxs: dict = defaultdict(list)
|
|
144
|
+
for i, fid in enumerate(feature_ids):
|
|
145
|
+
gene_to_idxs[fid_to_gene[fid]].append(i)
|
|
146
|
+
|
|
147
|
+
gene_ids = list(gene_to_idxs.keys())
|
|
148
|
+
gamma_gene = np.zeros((len(gene_ids), gamma.shape[1]), dtype=np.float32)
|
|
149
|
+
for j, gid in enumerate(gene_ids):
|
|
150
|
+
gamma_gene[j] = gamma[gene_to_idxs[gid]].mean(axis=0)
|
|
151
|
+
|
|
152
|
+
row_sums = gamma_gene.sum(axis=1, keepdims=True)
|
|
153
|
+
row_sums = np.where(row_sums > 0, row_sums, 1.0)
|
|
154
|
+
gamma_gene /= row_sums
|
|
155
|
+
return gamma_gene, gene_ids
|
|
156
|
+
|
|
157
|
+
|
|
123
158
|
def handle_extract_modules(args, logger: logging.Logger) -> None:
|
|
124
159
|
import numpy as np
|
|
125
160
|
from bsvae.utils.modelIO import load_model
|
|
@@ -144,6 +179,30 @@ def handle_extract_modules(args, logger: logging.Logger) -> None:
|
|
|
144
179
|
model.n_modules, len(feature_ids),
|
|
145
180
|
)
|
|
146
181
|
|
|
182
|
+
if args.aggregate_to_gene:
|
|
183
|
+
if not args.tx2gene:
|
|
184
|
+
logger.warning("--aggregate-to-gene requires --tx2gene; skipping.")
|
|
185
|
+
else:
|
|
186
|
+
gamma_gene, gene_ids = _aggregate_gamma_to_gene(
|
|
187
|
+
result["gamma"], result["feature_ids"], args.tx2gene
|
|
188
|
+
)
|
|
189
|
+
hard_gene = gamma_gene.argmax(axis=1).astype(np.int32)
|
|
190
|
+
out = Path(args.output_dir)
|
|
191
|
+
np.savez_compressed(
|
|
192
|
+
out / "gamma_gene.npz",
|
|
193
|
+
gamma=gamma_gene,
|
|
194
|
+
feature_ids=np.array(gene_ids, dtype=object),
|
|
195
|
+
)
|
|
196
|
+
np.savez_compressed(
|
|
197
|
+
out / "hard_assignments_gene.npz",
|
|
198
|
+
hard_assignments=hard_gene,
|
|
199
|
+
feature_ids=np.array(gene_ids, dtype=object),
|
|
200
|
+
)
|
|
201
|
+
logger.info(
|
|
202
|
+
"Gene-level aggregation: %d transcripts → %d genes",
|
|
203
|
+
len(result["feature_ids"]), len(gene_ids),
|
|
204
|
+
)
|
|
205
|
+
|
|
147
206
|
if args.soft_eigengenes and args.expr:
|
|
148
207
|
import pandas as pd
|
|
149
208
|
expr = pd.read_csv(args.expr, index_col=0, sep="\t" if args.expr.endswith(".tsv") else ",")
|
|
@@ -320,7 +320,7 @@ def parse_args(cli_args=None):
|
|
|
320
320
|
p.add_argument("--kl-cycle-length", type=int, default=50)
|
|
321
321
|
p.add_argument("--sep-strength", type=float, default=0.1)
|
|
322
322
|
p.add_argument("--sep-alpha", type=float, default=2.0)
|
|
323
|
-
p.add_argument("--bal-strength", type=float, default=0.
|
|
323
|
+
p.add_argument("--bal-strength", type=float, default=0.1)
|
|
324
324
|
p.add_argument("--bal-ema-blend", type=float, default=0.5)
|
|
325
325
|
p.add_argument("--pi-entropy-strength", type=float, default=0.0)
|
|
326
326
|
p.add_argument("--hier-strength", type=float, default=0.0)
|
|
@@ -85,11 +85,16 @@ def parse_args(cli_args=None):
|
|
|
85
85
|
p.add_argument("--use-batch-norm", action="store_true", dest="use_batch_norm", default=True)
|
|
86
86
|
p.add_argument("--sigma-min", type=float, default=0.3,
|
|
87
87
|
help="GMM component σ floor (default: 0.3).")
|
|
88
|
+
p.add_argument("--normalize-input", action="store_true", default=False,
|
|
89
|
+
help="Z-score each feature profile across samples before encoding. "
|
|
90
|
+
"Recommended for count-derived data (log2-CPM, etc.).")
|
|
88
91
|
|
|
89
92
|
# --- Loss hyperparameters ---
|
|
90
93
|
p.add_argument("--beta", type=float, default=1.0, help="KL weight (default: 1.0).")
|
|
91
|
-
p.add_argument("--free-bits", type=float, default=0.
|
|
92
|
-
help="Per-dim KL lower bound in nats (default: 0.
|
|
94
|
+
p.add_argument("--free-bits", type=float, default=0.0,
|
|
95
|
+
help="Per-dim KL lower bound in nats (default: 0.0). "
|
|
96
|
+
"Non-zero values hurt GMM cluster recovery by flooring γ to uniform; "
|
|
97
|
+
"leave at 0.0 for normal use.")
|
|
93
98
|
p.add_argument("--kl-warmup-epochs", type=int, default=0,
|
|
94
99
|
help="Epochs for β linear ramp within Phase 2 (default: 0).")
|
|
95
100
|
p.add_argument("--kl-anneal-mode", choices=["linear", "cyclical"], default="linear")
|
|
@@ -98,16 +103,36 @@ def parse_args(cli_args=None):
|
|
|
98
103
|
help="λ_sep: σ-scaled separation loss weight (default: 0.1).")
|
|
99
104
|
p.add_argument("--sep-alpha", type=float, default=2.0,
|
|
100
105
|
help="α: margin multiplier for separation loss (default: 2.0).")
|
|
101
|
-
p.add_argument("--bal-strength", type=float, default=0.
|
|
102
|
-
help="λ_bal: γ-usage balance loss weight (default: 0.
|
|
106
|
+
p.add_argument("--bal-strength", type=float, default=0.1,
|
|
107
|
+
help="λ_bal: γ-usage balance loss weight (default: 0.1). "
|
|
108
|
+
"Higher values force uniform usage; lower values allow natural "
|
|
109
|
+
"module-size variation but risk dead components.")
|
|
103
110
|
p.add_argument("--bal-ema-blend", type=float, default=0.5,
|
|
104
111
|
help="Balance loss blend α between EMA and batch (default: 0.5).")
|
|
112
|
+
p.add_argument("--masked-recon", action="store_true", default=False,
|
|
113
|
+
help="Compute reconstruction loss only on non-zero input entries. "
|
|
114
|
+
"Recommended for zero-inflated count data.")
|
|
105
115
|
p.add_argument("--pi-entropy-strength", type=float, default=0.0,
|
|
106
116
|
help="λ_pi: KL(uniform || π) penalty weight (default: 0.0).")
|
|
107
117
|
p.add_argument("--hier-strength", type=float, default=0.0,
|
|
108
118
|
help="λ_hier: hierarchical isoform loss weight (default: 0.0=disabled).")
|
|
119
|
+
p.add_argument("--corr-strength", type=float, default=0.0,
|
|
120
|
+
help="λ_corr: reconstruction-space pairwise correlation loss weight (default: 0.0=disabled). "
|
|
121
|
+
"Penalises MSE between the batch-level Pearson correlation matrices of inputs "
|
|
122
|
+
"and reconstructions. Recommended: 0.05–0.2 for NB count data with normalize-input.")
|
|
123
|
+
p.add_argument("--latent-corr-strength", type=float, default=0.0,
|
|
124
|
+
help="λ_lcorr: latent-space correlation alignment loss weight (default: 0.0=disabled). "
|
|
125
|
+
"Aligns cosine similarities of encoder μ with input Pearson correlations. "
|
|
126
|
+
"More direct than --corr-strength; recommended: 0.1–1.0 for NB count data.")
|
|
109
127
|
p.add_argument("--tx2gene", type=str, default=None,
|
|
110
128
|
help="TSV with (transcript_id, gene_id) for hierarchical loss.")
|
|
129
|
+
p.add_argument("--isoform-stratified", action="store_true", default=False,
|
|
130
|
+
help="Use IsoformStratifiedSampler so isoforms of the same gene "
|
|
131
|
+
"co-occur in batches, making --hier-strength effective. "
|
|
132
|
+
"Requires --tx2gene.")
|
|
133
|
+
p.add_argument("--p-multi", type=float, default=0.5,
|
|
134
|
+
help="Probability of drawing a full batch from multi-isoform features "
|
|
135
|
+
"(default: 0.5). Only active with --isoform-stratified.")
|
|
111
136
|
|
|
112
137
|
# --- Evaluation ---
|
|
113
138
|
p.add_argument("--no-eval", action="store_true", default=False,
|
|
@@ -145,12 +170,53 @@ def main(args):
|
|
|
145
170
|
|
|
146
171
|
# --- Hierarchy ---
|
|
147
172
|
gene_groups = {}
|
|
148
|
-
if args.hier_strength > 0
|
|
173
|
+
if args.tx2gene and (args.hier_strength > 0 or args.isoform_stratified):
|
|
149
174
|
from bsvae.utils.hierarchy import load_tx2gene, group_isoforms_by_gene
|
|
150
175
|
tx2gene = load_tx2gene(path=args.tx2gene, feature_ids=feature_ids)
|
|
151
176
|
gene_groups = group_isoforms_by_gene(tx2gene, feature_ids)
|
|
152
177
|
logger.info("Hierarchical groups: %d multi-isoform genes", len(gene_groups))
|
|
153
178
|
|
|
179
|
+
# Validate gene_groups indices are in-bounds
|
|
180
|
+
if gene_groups:
|
|
181
|
+
n_features_total = len(feature_ids)
|
|
182
|
+
for gene_id, idxs in gene_groups.items():
|
|
183
|
+
bad = [i for i in idxs if i < 0 or i >= n_features_total]
|
|
184
|
+
if bad:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"gene_groups['{gene_id}'] contains out-of-bounds indices {bad} "
|
|
187
|
+
f"(dataset has {n_features_total} features)"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Replace DataLoader with IsoformStratifiedSampler if requested
|
|
191
|
+
if args.isoform_stratified and gene_groups:
|
|
192
|
+
import torch.utils.data as tud
|
|
193
|
+
from bsvae.utils.hierarchy import IsoformStratifiedSampler
|
|
194
|
+
multi_indices = sorted({idx for idxs in gene_groups.values() for idx in idxs})
|
|
195
|
+
sampler = IsoformStratifiedSampler(
|
|
196
|
+
n_features=len(feature_ids),
|
|
197
|
+
multi_isoform_indices=multi_indices,
|
|
198
|
+
batch_size=args.batch_size,
|
|
199
|
+
p_multi=args.p_multi,
|
|
200
|
+
seed=args.seed,
|
|
201
|
+
)
|
|
202
|
+
train_loader = tud.DataLoader(
|
|
203
|
+
train_loader.dataset,
|
|
204
|
+
batch_size=args.batch_size,
|
|
205
|
+
sampler=sampler,
|
|
206
|
+
drop_last=True,
|
|
207
|
+
pin_memory=train_loader.pin_memory,
|
|
208
|
+
num_workers=train_loader.num_workers,
|
|
209
|
+
)
|
|
210
|
+
logger.info(
|
|
211
|
+
"IsoformStratifiedSampler: %d multi-isoform features, p_multi=%.2f",
|
|
212
|
+
len(multi_indices), args.p_multi,
|
|
213
|
+
)
|
|
214
|
+
elif args.isoform_stratified and not gene_groups:
|
|
215
|
+
logger.warning(
|
|
216
|
+
"--isoform-stratified requested but no multi-isoform gene groups found; "
|
|
217
|
+
"using standard random sampler."
|
|
218
|
+
)
|
|
219
|
+
|
|
154
220
|
# --- Model ---
|
|
155
221
|
model = GMMModuleVAE(
|
|
156
222
|
n_features=n_features,
|
|
@@ -160,6 +226,7 @@ def main(args):
|
|
|
160
226
|
dropout=args.dropout,
|
|
161
227
|
use_batch_norm=args.use_batch_norm,
|
|
162
228
|
sigma_min=args.sigma_min,
|
|
229
|
+
normalize_input=args.normalize_input,
|
|
163
230
|
).to(device)
|
|
164
231
|
logger.info(
|
|
165
232
|
"Model: %d features, %d latent dims, %d modules | params=%d",
|
|
@@ -179,6 +246,10 @@ def main(args):
|
|
|
179
246
|
pi_entropy_strength=args.pi_entropy_strength,
|
|
180
247
|
bal_ema_blend=args.bal_ema_blend,
|
|
181
248
|
hier_strength=args.hier_strength,
|
|
249
|
+
normalize_input=args.normalize_input,
|
|
250
|
+
masked_recon=args.masked_recon,
|
|
251
|
+
corr_strength=args.corr_strength,
|
|
252
|
+
latent_corr_strength=args.latent_corr_strength,
|
|
182
253
|
)
|
|
183
254
|
warmup_loss = WarmupLoss(
|
|
184
255
|
beta=args.beta,
|
|
@@ -272,6 +272,11 @@ class GaussianMixturePrior(nn.Module):
|
|
|
272
272
|
K = self.K
|
|
273
273
|
X = mu_samples.cpu().numpy()
|
|
274
274
|
|
|
275
|
+
if len(X) < K:
|
|
276
|
+
raise ValueError(
|
|
277
|
+
f"kmeans_init_: need at least K={K} samples but got {len(X)}. "
|
|
278
|
+
"Increase the dataset size or reduce n_modules."
|
|
279
|
+
)
|
|
275
280
|
km = KMeans(n_clusters=K, random_state=0, n_init=10)
|
|
276
281
|
labels = km.fit_predict(X)
|
|
277
282
|
centers = torch.tensor(km.cluster_centers_, dtype=torch.float32)
|