cherimoya 0.0.1__py3-none-any.whl

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.
cherimoya/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ # cherimoya
2
+ # Author: Jacob Schreiber
3
+
4
+ from .cherimoya import Cherimoya
5
+
6
+ __version__ = '0.0.1'
cherimoya/cherimoya.py ADDED
@@ -0,0 +1,604 @@
1
+ # cherimoya.py
2
+ # Author: Jacob Schreiber <jmschreiber91@gmail.com>
3
+
4
+ """
5
+ An implementing of the Cherimoya deep learning model, which is a compact
6
+ architecture for predicting genomic modalities from sequence alone.
7
+ """
8
+
9
+ import h5py
10
+ import time
11
+ import numpy
12
+ import torch
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import triton
17
+ import triton.language as tl
18
+ import itertools
19
+
20
+ from .losses import MNLLLoss
21
+ from .losses import log1pMSELoss
22
+ from .losses import _mixture_loss
23
+
24
+ from .performance import pearson_corr
25
+ from .performance import calculate_performance_measures
26
+
27
+ from tqdm import tqdm
28
+
29
+ from tangermeme.predict import predict
30
+ from bpnetlite.logging import Logger
31
+
32
+ torch.backends.cudnn.benchmark = True
33
+ torch.set_float32_matmul_precision('high')
34
+
35
+
36
+ def autotune_configs():
37
+ num_warps = [4, 8, 16]
38
+ num_stages = [2, 3, 4, 5]
39
+ BLOCK_Ls = [32, 64, 128, 256]
40
+
41
+ configs = []
42
+ for num_warp, num_stage, L in itertools.product(num_warps, num_stages, BLOCK_Ls):
43
+ configs.append(triton.Config({
44
+ 'num_warps': num_warp,
45
+ 'num_stages': num_stage,
46
+ 'BLOCK_L': L
47
+ }))
48
+ return configs
49
+
50
+
51
+ @triton.autotune(
52
+ configs = autotune_configs(),
53
+ key=['C', 'L'],
54
+ )
55
+ @triton.jit
56
+ def fwd_conv_kernel(
57
+ X_ptr, W_ptr, Y_ptr, Mean_ptr, Rstd_ptr,
58
+ stride_xn, dilation, eps,
59
+ L: tl.constexpr,
60
+ C: tl.constexpr,
61
+ BLOCK_C: tl.constexpr,
62
+ BLOCK_L: tl.constexpr
63
+ ):
64
+
65
+ pid_n = tl.program_id(0)
66
+ offs_c = tl.arange(0, BLOCK_C)[None, :]
67
+ mask_c = offs_c < C
68
+
69
+ w_idx = W_ptr + offs_c
70
+ w0 = tl.load(w_idx, mask=mask_c, other=0.0).to(tl.float32)
71
+ w1 = tl.load(w_idx + C, mask=mask_c, other=0.0).to(tl.float32)
72
+ w2 = tl.load(w_idx + C*2, mask=mask_c, other=0.0).to(tl.float32)
73
+
74
+ running_sum = 0.0
75
+ running_sq_sum = 0.0
76
+ for l_start in tl.range(0, L, BLOCK_L):
77
+ offs = l_start + tl.arange(0, BLOCK_L)[:, None]
78
+ offs_l = offs - dilation
79
+ offs_r = offs + dilation
80
+
81
+ mask = (offs < L) & mask_c
82
+ mask_l = (offs_l >= 0) & mask
83
+ mask_r = (offs_r < L) & mask
84
+
85
+ x_idx = X_ptr + pid_n * stride_xn + offs_c
86
+ x1 = tl.load(x_idx + offs*C, mask=mask, other=0.0).to(tl.float32)
87
+ x0 = tl.load(x_idx + offs_l*C, mask=mask_l, other=0.0).to(tl.float32)
88
+ x2 = tl.load(x_idx + offs_r*C, mask=mask_r, other=0.0).to(tl.float32)
89
+
90
+ conv = x0*w0 + x1*w1 + x2*w2
91
+ conv2 = conv * conv
92
+
93
+ running_sum += tl.sum(conv)
94
+ running_sq_sum += tl.sum(conv2)
95
+
96
+ count = L * C
97
+ mean = running_sum / count
98
+ var = (running_sq_sum / count) - (mean * mean)
99
+ rstd = 1.0 / tl.sqrt(var + eps)
100
+
101
+ tl.store(Mean_ptr + pid_n, mean)
102
+ tl.store(Rstd_ptr + pid_n, rstd)
103
+
104
+ for l_start in tl.range(0, L, BLOCK_L):
105
+ offs = l_start + tl.arange(0, BLOCK_L)[:, None]
106
+ offs_l = offs - dilation
107
+ offs_r = offs + dilation
108
+
109
+ mask = (offs < L) & mask_c
110
+ mask_l = (offs_l >= 0) & mask
111
+ mask_r = (offs_r < L) & mask
112
+
113
+ x_idx = X_ptr + pid_n * stride_xn + offs_c
114
+ x1 = tl.load(x_idx + offs*C, mask=mask, other=0.0).to(tl.float32)
115
+ x0 = tl.load(x_idx + offs_l*C, mask=mask_l, other=0.0).to(tl.float32)
116
+ x2 = tl.load(x_idx + offs_r*C, mask=mask_r, other=0.0).to(tl.float32)
117
+
118
+ conv = x0*w0 + x1*w1 + x2*w2
119
+ x_hat = (conv - mean) * rstd
120
+
121
+ y_idx = Y_ptr + pid_n * stride_xn + offs * C + offs_c
122
+ tl.store(y_idx, x_hat, mask=mask)
123
+
124
+
125
+ @triton.autotune(
126
+ configs = autotune_configs(),
127
+ key=['C', 'L']
128
+ )
129
+ @triton.jit
130
+ def bwd_conv_kernel(
131
+ dY_ptr, X_ptr, W_ptr, Mean_ptr, Rstd_ptr,
132
+ dX_ptr, dW_ptr, stride_xn, dilation,
133
+ L: tl.constexpr,
134
+ C: tl.constexpr,
135
+ BLOCK_C: tl.constexpr,
136
+ BLOCK_L: tl.constexpr
137
+ ):
138
+ pid_n = tl.program_id(0)
139
+ offs_c = tl.arange(0, BLOCK_C)
140
+ mask_c = offs_c < C
141
+
142
+ mean = tl.load(Mean_ptr + pid_n)
143
+ rstd = tl.load(Rstd_ptr + pid_n)
144
+
145
+ w_idx = W_ptr + offs_c
146
+ w0 = tl.load(w_idx, mask=mask_c)[None, :].to(tl.float32)
147
+ w1 = tl.load(w_idx + C, mask=mask_c)[None, :].to(tl.float32)
148
+ w2 = tl.load(w_idx + C*2, mask=mask_c)[None, :].to(tl.float32)
149
+
150
+ sum_dy = 0.0
151
+ sum_dy_xhat = 0.0
152
+
153
+ mask_c = mask_c[None, :]
154
+ offs_c = offs_c[None, :]
155
+
156
+ for l_start in tl.range(0, L, BLOCK_L):
157
+ offs = l_start + tl.arange(0, BLOCK_L)[:, None]
158
+ offs_l = offs - dilation
159
+ offs_r = offs + dilation
160
+
161
+ mask = (offs < L) & mask_c
162
+ mask_l = (offs_l >= 0) & mask
163
+ mask_r = (offs_r < L) & mask
164
+
165
+ x_idx = X_ptr + pid_n * stride_xn + offs_c
166
+ x1 = tl.load(x_idx + offs*C, mask=mask, other=0.0).to(tl.float32)
167
+ x0 = tl.load(x_idx + offs_l*C, mask=mask_l, other=0.0).to(tl.float32)
168
+ x2 = tl.load(x_idx + offs_r*C, mask=mask_r, other=0.0).to(tl.float32)
169
+
170
+ conv = x0*w0 + x1*w1 + x2*w2
171
+ x_hat = (conv - mean) * rstd
172
+
173
+ y_idx = dY_ptr + pid_n * stride_xn + offs * C + offs_c
174
+ dy = tl.load(y_idx, mask=mask, other=0.0).to(tl.float32)
175
+
176
+ sum_dy += tl.sum(dy)
177
+ sum_dy_xhat += tl.sum(dy * x_hat)
178
+
179
+ dw0 = tl.zeros((1, BLOCK_C), dtype=tl.float32)
180
+ dw1 = tl.zeros((1, BLOCK_C), dtype=tl.float32)
181
+ dw2 = tl.zeros((1, BLOCK_C), dtype=tl.float32)
182
+
183
+ for l_start in tl.range(0, L, BLOCK_L):
184
+ offs = l_start + tl.arange(0, BLOCK_L)[:, None]
185
+ offs_l = offs - dilation
186
+ offs_r = offs + dilation
187
+
188
+ mask = (offs < L) & mask_c
189
+ mask_l = (offs_l >= 0) & mask
190
+ mask_r = (offs_r < L) & mask
191
+
192
+ x_idx = X_ptr + pid_n * stride_xn + offs_c
193
+ x1 = tl.load(x_idx + offs*C, mask=mask, other=0.0).to(tl.float32)
194
+ x0 = tl.load(x_idx + offs_l*C, mask=mask_l, other=0.0).to(tl.float32)
195
+ x2 = tl.load(x_idx + offs_r*C, mask=mask_r, other=0.0).to(tl.float32)
196
+
197
+ conv = x0*w0 + x1*w1 + x2*w2
198
+ x_hat = (conv - mean) * rstd
199
+
200
+ ###
201
+
202
+ dy_idx = dY_ptr + pid_n * stride_xn + offs * C + offs_c
203
+ dy = tl.load(dy_idx, mask=mask, other=0.0).to(tl.float32)
204
+
205
+ count = L * C
206
+ d_conv = (rstd / count) * (count * dy - sum_dy - x_hat * sum_dy_xhat)
207
+
208
+ dw0 += tl.sum(d_conv * x0, axis=0)[None, :]
209
+ dw1 += tl.sum(d_conv * x1, axis=0)[None, :]
210
+ dw2 += tl.sum(d_conv * x2, axis=0)[None, :]
211
+
212
+ dx_idx0 = dX_ptr + pid_n * stride_xn + offs * C + offs_c
213
+
214
+ dx1 = tl.load(dx_idx0, mask=mask, other=0.0)
215
+ dx0 = tl.load(dx_idx0 - dilation*C, mask=mask_l, other=0.0)
216
+ dx2 = tl.load(dx_idx0 + dilation*C, mask=mask_r, other=0.0)
217
+
218
+ dx1 += d_conv * w1
219
+ dx0 += d_conv * w0
220
+ dx2 += d_conv * w2
221
+
222
+ tl.store(dx_idx0, dx1, mask=mask)
223
+ tl.store(dx_idx0 - dilation*C, dx0, mask=mask_l)
224
+ tl.store(dx_idx0 + dilation*C, dx2, mask=mask_r)
225
+
226
+
227
+ dw_idx = dW_ptr + pid_n * (C * 3) + offs_c
228
+ tl.store(dw_idx, dw0, mask=mask_c)
229
+ tl.store(dw_idx + C, dw1, mask=mask_c)
230
+ tl.store(dw_idx + C*2, dw2, mask=mask_c)
231
+
232
+
233
+ class FusedDilatedConvNormFunc(torch.autograd.Function):
234
+ @staticmethod
235
+ def forward(ctx, x, w, dilation):
236
+ N, L, C = x.shape
237
+ BLOCK_C = triton.next_power_of_2(C)
238
+ eps = 1e-3
239
+
240
+ mean = torch.empty((N,), dtype=torch.float32, device=x.device)
241
+ rstd = torch.empty((N,), dtype=torch.float32, device=x.device)
242
+ y = torch.empty_like(x)
243
+
244
+ fwd_conv_kernel[(N,)](
245
+ x, w, y, mean, rstd,
246
+ x.stride(0), dilation, eps,
247
+ L, C, BLOCK_C=BLOCK_C
248
+ )
249
+
250
+ ctx.save_for_backward(x, w, mean, rstd)
251
+ ctx.dilation = dilation
252
+ return y
253
+
254
+ @staticmethod
255
+ def backward(ctx, dy):
256
+ x, w, mean, rstd = ctx.saved_tensors
257
+ N, L, C = x.shape
258
+ BLOCK_C = triton.next_power_of_2(C)
259
+
260
+ dy = dy.contiguous()
261
+ dx = torch.zeros_like(x, dtype=x.dtype)
262
+ dw = torch.empty((N, 3, C), device=x.device, dtype=torch.float32)
263
+
264
+ bwd_conv_kernel[(N,)](
265
+ dy, x, w, mean, rstd, dx, dw,
266
+ x.stride(0), ctx.dilation,
267
+ L, C, BLOCK_C,
268
+ )
269
+
270
+ dw = dw.sum(dim=0)
271
+ return dx.to(x.dtype), dw.to(x.dtype), None
272
+
273
+
274
+ ###
275
+
276
+
277
+ class CheriBlock(torch.nn.Module):
278
+ def __init__(self, n_filters, dilation, eps=0.01):
279
+ super().__init__()
280
+ self.n_filters = n_filters
281
+ self.dilation = dilation
282
+
283
+ self.conv_weight = torch.nn.Parameter(torch.randn(3, n_filters))
284
+ self.linear1 = torch.nn.Linear(n_filters, 2*n_filters, bias=False)
285
+ self.linear2 = torch.nn.Linear(2*n_filters, n_filters, bias=False)
286
+ self.gamma = torch.nn.Parameter(torch.ones(1, n_filters) * eps)
287
+ self.activation = torch.nn.GELU(approximate='tanh')
288
+
289
+ torch.nn.init.trunc_normal_(self.conv_weight, std=0.02)
290
+ torch.nn.init.trunc_normal_(self.linear1.weight, std=0.02)
291
+ torch.nn.init.trunc_normal_(self.linear2.weight, std=0.02)
292
+
293
+ def forward(self, X):
294
+ X_conv = FusedDilatedConvNormFunc.apply(X, self.conv_weight, self.dilation)
295
+ X_mlp = self.linear2(self.activation(self.linear1(X_conv)))
296
+ return X + X_mlp * self.gamma
297
+
298
+
299
+ class CheriBlock2(torch.nn.Module):
300
+ def __init__(self, n_filters, dilation, eps=0.01):
301
+ super().__init__()
302
+ self.n_filters = n_filters
303
+ self.dilation = dilation
304
+
305
+ self.conv = torch.nn.Conv1d(n_filters, n_filters, groups=n_filters, dilation=dilation, padding=dilation, kernel_size=3)
306
+ #self.norm = torch.nn.LayerNorm((n_filters, 2114), elementwise_affine=False, bias=False, eps=1e-3)
307
+ self.linear1 = torch.nn.Conv1d(n_filters, 3*n_filters, kernel_size=1, bias=False)
308
+ self.linear2 = torch.nn.Conv1d(3*n_filters, n_filters, kernel_size=1, bias=False)
309
+ self.gamma = torch.nn.Parameter(torch.ones(n_filters, 1) * eps)
310
+ self.activation = torch.nn.GELU(approximate='tanh')
311
+
312
+ torch.nn.init.trunc_normal_(self.conv.weight, std=0.02)
313
+ torch.nn.init.trunc_normal_(self.linear1.weight, std=0.02)
314
+ torch.nn.init.trunc_normal_(self.linear2.weight, std=0.02)
315
+
316
+ def forward(self, X):
317
+ X_conv = self.conv(X)
318
+ X_conv = self.norm(X_conv)
319
+ X_mlp = self.linear2(self.activation(self.linear1(X_conv)))
320
+ X = torch.add(X, X_mlp * self.gamma)
321
+ return X
322
+
323
+
324
+
325
+ class Cherimoya(torch.nn.Module):
326
+ def __init__(self, n_filters=64, n_layers=9, n_outputs=1,
327
+ n_control_tracks=0, name=None, trimming=None,
328
+ single_count_output=True, verbose=True):
329
+ super(Cherimoya, self).__init__()
330
+ self.n_filters = n_filters
331
+ self.n_layers = n_layers
332
+ self.n_outputs = n_outputs
333
+ self.n_control_tracks = n_control_tracks
334
+
335
+ self.name = name or "cherimoya.{}.{}".format(n_filters, n_layers)
336
+ self.trimming = trimming or 46 + sum(2**i for i in range(n_layers))
337
+
338
+ self.iconv = torch.nn.Conv1d(4, n_filters, kernel_size=19, padding=9)
339
+ self.igelu = torch.nn.GELU(approximate='tanh')
340
+
341
+ self.blocks = torch.nn.ModuleList([
342
+ CheriBlock(n_filters, 2**i) for i in range(self.n_layers)
343
+ ])
344
+
345
+ self.fconv = torch.nn.Conv1d(n_filters+n_control_tracks, n_outputs,
346
+ kernel_size=75, padding=37)
347
+
348
+ self.lw0 = torch.nn.Parameter(torch.ones(1))
349
+ self.lw1 = torch.nn.Parameter(torch.ones(1))
350
+
351
+ n_count_control = 1 if n_control_tracks > 0 else 0
352
+ n_count_outputs = 1 if single_count_output else n_outputs
353
+ self.linear = torch.nn.Linear(n_filters+n_count_control, n_count_outputs)
354
+
355
+ torch.nn.init.trunc_normal_(self.iconv.weight, std=0.02)
356
+ torch.nn.init.trunc_normal_(self.fconv.weight, std=0.02)
357
+ torch.nn.init.trunc_normal_(self.linear.weight, std=0.02)
358
+
359
+ torch.nn.init.zeros_(self.iconv.bias)
360
+ torch.nn.init.zeros_(self.fconv.bias)
361
+ torch.nn.init.zeros_(self.linear.bias)
362
+
363
+ self.logger = Logger(["Epoch", "Iteration", "Training Time",
364
+ "Validation Time", "Training MNLL", "Training Count MSE",
365
+ "Validation MNLL", "Validation Profile Pearson",
366
+ "Validation Count Pearson", "Validation Count MSE", "Saved?"],
367
+ verbose=verbose)
368
+
369
+
370
+ @torch.compile(mode='max-autotune')
371
+ def forward(self, X, X_ctl=None):
372
+ """A forward pass of the model.
373
+
374
+ This method takes in a nucleotide sequence X, a corresponding
375
+ per-position value from a control track, and a per-locus value
376
+ from the control track and makes predictions for the profile
377
+ and for the counts. This per-locus value is usually the
378
+ log(sum(X_ctl_profile)+1) when the control is an experimental
379
+ read track but can also be the output from another model.
380
+
381
+ Parameters
382
+ ----------
383
+ X: torch.tensor, shape=(batch_size, 4, length)
384
+ The one-hot encoded batch of sequences.
385
+
386
+ X_ctl: torch.tensor or None, shape=(batch_size, n_strands, length)
387
+ A value representing the signal of the control at each position in
388
+ the sequence. If no controls, pass in None. Default is None.
389
+
390
+ Returns
391
+ -------
392
+ y_profile: torch.tensor, shape=(batch_size, n_strands, out_length)
393
+ The output predictions for each strand trimmed to the output
394
+ length.
395
+ """
396
+
397
+ start, end = self.trimming, X.shape[2] - self.trimming
398
+
399
+ X = self.igelu(self.iconv(X))
400
+ X = X.transpose(1, 2).contiguous()
401
+ for i in range(self.n_layers):
402
+ X = self.blocks[i](X)
403
+
404
+ X = X.transpose(1, 2).contiguous()
405
+ if X_ctl is None:
406
+ X_w_ctl = X
407
+ else:
408
+ X_w_ctl = torch.cat([X, X_ctl], dim=1)
409
+
410
+ y_profile = self.fconv(X_w_ctl)[:, :, start:end]
411
+
412
+ # counts prediction
413
+ X = torch.mean(X[:, :, start-37:end+37].float(), dim=2)
414
+ if X_ctl is not None:
415
+ X_ctl = torch.sum(X_ctl[:, :, start-37:end+37].float(), dim=(1, 2))
416
+ X_ctl = X_ctl.unsqueeze(-1)
417
+ X = torch.cat([X, torch.log(X_ctl+1)], dim=-1)
418
+
419
+ y_counts = self.linear(X)
420
+ return y_profile, y_counts
421
+
422
+
423
+ def fit(self, training_data, muon_optimizer, adam_optimizer, muon_scheduler,
424
+ adam_scheduler, X_valid, X_ctl_valid, y_valid, max_epochs=100, batch_size=64,
425
+ dtype='float32', device='cuda', early_stopping=None):
426
+ """Fit the model to data and validate it periodically.
427
+
428
+ This method controls the training of a BPNet model. It will fit the
429
+ model to examples generated by the `training_data` DataLoader object
430
+ and, if validation data is provided, will validate the model against
431
+ it at the end of each epoch and return those values.
432
+
433
+ Two versions of the model will be saved: the best model found during
434
+ training according to the validation measures, and the final model
435
+ at the end of training. Additionally, a log will be saved of the
436
+ training and validation statistics, e.g. time and performance.
437
+
438
+
439
+ Parameters
440
+ ----------
441
+ training_data: torch.utils.data.DataLoader
442
+ A generator that produces examples to train on. If n_control_tracks
443
+ is greater than 0, must product two inputs, otherwise must produce
444
+ only one input.
445
+
446
+ muon_optimizer: torch.optim.Optimizer
447
+ A Muon optimizer to control the training of the 2D non-head/non-tail layers
448
+ in the model. This is mostly the dense layers and depth-wise convolutions of
449
+ the Cheri blocks.
450
+
451
+ adam_optimizer: torch.optim.Optimizer
452
+ An Adam/W optimizer to control the training of the other parametrers. This
453
+ should be the head/tail layers, the bias terms, and any other parameters
454
+ that are not 2D matrices.
455
+
456
+ muon_scheduler: torch.optim.lr_scheduler
457
+ The scheduler to use for the Muon optimizer. This should likely be a cosine
458
+ decay with a warmup phase.
459
+
460
+ adam_scheduler: torch.optim.lr_scheduler
461
+ The scheduler to use for the Adam/W optimizer. This should likely be the
462
+ same cosine decay with a warmup phase used for the Muon optimizer.
463
+
464
+ X_valid: torch.tensor, shape=(n, 4, length)
465
+ A block of sequences to validate on at the end of each epoch.
466
+
467
+ X_ctl_valid: torch.tensor or None, shape=(n, n_control_tracks, length)
468
+ A block of control sequences to use for making the validation set
469
+ predictions at the end of each epoch. If n_control_tracks is None, pass in
470
+ None. Default is None.
471
+
472
+ y_valid: torch.tensor or None, shape=(n, n_outputs, output_length)
473
+ A block of signals to validate against at the end of each epochs.
474
+
475
+ max_epochs: int
476
+ The maximum number of epochs to train for, as measured by the
477
+ number of times that `training_data` is exhausted. Default is 100.
478
+
479
+ batch_size: int, optional
480
+ The number of examples to include in each batch. Default is 64.
481
+
482
+ dtype: str or torch.dtype
483
+ The torch.dtype to use when training. Usually, this will be torch.float32
484
+ or torch.bfloat16. Default is torch.float32.
485
+
486
+ device: str
487
+ The device to use for training and inference. Typically, this will be
488
+ 'cuda' but can be anything supported by torch. Default is 'cuda'.
489
+
490
+ early_stopping: int or None, optional
491
+ Whether to stop training early. If None, continue training until
492
+ max_epochs is reached. If an integer, continue training until that
493
+ number of epochs has been hit without improvement in performance.
494
+ Default is None.
495
+ """
496
+
497
+ if X_valid is not None:
498
+ y_valid_counts = y_valid.sum(dim=2)
499
+
500
+ if X_ctl_valid is not None:
501
+ X_ctl_valid = (X_ctl_valid,)
502
+
503
+ dtype = getattr(torch, dtype) if isinstance(dtype, str) else dtype
504
+
505
+ iteration = 0
506
+ early_stop_count = 0
507
+ best_loss = float("inf")
508
+ self.logger.start()
509
+
510
+ ###
511
+
512
+ for epoch in range(max_epochs):
513
+ tic = time.time()
514
+
515
+ for data in training_data:
516
+ X, y, labels = data[0], data[-2], data[-1]
517
+ X_ctl = data[1].to(device) if len(data) == 4 else None
518
+
519
+ if X.shape[0] != batch_size:
520
+ continue
521
+
522
+ X = X.to(device).float()
523
+ y = y.to(device)
524
+
525
+ # Clear the optimizer and set the model to training mode
526
+ muon_optimizer.zero_grad()
527
+ adam_optimizer.zero_grad()
528
+ self.train()
529
+
530
+ # Make one training step
531
+ with torch.autocast(device_type=device, dtype=dtype):
532
+ y_hat_logits, y_hat_logcounts = self(X, X_ctl)
533
+
534
+ profile_loss, count_loss = _mixture_loss(y, y_hat_logits.float(),
535
+ y_hat_logcounts.float(), )
536
+
537
+
538
+ w0 = (1.0 / (2.0 * self.lw0 ** 2))
539
+ w1 = (1.0 / (2.0 * self.lw1 ** 2))
540
+ loss = w0*profile_loss + w1*count_loss
541
+
542
+ if self.lw0.requires_grad == True:
543
+ loss += torch.sum(torch.log(self.lw0) ** 2 + torch.log(self.lw1) ** 2)
544
+
545
+ loss.backward()
546
+
547
+ muon_optimizer.step()
548
+ adam_optimizer.step()
549
+
550
+ muon_scheduler.step()
551
+ adam_scheduler.step()
552
+
553
+ iteration += 1
554
+
555
+ train_time = time.time() - tic
556
+
557
+ if self.lw0.requires_grad == True and torch.abs(self.lw0.grad).sum() < 1:
558
+ self.lw0.requires_grad = False
559
+ self.lw1.requires_grad = False
560
+
561
+ # Validate the model at the end of the epoch
562
+ with torch.no_grad():
563
+ self.eval()
564
+ tic = time.time()
565
+
566
+ y_hat_logits, y_hat_logcounts = predict(self, X_valid, args=X_ctl_valid,
567
+ batch_size=batch_size, dtype=dtype, device=device)
568
+
569
+ valid_profile_loss, valid_count_loss = _mixture_loss(y_valid,
570
+ y_hat_logits, y_hat_logcounts)
571
+
572
+ valid_loss = w0*valid_profile_loss + w1*valid_count_loss
573
+
574
+ measures = calculate_performance_measures(y_hat_logits,
575
+ y_valid, y_hat_logcounts, measures=['profile_pearson', 'count_pearson'])
576
+
577
+ valid_profile_corr = numpy.nan_to_num(measures['profile_pearson'])
578
+ valid_count_corr = numpy.nan_to_num(measures['count_pearson'])
579
+ valid_time = time.time() - tic
580
+
581
+ self.logger.add([epoch,
582
+ iteration,
583
+ train_time,
584
+ valid_time,
585
+ profile_loss.item(),
586
+ count_loss.item(),
587
+ valid_profile_loss.item(),
588
+ valid_profile_corr.mean(),
589
+ valid_count_corr.mean(),
590
+ valid_count_loss.item(),
591
+ (valid_loss < best_loss).item()])
592
+
593
+ self.logger.save("{}.log".format(self.name))
594
+
595
+ if valid_loss < best_loss:
596
+ torch.save(self, "{}.torch".format(self.name))
597
+ best_loss = valid_loss
598
+ early_stop_count = -1
599
+
600
+ early_stop_count += 1
601
+ if early_stopping is not None and early_stop_count >= early_stopping:
602
+ break
603
+
604
+ torch.save(self, "{}.final.torch".format(self.name))