ledidi 0.0.2__py3.8.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,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: ledidi
3
+ Version: 0.0.2
4
+ Summary: Ledidi is an optimization approach for designing edits to biological sequences.
5
+ Home-page: http://pypi.python.org/pypi/ledidi/
6
+ Author: Yang Lu and Jacob Schreiber
7
+ Author-email: jmschreiber91@gmail.com
8
+ License: LICENSE.txt
9
+ Description: UNKNOWN
10
+ Platform: UNKNOWN
EGG-INFO/SOURCES.txt ADDED
@@ -0,0 +1,8 @@
1
+ README.md
2
+ setup.py
3
+ ledidi/__init__.py
4
+ ledidi/ledidi.py
5
+ ledidi.egg-info/PKG-INFO
6
+ ledidi.egg-info/SOURCES.txt
7
+ ledidi.egg-info/dependency_links.txt
8
+ 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: Yang Lu <ylu465@uw.edu> and Jacob Schreiber <jmschreiber91@gmail.com>
3
+
4
+ from ledidi import ledidi
5
+
6
+ __version__ = '0.0.2'
Binary file
ledidi/ledidi.py ADDED
@@ -0,0 +1,278 @@
1
+ # ledidi.py
2
+ # Authors: Yang Lu <ylu465@uw.edu> and Jacob Schreiber <jmschreiber91@gmail.com>
3
+
4
+ MIN_W = 0.001
5
+
6
+ import numpy
7
+ from scipy.special import logsumexp
8
+
9
+ import tensorflow as tf
10
+ import tensorflow.keras.backend as k
11
+
12
+ class TensorFlowRegressor():
13
+ """A wrapper for a TensorFlow regression model.
14
+
15
+ This wrapper holds a TensorFlow model that has regression outputs. The
16
+ methods implemented here are useful for calculating gradients and losses
17
+ given certain masks.
18
+ """
19
+
20
+ def __init__(self, model, verbose=False):
21
+ """
22
+ Parameters
23
+ ----------
24
+ model : TensorFlow model
25
+ The regression model that is being wrapped.
26
+
27
+ verbose : bool, optional
28
+ Whether to print out logs related to use of this object.
29
+ Default is False.
30
+ """
31
+
32
+ self.verbose = verbose
33
+ self._model = model
34
+ self._input = model.input
35
+ self._output = model.output
36
+
37
+ self._input_shape = k.int_shape(self._input)
38
+ self._output_shape = k.int_shape(self._output)
39
+
40
+ if self.verbose == True:
41
+ print("TensorFlowRegressor model input_shape={}".format(
42
+ self._input_shape))
43
+ print("TensorFlowRegressor model output_shape={}".format(
44
+ self._output_shape))
45
+
46
+ def loss_gradient(self, x, y, mask=None):
47
+ """Compute the gradient of the loss function || f(x)-y ||^2 w.r.t. `x`.
48
+
49
+ Parameters
50
+ ----------
51
+ x : numpy.ndarray
52
+ The input sequence to the model.
53
+
54
+ y : numpy.ndarray
55
+ The output to calculate the loss w.r.t.
56
+
57
+ mask : numpy.ndarray or None, optional
58
+ A binary mask indicating the outputs to calculate the loss over.
59
+ Default is None.
60
+
61
+ Returns
62
+ -------
63
+ grads : numpy.ndarray
64
+ An array of gradients with the same shape as `x`.
65
+ """
66
+
67
+ if mask is None:
68
+ mask = numpy.ones(y.shape, dtype='float32')
69
+
70
+ mask = tf.convert_to_tensor(mask / mask.sum())
71
+
72
+ x_var = tf.convert_to_tensor(x)
73
+ with tf.GradientTape() as tape:
74
+ tape.watch(x_var)
75
+ pred_y = self._model(x_var, training=False)
76
+ loss = k.sum(tf.multiply(mask, k.square(pred_y - y)))
77
+
78
+ grads = tape.gradient(loss, [x_var])[0]
79
+ assert grads.shape == x.shape
80
+ return grads
81
+
82
+
83
+ def loss(self, x, y, mask=None):
84
+ """Compute the loss function || f(x)-y ||^2
85
+
86
+ Parameters
87
+ ----------
88
+ x : numpy.ndarray
89
+ The input sequence to the model.
90
+
91
+ y : numpy.ndarray
92
+ The output to calculate the loss w.r.t.
93
+
94
+ mask : numpy.ndarray or None, optional
95
+ A binary mask indicating the outputs to calculate the loss over.
96
+ Default is None.
97
+
98
+ Returns
99
+ -------
100
+ loss : float64
101
+ The loss calculated between the provided output and the model
102
+ predictions.
103
+ """
104
+
105
+ if mask is None:
106
+ mask = numpy.ones(y.shape, dtype='float64')
107
+
108
+ pred_y = self._model.predict(x.astype('float64'))
109
+ assert pred_y.shape == y.shape
110
+
111
+ loss = numpy.sum(mask * numpy.square(pred_y - y)) / mask.sum()
112
+ return loss
113
+
114
+ class Ledidi(object):
115
+ """The Ledidi sequence designer.
116
+
117
+ This object is a wrapper for a differentiable model and the hyperparameters
118
+ of the optimization process. Any model object can be used as long as the
119
+ functions `loss_gradient` and `loss` are implemented (see above).
120
+
121
+ Ledidi optimizes the following objective function:
122
+
123
+ min_x ||X - X_{0}||_{1} + lambda||f(X) - y_hat||_{2}^{2}
124
+
125
+ where the first term is the sequence loss, i.e. the number of edits made to
126
+ the sequence, and the second term is the output loss, i.e. the Euclidean
127
+ distance between the output from the model and the given output.
128
+
129
+ This objective is not easy to solve directly because X is discrete but
130
+ Ledidi uses a Gumbel-softmax reparameterization to overcome this difficulty.
131
+
132
+ Parameters
133
+ ----------
134
+ model : object
135
+ A neural network or a wrapper for a neural network that has the
136
+ `loss` and `loss_gradient` functions implemented. These models can come
137
+ from any framework.
138
+
139
+ tau : float64, positive, optional
140
+ The temperature of the Gumbel-softmax reparameterization. High values
141
+ force the continuous version of the sequence to approach the uniform
142
+ distribution whereas low values force it to match the discrete
143
+ distribution smoothly. Default is 3.
144
+
145
+ l : float64, positive, optional
146
+ The weight of the second term in the loss function. Setting l to a
147
+ small value encourages making a small number of edits, whereas setting
148
+ it to a large value encourages matching the given distribution more
149
+ closely. Default is 10.
150
+
151
+ max_iter : int, positive
152
+ The maximum number of iterations to perform before ending optimization.
153
+ Default is 100.
154
+
155
+ lr : float, positive
156
+ The learning rate, i.e. the step size when making updates. When this is
157
+ small, more precise edits can be found, but optimization takes longer.
158
+ Default is 1e-3.
159
+
160
+ mask : numpy.ndarray, optional
161
+ An array with the same shape as the output of the model that indicates
162
+ which losses should be used in optimization, i.e. the outputs that
163
+ the user cares to optimize over.
164
+
165
+ early_stopping : int, optional
166
+ The number of iterations with no improvement in the objective function
167
+ to perform before ending optimization, i.e. the patience. Default is
168
+ 10.
169
+
170
+ min_x : float, optional
171
+ A parameter of the Gumbel-softmax distribution. Default is 0.01.
172
+
173
+ max_x : float, optional
174
+ A parameter of the Gumbel-softmax distribution. Default is 0.99.
175
+
176
+ verbose: bool, optional
177
+ Whether to print logs associated with this object. Default is True.
178
+ """
179
+
180
+ def __init__(self, model, tau=3, l=10, max_iter=100, lr=1e-3, mask=None,
181
+ early_stopping=100, min_x=0.01, max_x=0.99, verbose=True):
182
+ self.model = model
183
+ self.tau = tau
184
+ self.l = l
185
+ self.max_iter = max_iter
186
+ self.lr = lr
187
+ self.early_stopping = early_stopping
188
+ self.min_x = min_x
189
+ self.max_x = max_x
190
+ self.mask = mask
191
+ self.verbose = verbose
192
+
193
+ def _from_x_to_w(self, x, tau, g, min_x=0.01, max_x=0.99):
194
+ x = numpy.maximum(x, min_x)
195
+ x = numpy.minimum(x, max_x)
196
+ w = numpy.exp(numpy.log(x) * tau - g)
197
+ w = numpy.maximum(w, MIN_W)
198
+ return w
199
+
200
+ def _from_w_to_x(self, w, tau, g):
201
+ w = numpy.maximum(w, MIN_W)
202
+ x = numpy.array((numpy.log(w) + g) / tau)[0]
203
+ x = numpy.exp(x.T - logsumexp(x, axis=1)).T
204
+ x = numpy.expand_dims(x, 0)
205
+ return x
206
+
207
+ def fit_transform(self, seq, epi_bar):
208
+ missing_indices = numpy.where(numpy.sum(seq[0], axis=1)<=0)[0]
209
+ tau = self.tau
210
+
211
+ if self.verbose:
212
+ print('batch_missing_loc_indices={}'.format(missing_indices.shape[0]))
213
+
214
+ g = -numpy.log(-numpy.log(numpy.random.uniform(MIN_W, 1, size=seq.shape)))
215
+ curr_w = self._from_x_to_w(seq, tau, g, self.min_x, self.max_x)
216
+ curr_x = self._from_w_to_x(curr_w, tau, g)
217
+ curr_x[0, missing_indices, :] = 0
218
+
219
+ ref_x = curr_x.copy()
220
+
221
+ curr_w_surrogate = curr_w.copy()
222
+ curr_x_surrogate = curr_x.copy()
223
+
224
+ best_total_loss = float("inf")
225
+ best_total_loss_discrete = float("inf")
226
+ best_seq = None
227
+ early_stopping_iters = 0
228
+
229
+ for i in range(self.max_iter):
230
+ curr_x_discrete = numpy.zeros_like(curr_x, dtype=int)
231
+ curr_x_discrete[0, numpy.arange(seq.shape[1]), numpy.argmax(curr_x[0], axis=1)] = 1
232
+ curr_x_discrete[0, missing_indices, :] = 0
233
+
234
+ seq_loss = numpy.sum(numpy.fabs(curr_x - ref_x))
235
+ seq_loss_discrete = numpy.sum(numpy.abs(curr_x_discrete - seq)) / 2
236
+
237
+ epi_loss = self.model.loss(curr_x, epi_bar, mask=self.mask)
238
+ epi_loss_discrete = self.model.loss(curr_x_discrete, epi_bar, mask=self.mask)
239
+
240
+ total_loss = seq_loss + self.l * epi_loss
241
+ total_loss_discrete = seq_loss_discrete + self.l * epi_loss_discrete
242
+
243
+ if self.verbose:
244
+ print('iter={}\tseq_loss={:4.4}\tseq_loss_discrete={:4.4}\tepi_loss={:4.4}\tepi_loss_discrete={:4.4}\ttotal_loss={:4.4}\ttotal_loss_discrete:{:4.4}'.format(
245
+ i, seq_loss, seq_loss_discrete, epi_loss, epi_loss_discrete, total_loss, total_loss_discrete))
246
+
247
+ loss_to_x_grad = self.model.loss_gradient(curr_x_surrogate, epi_bar, mask=self.mask)
248
+
249
+ x_to_w_grad = (curr_x_surrogate - curr_x_surrogate*curr_x_surrogate) / tau
250
+ x_to_ref_sgn = numpy.asarray((curr_x_surrogate - ref_x)>=0, dtype=float)
251
+ x_to_ref_sgn[x_to_ref_sgn<=0]=-1
252
+ loss_to_w_grad = (x_to_ref_sgn + self.l * loss_to_x_grad) * x_to_w_grad
253
+
254
+ new_w = curr_w_surrogate - self.lr * loss_to_w_grad
255
+ curr_w_surrogate = new_w + (1.0 * i / (i+2)) * (new_w - curr_w)
256
+ curr_w = new_w
257
+
258
+ g = -numpy.log(-numpy.log(numpy.random.uniform(MIN_W, 1, size=seq.shape)))
259
+ curr_x = self._from_w_to_x(curr_w, tau, g)
260
+ curr_x_surrogate = self._from_w_to_x(curr_w_surrogate, tau, g)
261
+
262
+ curr_x[0, missing_indices, :] = 0
263
+ curr_x_surrogate[0, missing_indices, :] = 0
264
+
265
+ if total_loss_discrete < best_total_loss_discrete:
266
+ best_total_loss_discrete = total_loss_discrete
267
+ best_sequence = curr_x_discrete.copy()
268
+
269
+ if total_loss < best_total_loss:
270
+ best_total_loss = total_loss
271
+ early_stopping_iters = 0
272
+ else:
273
+ early_stopping_iters += 1
274
+
275
+ if early_stopping_iters == self.early_stopping:
276
+ break
277
+
278
+ return best_sequence