ledidi 1.0.0__py3.9.egg

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.
EGG-INFO/PKG-INFO ADDED
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.1
2
+ Name: ledidi
3
+ Version: 1.0.0
4
+ Summary: Ledidi is an optimization approach for designing edits to biological sequences.
5
+ Home-page: http://pypi.python.org/pypi/ledidi/
6
+ Author: Jacob Schreiber and Yang Lu
7
+ Author-email: jmschreiber91@gmail.com
8
+ License: LICENSE.txt
9
+ License-File: LICENSE
EGG-INFO/SOURCES.txt ADDED
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ ledidi/__init__.py
5
+ ledidi/ledidi.py
6
+ ledidi.egg-info/PKG-INFO
7
+ ledidi.egg-info/SOURCES.txt
8
+ ledidi.egg-info/dependency_links.txt
9
+ ledidi.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+
EGG-INFO/top_level.txt ADDED
@@ -0,0 +1 @@
1
+ ledidi
EGG-INFO/zip-safe ADDED
@@ -0,0 +1 @@
1
+
ledidi/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ # __init__.py
2
+ # Authors: Jacob Schreiber <jmschreiber91@gmail.com>
3
+
4
+ __version__ = '1.0.0'
5
+
6
+ from .ledidi import Ledidi
Binary file
ledidi/ledidi.py ADDED
@@ -0,0 +1,227 @@
1
+ # ledidi.py
2
+ # Authors: Jacob Schreiber <jmschreiber91@gmail.com>
3
+ # adapted from code written by Yang Lu
4
+
5
+ import time
6
+ import torch
7
+
8
+ class Ledidi(torch.nn.Module):
9
+ """Ledidi is a method for editing categorical sequences.
10
+
11
+ Ledidi is a method for editing categorical sequences, such as those
12
+ comprised of nucleotides or amino acids, to exhibit desired properties in
13
+ a small number of edits. It does so through the use of an oracle model,
14
+ which is a differentiable model that accepts a categorical sequence as
15
+ input and makes relevant predictions. For instance, the model might take
16
+ in one-hot encoded nucleotide sequence and predict the strength of binding
17
+ for a particular transcription factor.
18
+
19
+ Given a sequence and a desired output, Ledidi uses gradient descent to
20
+ design edits that bring the predicted output from the model closer to the
21
+ desired output. Because the sequences that predictions are being made for
22
+ must be categorical this involves using the Gumbel-softmax
23
+ reparameterization trick.
24
+
25
+
26
+ Parameters
27
+ ----------
28
+ model: torch.nn.Module
29
+ A model to use as an oracle that will be frozen as a part of the
30
+ Ledidi procedure.
31
+
32
+ shape: tuple of two integers
33
+ The number of categories and the number of positions, respectively,
34
+ in the sequence to be edited. For nucleotides this might be (4, 1000).
35
+
36
+ target: int or None
37
+ When given a multi-task model, the target to slice out and feed into
38
+ output_loss when calculating the gradient. If None, perform no slicing.
39
+ Default is None.
40
+
41
+ input_loss: torch.nn.Loss, optional
42
+ A loss to apply to the input space. By default this is the L1 loss
43
+ which corresponds to the number of positions that have been edited.
44
+ This loss is also divided by 2 to account for each edit changing
45
+ two values within that position. Default is torch.nn.L1Loss.
46
+
47
+ output_loss: torch.nn.Loss, optional
48
+ A loss to apply to the output space. By default this is the L2 loss
49
+ which corresponds to the mean squared error between the predicted values
50
+ and the desired values.
51
+
52
+ tau: float, positive, optional
53
+ The sharpness of the sampled values from the Gumbel distribution used
54
+ to generate the one-hot encodings at each step. Higher values mean
55
+ sharper, i.e., more closely match the argmax of each position.
56
+ Default is 1.
57
+
58
+ l: float, positive, optional
59
+ The mixing weight parameter between the input loss and the output loss,
60
+ applied to the output loss. The larger this value is the more important
61
+ it is that the output loss is minimized. Default is 100.
62
+
63
+ batch_size: int, optional
64
+ The number of sequences to generate at each step and average loss over.
65
+ Default is 32.
66
+
67
+ max_iter: int, optional
68
+ The maximum number of iterations to continue generating samples.
69
+ Default is 5000.
70
+
71
+ report_iter: int optional
72
+ The number of iterations to perform before reporting results of the
73
+ optimization. Default is 100.
74
+
75
+ lr: float, optional
76
+ The learning rate of the procedure. Default is 1e-2.
77
+
78
+ input_mask: torch.Tensor or None, shape=(shape[-1],)
79
+ A mask indicating what positions cannot be edited. This will set the
80
+ initial weights mask to -inf at those positions. If None, no positions
81
+ are masked out. Default is None.
82
+
83
+ eps: float, optional
84
+ The epsilon to add to the one-hot encoding. Because the first step
85
+ of the procedure is to take log(X + eps) the smaller eps is the
86
+ higher a value in the design weight needs to be achieved before
87
+ an edit can be induced. Default is 1e-4.
88
+
89
+ verbose: bool, optional
90
+ Whether to print the loss during design. Default is True.
91
+ """
92
+
93
+ def __init__(self, model, shape, target=None, input_loss=torch.nn.L1Loss(
94
+ reduction='sum'), output_loss=torch.nn.MSELoss(), tau=1, l=100,
95
+ batch_size=32, max_iter=5000, report_iter=100, lr=1e-2, input_mask=None,
96
+ eps=1e-4, verbose=True):
97
+ super().__init__()
98
+
99
+ for param in model.parameters():
100
+ param.requires_grad = False
101
+
102
+ self.model = model.eval()
103
+ self.input_loss = input_loss
104
+ self.output_loss = output_loss
105
+ self.tau = tau
106
+ self.l = l
107
+ self.batch_size = batch_size
108
+ self.max_iter = max_iter
109
+ self.report_iter = report_iter
110
+ self.lr = lr
111
+ self.input_mask = input_mask
112
+ self.eps = eps
113
+ self.verbose = verbose
114
+
115
+ if target is None:
116
+ self.target = slice(target)
117
+ else:
118
+ self.target = target
119
+
120
+ self.weights = torch.nn.Parameter(torch.zeros(1, *shape,
121
+ dtype=torch.float32, requires_grad=True))
122
+
123
+ def forward(self, X):
124
+ """Generate a set of edits given a sequence.
125
+
126
+ This method will take in the one-hot encoded sequence and the current
127
+ learned weight filter and propose edits based on the Gumbel-softmax
128
+ distribution.
129
+
130
+
131
+ Parameters
132
+ ----------
133
+ X: torch.Tensor, shape=(1, n_channels, length)
134
+ A tensor containing a single one-hot encoded sequence to propose
135
+ edits for. This sequence is then expanded out to the desired batch
136
+ size to generate a batch of edits.
137
+
138
+
139
+ Returns
140
+ -------
141
+ y: torch.Tensor, shape=(batch_size, n_channels, length)
142
+ A tensor containing a batch of one-hot encoded sequences which
143
+ may contain one or more edits compared to the sequence that was
144
+ passed in.
145
+ """
146
+
147
+ logits = torch.log(X + self.eps) + self.weights
148
+ logits = logits.expand(self.batch_size, *(-1 for i in range(X.ndim-1)))
149
+ return torch.nn.functional.gumbel_softmax(logits, tau=self.tau,
150
+ hard=True, dim=1)
151
+
152
+ def fit_transform(self, X, y_bar):
153
+ """Appply the Ledidi procedure to design edits for a sequence.
154
+
155
+ This procedure takes in a single sequence and a desired output from
156
+ the model and designs edits that cause the model to predict the desired
157
+ output. This is done primarily by learning a weight matrix of logits
158
+ that can be added the log'd one-hot encoded sequence. These weights
159
+ are the only weights learned during the procedure.
160
+
161
+
162
+ Parameters
163
+ ----------
164
+ X: torch.Tensor, shape=(1, n_channels, length)
165
+ A tensor containing a single one-hot encoded sequence to propose
166
+ edits for. This sequence is then expanded out to the desired batch
167
+ size to generate a batch of edits.
168
+
169
+ y_bar: torch.Tensor, shape=(1, *)
170
+ The desired output from the model. Any shape for this tensor is
171
+ permissable so long as the `output_loss` function can handle
172
+ comparing it to the output from the given model.
173
+
174
+
175
+ Returns
176
+ -------
177
+ y: torch.Tensor, shape=(batch_size, n_channels, length)
178
+ A tensor containing a batch of one-hot encoded sequences which
179
+ may contain one or more edits compared to the sequence that was
180
+ passed in.
181
+ """
182
+
183
+ optimizer = torch.optim.AdamW((self.weights,), lr=self.lr)
184
+
185
+ if self.input_mask is not None:
186
+ self.weights.requires_grad = False
187
+ self.weights.T[self.input_mask] = float("-inf")
188
+ self.weights[X.type(torch.bool)] = 0
189
+ self.weights.requires_grad = True
190
+
191
+ y_hat = self.model(X)[:, self.target]
192
+
193
+ output_loss = self.output_loss(y_hat, y_bar).item()
194
+ best_total_loss = self.l * output_loss
195
+ best_sequence = X
196
+
197
+ if self.verbose:
198
+ print(("iter=I\tinput_loss=0\toutput_loss={:4.4}\t" +
199
+ "total_loss={:4.4}").format(output_loss, best_total_loss))
200
+ tic = time.time()
201
+
202
+ for i in range(self.max_iter+1):
203
+ X_hat = self(X)
204
+ y_hat = self.model(X_hat)[:, self.target]
205
+
206
+ input_loss = self.input_loss(X_hat, X) / (X_hat.shape[0] * 2)
207
+ output_loss = self.output_loss(y_hat, y_bar)
208
+
209
+ total_loss = input_loss + self.l * output_loss
210
+ total_loss_ = total_loss.item()
211
+
212
+ if self.verbose and i % self.report_iter == 0:
213
+ print(("iter={}\tinput_loss={:4.4}\toutput_loss={:4.4}\t" +
214
+ "total_loss={:4.4}\ttime={:4.4}").format(i,
215
+ input_loss.item(), output_loss.item(),
216
+ total_loss_, time.time() - tic))
217
+ tic = time.time()
218
+
219
+ optimizer.zero_grad()
220
+ total_loss.backward()
221
+ optimizer.step()
222
+
223
+ if total_loss_ < best_total_loss:
224
+ best_total_loss = total_loss_
225
+ best_sequence = torch.clone(X_hat)
226
+
227
+ return best_sequence
ledidi.egg-link ADDED
@@ -0,0 +1,2 @@
1
+ /users/jmschr/github/ledidi
2
+ .