bsvae 0.1.1__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.1.1/PKG-INFO +168 -0
- bsvae-0.1.1/README.md +146 -0
- bsvae-0.1.1/pyproject.toml +40 -0
- bsvae-0.1.1/src/bsvae/__init__.py +0 -0
- bsvae-0.1.1/src/bsvae/hyperparam.ini +82 -0
- bsvae-0.1.1/src/bsvae/main.py +237 -0
- bsvae-0.1.1/src/bsvae/models/__init__.py +4 -0
- bsvae-0.1.1/src/bsvae/models/decoder.py +90 -0
- bsvae-0.1.1/src/bsvae/models/encoder.py +71 -0
- bsvae-0.1.1/src/bsvae/models/losses.py +116 -0
- bsvae-0.1.1/src/bsvae/models/structured.py +75 -0
- bsvae-0.1.1/src/bsvae/models/vae.py +29 -0
- bsvae-0.1.1/src/bsvae/utils/__init__.py +2 -0
- bsvae-0.1.1/src/bsvae/utils/datasets.py +143 -0
- bsvae-0.1.1/src/bsvae/utils/evaluate.py +77 -0
- bsvae-0.1.1/src/bsvae/utils/helpers.py +156 -0
- bsvae-0.1.1/src/bsvae/utils/initialization.py +30 -0
- bsvae-0.1.1/src/bsvae/utils/mapping.py +218 -0
- bsvae-0.1.1/src/bsvae/utils/modelIO.py +148 -0
- bsvae-0.1.1/src/bsvae/utils/ppi.py +153 -0
- bsvae-0.1.1/src/bsvae/utils/training.py +130 -0
bsvae-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bsvae
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Kynon J Benjamin
|
|
6
|
+
Author-email: kj.benjamin90@gmail.com
|
|
7
|
+
Requires-Python: >=3.11,<3.14
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: mygene (>=3.2.2,<4.0.0)
|
|
13
|
+
Requires-Dist: networkx (>=3.5,<4.0)
|
|
14
|
+
Requires-Dist: numpy (<2.3)
|
|
15
|
+
Requires-Dist: pandas (>=2.3.2,<3.0.0)
|
|
16
|
+
Requires-Dist: scikit-learn (>=1.7.2,<2.0.0)
|
|
17
|
+
Requires-Dist: scipy (>=1.16.2,<2.0.0)
|
|
18
|
+
Requires-Dist: torch (>=2.8.0,<3.0.0)
|
|
19
|
+
Requires-Dist: tqdm (>=4.67.1,<5.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# BSVAE: Biologically Structured Variational Autoencoder
|
|
23
|
+
|
|
24
|
+
[](https://pytorch.org/)
|
|
25
|
+
[](LICENSE)
|
|
26
|
+
|
|
27
|
+
**BSVAE** is a PyTorch package for **Structured Factor Variational Autoencoders** (StructuredFactorVAE).
|
|
28
|
+
It is designed for **gene expression modeling with biological priors**, integrating **protein–protein interaction (PPI) networks** and **sparsity constraints** for interpretable latent representations.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Structured VAE architecture** (`StructuredFactorVAE`)
|
|
35
|
+
- Factorized encoder/decoder with group sparsity
|
|
36
|
+
- Optional Laplacian regularization from PPI networks
|
|
37
|
+
- **Dataset utilities**
|
|
38
|
+
- Load gene expression matrices (`GeneExpression`)
|
|
39
|
+
- Support for CSV full-matrix mode or pre-split train/test mode
|
|
40
|
+
- **Biological priors**
|
|
41
|
+
- Fetch and cache STRING PPI networks by NCBI TaxID
|
|
42
|
+
- Map gene symbols / Ensembl IDs to protein IDs using MyGene.info or BioMart
|
|
43
|
+
- **Training and evaluation**
|
|
44
|
+
- Unified training loop (`Trainer`)
|
|
45
|
+
- Evaluation with reconstruction, KL, sparsity, and Laplacian penalties (`Evaluator`)
|
|
46
|
+
- **Reproducibility**
|
|
47
|
+
- Save/load models + metadata (`modelIO`)
|
|
48
|
+
- Configurable hyperparameters via `hyperparam.ini`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Clone the repo and install with [Poetry](https://python-poetry.org/) (recommended):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/YOUR-LAB/BSVAE.git
|
|
58
|
+
cd BSVAE
|
|
59
|
+
poetry install
|
|
60
|
+
````
|
|
61
|
+
|
|
62
|
+
Or using pip:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Dependencies:
|
|
69
|
+
|
|
70
|
+
* Python 3.9+
|
|
71
|
+
* PyTorch ≥ 2.0
|
|
72
|
+
* pandas, numpy, scikit-learn
|
|
73
|
+
* networkx, scipy
|
|
74
|
+
* mygene (for gene annotation)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Quickstart
|
|
79
|
+
|
|
80
|
+
### 1. Prepare gene expression data
|
|
81
|
+
|
|
82
|
+
BSVAE expects **genes × samples** CSVs.
|
|
83
|
+
|
|
84
|
+
* **Full-matrix mode**:
|
|
85
|
+
Provide `expr.csv` with all samples → 10-fold CV split is created.
|
|
86
|
+
* **Pre-split mode**:
|
|
87
|
+
Provide directory with `X_train.csv` and `X_test.csv`.
|
|
88
|
+
|
|
89
|
+
### 2. Train a model
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
poetry run python -m bsvae.main exp1 \
|
|
93
|
+
--gene-expression-filename data/expr.csv \
|
|
94
|
+
--epochs 50 \
|
|
95
|
+
--latent-dim 10 \
|
|
96
|
+
--ppi-taxid 9606
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
* Results (checkpoints, logs, metadata) saved under `results/exp1/`.
|
|
100
|
+
|
|
101
|
+
### 3. Evaluate a trained model
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
poetry run python -m bsvae.main exp1 \
|
|
105
|
+
--gene-expression-filename data/expr.csv \
|
|
106
|
+
--is-eval-only
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ⚙Configuration
|
|
112
|
+
|
|
113
|
+
Hyperparameters can be set via `hyperparam.ini`:
|
|
114
|
+
|
|
115
|
+
```ini
|
|
116
|
+
[Custom]
|
|
117
|
+
seed = 42
|
|
118
|
+
no_cuda = False
|
|
119
|
+
epochs = 100
|
|
120
|
+
batch_size = 64
|
|
121
|
+
latent_dim = 10
|
|
122
|
+
hidden_dims = [128, 64]
|
|
123
|
+
dropout = 0.1
|
|
124
|
+
l1_strength = 1e-3
|
|
125
|
+
lap_strength = 1e-4
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Override from CLI if needed:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
--epochs 50 --latent-dim 20
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## PPI Priors
|
|
137
|
+
|
|
138
|
+
BSVAE supports automatic download & caching of **STRING v12.0 PPI networks**.
|
|
139
|
+
|
|
140
|
+
* Supported species (via NCBI TaxID):
|
|
141
|
+
|
|
142
|
+
* Human (`9606`)
|
|
143
|
+
* Mouse (`10090`)
|
|
144
|
+
* Rat (`10116`)
|
|
145
|
+
* Fly (`7227`)
|
|
146
|
+
* Cache location defaults to `~/.bsvae/ppi` (override via `--ppi-cache`).
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Citation
|
|
151
|
+
|
|
152
|
+
If you use **BSVAE** in your research, please cite:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
@article{Benjamin2025bsvae,
|
|
156
|
+
title={Structured Factor Variational Autoencoder with Biological Priors},
|
|
157
|
+
author={Kynon J. M. Benjamin},
|
|
158
|
+
year={2025},
|
|
159
|
+
journal={N/A}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
168
|
+
|
bsvae-0.1.1/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# BSVAE: Biologically Structured Variational Autoencoder
|
|
2
|
+
|
|
3
|
+
[](https://pytorch.org/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
**BSVAE** is a PyTorch package for **Structured Factor Variational Autoencoders** (StructuredFactorVAE).
|
|
7
|
+
It is designed for **gene expression modeling with biological priors**, integrating **protein–protein interaction (PPI) networks** and **sparsity constraints** for interpretable latent representations.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Structured VAE architecture** (`StructuredFactorVAE`)
|
|
14
|
+
- Factorized encoder/decoder with group sparsity
|
|
15
|
+
- Optional Laplacian regularization from PPI networks
|
|
16
|
+
- **Dataset utilities**
|
|
17
|
+
- Load gene expression matrices (`GeneExpression`)
|
|
18
|
+
- Support for CSV full-matrix mode or pre-split train/test mode
|
|
19
|
+
- **Biological priors**
|
|
20
|
+
- Fetch and cache STRING PPI networks by NCBI TaxID
|
|
21
|
+
- Map gene symbols / Ensembl IDs to protein IDs using MyGene.info or BioMart
|
|
22
|
+
- **Training and evaluation**
|
|
23
|
+
- Unified training loop (`Trainer`)
|
|
24
|
+
- Evaluation with reconstruction, KL, sparsity, and Laplacian penalties (`Evaluator`)
|
|
25
|
+
- **Reproducibility**
|
|
26
|
+
- Save/load models + metadata (`modelIO`)
|
|
27
|
+
- Configurable hyperparameters via `hyperparam.ini`
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Clone the repo and install with [Poetry](https://python-poetry.org/) (recommended):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/YOUR-LAB/BSVAE.git
|
|
37
|
+
cd BSVAE
|
|
38
|
+
poetry install
|
|
39
|
+
````
|
|
40
|
+
|
|
41
|
+
Or using pip:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Dependencies:
|
|
48
|
+
|
|
49
|
+
* Python 3.9+
|
|
50
|
+
* PyTorch ≥ 2.0
|
|
51
|
+
* pandas, numpy, scikit-learn
|
|
52
|
+
* networkx, scipy
|
|
53
|
+
* mygene (for gene annotation)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quickstart
|
|
58
|
+
|
|
59
|
+
### 1. Prepare gene expression data
|
|
60
|
+
|
|
61
|
+
BSVAE expects **genes × samples** CSVs.
|
|
62
|
+
|
|
63
|
+
* **Full-matrix mode**:
|
|
64
|
+
Provide `expr.csv` with all samples → 10-fold CV split is created.
|
|
65
|
+
* **Pre-split mode**:
|
|
66
|
+
Provide directory with `X_train.csv` and `X_test.csv`.
|
|
67
|
+
|
|
68
|
+
### 2. Train a model
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
poetry run python -m bsvae.main exp1 \
|
|
72
|
+
--gene-expression-filename data/expr.csv \
|
|
73
|
+
--epochs 50 \
|
|
74
|
+
--latent-dim 10 \
|
|
75
|
+
--ppi-taxid 9606
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
* Results (checkpoints, logs, metadata) saved under `results/exp1/`.
|
|
79
|
+
|
|
80
|
+
### 3. Evaluate a trained model
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
poetry run python -m bsvae.main exp1 \
|
|
84
|
+
--gene-expression-filename data/expr.csv \
|
|
85
|
+
--is-eval-only
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## ⚙Configuration
|
|
91
|
+
|
|
92
|
+
Hyperparameters can be set via `hyperparam.ini`:
|
|
93
|
+
|
|
94
|
+
```ini
|
|
95
|
+
[Custom]
|
|
96
|
+
seed = 42
|
|
97
|
+
no_cuda = False
|
|
98
|
+
epochs = 100
|
|
99
|
+
batch_size = 64
|
|
100
|
+
latent_dim = 10
|
|
101
|
+
hidden_dims = [128, 64]
|
|
102
|
+
dropout = 0.1
|
|
103
|
+
l1_strength = 1e-3
|
|
104
|
+
lap_strength = 1e-4
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Override from CLI if needed:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
--epochs 50 --latent-dim 20
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## PPI Priors
|
|
116
|
+
|
|
117
|
+
BSVAE supports automatic download & caching of **STRING v12.0 PPI networks**.
|
|
118
|
+
|
|
119
|
+
* Supported species (via NCBI TaxID):
|
|
120
|
+
|
|
121
|
+
* Human (`9606`)
|
|
122
|
+
* Mouse (`10090`)
|
|
123
|
+
* Rat (`10116`)
|
|
124
|
+
* Fly (`7227`)
|
|
125
|
+
* Cache location defaults to `~/.bsvae/ppi` (override via `--ppi-cache`).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Citation
|
|
130
|
+
|
|
131
|
+
If you use **BSVAE** in your research, please cite:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
@article{Benjamin2025bsvae,
|
|
135
|
+
title={Structured Factor Variational Autoencoder with Biological Priors},
|
|
136
|
+
author={Kynon J. M. Benjamin},
|
|
137
|
+
year={2025},
|
|
138
|
+
journal={N/A}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "bsvae"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = ""
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = ["Kynon J Benjamin <kj.benjamin90@gmail.com>"]
|
|
7
|
+
packages = [{ include = "bsvae", from = "src" }]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = ">=3.11,<3.14"
|
|
11
|
+
torch = ">=2.8.0,<3.0.0"
|
|
12
|
+
numpy = "<2.3"
|
|
13
|
+
pandas = ">=2.3.2,<3.0.0"
|
|
14
|
+
scikit-learn = ">=1.7.2,<2.0.0"
|
|
15
|
+
tqdm = ">=4.67.1,<5.0.0"
|
|
16
|
+
mygene = ">=3.2.2,<4.0.0"
|
|
17
|
+
networkx = ">=3.5,<4.0"
|
|
18
|
+
scipy = ">=1.16.2,<2.0.0"
|
|
19
|
+
|
|
20
|
+
[tool.poetry.scripts]
|
|
21
|
+
bsvae-train = "bsvae.main:cli"
|
|
22
|
+
|
|
23
|
+
[tool.poetry.group.dev.dependencies]
|
|
24
|
+
pytest = ">=8.4.2,<9.0.0"
|
|
25
|
+
pytest-cov = ">=7.0.0,<8.0.0"
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
pythonpath = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.poetry-dynamic-versioning]
|
|
31
|
+
enable = false
|
|
32
|
+
style = "pep440"
|
|
33
|
+
dirty = false
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = [
|
|
37
|
+
"poetry-core>=2.0.0,<3.0.0",
|
|
38
|
+
"poetry-dynamic-versioning[plugin]"
|
|
39
|
+
]
|
|
40
|
+
build-backend = "poetry_dynamic_versioning.backend"
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[Custom]
|
|
2
|
+
# General options
|
|
3
|
+
log_level = "info"
|
|
4
|
+
no_progress_bar = False
|
|
5
|
+
no_cuda = False
|
|
6
|
+
seed = 1234
|
|
7
|
+
|
|
8
|
+
# Training options
|
|
9
|
+
epochs = 100
|
|
10
|
+
batch_size = 64
|
|
11
|
+
lr = 5e-4
|
|
12
|
+
checkpoint_every = 30
|
|
13
|
+
dataset = "genenet"
|
|
14
|
+
experiment = "structured"
|
|
15
|
+
|
|
16
|
+
# Model options
|
|
17
|
+
model = "StructuredFactorVAE"
|
|
18
|
+
latent_dim = 10
|
|
19
|
+
hidden_dims = [512, 256] # encoder hidden layers
|
|
20
|
+
dropout = 0.1
|
|
21
|
+
init_sd = 0.02
|
|
22
|
+
learn_var = False # decoder per-gene variance
|
|
23
|
+
|
|
24
|
+
# Loss options
|
|
25
|
+
loss = "beta"
|
|
26
|
+
beta = 1.0 # KL weight
|
|
27
|
+
l1_strength = 1e-3 # sparsity regularizer
|
|
28
|
+
lap_strength = 1e-4 # Laplacian smoothness
|
|
29
|
+
|
|
30
|
+
# Evaluations
|
|
31
|
+
is_metrics = True
|
|
32
|
+
no_test = False
|
|
33
|
+
is_eval_only = False
|
|
34
|
+
eval_batchsize = 512
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ### DATASET COMMON ###
|
|
38
|
+
[Common_genenet]
|
|
39
|
+
dataset = "genenet"
|
|
40
|
+
checkpoint_every = 100
|
|
41
|
+
epochs = 400
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ### LOSS COMMON ###
|
|
45
|
+
[Common_VAE]
|
|
46
|
+
loss = "VAE"
|
|
47
|
+
lr = 5e-4
|
|
48
|
+
[Common_beta]
|
|
49
|
+
loss = "beta"
|
|
50
|
+
beta = 4.0
|
|
51
|
+
lr = 5e-4
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ### EXPERIMENT SPECIFIC ###
|
|
55
|
+
|
|
56
|
+
[VAE_genenet]
|
|
57
|
+
dataset = "genenet"
|
|
58
|
+
model = "StructuredFactorVAE"
|
|
59
|
+
loss = "VAE"
|
|
60
|
+
latent_dim = 8
|
|
61
|
+
epochs = 400
|
|
62
|
+
checkpoint_every = 50
|
|
63
|
+
lr = 1e-4
|
|
64
|
+
l1_strength = 1e-3
|
|
65
|
+
lap_strength = 1e-4
|
|
66
|
+
|
|
67
|
+
[beta_genenet]
|
|
68
|
+
dataset = "genenet"
|
|
69
|
+
model = "StructuredFactorVAE"
|
|
70
|
+
loss = "beta"
|
|
71
|
+
beta = 4.0
|
|
72
|
+
latent_dim = 8
|
|
73
|
+
epochs = 400
|
|
74
|
+
checkpoint_every = 50
|
|
75
|
+
lr = 1e-4
|
|
76
|
+
l1_strength = 5e-4
|
|
77
|
+
lap_strength = 5e-5
|
|
78
|
+
|
|
79
|
+
[debug]
|
|
80
|
+
epochs = 1
|
|
81
|
+
log_level = "debug"
|
|
82
|
+
no_test = True
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
import argparse
|
|
6
|
+
from os.path import join, dirname
|
|
7
|
+
from configparser import ConfigParser
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
from torch import optim
|
|
11
|
+
|
|
12
|
+
from bsvae.models import StructuredFactorVAE
|
|
13
|
+
from bsvae.models.losses import BaseLoss
|
|
14
|
+
from bsvae.utils.datasets import get_dataloaders
|
|
15
|
+
from bsvae.utils.helpers import (
|
|
16
|
+
set_seed,
|
|
17
|
+
get_device,
|
|
18
|
+
get_n_params,
|
|
19
|
+
create_safe_directory,
|
|
20
|
+
FormatterNoDuplicate,
|
|
21
|
+
get_config_section,
|
|
22
|
+
update_namespace_,
|
|
23
|
+
)
|
|
24
|
+
from bsvae.utils import Trainer, Evaluator
|
|
25
|
+
from bsvae.utils.ppi import load_ppi_laplacian
|
|
26
|
+
from bsvae.utils.modelIO import save_model, load_model, load_metadata
|
|
27
|
+
|
|
28
|
+
RES_DIR = "results"
|
|
29
|
+
|
|
30
|
+
def load_config(config_path: str, section: str = "Custom") -> dict:
|
|
31
|
+
"""Load hyperparameters from .ini file."""
|
|
32
|
+
parser = ConfigParser()
|
|
33
|
+
parser.read(config_path)
|
|
34
|
+
if section not in parser:
|
|
35
|
+
raise ValueError(f"Config section [{section}] not found in {config_path}")
|
|
36
|
+
return {k: ast_literal_eval(v) for k, v in parser[section].items()}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ast_literal_eval(val):
|
|
40
|
+
"""Helper to parse lists, ints, floats from strings safely."""
|
|
41
|
+
import ast
|
|
42
|
+
try:
|
|
43
|
+
return ast.literal_eval(val)
|
|
44
|
+
except Exception:
|
|
45
|
+
return val
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_arguments(cli_args):
|
|
49
|
+
"""Parse CLI args, with defaults pulled from hyperparam.ini."""
|
|
50
|
+
pre_parser = argparse.ArgumentParser(add_help=False)
|
|
51
|
+
pre_parser.add_argument("--config", "-c", type=str,
|
|
52
|
+
default=join(dirname(__file__), "hyperparam.ini"),
|
|
53
|
+
help="Path to hyperparam.ini")
|
|
54
|
+
pre_parser.add_argument("--section", type=str, default="Custom",
|
|
55
|
+
help="Section of .ini to load")
|
|
56
|
+
config_args, _ = pre_parser.parse_known_args(cli_args)
|
|
57
|
+
|
|
58
|
+
# Load defaults from config
|
|
59
|
+
config = load_config(config_args.config, config_args.section)
|
|
60
|
+
|
|
61
|
+
parser = argparse.ArgumentParser(
|
|
62
|
+
description="Training and evaluation for StructuredFactorVAE.",
|
|
63
|
+
formatter_class=FormatterNoDuplicate,
|
|
64
|
+
parents=[pre_parser],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# General
|
|
68
|
+
parser.add_argument("name", type=str, help="Experiment name.")
|
|
69
|
+
parser.add_argument("--seed", type=int, default=config.get("seed", 13))
|
|
70
|
+
parser.add_argument("--no-cuda", action="store_true",
|
|
71
|
+
default=config.get("no_cuda", False))
|
|
72
|
+
|
|
73
|
+
# Training
|
|
74
|
+
parser.add_argument("--epochs", type=int, default=config.get("epochs", 100))
|
|
75
|
+
parser.add_argument("--batch-size", type=int,
|
|
76
|
+
default=config.get("batch_size", 64))
|
|
77
|
+
parser.add_argument("--lr", type=float, default=config.get("lr", 5e-4))
|
|
78
|
+
parser.add_argument("--checkpoint-every", type=int,
|
|
79
|
+
default=config.get("checkpoint_every", 10))
|
|
80
|
+
|
|
81
|
+
# Model
|
|
82
|
+
parser.add_argument("--latent-dim", "-z", type=int,
|
|
83
|
+
default=config.get("latent_dim", 10))
|
|
84
|
+
parser.add_argument("--hidden-dims", "-Z", type=ast_literal_eval,
|
|
85
|
+
default=config.get("hidden_dims", [128, 64]))
|
|
86
|
+
parser.add_argument("--dropout", type=float,
|
|
87
|
+
default=config.get("dropout", 0.1))
|
|
88
|
+
parser.add_argument("--learn-var", action="store_true",
|
|
89
|
+
default=config.get("learn_var", False))
|
|
90
|
+
parser.add_argument("--init-sd", type=float,
|
|
91
|
+
default=config.get("init_sd", 0.02))
|
|
92
|
+
|
|
93
|
+
# Loss
|
|
94
|
+
parser.add_argument("--loss", type=str, default=config.get("loss", "VAE"),
|
|
95
|
+
choices=["VAE", "beta"])
|
|
96
|
+
parser.add_argument("--beta", type=float, default=config.get("beta", 1.0))
|
|
97
|
+
parser.add_argument("--l1-strength", type=float,
|
|
98
|
+
default=config.get("l1_strength", 1e-3))
|
|
99
|
+
parser.add_argument("--lap-strength", type=float,
|
|
100
|
+
default=config.get("lap_strength", 1e-4))
|
|
101
|
+
|
|
102
|
+
# Evaluation
|
|
103
|
+
parser.add_argument("--is-eval-only", action="store_true",
|
|
104
|
+
default=config.get("is_eval_only"))
|
|
105
|
+
parser.add_argument("--no-test", action="store_true",
|
|
106
|
+
default=config.get("no_test"))
|
|
107
|
+
parser.add_argument("--eval-batchsize", type=int,
|
|
108
|
+
default=config.get("eval_batchsize"))
|
|
109
|
+
|
|
110
|
+
# Dataset
|
|
111
|
+
parser.add_argument("--dataset", type=str,
|
|
112
|
+
default=config.get("dataset", "genenet"))
|
|
113
|
+
parser.add_argument("--gene-expression-filename", type=str,
|
|
114
|
+
default=config.get("gene_expression_filename", None),
|
|
115
|
+
help="CSV with gene expression (genes x samples).")
|
|
116
|
+
parser.add_argument("--gene-expression-dir", type=str,
|
|
117
|
+
default=config.get("gene_expression_dir", None),
|
|
118
|
+
help="Directory with train/test splits (X_train.csv, X_test.csv).")
|
|
119
|
+
|
|
120
|
+
# PPI Priors
|
|
121
|
+
parser.add_argument("--ppi-taxid", type=str, default=config.get("ppi_taxid", "9606"))
|
|
122
|
+
parser.add_argument("--ppi-cache", type=str, default=config.get("ppi_cache", None))
|
|
123
|
+
|
|
124
|
+
args = parser.parse_args(cli_args)
|
|
125
|
+
|
|
126
|
+
# Validate dataset input
|
|
127
|
+
if bool(args.gene_expression_filename) == bool(args.gene_expression_dir):
|
|
128
|
+
parser.error("Specify exactly one of --gene-expression-filename or --gene-expression-dir.")
|
|
129
|
+
|
|
130
|
+
parser.set_defaults(**vars(args))
|
|
131
|
+
return parser.parse_args(cli_args)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def setup_logging():
|
|
135
|
+
log_fmt = "%(asctime)s %(levelname)s: %(message)s"
|
|
136
|
+
logging.basicConfig(level=logging.INFO, format=log_fmt)
|
|
137
|
+
return logging.getLogger(__name__)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main(args):
|
|
141
|
+
logger = setup_logging()
|
|
142
|
+
set_seed(args.seed)
|
|
143
|
+
device = get_device(use_gpu=not args.no_cuda)
|
|
144
|
+
exp_dir = join(RES_DIR, args.name)
|
|
145
|
+
logger.info(f"Experiment directory: {exp_dir}")
|
|
146
|
+
|
|
147
|
+
# Training
|
|
148
|
+
if not args.is_eval_only:
|
|
149
|
+
create_safe_directory(exp_dir, logger=logger)
|
|
150
|
+
|
|
151
|
+
# Data
|
|
152
|
+
train_loader = get_dataloaders(
|
|
153
|
+
dataset=args.dataset,
|
|
154
|
+
batch_size=args.batch_size,
|
|
155
|
+
logger=logger,
|
|
156
|
+
train=True,
|
|
157
|
+
drop_last=True,
|
|
158
|
+
gene_expression_filename=args.gene_expression_filename,
|
|
159
|
+
gene_expression_dir=args.gene_expression_dir,
|
|
160
|
+
)
|
|
161
|
+
n_genes = train_loader.dataset[0][0].shape[-1]
|
|
162
|
+
logger.info(f"Training dataset size: {len(train_loader.dataset)}")
|
|
163
|
+
|
|
164
|
+
# PPI Laplacian
|
|
165
|
+
try:
|
|
166
|
+
gene_list = getattr(train_loader.dataset, "genes", None)
|
|
167
|
+
if gene_list is not None:
|
|
168
|
+
L, G = load_ppi_laplacian(
|
|
169
|
+
gene_list,
|
|
170
|
+
taxid=args.ppi_taxid,
|
|
171
|
+
min_score=700,
|
|
172
|
+
cache_dir=args.ppi_cache or "~/.bsvae/ppi",
|
|
173
|
+
)
|
|
174
|
+
logger.info(f"PPI Graph loaded: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
|
|
175
|
+
else:
|
|
176
|
+
L = None
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.warning(f"Could not load PPI Laplacian: {e}")
|
|
179
|
+
L = None
|
|
180
|
+
|
|
181
|
+
# Model
|
|
182
|
+
model = StructuredFactorVAE(
|
|
183
|
+
n_genes=n_genes,
|
|
184
|
+
n_latent=args.latent_dim,
|
|
185
|
+
hidden_dims=args.hidden_dims,
|
|
186
|
+
dropout=args.dropout,
|
|
187
|
+
init_sd=args.init_sd,
|
|
188
|
+
learn_var=args.learn_var,
|
|
189
|
+
L=L
|
|
190
|
+
).to(device)
|
|
191
|
+
logger.info(f"Model initialized with {n_genes} genes, {args.latent_dim} latent dims")
|
|
192
|
+
|
|
193
|
+
# Loss + optimizer
|
|
194
|
+
optimizer = optim.Adam(model.parameters(), lr=args.lr)
|
|
195
|
+
loss_f = BaseLoss(beta=args.beta,
|
|
196
|
+
l1_strength=args.l1_strength,
|
|
197
|
+
lap_strength=args.lap_strength)
|
|
198
|
+
|
|
199
|
+
trainer = Trainer(
|
|
200
|
+
model, optimizer, loss_f, device=device, logger=logger,
|
|
201
|
+
save_dir=exp_dir, is_progress_bar=not args.no_cuda,
|
|
202
|
+
)
|
|
203
|
+
trainer(train_loader, epochs=int(args.epochs),
|
|
204
|
+
checkpoint_every=int(args.checkpoint_every))
|
|
205
|
+
save_model(trainer.model, exp_dir, metadata=vars(args))
|
|
206
|
+
|
|
207
|
+
# Evaluation
|
|
208
|
+
if not args.no_test:
|
|
209
|
+
model = load_model(exp_dir, is_gpu=not args.no_cuda)
|
|
210
|
+
metadata = load_metadata(exp_dir)
|
|
211
|
+
test_loader = get_dataloaders(
|
|
212
|
+
dataset="genenet",
|
|
213
|
+
batch_size=args.eval_batchsize,
|
|
214
|
+
shuffle=False,
|
|
215
|
+
logger=logger,
|
|
216
|
+
train=False,
|
|
217
|
+
drop_last=True,
|
|
218
|
+
gene_expression_filename=args.gene_expression_filename,
|
|
219
|
+
gene_expression_dir=args.gene_expression_dir,
|
|
220
|
+
)
|
|
221
|
+
loss_f = BaseLoss(beta=args.beta,
|
|
222
|
+
l1_strength=args.l1_strength,
|
|
223
|
+
lap_strength=args.lap_strength)
|
|
224
|
+
evaluator = Evaluator(
|
|
225
|
+
model, loss_f, device=device, logger=logger, save_dir=exp_dir,
|
|
226
|
+
is_progress_bar=not args.no_cuda,
|
|
227
|
+
)
|
|
228
|
+
evaluator(test_loader)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def cli():
|
|
232
|
+
args = parse_arguments(sys.argv[1:])
|
|
233
|
+
main(args)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
cli()
|