kernel-elastic-autoencoder 1.0.0__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.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: kernel_elastic_autoencoder
3
+ Version: 1.0.0
4
+ Summary: Implementation of Kernel-Elastic Autoencoder for Molecular Design (https://doi.org/10.1093/pnasnexus/pgae168)
5
+ License: MIT
6
+ Author: Felix Rotter-McCartney
7
+ Author-email: felix.rotter@mail.utoronto.ca
8
+ Requires-Python: >=3.12,<3.15
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: huggingface-hub (>=1.22.0,<2.0.0)
15
+ Requires-Dist: pandas (>=3.0.3,<4.0.0)
16
+ Requires-Dist: pydantic (>=2.13.4,<3.0.0)
17
+ Requires-Dist: safetensors (>=0.8.0,<0.9.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ ## `kernel_elastic_autoencoder`
21
+
22
+ `kernel_elastic_autoencoder` is a library implementing the architecture and techniques described
23
+ in [this publication](https://doi.org/10.1093/pnasnexus/pgae168) from Li et al. I am not affiliated with the authors of
24
+ the original paper, and this implementation is provided as-is, with no guarantee of completeness.
25
+
26
+ ### Installation
27
+
28
+ `kernel_elastic_autoencoder` can be installed with pip, and regular builds are provided on PyPI:
29
+
30
+ pip install kernel_elastic_autoencoder
31
+
32
+ > Please note that `torch` is not included as a dependency due to its many hardware-accelerator-dependent versions, so
33
+ > take care to install the appropriate version manually.
34
+
35
+ Distribution builds are also provided here on GitHub Releases. New builds are triggered by the CD Action, so they will
36
+ be made available as soon as a new PR is merged to `main`.
37
+
38
+ Alternatively, for development purposes, `kernel_elastic_autoencoder` may be installed from source provided here. Builds
39
+ and deps are managed with `poetry`.
40
+
41
+ ### Documentation
42
+
43
+ API documentation is generated with `pdoc` and covers the `__all__`-exported interfaces. It is available on
44
+ [GitHub Pages](https://cancelradius.github.io/kernel_elastic_autoencoder).
@@ -0,0 +1,25 @@
1
+ ## `kernel_elastic_autoencoder`
2
+
3
+ `kernel_elastic_autoencoder` is a library implementing the architecture and techniques described
4
+ in [this publication](https://doi.org/10.1093/pnasnexus/pgae168) from Li et al. I am not affiliated with the authors of
5
+ the original paper, and this implementation is provided as-is, with no guarantee of completeness.
6
+
7
+ ### Installation
8
+
9
+ `kernel_elastic_autoencoder` can be installed with pip, and regular builds are provided on PyPI:
10
+
11
+ pip install kernel_elastic_autoencoder
12
+
13
+ > Please note that `torch` is not included as a dependency due to its many hardware-accelerator-dependent versions, so
14
+ > take care to install the appropriate version manually.
15
+
16
+ Distribution builds are also provided here on GitHub Releases. New builds are triggered by the CD Action, so they will
17
+ be made available as soon as a new PR is merged to `main`.
18
+
19
+ Alternatively, for development purposes, `kernel_elastic_autoencoder` may be installed from source provided here. Builds
20
+ and deps are managed with `poetry`.
21
+
22
+ ### Documentation
23
+
24
+ API documentation is generated with `pdoc` and covers the `__all__`-exported interfaces. It is available on
25
+ [GitHub Pages](https://cancelradius.github.io/kernel_elastic_autoencoder).
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "kernel_elastic_autoencoder"
3
+ version = "1.0.0"
4
+ description = "Implementation of Kernel-Elastic Autoencoder for Molecular Design (https://doi.org/10.1093/pnasnexus/pgae168)"
5
+ authors = [
6
+ { name = "Felix Rotter-McCartney", email = "felix.rotter@mail.utoronto.ca" }
7
+ ]
8
+ readme = "README.md"
9
+ license = { text = "MIT" }
10
+ requires-python = ">=3.12,<3.15"
11
+ dependencies = [
12
+ "pydantic (>=2.13.4,<3.0.0)",
13
+ "huggingface-hub (>=1.22.0,<2.0.0)",
14
+ "pandas (>=3.0.3,<4.0.0)",
15
+ "safetensors (>=0.8.0,<0.9.0)",
16
+ ]
17
+
18
+ [tool.poetry]
19
+ packages = [{ include = "kernel_elastic_autoencoder", from = "src" }]
20
+
21
+ [tool.semantic_release]
22
+ version_toml = ["pyproject.toml:project.version"]
23
+ commit_parser = "conventional"
24
+ build_command = "pip install build && python -m build --sdist --wheel ."
25
+
26
+ [tool.semantic_release.branches.main]
27
+ match = "(main|master)"
28
+ prerelease = false
29
+
30
+ [tool.semantic_release.remote.token]
31
+ env = "GH_TOKEN"
32
+
33
+ [build-system]
34
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
35
+ build-backend = "poetry.core.masonry.api"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "pytest (>=9.1.1,<10.0.0)",
40
+ "ruff (>=0.15.20,<0.17.0)",
41
+ "pytest-cov (>=7.1.0,<8.0.0)",
42
+ "pytest-xdist (>=3.8.0,<4.0.0)",
43
+ "ty (>=0.0.62,<0.0.64)",
44
+ "python-semantic-release (>=10.6.1,<11.0.0)",
45
+ "pdoc (>=16.0.0,<17.0.0)",
46
+ ]
@@ -0,0 +1,44 @@
1
+ from kernel_elastic_autoencoder.collate import Collated, Collator, DataframeCollator
2
+ from kernel_elastic_autoencoder.config import (
3
+ ExperimentConfig,
4
+ ModelCommonConfig,
5
+ ModelConfig,
6
+ ModelDecoderConfig,
7
+ ModelEncoderConfig,
8
+ ModelInputConfig,
9
+ TrainingCommonConfig,
10
+ TrainingConfig,
11
+ TrainingHyperparameterConfig,
12
+ TrainingOptimizerConfig,
13
+ )
14
+ from kernel_elastic_autoencoder.losses import Loss
15
+ from kernel_elastic_autoencoder.model import Model
16
+ from kernel_elastic_autoencoder.pipeline import Completion, Pipeline
17
+ from kernel_elastic_autoencoder.sample import Sampler, Top1Sampler
18
+ from kernel_elastic_autoencoder.tokenizer import Tokenizer
19
+ from kernel_elastic_autoencoder.training import Trainer, TrainerCallback
20
+
21
+ __all__ = [
22
+ "ExperimentConfig",
23
+ "ModelConfig",
24
+ "ModelCommonConfig",
25
+ "ModelInputConfig",
26
+ "ModelEncoderConfig",
27
+ "ModelDecoderConfig",
28
+ "TrainingConfig",
29
+ "TrainingCommonConfig",
30
+ "TrainingHyperparameterConfig",
31
+ "TrainingOptimizerConfig",
32
+ "Pipeline",
33
+ "Completion",
34
+ "Model",
35
+ "Loss",
36
+ "Tokenizer",
37
+ "Collator",
38
+ "Collated",
39
+ "DataframeCollator",
40
+ "Sampler",
41
+ "Top1Sampler",
42
+ "Trainer",
43
+ "TrainerCallback",
44
+ ]
@@ -0,0 +1,164 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import torch
4
+ from pandas import DataFrame
5
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
+
7
+ from kernel_elastic_autoencoder.config import ModelConfig
8
+ from kernel_elastic_autoencoder.tokenizer import Tokenizer
9
+
10
+
11
+ class Collated(BaseModel):
12
+ """Return schema for Collator.__call__. Represents a store of tensors ready for direct input to a Model."""
13
+
14
+ model_config = ConfigDict(arbitrary_types_allowed=True)
15
+ input_ids: torch.Tensor = Field(
16
+ ...,
17
+ description="Tensor of dimension (B, S) containing indices of input tokens, generated by a Tokenizer.",
18
+ )
19
+ conditions: torch.Tensor = Field(
20
+ ...,
21
+ description="Tensor of dimension (B, C) containing condition values for each sequence.",
22
+ )
23
+ token_mask: torch.Tensor = Field(
24
+ ...,
25
+ description="Tensor of dimension (B, S) containing a boolean padding mask for each sequence.",
26
+ )
27
+ condition_mask: torch.Tensor = Field(
28
+ ...,
29
+ description="Tensor of dimension (B, C) containing a boolean condition mask for each sequence",
30
+ )
31
+
32
+ @model_validator(mode="after")
33
+ def _check_shapes(self) -> "Collated":
34
+ if (self.input_ids.shape != self.token_mask.shape) or (
35
+ self.conditions.shape != self.condition_mask.shape
36
+ ):
37
+ raise ValueError("Mask shapes don't match source")
38
+ if (self.input_ids.dim() != 2) or (self.conditions.dim() != 2):
39
+ raise ValueError("Tensors should have a batch dimension")
40
+ if (self.token_mask.dtype != torch.bool) or (
41
+ self.condition_mask.dtype != torch.bool
42
+ ):
43
+ raise ValueError("Mask tensors should have bool dtype")
44
+ return self
45
+
46
+
47
+ class Collator[T](ABC):
48
+ """Base class for collators.
49
+
50
+ Provides a specification for preparing various data types for model input.
51
+ """
52
+
53
+ def __init__(self, model_config: ModelConfig, tokenizer: Tokenizer):
54
+ """Instantiates a Collator object.
55
+
56
+ Args:
57
+ model_config: Model configuration object.
58
+ tokenizer: Object implementing the Tokenizer protocol.
59
+ """
60
+ self.model_config = model_config
61
+ """Model configuration object."""
62
+ self.tokenizer = tokenizer
63
+ """Object implementing the Tokenizer protocol."""
64
+
65
+ def __call__(
66
+ self,
67
+ dataset: T,
68
+ seq_feature: str,
69
+ cond_features: list[str],
70
+ padding: bool,
71
+ add_special_tokens: bool,
72
+ device: torch.device | None,
73
+ **kwargs,
74
+ ) -> Collated:
75
+ return self.collate_data(
76
+ dataset,
77
+ seq_feature,
78
+ cond_features,
79
+ padding,
80
+ add_special_tokens,
81
+ device,
82
+ **kwargs,
83
+ )
84
+
85
+ @abstractmethod
86
+ def collate_data(
87
+ self,
88
+ dataset: T,
89
+ seq_feature: str,
90
+ cond_features: list[str],
91
+ padding: bool,
92
+ add_special_tokens: bool,
93
+ device: torch.device | None,
94
+ **kwargs,
95
+ ) -> Collated:
96
+ """Interface method for implementing data collation.
97
+
98
+ Args:
99
+ dataset: Dataset object of target data type.
100
+ seq_feature: Name of sequence feature or column.
101
+ cond_features: List of names of condition features or columns.
102
+ padding: Whether or not to uniformly pad outputs to the maximum length specified in the model configuration.
103
+ add_special_tokens: Whether or not to add special tokens according to a template. Special tokens will be
104
+ added by default during training, but will be omitted during inference, where the specified bos_token_id
105
+ will be appended before each pass through the decoder.
106
+ device: Torch device to which the produced tensors are moved.
107
+ **kwargs: Keyword arguments.
108
+
109
+ Returns:
110
+ Collated: Return schema representing a store of tensors ready for direct input to a Model.
111
+ """
112
+ ...
113
+
114
+
115
+ class DataframeCollator(Collator):
116
+ """Collator designed for pandas.DataFrame inputs."""
117
+
118
+ def collate_data(
119
+ self,
120
+ dataset: DataFrame,
121
+ seq_feature: str,
122
+ cond_features: list[str],
123
+ padding: bool,
124
+ add_special_tokens: bool,
125
+ device: torch.device | None,
126
+ **kwargs,
127
+ ) -> Collated:
128
+ """Implementation of data collation for pandas.DataFrame inputs.
129
+
130
+ Args:
131
+ dataset: Dataset object of pandas.DataFrame type.
132
+ seq_feature: Name of sequence column.
133
+ cond_features: List of names of condition columns.
134
+ padding: Whether or not to uniformly pad outputs to the maximum length specified in the model configuration.
135
+ add_special_tokens: Whether or not to add special tokens according to a template. Special tokens will be
136
+ added by default during training, but will be omitted during inference, where the specified bos_token_id
137
+ will be appended before each pass through the decoder.
138
+ device: Torch device to which the produced tensors are moved.
139
+ **kwargs: Keyword arguments passed to Tokenizer.encode.
140
+
141
+ Returns:
142
+ Collated: Return schema representing a store of tensors ready for direct input to a Model.
143
+ """
144
+ df = dataset.copy()
145
+ input_ids = self.tokenizer.encode(
146
+ seq=df[seq_feature],
147
+ padding=padding,
148
+ max_length=self.model_config.input.max_len,
149
+ add_special_tokens=add_special_tokens,
150
+ **kwargs,
151
+ )
152
+ conditions = torch.from_numpy(df[cond_features].to_numpy()).to(
153
+ device=device, dtype=torch.float
154
+ )
155
+ token_mask = (input_ids != self.model_config.common.padding_idx).to(torch.bool)
156
+ condition_mask = (conditions != self.model_config.common.padding_value).to(
157
+ torch.bool
158
+ )
159
+ return Collated(
160
+ input_ids=input_ids,
161
+ conditions=conditions,
162
+ token_mask=token_mask,
163
+ condition_mask=condition_mask,
164
+ )
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from pathlib import Path
5
+
6
+ from pydantic import (
7
+ BaseModel,
8
+ ConfigDict,
9
+ Field,
10
+ FilePath,
11
+ ImportString,
12
+ NonNegativeInt,
13
+ PositiveFloat,
14
+ PositiveInt,
15
+ )
16
+
17
+
18
+ class Config(BaseModel):
19
+ model_config = ConfigDict(frozen=True)
20
+
21
+ def to_dict(self) -> dict:
22
+ return self.model_dump()
23
+
24
+ @classmethod
25
+ def from_dict(cls, d: dict) -> Config:
26
+ return cls.model_validate(d)
27
+
28
+ def to_json(self, f: str):
29
+ return Path(f).write_text(self.model_dump_json(indent=2))
30
+
31
+ @classmethod
32
+ def from_json(cls, f: FilePath):
33
+ return cls.model_validate_json(Path(f).read_text())
34
+
35
+
36
+ class ExperimentConfig(Config):
37
+ """Configuration schema for an experiment.
38
+
39
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
40
+ """
41
+
42
+ model: ModelConfig = Field(..., description="ModelConfig object.")
43
+ training: TrainingConfig = Field(..., description="TrainingConfig object.")
44
+
45
+
46
+ class ModelInputConfig(Config):
47
+ """ModelConfig schema block containing input parameters.
48
+
49
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
50
+ """
51
+
52
+ max_len: PositiveInt = Field(
53
+ default=150,
54
+ description="Maximum sequence length through the encoder and decoder.",
55
+ )
56
+ vocab_size: PositiveInt = Field(
57
+ default=50,
58
+ description="Vocabulary size of tokenizer. Should be fetched with Tokenizer.vocab_size as specified in the "
59
+ "Tokenizer Protocol.",
60
+ )
61
+ condition_channels: PositiveInt = Field(
62
+ default=2,
63
+ description="Number of condition channels, should correspond to number of numerical columns in your "
64
+ "dataset/inputs.",
65
+ )
66
+
67
+
68
+ class ModelCommonConfig(Config):
69
+ """ModelConfig schema block containing common architecture parameters.
70
+
71
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
72
+ """
73
+
74
+ embedding_dim: PositiveInt = Field(
75
+ default=128, description="Embedding dimension used by nn.Embedding layers."
76
+ )
77
+ pooling_dim: PositiveInt = Field(
78
+ default=10,
79
+ description="Sequence length dimension to which inputs are pooled after condition concatenation through the "
80
+ "encoder. Proportional to the dimension of latent vectors.",
81
+ )
82
+ padding_idx: NonNegativeInt = Field(
83
+ default=0,
84
+ description="Index of padding token. Used internally to zero vectors corresponding to padding tokens through "
85
+ "embedding layers. Should be fetched with Tokenizer.pad_token_id as specified in the Tokenizer "
86
+ "protocol.",
87
+ )
88
+ padding_value: float = Field(
89
+ default=-1e10,
90
+ description='Value of "padded" condition fields. Should be set according to the value you use as a '
91
+ "placeholder for blank numerical fields in your dataset.",
92
+ )
93
+
94
+
95
+ class ModelEncoderConfig(Config):
96
+ """ModelConfig schema block containing architecture parameters of the encoder.
97
+
98
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
99
+ """
100
+
101
+ num_layers: PositiveInt = Field(
102
+ default=6, description="Number of Transformer encoder layers in the encoder."
103
+ )
104
+ num_heads: PositiveInt = Field(
105
+ default=4,
106
+ description="Number of self-attention heads used in each Transformer encoder layer in the encoder.",
107
+ )
108
+ feedforward_scale: PositiveInt = Field(
109
+ default=4,
110
+ description="Factor by which the dimension of the hidden layer in the FFNs differs from the dimension of the "
111
+ "input in the encoder. Applies to Transformer and Compression FFNs.",
112
+ )
113
+ dropout: PositiveFloat = Field(
114
+ default=0.0,
115
+ ge=0.0,
116
+ le=1.0,
117
+ description="Dropout rate applied to Transformer encoder layers in the encoder.",
118
+ )
119
+
120
+
121
+ class ModelDecoderConfig(Config):
122
+ """ModelConfig schema block containing architecture parameters of the decoder.
123
+
124
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
125
+ """
126
+
127
+ num_layers: PositiveInt = Field(
128
+ default=6, description="Number of Transformer decoder layers in the decoder."
129
+ )
130
+ num_heads: PositiveInt = Field(
131
+ default=4,
132
+ description="Number of self- and cross-attention heads used in each Transformer decoder layer in the decoder.",
133
+ )
134
+ feedforward_scale: PositiveInt = Field(
135
+ default=4,
136
+ description="Factor by which the dimension of the hidden layer in the FFNs differs from the dimension of the "
137
+ "input in the decoder. Applies to Transformer and Mixing FFNs.",
138
+ )
139
+ dropout: PositiveFloat = Field(
140
+ default=0.1,
141
+ ge=0.0,
142
+ le=1.0,
143
+ description="Dropout rate applied to Transformer decoder layers in the decoder.",
144
+ )
145
+
146
+
147
+ class ModelConfig(Config):
148
+ """Configuration schema for a Model. Contains all parameters needed to instantiate a Model.
149
+
150
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
151
+ """
152
+
153
+ input: ModelInputConfig = Field(
154
+ default=ModelInputConfig(), description="ModelInputConfig object."
155
+ )
156
+ common: ModelCommonConfig = Field(
157
+ default=ModelCommonConfig(), description="ModelCommonConfig object."
158
+ )
159
+ encoder: ModelEncoderConfig = Field(
160
+ default=ModelEncoderConfig(), description="ModelEncoderConfig object."
161
+ )
162
+ decoder: ModelDecoderConfig = Field(
163
+ default=ModelDecoderConfig(), description="ModelDecoderConfig object."
164
+ )
165
+
166
+
167
+ class TrainingCommonConfig(Config):
168
+ """TrainingConfig schema block containing common training parameters.
169
+
170
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
171
+ """
172
+
173
+ max_epochs: PositiveInt = Field(
174
+ default=200, description="Maximum number of epochs to train for."
175
+ )
176
+ batch_size: PositiveInt = Field(
177
+ default=64,
178
+ description="Batch size for training. Large values are highly prone to OOM because batches are all padded to "
179
+ "a fixed length, as opposed to dynamically.",
180
+ )
181
+
182
+
183
+ class TrainingHyperparameterConfig(Config):
184
+ """TrainingConfig schema block containing hyperparameters.
185
+
186
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
187
+ """
188
+
189
+ hp_lambda: float = Field(
190
+ default=3.5,
191
+ description=r"Hyperparameter $\lambda$, as used in WCEL and m-MMD losses. Roughly, controls how strongly the "
192
+ r"shape of the latent vector distribution is penalized.",
193
+ )
194
+ hp_delta: float = Field(
195
+ default=1.0,
196
+ description=r"Hyperparameter $\delta$, as used in WCEL loss. Roughly, controls the relative weights of the "
197
+ r"vanilla-AE and VAE objectives in the reconstruction loss.",
198
+ )
199
+ hp_sigma: float = Field(
200
+ default=math.sqrt(32),
201
+ description=r"Hyperparameter $\sigma$, as used in the Kernel function applied in m-MMD loss. Roughly, "
202
+ r"used as a scaling factor to control the sizes of gradients produced by the m-MMD loss.",
203
+ )
204
+ kernel_dist_size: PositiveInt = Field(
205
+ default=1000,
206
+ description="Size of the sampled distribution of vectors used to penalize the shape of the latent vector "
207
+ "distribution through the kernel.",
208
+ )
209
+
210
+
211
+ class TrainingOptimizerConfig(Config):
212
+ """TrainingConfig schema block containing configurations for the optimizer and scheduler.
213
+
214
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
215
+ """
216
+
217
+ optimizer_fn: ImportString = Field(
218
+ default="torch.optim.AdamW",
219
+ description="Optimizer function import string. Should come from torch.optim.",
220
+ )
221
+ optimizer_params: dict = Field(
222
+ default={},
223
+ description="Dictionary of optimizer parameters to pass to **kwargs.",
224
+ )
225
+ scheduler_fn: ImportString = Field(
226
+ default="torch.optim.lr_scheduler.LinearLR",
227
+ description="Scheduler function import string. Should come from torch.optim.lr_scheduler.",
228
+ )
229
+ scheduler_params: dict = Field(
230
+ default={},
231
+ description="Dictionary of scheduler parameters to pass to **kwargs.",
232
+ )
233
+
234
+
235
+ class TrainingConfig(Config):
236
+ """Configuration schema for a Trainer. Contains all parameters needed to instantiate a Trainer. Hyperparameter defaults are set according to experimentally-determined best practice in the original paper.
237
+
238
+ TODO: Pydantic configs will likely be deprecated or heavily refactored in the near future.
239
+ """
240
+
241
+ common: TrainingCommonConfig = Field(
242
+ default=TrainingCommonConfig(), description="TrainingCommonConfig object."
243
+ )
244
+ hyperparameters: TrainingHyperparameterConfig = Field(
245
+ default=TrainingHyperparameterConfig(),
246
+ description="TrainingHyperparameterConfig object.",
247
+ )
248
+ optimizer: TrainingOptimizerConfig = Field(
249
+ default=TrainingOptimizerConfig(), description="TrainingOptimizerConfig object."
250
+ )