embkit 0.4__tar.gz → 0.5__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.
- embkit-0.5/PKG-INFO +89 -0
- embkit-0.5/README.md +67 -0
- {embkit-0.4 → embkit-0.5}/pyproject.toml +2 -2
- {embkit-0.4 → embkit-0.5}/setup.py +1 -1
- {embkit-0.4 → embkit-0.5}/src/embkit/align.py +44 -16
- embkit-0.5/src/embkit/c_bio/api.py +31 -0
- embkit-0.5/src/embkit/commands/matrix.py +97 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/model.py +50 -17
- {embkit-0.4 → embkit-0.5}/src/embkit/constraints/pathway_constraint.py +2 -1
- embkit-0.5/src/embkit/datasets/__init__.py +172 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/encoding/__init__.py +140 -14
- {embkit-0.4 → embkit-0.5}/src/embkit/encoding/genome.py +1 -1
- {embkit-0.4 → embkit-0.5}/src/embkit/estimator/vae_estimator.py +10 -7
- {embkit-0.4 → embkit-0.5}/src/embkit/factory/__init__.py +4 -4
- embkit-0.5/src/embkit/factory/core.py +153 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/factory/layers.py +85 -25
- {embkit-0.4 → embkit-0.5}/src/embkit/factory/mapping.py +58 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/factory/registry.py +31 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/files/h5.py +69 -6
- embkit-0.5/src/embkit/files/json.py +34 -0
- embkit-0.5/src/embkit/losses/__init__.py +23 -0
- embkit-0.5/src/embkit/losses/base.py +74 -0
- embkit-0.5/src/embkit/losses/vae_loss.py +242 -0
- embkit-0.5/src/embkit/models/pair.py +70 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/models/vae/__init__.py +3 -4
- embkit-0.5/src/embkit/models/vae/decoder.py +115 -0
- embkit-0.5/src/embkit/models/vae/encoder.py +384 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/models/vae/net_vae.py +146 -16
- {embkit-0.4 → embkit-0.5}/src/embkit/models/vae/rna_vae.py +73 -143
- embkit-0.5/src/embkit/models/vae/vae.py +400 -0
- embkit-0.5/src/embkit/modules/mha.py +135 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/optimize/__init__.py +43 -12
- embkit-0.5/src/embkit/optimize/multitask.py +276 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/pathway.py +3 -2
- {embkit-0.4 → embkit-0.5}/src/embkit/preprocessing/normalize.py +36 -1
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/c_bio_portal.py +9 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/gtex.py +21 -5
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/resource.py +38 -4
- embkit-0.5/src/embkit.egg-info/PKG-INFO +89 -0
- {embkit-0.4 → embkit-0.5}/src/embkit.egg-info/SOURCES.txt +4 -1
- embkit-0.4/PKG-INFO +0 -71
- embkit-0.4/README.md +0 -49
- embkit-0.4/src/embkit/c_bio/api.py +0 -16
- embkit-0.4/src/embkit/commands/matrix.py +0 -54
- embkit-0.4/src/embkit/datasets/__init__.py +0 -51
- embkit-0.4/src/embkit/factory/core.py +0 -100
- embkit-0.4/src/embkit/files/json.py +0 -20
- embkit-0.4/src/embkit/losses/__init__.py +0 -1
- embkit-0.4/src/embkit/losses/vae_loss.py +0 -114
- embkit-0.4/src/embkit/models/vae/base_vae.py +0 -240
- embkit-0.4/src/embkit/models/vae/decoder.py +0 -97
- embkit-0.4/src/embkit/models/vae/encoder.py +0 -180
- embkit-0.4/src/embkit/models/vae/vae.py +0 -111
- embkit-0.4/src/embkit.egg-info/PKG-INFO +0 -71
- {embkit-0.4 → embkit-0.5}/setup.cfg +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/__main__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/bmeg.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/c_bio/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/align.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/cbio.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/protein.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/commands/resources.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/constraints/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/encoding/protein.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/estimator/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/files/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/files/loaders.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/files/read_csv.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/metrics/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/metrics/distance/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/metrics/distance/metrics.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/models/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/models/ffnn.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/modules/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/modules/masked_linear.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/modules/pairwise_comparison_layer.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/modules/tsp.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/preprocessing/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/preprocessing/dataset.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/hugo.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/sif.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/resources/util.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/utilities/__init__.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/utilities/kmeans.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/utilities/pca.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit/version.py +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit.egg-info/dependency_links.txt +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit.egg-info/entry_points.txt +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit.egg-info/requires.txt +0 -0
- {embkit-0.4 → embkit-0.5}/src/embkit.egg-info/top_level.txt +0 -0
- {embkit-0.4 → embkit-0.5}/tests/test_main.py +0 -0
embkit-0.5/PKG-INFO
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: embkit
|
|
3
|
+
Version: 0.5
|
|
4
|
+
Summary: Embedding Kit for embedding models
|
|
5
|
+
Author-email: Kyle Ellrott <ellrott@ohsu.edu>, Raphael Kirchgaessner <kirchgae@ohsu.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: embeddings,machine learning,deep learning
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: torch>=2.0
|
|
11
|
+
Requires-Dist: pandas
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: click
|
|
14
|
+
Requires-Dist: scikit-learn
|
|
15
|
+
Requires-Dist: scipy
|
|
16
|
+
Requires-Dist: gripql
|
|
17
|
+
Requires-Dist: tqdm
|
|
18
|
+
Requires-Dist: biopython
|
|
19
|
+
Requires-Dist: fair-esm
|
|
20
|
+
Requires-Dist: faiss-cpu
|
|
21
|
+
Requires-Dist: h5py
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Embedding Kit
|
|
25
|
+
|
|
26
|
+
Methods for data normalization, embedding, synthesis and transformation in computational biology workflows.
|
|
27
|
+
|
|
28
|
+
Embedding Kit (embkit) is a toolkit for building and applying embedding models. It combines:
|
|
29
|
+
|
|
30
|
+
- CLI commands for repeatable model training and encoding
|
|
31
|
+
- PyTorch VAE model components for custom pipelines
|
|
32
|
+
- Utilities for loading, normalizing, and aligning large molecular datasets
|
|
33
|
+
- ESM2-based protein sequence embeddings
|
|
34
|
+
|
|
35
|
+
Use Embedding Kit when you want to move from tabular molecular data (RNA-seq, proteomics, methylation) to trainable latent representations that can be reused for downstream analysis.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **VAE & NetVAE training** — Train variational autoencoders, including pathway-constrained NetVAE models, from tabular or HDF5 matrices.
|
|
40
|
+
- **Beta-KL scheduling** — Schedule the KL-regularization weight across training epochs for stable convergence.
|
|
41
|
+
- **Protein embeddings** — Generate sequence embeddings from FASTA files using ESM2 models.
|
|
42
|
+
- **Normalization utilities** — Min-max and exponential min-max normalization for expression matrices.
|
|
43
|
+
- **Device auto-detection** — Runs on CPU, CUDA, or Apple Metal GPUs.
|
|
44
|
+
- **Python API** — Composable layers, losses, and model factories for custom pipelines beyond the CLI.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install embkit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Training in Python
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from embkit import dataframe_loader
|
|
57
|
+
from embkit.models.vae import VAE
|
|
58
|
+
from embkit.factory.layers import Layer
|
|
59
|
+
from embkit.losses import BCEWithLogitsVAELoss
|
|
60
|
+
from embkit.factory import save, load
|
|
61
|
+
from embkit import optimize
|
|
62
|
+
|
|
63
|
+
loader = dataframe_loader(df_norm, batch_size=256)
|
|
64
|
+
|
|
65
|
+
vae = VAE(
|
|
66
|
+
features=list(df_norm.columns),
|
|
67
|
+
latent_dim=128,
|
|
68
|
+
encoder_layers=[Layer(512, activation="relu"), Layer(256, activation="relu")],
|
|
69
|
+
decoder_layers=[Layer(512, activation="relu")],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
optimize.fit_vae(vae, X=loader, epochs=60, lr=1e-3, loss=BCEWithLogitsVAELoss())
|
|
73
|
+
|
|
74
|
+
save(vae, "vae.model")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
Full documentation, including core concepts, the training guide, the CLI reference, and API docs, is available in the [docs folder](docs/). To build it locally, see [DEV.md](DEV.md).
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
|
84
|
+
|
|
85
|
+
## Authors
|
|
86
|
+
|
|
87
|
+
- Kyle Ellrott — ellrott@ohsu.edu
|
|
88
|
+
- Raphael Kirchgaessner — kirchgae@ohsu.edu
|
|
89
|
+
|
embkit-0.5/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
# Embedding Kit
|
|
3
|
+
|
|
4
|
+
Methods for data normalization, embedding, synthesis and transformation in computational biology workflows.
|
|
5
|
+
|
|
6
|
+
Embedding Kit (embkit) is a toolkit for building and applying embedding models. It combines:
|
|
7
|
+
|
|
8
|
+
- CLI commands for repeatable model training and encoding
|
|
9
|
+
- PyTorch VAE model components for custom pipelines
|
|
10
|
+
- Utilities for loading, normalizing, and aligning large molecular datasets
|
|
11
|
+
- ESM2-based protein sequence embeddings
|
|
12
|
+
|
|
13
|
+
Use Embedding Kit when you want to move from tabular molecular data (RNA-seq, proteomics, methylation) to trainable latent representations that can be reused for downstream analysis.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **VAE & NetVAE training** — Train variational autoencoders, including pathway-constrained NetVAE models, from tabular or HDF5 matrices.
|
|
18
|
+
- **Beta-KL scheduling** — Schedule the KL-regularization weight across training epochs for stable convergence.
|
|
19
|
+
- **Protein embeddings** — Generate sequence embeddings from FASTA files using ESM2 models.
|
|
20
|
+
- **Normalization utilities** — Min-max and exponential min-max normalization for expression matrices.
|
|
21
|
+
- **Device auto-detection** — Runs on CPU, CUDA, or Apple Metal GPUs.
|
|
22
|
+
- **Python API** — Composable layers, losses, and model factories for custom pipelines beyond the CLI.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install embkit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Training in Python
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from embkit import dataframe_loader
|
|
35
|
+
from embkit.models.vae import VAE
|
|
36
|
+
from embkit.factory.layers import Layer
|
|
37
|
+
from embkit.losses import BCEWithLogitsVAELoss
|
|
38
|
+
from embkit.factory import save, load
|
|
39
|
+
from embkit import optimize
|
|
40
|
+
|
|
41
|
+
loader = dataframe_loader(df_norm, batch_size=256)
|
|
42
|
+
|
|
43
|
+
vae = VAE(
|
|
44
|
+
features=list(df_norm.columns),
|
|
45
|
+
latent_dim=128,
|
|
46
|
+
encoder_layers=[Layer(512, activation="relu"), Layer(256, activation="relu")],
|
|
47
|
+
decoder_layers=[Layer(512, activation="relu")],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
optimize.fit_vae(vae, X=loader, epochs=60, lr=1e-3, loss=BCEWithLogitsVAELoss())
|
|
51
|
+
|
|
52
|
+
save(vae, "vae.model")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
Full documentation, including core concepts, the training guide, the CLI reference, and API docs, is available in the [docs folder](docs/). To build it locally, see [DEV.md](DEV.md).
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
62
|
+
|
|
63
|
+
## Authors
|
|
64
|
+
|
|
65
|
+
- Kyle Ellrott — ellrott@ohsu.edu
|
|
66
|
+
- Raphael Kirchgaessner — kirchgae@ohsu.edu
|
|
67
|
+
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "embkit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5"
|
|
8
8
|
description = "Embedding Kit for embedding models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
|
-
license =
|
|
11
|
+
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Kyle Ellrott", email = "ellrott@ohsu.edu" },
|
|
14
14
|
{ name = "Raphael Kirchgaessner", email = "kirchgae@ohsu.edu" }
|
|
@@ -58,8 +58,7 @@ def matrix_spearman_alignment_linear(a, b, cutoff=0.0):
|
|
|
58
58
|
|
|
59
59
|
def procrustes(X, Y):
|
|
60
60
|
"""
|
|
61
|
-
Computes the Procrustes transformation (optimal PURE rotation)
|
|
62
|
-
to align X to Y, correcting for reflections.
|
|
61
|
+
Computes the Procrustes transformation (optimal PURE rotation) to align X to Y, correcting for reflections.
|
|
63
62
|
|
|
64
63
|
Args:
|
|
65
64
|
X: The first matrix (N_points, N_dims).
|
|
@@ -67,6 +66,7 @@ def procrustes(X, Y):
|
|
|
67
66
|
|
|
68
67
|
Returns:
|
|
69
68
|
R: The optimal rotation matrix (guaranteed det(R) = +1).
|
|
69
|
+
S: Singular values of X.T @ Y
|
|
70
70
|
"""
|
|
71
71
|
assert X.shape == Y.shape
|
|
72
72
|
|
|
@@ -74,37 +74,65 @@ def procrustes(X, Y):
|
|
|
74
74
|
|
|
75
75
|
U, S, Vt = np.linalg.svd(M)
|
|
76
76
|
|
|
77
|
-
R = np.dot(U, Vt)
|
|
77
|
+
R = np.dot(U, Vt) # R can be optimized indep. of scalar
|
|
78
78
|
|
|
79
79
|
if np.linalg.det(R) < 0:
|
|
80
80
|
Vt_corrected = Vt.copy()
|
|
81
81
|
Vt_corrected[-1, :] *= -1
|
|
82
82
|
R = np.dot(U, Vt_corrected)
|
|
83
83
|
|
|
84
|
-
return R
|
|
85
|
-
|
|
84
|
+
return R,S
|
|
86
85
|
|
|
87
86
|
def procrustes_scale(X, Y):
|
|
88
87
|
"""
|
|
89
|
-
Compute the procrustes transformation (R), then
|
|
88
|
+
Compute the procrustes transformation (R), then a single (scalar) scale factor k = ||Y||_F / ||X||_F (equivalently ||Y||_F / ||X @ R||_F, since rotation preserves Frobenius norm).
|
|
89
|
+
Note: Scaling is calculated without Least-Squares, unlike more traditional approaches, to match Y's spread directly and avoid shrinking under noisy correspondances.
|
|
90
90
|
|
|
91
|
-
To apply
|
|
91
|
+
To apply transformation:
|
|
92
92
|
src.dot(R) * k
|
|
93
93
|
|
|
94
|
-
|
|
95
94
|
Args:
|
|
96
95
|
X: The first matrix (N_points, N_dims).
|
|
97
96
|
Y: The second matrix (N_points, N_dims).
|
|
98
97
|
|
|
99
98
|
Returns:
|
|
100
99
|
R: The optimal rotation matrix (guaranteed det(R) = +1).
|
|
101
|
-
k:
|
|
100
|
+
k: Scalar scaling factor, ||Y||_F / ||X||_F
|
|
101
|
+
S: Singular values of X.T @ Y
|
|
102
102
|
|
|
103
103
|
"""
|
|
104
|
-
R = procrustes(X,Y)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
R, S = procrustes(X, Y)
|
|
105
|
+
Xnorm = np.linalg.norm(X)
|
|
106
|
+
k = np.linalg.norm(Y) / Xnorm if Xnorm != 0 else 0.0 # Unlike Least Squares (superceded), we dont bias towards shrinking under noisy correspondence.
|
|
107
|
+
return R, k, S
|
|
108
|
+
|
|
109
|
+
def procrustes_scale_centered(X, Y):
|
|
110
|
+
"""
|
|
111
|
+
Mean-centers X and Y, fits the rotation, then scales: k = ||Yc||_F / ||Xc||_F (equivalently ||Yc||_F / ||Xc @ R||_F, since rotation preserves Frobenius norm).
|
|
112
|
+
Note: Scaling is calculated without Least-Squares, unlike more traditional approaches, to match Y's spread directly and avoid shrinking under noisy correspondances.
|
|
113
|
+
|
|
114
|
+
To apply transformation:
|
|
115
|
+
(src - Xmean).dot(R) * k + Ymean
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
X: The first matrix (N_points, N_dims).
|
|
119
|
+
Y: The second matrix (N_points, N_dims).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
R: The optimal rotation matrix (guaranteed det(R) = +1).
|
|
123
|
+
k: Scalar scaling factor, ||Yc||_F / ||Xc||_F
|
|
124
|
+
Xmean: Per-dimension mean of X (shape: (N_dims,)), subtract before applying R/k
|
|
125
|
+
Ymean: Per-dimension mean of Y (shape: (N_dims,)), add back after applying R/k
|
|
126
|
+
S: Singular values of (X - Xmean).T @ (Y - Ymean)
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
Xmean = np.array(X).mean(axis=0)
|
|
130
|
+
Ymean = np.array(Y).mean(axis=0)
|
|
131
|
+
Xc = np.array(X) - Xmean
|
|
132
|
+
Yc = np.array(Y) - Ymean
|
|
133
|
+
R, k, S = procrustes_scale(Xc, Yc)
|
|
134
|
+
return R, k, Xmean, Ymean, S
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CBIOAPI:
|
|
8
|
+
"""
|
|
9
|
+
Small client for the cBioPortal public API.
|
|
10
|
+
|
|
11
|
+
cBioPortal is a public catalog of cancer genomics studies. This helper
|
|
12
|
+
wraps the lightweight metadata endpoints (like listing available studies)
|
|
13
|
+
that don't require downloading study data — use the
|
|
14
|
+
:class:`~embkit.resources.c_bio_portal.CBIOPortal` resource for fetching an
|
|
15
|
+
actual study's molecular profile data.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def list_studies():
|
|
20
|
+
"""
|
|
21
|
+
List the cBioPortal studies available for download.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
The API's JSON list of studies, or ``None`` if the request failed.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
studies = requests.get("https://www.cbioportal.org/api/studies").json()
|
|
28
|
+
return studies
|
|
29
|
+
except requests.RequestException as e:
|
|
30
|
+
logger.error("Error fetching studies: %s", e)
|
|
31
|
+
return None
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
embkit matrix — preprocessing of expression/count matrices.
|
|
3
|
+
|
|
4
|
+
The ``matrix`` command group prepares the tab-separated matrices that become the
|
|
5
|
+
input to an embedding model. Its subcommands implement the "normalize" and "pca"
|
|
6
|
+
major operations: rescaling raw feature values to a comparable ``[0, 1]`` range,
|
|
7
|
+
and projecting a matrix onto its leading principal components. These are the
|
|
8
|
+
data-preparation steps you run before (and independently of) training.
|
|
9
|
+
"""
|
|
10
|
+
import click
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from ..utilities.pca import run_pca
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
matrix = click.Group(name="matrix", help="Model commands.")
|
|
17
|
+
|
|
18
|
+
@matrix.command()
|
|
19
|
+
@click.argument("srcs", nargs=-1)
|
|
20
|
+
@click.option("--out", "-o", required=True)
|
|
21
|
+
@click.option("--features", default=None, type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str))
|
|
22
|
+
@click.option("--col-quantile", "col_quantile", default=False, is_flag=True)
|
|
23
|
+
@click.option("--quantile-max", "quantile_max", default=0.9, type=float)
|
|
24
|
+
@click.option("--precision", "-p", type=int, default=5)
|
|
25
|
+
def normalize(srcs, out, features, col_quantile, quantile_max, precision):
|
|
26
|
+
"""
|
|
27
|
+
embkit matrix normalize — rescale one or more matrices for embedding training.
|
|
28
|
+
|
|
29
|
+
Reads tab-separated expression/count matrices, optionally subsets them to a
|
|
30
|
+
list of features, concatenates them, and writes a single normalized matrix in
|
|
31
|
+
which each sample's values fall in ``[0, 1]``. This is the preprocessing step
|
|
32
|
+
that puts the input on a comparable scale before a VAE / NetVAE is trained.
|
|
33
|
+
|
|
34
|
+
By default each *sample* (row) is divided by its own ``quantile_max``
|
|
35
|
+
quantile; passing ``--col-quantile`` divides each *feature* (column) by its
|
|
36
|
+
quantile instead.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
srcs: One or more input TSV matrices (index column required).
|
|
40
|
+
out: Path to write the normalized TSV.
|
|
41
|
+
features: Optional file listing feature names to keep.
|
|
42
|
+
col_quantile: Normalize per feature (column) rather than per sample.
|
|
43
|
+
quantile_max: The quantile used as the divisor (default 0.9).
|
|
44
|
+
precision: Number of decimals to round to in the output.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
features_list = None
|
|
48
|
+
if features is not None:
|
|
49
|
+
with open(features, encoding="ascii") as handle:
|
|
50
|
+
features_list = list(line.rstrip() for line in handle)
|
|
51
|
+
|
|
52
|
+
dfs = []
|
|
53
|
+
for i in srcs:
|
|
54
|
+
df = pd.read_csv(i, sep="\t", index_col=0)
|
|
55
|
+
if features_list is not None:
|
|
56
|
+
df = df[features_list]
|
|
57
|
+
dfs.append(df)
|
|
58
|
+
if len(dfs) == 0:
|
|
59
|
+
click.echo("No matrices defined")
|
|
60
|
+
return
|
|
61
|
+
df = pd.concat( dfs )
|
|
62
|
+
|
|
63
|
+
if not col_quantile:
|
|
64
|
+
normDF = (df.transpose() / df.quantile(quantile_max, axis=1)).transpose().clip(upper=1.0, lower=0.0).fillna(0.0)
|
|
65
|
+
else:
|
|
66
|
+
normDF = (df / df.quantile(quantile_max)).clip(upper=1.0, lower=0.0).fillna(0.0)
|
|
67
|
+
normDF.round(decimals=precision).to_csv(out, sep="\t")
|
|
68
|
+
|
|
69
|
+
@matrix.command()
|
|
70
|
+
@click.argument("input_path", type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str))
|
|
71
|
+
@click.option("--pca-size", required=True, type=int, help="Number of principal components.")
|
|
72
|
+
@click.option("--out", "-o", default=None, type=str, help="Output TSV path.")
|
|
73
|
+
def pca(input_path, pca_size, out):
|
|
74
|
+
"""
|
|
75
|
+
embkit matrix pca — project a matrix onto its leading principal components.
|
|
76
|
+
|
|
77
|
+
Runs principal component analysis on a tab-separated matrix and writes the
|
|
78
|
+
first ``pca_size`` components to a TSV. Useful for reducing dimensionality
|
|
79
|
+
before downstream analysis or visualization (e.g. inspecting whether a
|
|
80
|
+
normalized matrix separates samples as expected).
|
|
81
|
+
|
|
82
|
+
If no ``--out`` is given, the output defaults to ``<input_stem>.pca.tsv``.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
input_path: Input TSV matrix.
|
|
86
|
+
pca_size: Number of principal components to retain (must be > 0).
|
|
87
|
+
out: Output TSV path (optional; defaults to ``<input_stem>.pca.tsv``).
|
|
88
|
+
"""
|
|
89
|
+
if pca_size <= 0:
|
|
90
|
+
raise click.BadParameter("--pca-size must be a positive integer.")
|
|
91
|
+
|
|
92
|
+
if out is None:
|
|
93
|
+
out = f"{Path(input_path).stem}.pca.tsv"
|
|
94
|
+
click.echo(f"No output path provided, using default naming: {out}")
|
|
95
|
+
|
|
96
|
+
run_pca(input_file=input_path, pca_size=pca_size, output_file=out)
|
|
97
|
+
click.echo(f"PCA saved to {out}")
|
|
@@ -13,11 +13,11 @@ from ..files import H5Reader
|
|
|
13
13
|
from ..factory import save, load
|
|
14
14
|
from ..factory.layers import Layer, LayerList
|
|
15
15
|
from ..optimize import fit_vae
|
|
16
|
-
from ..models.vae.vae import
|
|
16
|
+
from ..models.vae.vae import BaseVAE
|
|
17
17
|
from ..models.vae.net_vae import NetVAE
|
|
18
18
|
from ..preprocessing import ExpMinMaxScaler, get_dataset_nonzero_mask
|
|
19
19
|
from ..datasets import DatasetMask
|
|
20
|
-
from ..losses import
|
|
20
|
+
from ..losses import get_vae_loss
|
|
21
21
|
from ..pathway import extract_sif_interactions, feature_map_intersect, build_feature_map_indices
|
|
22
22
|
|
|
23
23
|
model = click.Group(name="model", help="VAE Model commands.")
|
|
@@ -37,6 +37,7 @@ model = click.Group(name="model", help="VAE Model commands.")
|
|
|
37
37
|
@click.option("--schedule", "-s", type=str, default=None, help="20:0,20:0.1,40:.3,40:.4")
|
|
38
38
|
@click.option("--loss", type=click.Choice(["mse", "bce", "bce-logit"]), default="bce-logit")
|
|
39
39
|
@click.option("--save-stats", is_flag=True)
|
|
40
|
+
@click.option("--save-latent", is_flag=True)
|
|
40
41
|
@click.option("--zero-mask", default=None, type=float)
|
|
41
42
|
@click.option("--seed", default=42, type=int)
|
|
42
43
|
@click.option("--bfloat16", is_flag=True)
|
|
@@ -54,6 +55,7 @@ def train_vae(input_path: str,
|
|
|
54
55
|
schedule:str,
|
|
55
56
|
zero_mask: float,
|
|
56
57
|
save_stats: bool,
|
|
58
|
+
save_latent:bool,
|
|
57
59
|
seed: int,
|
|
58
60
|
bfloat16: bool,
|
|
59
61
|
sampling: bool
|
|
@@ -100,12 +102,11 @@ def train_vae(input_path: str,
|
|
|
100
102
|
|
|
101
103
|
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
|
|
102
104
|
|
|
103
|
-
|
|
104
105
|
layer_sizes = list( int(i) for i in encode_layers.split(",") )
|
|
105
106
|
enc_layers_list = LayerList( layer_sizes )
|
|
106
107
|
|
|
107
108
|
layer_sizes = list( int(i) for i in decode_layers.split(",") )
|
|
108
|
-
dec_layers_list = LayerList( layer_sizes,
|
|
109
|
+
dec_layers_list = LayerList( layer_sizes, output_activation=final_activation )
|
|
109
110
|
|
|
110
111
|
beta_schedule = None
|
|
111
112
|
if schedule is not None:
|
|
@@ -113,18 +114,14 @@ def train_vae(input_path: str,
|
|
|
113
114
|
for b in schedule.split(","):
|
|
114
115
|
e, b = b.split(":")
|
|
115
116
|
beta_schedule.append( (float(b), int(e)) )
|
|
116
|
-
vae =
|
|
117
|
+
vae = BaseVAE(features=features,
|
|
117
118
|
latent_dim=latent,
|
|
118
119
|
encoder_layers=enc_layers_list,
|
|
119
120
|
decoder_layers=dec_layers_list,
|
|
120
121
|
sampling=sampling,
|
|
121
122
|
device=device, dtype=dtype)
|
|
122
123
|
|
|
123
|
-
loss_func =
|
|
124
|
-
if loss == "mse":
|
|
125
|
-
loss_func = mse
|
|
126
|
-
elif loss == "bce":
|
|
127
|
-
loss_func = bce
|
|
124
|
+
loss_func = get_vae_loss(loss)
|
|
128
125
|
|
|
129
126
|
fit_vae(vae, dataloader, epochs=epochs,
|
|
130
127
|
beta_schedule=beta_schedule, lr=learning_rate, loss=loss_func)
|
|
@@ -143,6 +140,46 @@ def train_vae(input_path: str,
|
|
|
143
140
|
stats.to_csv(stats_path, sep="\t")
|
|
144
141
|
click.echo(f"Stats saved, to {stats_path}")
|
|
145
142
|
|
|
143
|
+
if save_latent:
|
|
144
|
+
vae.eval() # Makes output deterministic. Otherwise, model in training.
|
|
145
|
+
losses_df = pd.DataFrame({
|
|
146
|
+
"epoch": range(1, len(vae.history["loss"]) + 1),
|
|
147
|
+
"loss": vae.history["loss"],
|
|
148
|
+
"recon": vae.history["recon"],
|
|
149
|
+
"kl": vae.history["kl"]})
|
|
150
|
+
|
|
151
|
+
losses_df.to_csv(f"{out}.losses_stats.tsv", sep="\t", index=False)
|
|
152
|
+
click.echo(f"Training losses saved, to {out}.losses_stats.tsv")
|
|
153
|
+
|
|
154
|
+
exportloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
|
|
155
|
+
all_mu = []
|
|
156
|
+
all_logvar = []
|
|
157
|
+
|
|
158
|
+
with torch.no_grad():
|
|
159
|
+
for batch in exportloader:
|
|
160
|
+
x_tensor = batch[0] if isinstance(batch, (tuple, list)) else batch
|
|
161
|
+
x_tensor = x_tensor.to(device=device, dtype=dtype)
|
|
162
|
+
_, mu, logvar, _ = vae(x_tensor)
|
|
163
|
+
all_mu.append(mu.cpu())
|
|
164
|
+
all_logvar.append(logvar.cpu())
|
|
165
|
+
|
|
166
|
+
mu = torch.cat(all_mu, dim=0)
|
|
167
|
+
logvar = torch.cat(all_logvar, dim=0)
|
|
168
|
+
std = torch.sqrt(torch.exp(logvar)) # sigma
|
|
169
|
+
|
|
170
|
+
index = df.index if df is not None else None
|
|
171
|
+
|
|
172
|
+
mu_np = mu.to(device="cpu", dtype=torch.float32).detach().numpy()
|
|
173
|
+
std_np = std.to(device="cpu", dtype=torch.float32).detach().numpy()
|
|
174
|
+
mu_df = pd.DataFrame(mu_np, index=index, columns=[f"mu_{i}" for i in range(mu.shape[1])])
|
|
175
|
+
std_df = pd.DataFrame(std_np, index=index, columns=[f"std_{i}" for i in range(std.shape[1])])
|
|
176
|
+
mu_path = f"{out}.latent_mu.tsv"
|
|
177
|
+
std_path = f"{out}.latent_std.tsv"
|
|
178
|
+
mu_df.to_csv(mu_path, sep="\t")
|
|
179
|
+
std_df.to_csv(std_path, sep="\t")
|
|
180
|
+
click.echo(f"Latent mu saved, to {mu_path}")
|
|
181
|
+
click.echo(f"Latent std (sigma) saved, to {std_path}")
|
|
182
|
+
|
|
146
183
|
|
|
147
184
|
@model.command()
|
|
148
185
|
@click.argument("input_path", type=click.Path(exists=True, dir_okay=False, readable=True, path_type=str))
|
|
@@ -153,7 +190,7 @@ def train_vae(input_path: str,
|
|
|
153
190
|
@click.option("--out", "-o", type=str, default=None)
|
|
154
191
|
@click.option("--schedule", "-s", type=str, default=None, help="20:0,20:0.1,40:.3,40:.4")
|
|
155
192
|
@click.option("--loss", type=click.Choice(["mse", "bce", "bce-logit"]), default="bce-logit")
|
|
156
|
-
@click.option("--min-group-size", type=int, default=
|
|
193
|
+
@click.option("--min-group-size", type=int, default=2, show_default=True, help="Minimum group size filter for pathway feature map (including self if present).")
|
|
157
194
|
@click.option("--group-layer-scale", default="5,2,1", show_default=True,
|
|
158
195
|
help="Comma-separated per-group widths for NetVAE masked layers.")
|
|
159
196
|
@click.option("--save-stats", is_flag=True)
|
|
@@ -197,11 +234,7 @@ def train_netvae(input_path: str, pathway_sif:str, out:str,
|
|
|
197
234
|
if not gcounts or any(v <= 0 for v in gcounts):
|
|
198
235
|
raise click.BadParameter("--group-layer-scale must contain one or more positive integers.")
|
|
199
236
|
|
|
200
|
-
loss_func =
|
|
201
|
-
if loss == "mse":
|
|
202
|
-
loss_func = mse
|
|
203
|
-
elif loss == "bce":
|
|
204
|
-
loss_func = bce
|
|
237
|
+
loss_func = get_vae_loss(loss)
|
|
205
238
|
|
|
206
239
|
if beta_schedule is None:
|
|
207
240
|
beta_schedule = [(0.0, epochs)]
|
|
@@ -243,7 +276,7 @@ def encode(input_path: str, model_path:str, normalize:str, out:str):
|
|
|
243
276
|
m.to(get_device())
|
|
244
277
|
result = m.encoder(df_tensor)
|
|
245
278
|
|
|
246
|
-
matrix = result[
|
|
279
|
+
matrix = result[2].detach().cpu().numpy()
|
|
247
280
|
out_df = pd.DataFrame(matrix, index=df.index)
|
|
248
281
|
out_df.to_csv(out, sep="\t")
|
|
249
282
|
|
|
@@ -2,6 +2,7 @@ from typing import Any, Dict, List, Literal, Optional
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
|
+
from ..factory import nn_module
|
|
5
6
|
from ..factory.layers import ConstraintInfo
|
|
6
7
|
from ..pathway import (
|
|
7
8
|
_normalize_index,
|
|
@@ -12,7 +13,7 @@ from ..pathway import (
|
|
|
12
13
|
|
|
13
14
|
ConstraintOP = Literal["features-to-group", "group-to-features", "group-to-group"]
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
@nn_module
|
|
16
17
|
class PathwayConstraintInfo(ConstraintInfo):
|
|
17
18
|
"""
|
|
18
19
|
ConstraintInfo for pathway-based masking.
|