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 +10 -0
- EGG-INFO/SOURCES.txt +8 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/top_level.txt +1 -0
- EGG-INFO/zip-safe +1 -0
- ledidi/__init__.py +6 -0
- ledidi/__pycache__/__init__.cpython-38.pyc +0 -0
- ledidi/__pycache__/ledidi.cpython-38.pyc +0 -0
- ledidi/ledidi.py +278 -0
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 @@
|
|
|
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
|
Binary file
|
|
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
|