cellarr 0.2.2__tar.gz → 0.2.3__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.
- {cellarr-0.2.2 → cellarr-0.2.3}/PKG-INFO +27 -1
- {cellarr-0.2.2 → cellarr-0.2.3}/README.md +26 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/tutorial.md +27 -0
- cellarr-0.2.3/src/cellarr/autoencoder.py +444 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/build_cellarrdataset.py +15 -8
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/PKG-INFO +27 -1
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/SOURCES.txt +1 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_dataloader.py +54 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.coveragerc +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.github/workflows/pypi-publish.yml +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.github/workflows/pypi-test.yml +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.gitignore +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.pre-commit-config.yaml +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/.readthedocs.yml +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/AUTHORS.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/CHANGELOG.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/CONTRIBUTING.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/LICENSE.txt +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.jpg +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.png +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/assets/cellarr.svg +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/Makefile +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/_static/.gitignore +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/authors.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/changelog.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/conf.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/contributing.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/index.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/license.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/notes.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/readme.md +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/docs/requirements.txt +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/pyproject.toml +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/setup.cfg +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/setup.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/CellArrDataset.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/CellArrDatasetSlice.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/__init__.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/build_options.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/buildutils_tiledb_array.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/buildutils_tiledb_frame.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/dataloader.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/queryutils_tiledb_frame.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr/utils_anndata.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/dependency_links.txt +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/not-zip-safe +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/requires.txt +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/src/cellarr.egg-info/top_level.txt +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/conftest.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/data/adata.h5ad +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_anndata_utils.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_build.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tests/test_query.py +0 -0
- {cellarr-0.2.2 → cellarr-0.2.3}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cellarr
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: TileDB-based array storage for genomics data collections.
|
|
5
5
|
Home-page: https://github.com/BiocPy/cellarr
|
|
6
6
|
Author: Jayaram Kancherla
|
|
@@ -165,6 +165,32 @@ datamodule = DataModule(
|
|
|
165
165
|
)
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import pytorch_lightning as pl
|
|
172
|
+
from cellarr.autoencoder import AutoEncoder
|
|
173
|
+
|
|
174
|
+
autoencoder = AutoEncoder(
|
|
175
|
+
n_genes=len(datamodule.gene_indices),
|
|
176
|
+
latent_dim=128,
|
|
177
|
+
hidden_dim=[1024, 1024, 1024],
|
|
178
|
+
dropout=0.5,
|
|
179
|
+
input_dropout=0.4,
|
|
180
|
+
residual=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
model_path = "/path/to/model/mymodel/"
|
|
184
|
+
params = {
|
|
185
|
+
"max_epochs": 500,
|
|
186
|
+
"logger": True,
|
|
187
|
+
"log_every_n_steps": 1,
|
|
188
|
+
"limit_train_batches": 100, # to specify number of batches per epoch
|
|
189
|
+
}
|
|
190
|
+
trainer = pl.Trainer(**params)
|
|
191
|
+
trainer.fit(autoencoder, datamodule=datamodule)
|
|
192
|
+
autoencoder.save_all(model_path=model_path)
|
|
193
|
+
```
|
|
168
194
|
<!-- pyscaffold-notes -->
|
|
169
195
|
|
|
170
196
|
## Note
|
|
@@ -135,6 +135,32 @@ datamodule = DataModule(
|
|
|
135
135
|
)
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import pytorch_lightning as pl
|
|
142
|
+
from cellarr.autoencoder import AutoEncoder
|
|
143
|
+
|
|
144
|
+
autoencoder = AutoEncoder(
|
|
145
|
+
n_genes=len(datamodule.gene_indices),
|
|
146
|
+
latent_dim=128,
|
|
147
|
+
hidden_dim=[1024, 1024, 1024],
|
|
148
|
+
dropout=0.5,
|
|
149
|
+
input_dropout=0.4,
|
|
150
|
+
residual=False,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
model_path = "/path/to/model/mymodel/"
|
|
154
|
+
params = {
|
|
155
|
+
"max_epochs": 500,
|
|
156
|
+
"logger": True,
|
|
157
|
+
"log_every_n_steps": 1,
|
|
158
|
+
"limit_train_batches": 100, # to specify number of batches per epoch
|
|
159
|
+
}
|
|
160
|
+
trainer = pl.Trainer(**params)
|
|
161
|
+
trainer.fit(autoencoder, datamodule=datamodule)
|
|
162
|
+
autoencoder.save_all(model_path=model_path)
|
|
163
|
+
```
|
|
138
164
|
<!-- pyscaffold-notes -->
|
|
139
165
|
|
|
140
166
|
## Note
|
|
@@ -237,6 +237,33 @@ batch = next(iter(dataloader))
|
|
|
237
237
|
expression, labels, studies = batch
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
+
The dataloader can then be used in training models. The package provides a simple autoencoder to serve as a template for the user's own models.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import pytorch_lightning as pl
|
|
244
|
+
from cellarr.autoencoder import AutoEncoder
|
|
245
|
+
|
|
246
|
+
autoencoder = AutoEncoder(
|
|
247
|
+
n_genes=len(datamodule.gene_indices),
|
|
248
|
+
latent_dim=128,
|
|
249
|
+
hidden_dim=[1024, 1024, 1024],
|
|
250
|
+
dropout=0.5,
|
|
251
|
+
input_dropout=0.4,
|
|
252
|
+
residual=False,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
model_path = "/path/to/model/mymodel/"
|
|
256
|
+
params = {
|
|
257
|
+
"max_epochs": 500,
|
|
258
|
+
"logger": True,
|
|
259
|
+
"log_every_n_steps": 1,
|
|
260
|
+
"limit_train_batches": 100, # to specify number of batches per epoch
|
|
261
|
+
}
|
|
262
|
+
trainer = pl.Trainer(**params)
|
|
263
|
+
trainer.fit(autoencoder, datamodule=datamodule)
|
|
264
|
+
autoencoder.save_all(model_path=model_path)
|
|
265
|
+
```
|
|
266
|
+
|
|
240
267
|
---
|
|
241
268
|
|
|
242
269
|
Check out the [documentation](https://biocpy.github.io/cellarr/api/modules.html) for more details.
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import torch
|
|
3
|
+
from torch import nn
|
|
4
|
+
import torch.nn.functional as F
|
|
5
|
+
from typing import List
|
|
6
|
+
import pytorch_lightning as pl
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Encoder(nn.Module):
|
|
10
|
+
"""A class that encapsulates the encoder."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
n_genes: int,
|
|
15
|
+
latent_dim: int = 128,
|
|
16
|
+
hidden_dim: List[int] = [1024, 1024],
|
|
17
|
+
dropout: float = 0.5,
|
|
18
|
+
input_dropout: float = 0.4,
|
|
19
|
+
residual: bool = False,
|
|
20
|
+
):
|
|
21
|
+
"""Constructor.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
n_genes:
|
|
25
|
+
The number of genes in the gene space, representing the input dimensions.
|
|
26
|
+
|
|
27
|
+
latent_dim:
|
|
28
|
+
The latent space dimensions
|
|
29
|
+
|
|
30
|
+
hidden_dim:
|
|
31
|
+
A list of hidden layer dimensions, describing the number of layers and their dimensions.
|
|
32
|
+
Hidden layers are constructed in the order of the list for the encoder and in reverse
|
|
33
|
+
for the decoder.
|
|
34
|
+
|
|
35
|
+
dropout:
|
|
36
|
+
The dropout rate for hidden layers
|
|
37
|
+
|
|
38
|
+
input_dropout:
|
|
39
|
+
The dropout rate for the input layer
|
|
40
|
+
|
|
41
|
+
residual:
|
|
42
|
+
Use residual connections.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
super().__init__()
|
|
46
|
+
self.latent_dim = latent_dim
|
|
47
|
+
self.network = nn.ModuleList()
|
|
48
|
+
self.residual = residual
|
|
49
|
+
if self.residual:
|
|
50
|
+
assert len(set(hidden_dim)) == 1
|
|
51
|
+
for i in range(len(hidden_dim)):
|
|
52
|
+
if i == 0: # input layer
|
|
53
|
+
self.network.append(
|
|
54
|
+
nn.Sequential(
|
|
55
|
+
nn.Dropout(p=input_dropout),
|
|
56
|
+
nn.Linear(n_genes, hidden_dim[i]),
|
|
57
|
+
nn.BatchNorm1d(hidden_dim[i]),
|
|
58
|
+
nn.PReLU(),
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
else: # hidden layers
|
|
62
|
+
self.network.append(
|
|
63
|
+
nn.Sequential(
|
|
64
|
+
nn.Dropout(p=dropout),
|
|
65
|
+
nn.Linear(hidden_dim[i - 1], hidden_dim[i]),
|
|
66
|
+
nn.BatchNorm1d(hidden_dim[i]),
|
|
67
|
+
nn.PReLU(),
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
# output layer
|
|
71
|
+
self.network.append(nn.Linear(hidden_dim[-1], latent_dim))
|
|
72
|
+
|
|
73
|
+
def forward(self, x) -> torch.Tensor:
|
|
74
|
+
"""Forward.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
x: torch.Tensor
|
|
78
|
+
Input tensor corresponding to input layer.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Output tensor corresponding to output layer.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
for i, layer in enumerate(self.network):
|
|
85
|
+
if self.residual and (0 < i < len(self.network) - 1):
|
|
86
|
+
x = layer(x) + x
|
|
87
|
+
else:
|
|
88
|
+
x = layer(x)
|
|
89
|
+
return F.normalize(x, p=2, dim=1)
|
|
90
|
+
|
|
91
|
+
def save_state(self, filename: str):
|
|
92
|
+
"""Save model state.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
filename:
|
|
96
|
+
Filename to save the model state.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
torch.save({"state_dict": self.state_dict()}, filename)
|
|
100
|
+
|
|
101
|
+
def load_state(self, filename: str, use_gpu: bool = False):
|
|
102
|
+
"""Load model state.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
filename:
|
|
106
|
+
Filename containing the model state.
|
|
107
|
+
|
|
108
|
+
use_gpu:
|
|
109
|
+
Boolean indicating whether or not to use GPUs.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
if not use_gpu:
|
|
113
|
+
ckpt = torch.load(filename, map_location=torch.device("cpu"))
|
|
114
|
+
else:
|
|
115
|
+
ckpt = torch.load(filename)
|
|
116
|
+
self.load_state_dict(ckpt["state_dict"])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Decoder(nn.Module):
|
|
120
|
+
"""A class that encapsulates the decoder."""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
n_genes: int,
|
|
125
|
+
latent_dim: int = 128,
|
|
126
|
+
hidden_dim: List[int] = [1024, 1024],
|
|
127
|
+
dropout: float = 0.5,
|
|
128
|
+
residual: bool = False,
|
|
129
|
+
):
|
|
130
|
+
"""Constructor.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
n_genes:
|
|
134
|
+
The number of genes in the gene space, representing the input dimensions.
|
|
135
|
+
|
|
136
|
+
latent_dim:
|
|
137
|
+
The latent space dimensions
|
|
138
|
+
|
|
139
|
+
hidden_dim:
|
|
140
|
+
A list of hidden layer dimensions, describing the number of layers and their dimensions.
|
|
141
|
+
Hidden layers are constructed in the order of the list for the encoder and in reverse
|
|
142
|
+
for the decoder.
|
|
143
|
+
|
|
144
|
+
dropout:
|
|
145
|
+
The dropout rate for hidden layers
|
|
146
|
+
|
|
147
|
+
residual:
|
|
148
|
+
Use residual connections.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
super().__init__()
|
|
152
|
+
self.latent_dim = latent_dim
|
|
153
|
+
self.network = nn.ModuleList()
|
|
154
|
+
self.residual = residual
|
|
155
|
+
if self.residual:
|
|
156
|
+
assert len(set(hidden_dim)) == 1
|
|
157
|
+
for i in range(len(hidden_dim)):
|
|
158
|
+
if i == 0: # first hidden layer
|
|
159
|
+
self.network.append(
|
|
160
|
+
nn.Sequential(
|
|
161
|
+
nn.Linear(latent_dim, hidden_dim[i]),
|
|
162
|
+
nn.BatchNorm1d(hidden_dim[i]),
|
|
163
|
+
nn.PReLU(),
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
else: # other hidden layers
|
|
167
|
+
self.network.append(
|
|
168
|
+
nn.Sequential(
|
|
169
|
+
nn.Dropout(p=dropout),
|
|
170
|
+
nn.Linear(hidden_dim[i - 1], hidden_dim[i]),
|
|
171
|
+
nn.BatchNorm1d(hidden_dim[i]),
|
|
172
|
+
nn.PReLU(),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
# reconstruction layer
|
|
176
|
+
self.network.append(nn.Linear(hidden_dim[-1], n_genes))
|
|
177
|
+
|
|
178
|
+
def forward(self, x) -> torch.Tensor:
|
|
179
|
+
"""Forward.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
x:
|
|
183
|
+
Input tensor corresponding to input layer.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Output tensor corresponding to output layer.
|
|
187
|
+
"""
|
|
188
|
+
for i, layer in enumerate(self.network):
|
|
189
|
+
if self.residual and (0 < i < len(self.network) - 1):
|
|
190
|
+
x = layer(x) + x
|
|
191
|
+
else:
|
|
192
|
+
x = layer(x)
|
|
193
|
+
return x
|
|
194
|
+
|
|
195
|
+
def save_state(self, filename: str):
|
|
196
|
+
"""Save model state.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
filename:
|
|
200
|
+
Filename to save the model state.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
torch.save({"state_dict": self.state_dict()}, filename)
|
|
204
|
+
|
|
205
|
+
def load_state(self, filename: str, use_gpu: bool = False):
|
|
206
|
+
"""Load model state.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
filename:
|
|
210
|
+
Filename containing the model state.
|
|
211
|
+
|
|
212
|
+
use_gpu:
|
|
213
|
+
Boolean indicating whether or not to use GPUs.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
if not use_gpu:
|
|
217
|
+
ckpt = torch.load(filename, map_location=torch.device("cpu"))
|
|
218
|
+
else:
|
|
219
|
+
ckpt = torch.load(filename)
|
|
220
|
+
self.load_state_dict(ckpt["state_dict"])
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class AutoEncoder(pl.LightningModule):
|
|
224
|
+
"""A class encapsulating training."""
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
n_genes: int,
|
|
229
|
+
latent_dim: int = 128,
|
|
230
|
+
hidden_dim: List[int] = [1024, 1024],
|
|
231
|
+
dropout: float = 0.5,
|
|
232
|
+
input_dropout: float = 0.4,
|
|
233
|
+
lr: float = 5e-3,
|
|
234
|
+
residual: bool = False,
|
|
235
|
+
):
|
|
236
|
+
"""Constructor.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
n_genes:
|
|
240
|
+
The number of genes in the gene space, representing the input dimensions.
|
|
241
|
+
|
|
242
|
+
latent_dim:
|
|
243
|
+
The latent space dimensions. Defaults to 128.
|
|
244
|
+
|
|
245
|
+
hidden_dim:
|
|
246
|
+
A list of hidden layer dimensions, describing the number of layers and their dimensions.
|
|
247
|
+
Hidden layers are constructed in the order of the list for the encoder and in reverse
|
|
248
|
+
for the decoder.
|
|
249
|
+
|
|
250
|
+
dropout:
|
|
251
|
+
The dropout rate for hidden layers
|
|
252
|
+
|
|
253
|
+
input_dropout:
|
|
254
|
+
The dropout rate for the input layer
|
|
255
|
+
|
|
256
|
+
lr:
|
|
257
|
+
The initial learning rate
|
|
258
|
+
|
|
259
|
+
residual:
|
|
260
|
+
Use residual connections.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
super().__init__()
|
|
264
|
+
|
|
265
|
+
# network architecture
|
|
266
|
+
self.n_genes = n_genes
|
|
267
|
+
self.latent_dim = latent_dim
|
|
268
|
+
self.hidden_dim = hidden_dim
|
|
269
|
+
self.dropout = dropout
|
|
270
|
+
self.input_dropout = input_dropout
|
|
271
|
+
self.lr = lr
|
|
272
|
+
self.residual = residual
|
|
273
|
+
|
|
274
|
+
# networks
|
|
275
|
+
self.encoder = Encoder(
|
|
276
|
+
self.n_genes,
|
|
277
|
+
latent_dim=self.latent_dim,
|
|
278
|
+
hidden_dim=self.hidden_dim,
|
|
279
|
+
dropout=self.dropout,
|
|
280
|
+
input_dropout=self.input_dropout,
|
|
281
|
+
residual=self.residual,
|
|
282
|
+
)
|
|
283
|
+
self.decoder = Decoder(
|
|
284
|
+
self.n_genes,
|
|
285
|
+
latent_dim=self.latent_dim,
|
|
286
|
+
hidden_dim=list(reversed(self.hidden_dim)),
|
|
287
|
+
dropout=self.dropout,
|
|
288
|
+
residual=self.residual,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self.mse_loss_fn = nn.MSELoss()
|
|
292
|
+
self.scheduler = None
|
|
293
|
+
self.val_step_outputs = []
|
|
294
|
+
|
|
295
|
+
def configure_optimizers(self):
|
|
296
|
+
"""Configure optimizers."""
|
|
297
|
+
|
|
298
|
+
optimizer = torch.optim.AdamW(self.parameters(), self.lr, weight_decay=0.01)
|
|
299
|
+
self.scheduler = {
|
|
300
|
+
"scheduler": torch.optim.lr_scheduler.StepLR(optimizer, step_size=0.001),
|
|
301
|
+
"interval": "epoch",
|
|
302
|
+
"frequency": 1,
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
"optimizer": optimizer,
|
|
306
|
+
"lr_scheduler": self.scheduler,
|
|
307
|
+
} # pytorch-lightning required format
|
|
308
|
+
|
|
309
|
+
def forward(self, x):
|
|
310
|
+
"""Forward.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
x:
|
|
314
|
+
Input tensor corresponding to input layer.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Output tensor corresponding to the last encoder layer.
|
|
318
|
+
|
|
319
|
+
Output tensor corresponding to the last decoder layer.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
z = self.encoder(x)
|
|
323
|
+
x_hat = self.decoder(z)
|
|
324
|
+
return z, x_hat
|
|
325
|
+
|
|
326
|
+
def get_loss(self, batch):
|
|
327
|
+
"""Calculate the loss.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
batch:
|
|
331
|
+
A batch as defined by a pytorch DataLoader.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
The training loss
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
cells, labels, studies = batch
|
|
338
|
+
embedding, reconstruction = self(cells)
|
|
339
|
+
return self.mse_loss_fn(cells, reconstruction)
|
|
340
|
+
|
|
341
|
+
def training_step(self, batch, batch_idx):
|
|
342
|
+
"""Pytorch-lightning training step."""
|
|
343
|
+
|
|
344
|
+
loss = self.get_loss(batch)
|
|
345
|
+
self.log("train loss", loss, prog_bar=True, logger=True)
|
|
346
|
+
return loss
|
|
347
|
+
|
|
348
|
+
def on_validation_epoch_start(self):
|
|
349
|
+
"""Pytorch-lightning validation epoch start."""
|
|
350
|
+
|
|
351
|
+
super().on_validation_epoch_start()
|
|
352
|
+
self.val_step_outputs = []
|
|
353
|
+
|
|
354
|
+
def validation_step(self, batch, batch_idx):
|
|
355
|
+
"""Pytorch-lightning validation step."""
|
|
356
|
+
|
|
357
|
+
if self.trainer.datamodule.val_dataset is None:
|
|
358
|
+
return {}
|
|
359
|
+
return self._eval_step(batch, prefix="val")
|
|
360
|
+
|
|
361
|
+
def on_validation_epoch_end(self):
|
|
362
|
+
"""Pytorch-lightning validation epoch end evaluation."""
|
|
363
|
+
|
|
364
|
+
if self.trainer.datamodule.val_dataset is None:
|
|
365
|
+
return {}
|
|
366
|
+
return self._eval_epoch(prefix="val")
|
|
367
|
+
|
|
368
|
+
def _eval_step(self, batch, prefix: str):
|
|
369
|
+
"""Evaluation of validation or test step.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
batch:
|
|
373
|
+
A batch as defined by a pytorch DataLoader.
|
|
374
|
+
prefix:
|
|
375
|
+
A string prefix to label logs.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
A dictionary containing step evaluation metrics.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
loss = self.get_loss(batch)
|
|
382
|
+
|
|
383
|
+
losses = {
|
|
384
|
+
f"{prefix}_loss": loss,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if prefix == "val":
|
|
388
|
+
self.val_step_outputs.append(losses)
|
|
389
|
+
return losses
|
|
390
|
+
|
|
391
|
+
def _eval_epoch(self, prefix: str):
|
|
392
|
+
"""Evaluation of validation or test epoch.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
prefix:
|
|
396
|
+
A string prefix to label logs.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
A dictionary containing epoch evaluation metrics.
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
if prefix == "val":
|
|
403
|
+
step_outputs = self.val_step_outputs
|
|
404
|
+
|
|
405
|
+
loss = torch.Tensor([step[f"{prefix}_loss"] for step in step_outputs]).mean()
|
|
406
|
+
self.log(f"{prefix} loss", loss, logger=True)
|
|
407
|
+
|
|
408
|
+
losses = {
|
|
409
|
+
f"{prefix}_loss": loss,
|
|
410
|
+
}
|
|
411
|
+
return losses
|
|
412
|
+
|
|
413
|
+
def save_all(
|
|
414
|
+
self,
|
|
415
|
+
model_path: str,
|
|
416
|
+
):
|
|
417
|
+
if not os.path.isdir(model_path):
|
|
418
|
+
os.makedirs(model_path)
|
|
419
|
+
|
|
420
|
+
# save model
|
|
421
|
+
self.encoder.save_state(os.path.join(model_path, "encoder.ckpt"))
|
|
422
|
+
self.decoder.save_state(os.path.join(model_path, "decoder.ckpt"))
|
|
423
|
+
|
|
424
|
+
def load_state(
|
|
425
|
+
self,
|
|
426
|
+
encoder_filename: str,
|
|
427
|
+
decoder_filename: str,
|
|
428
|
+
use_gpu: bool = False,
|
|
429
|
+
):
|
|
430
|
+
"""Load model state.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
encoder_filename:
|
|
434
|
+
Filename containing the encoder model state.
|
|
435
|
+
|
|
436
|
+
decoder_filename:
|
|
437
|
+
Filename containing the decoder model state.
|
|
438
|
+
|
|
439
|
+
use_gpu:
|
|
440
|
+
Boolean indicating whether or not to use GPUs.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
self.encoder.load_state(encoder_filename, use_gpu)
|
|
444
|
+
self.decoder.load_state(decoder_filename, use_gpu)
|
|
@@ -291,9 +291,11 @@ def build_cellarrdataset(
|
|
|
291
291
|
sample_metadata = pd.DataFrame({"cellarr_sample": _samples})
|
|
292
292
|
elif isinstance(sample_metadata, str):
|
|
293
293
|
sample_metadata = pd.read_csv(sample_metadata, header=0)
|
|
294
|
-
|
|
294
|
+
if "cellarr_sample" not in sample_metadata.columns:
|
|
295
|
+
sample_metadata["cellarr_sample"] = _samples
|
|
295
296
|
elif isinstance(sample_metadata, pd.DataFrame):
|
|
296
|
-
|
|
297
|
+
if "cellarr_sample" not in sample_metadata.columns:
|
|
298
|
+
sample_metadata["cellarr_sample"] = _samples
|
|
297
299
|
else:
|
|
298
300
|
raise TypeError("'sample_metadata' is not an expected type.")
|
|
299
301
|
|
|
@@ -302,11 +304,13 @@ def build_cellarrdataset(
|
|
|
302
304
|
"Scanning all files for feature ids (e.g. gene symbols), this may take long",
|
|
303
305
|
UserWarning,
|
|
304
306
|
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
if "cellarr_original_gene_set" not in sample_metadata.columns:
|
|
308
|
+
gene_scan_set = uad.scan_for_features(files_cache, unique=False)
|
|
309
|
+
gene_set_str = [",".join(x) for x in gene_scan_set]
|
|
310
|
+
sample_metadata["cellarr_original_gene_set"] = gene_set_str
|
|
308
311
|
|
|
309
|
-
|
|
312
|
+
if "cellarr_cell_counts" not in sample_metadata.columns:
|
|
313
|
+
sample_metadata["cellarr_cell_counts"] = cell_counts
|
|
310
314
|
|
|
311
315
|
_col_types = utf.infer_column_types(
|
|
312
316
|
sample_metadata, sample_metadata_options.column_types
|
|
@@ -367,8 +371,11 @@ def build_cellarrdataset(
|
|
|
367
371
|
"Number of rows in 'cell_metadata' does not match the number of cells across files."
|
|
368
372
|
)
|
|
369
373
|
|
|
370
|
-
|
|
371
|
-
|
|
374
|
+
if "cellarr_sample" not in cell_metadata.columns:
|
|
375
|
+
cell_metadata["cellarr_sample"] = _sample_per_cell
|
|
376
|
+
|
|
377
|
+
if "cellarr_cell_index_in_sample" not in cell_metadata.columns:
|
|
378
|
+
cell_metadata["cellarr_cell_index_in_sample"] = _cell_index_in_sample
|
|
372
379
|
|
|
373
380
|
cell_metadata.reset_index(drop=True, inplace=True)
|
|
374
381
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cellarr
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: TileDB-based array storage for genomics data collections.
|
|
5
5
|
Home-page: https://github.com/BiocPy/cellarr
|
|
6
6
|
Author: Jayaram Kancherla
|
|
@@ -165,6 +165,32 @@ datamodule = DataModule(
|
|
|
165
165
|
)
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
+
The package also includes a simple autoencoder in the `pytorch-lightning` which makes use of the dataloader. This can be used as a template to create custom architectures and models.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import pytorch_lightning as pl
|
|
172
|
+
from cellarr.autoencoder import AutoEncoder
|
|
173
|
+
|
|
174
|
+
autoencoder = AutoEncoder(
|
|
175
|
+
n_genes=len(datamodule.gene_indices),
|
|
176
|
+
latent_dim=128,
|
|
177
|
+
hidden_dim=[1024, 1024, 1024],
|
|
178
|
+
dropout=0.5,
|
|
179
|
+
input_dropout=0.4,
|
|
180
|
+
residual=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
model_path = "/path/to/model/mymodel/"
|
|
184
|
+
params = {
|
|
185
|
+
"max_epochs": 500,
|
|
186
|
+
"logger": True,
|
|
187
|
+
"log_every_n_steps": 1,
|
|
188
|
+
"limit_train_batches": 100, # to specify number of batches per epoch
|
|
189
|
+
}
|
|
190
|
+
trainer = pl.Trainer(**params)
|
|
191
|
+
trainer.fit(autoencoder, datamodule=datamodule)
|
|
192
|
+
autoencoder.save_all(model_path=model_path)
|
|
193
|
+
```
|
|
168
194
|
<!-- pyscaffold-notes -->
|
|
169
195
|
|
|
170
196
|
## Note
|
|
@@ -31,6 +31,7 @@ docs/_static/.gitignore
|
|
|
31
31
|
src/cellarr/CellArrDataset.py
|
|
32
32
|
src/cellarr/CellArrDatasetSlice.py
|
|
33
33
|
src/cellarr/__init__.py
|
|
34
|
+
src/cellarr/autoencoder.py
|
|
34
35
|
src/cellarr/build_cellarrdataset.py
|
|
35
36
|
src/cellarr/build_options.py
|
|
36
37
|
src/cellarr/buildutils_tiledb_array.py
|
|
@@ -2,8 +2,10 @@ import tempfile
|
|
|
2
2
|
|
|
3
3
|
import anndata
|
|
4
4
|
import numpy as np
|
|
5
|
+
import os
|
|
5
6
|
import pandas as pd
|
|
6
7
|
import pytest
|
|
8
|
+
import pytorch_lightning as pl
|
|
7
9
|
import random
|
|
8
10
|
from scipy.sparse import rand
|
|
9
11
|
import tiledb
|
|
@@ -12,6 +14,7 @@ from cellarr import (
|
|
|
12
14
|
MatrixOptions,
|
|
13
15
|
)
|
|
14
16
|
from cellarr.dataloader import DataModule
|
|
17
|
+
from cellarr.autoencoder import AutoEncoder
|
|
15
18
|
|
|
16
19
|
__author__ = "Jayaram Kancherla"
|
|
17
20
|
__copyright__ = "Jayaram Kancherla"
|
|
@@ -78,3 +81,54 @@ def test_dataloader():
|
|
|
78
81
|
assert len(labels) == 100
|
|
79
82
|
assert len(studies) == 100
|
|
80
83
|
assert data.shape == (100, len(datamodule.gene_indices))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_autoencoder():
|
|
87
|
+
tempdir = tempfile.mkdtemp()
|
|
88
|
+
|
|
89
|
+
adata1 = generate_adata(1000, 100, study="test1")
|
|
90
|
+
adata2 = generate_adata(100, 1000, study="test2")
|
|
91
|
+
adata3 = generate_adata(100, 100, study="test3")
|
|
92
|
+
obs = pd.concat([adata1.obs, adata2.obs, adata3.obs])
|
|
93
|
+
obs = obs.reset_index(drop=True)
|
|
94
|
+
|
|
95
|
+
build_cellarrdataset(
|
|
96
|
+
output_path=tempdir,
|
|
97
|
+
files=[adata1, adata2, adata3],
|
|
98
|
+
cell_metadata=obs,
|
|
99
|
+
matrix_options=MatrixOptions(dtype=np.float32),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
datamodule = DataModule(
|
|
103
|
+
dataset_path=tempdir,
|
|
104
|
+
cell_metadata_uri="cell_metadata",
|
|
105
|
+
gene_annotation_uri="gene_annotation",
|
|
106
|
+
matrix_uri="counts",
|
|
107
|
+
val_studies=["test3"],
|
|
108
|
+
label_column_name="label",
|
|
109
|
+
study_column_name="study",
|
|
110
|
+
batch_size=100,
|
|
111
|
+
lognorm=True,
|
|
112
|
+
target_sum=1e4,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
autoencoder = AutoEncoder(
|
|
116
|
+
n_genes=len(datamodule.gene_indices),
|
|
117
|
+
latent_dim=128,
|
|
118
|
+
hidden_dim=[1024, 1024],
|
|
119
|
+
dropout=0.5,
|
|
120
|
+
input_dropout=0.4,
|
|
121
|
+
residual=False,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
params = {
|
|
125
|
+
"max_epochs": 2,
|
|
126
|
+
"logger": True,
|
|
127
|
+
"log_every_n_steps": 1,
|
|
128
|
+
"limit_train_batches": 4,
|
|
129
|
+
}
|
|
130
|
+
trainer = pl.Trainer(**params)
|
|
131
|
+
trainer.fit(autoencoder, datamodule=datamodule)
|
|
132
|
+
autoencoder.save_all(model_path=os.path.join(tempdir, "test"))
|
|
133
|
+
assert os.path.isfile(os.path.join(tempdir, "test", "encoder.ckpt"))
|
|
134
|
+
assert os.path.isfile(os.path.join(tempdir, "test", "decoder.ckpt"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|