netloader 3.11.0__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.
@@ -0,0 +1,630 @@
1
+ """
2
+ Classes for encoder, decoder, or autoencoder type architectures
3
+ """
4
+ from warnings import warn
5
+ from typing import Any, Self, Literal, cast
6
+
7
+ import torch
8
+ import numpy as np
9
+ from numpy import ndarray
10
+ from torch import Tensor, nn
11
+
12
+ from netloader.data import DataList
13
+ from netloader.utils import label_change
14
+ from netloader.transforms import BaseTransform
15
+ from netloader.network import Network, CompatibleNetwork
16
+ from netloader.architectures.base import BaseArchitecture
17
+ from netloader.loss_funcs import BaseLoss, MSELoss, CrossEntropyLoss
18
+ from netloader.utils.types import TensorListLike, NDArrayListLike, TensorT
19
+
20
+
21
+ class BaseEncoder(BaseArchitecture):
22
+ """
23
+ Base encoder architecture for predicting low-dimensional data from high-dimensional inputs.
24
+
25
+ Attributes
26
+ ----------
27
+ description : str
28
+ Description of the architecture
29
+ version : str
30
+ Version of the architecture
31
+ losses : tuple[list[LossCT], list[LossCT]]
32
+ Architecture training and validation losses as a float or dictionary of losses for each loss
33
+ function
34
+ transforms : dict[str, list[BaseTransform] | BaseTransform | None]
35
+ Keys for the output data from predict and corresponding transforms
36
+ idxs: ndarray | None
37
+ Training data indices with shape (N) and type int, where N is the number of elements in the
38
+ training dataset
39
+ classes : Tensor | None
40
+ Unique classes of shape (C) and type int/float, where C is the number of classes
41
+ optimiser : Optimizer
42
+ Architecture optimiser
43
+ scheduler : LRScheduler
44
+ Optimiser scheduler
45
+ net : Network | CompatibleNetwork
46
+ Neural network
47
+ """
48
+ def __init__(
49
+ self,
50
+ save_num: int | str,
51
+ states_dir: str,
52
+ net: nn.Module | Network | CompatibleNetwork,
53
+ *,
54
+ overwrite: bool = False,
55
+ mix_precision: bool = False,
56
+ learning_rate: float = 1e-3,
57
+ description: str = '',
58
+ verbose: Literal['epoch', 'full', 'plot', 'progress', None] = 'epoch',
59
+ classes: Tensor | None = None,
60
+ loss_func: BaseLoss | None = None,
61
+ transform: list[BaseTransform] | BaseTransform | None = None,
62
+ in_transform: list[BaseTransform] | BaseTransform | None = None,
63
+ optimiser_kwargs: dict[str, Any] | None = None,
64
+ scheduler_kwargs: dict[str, Any] | None = None) -> None:
65
+ """
66
+ Parameters
67
+ ----------
68
+ save_num : int | str
69
+ File number or name to save the architecture
70
+ states_dir : str
71
+ Directory to save the architecture
72
+ net : Module | Network | CompatibleNetwork
73
+ Network to predict low-dimensional data
74
+ overwrite : bool, Optional
75
+ If saving can overwrite an existing save file, if True and file with the same name
76
+ exists, an error will be raised, default = False
77
+ mix_precision: bool, Optional
78
+ If mixed precision should be used, default = False
79
+ learning_rate : float, Optional
80
+ Optimiser initial learning rate, if None, no optimiser or scheduler will be set,
81
+ default = 1e-3
82
+ description : str, Optional
83
+ Description of the architecture training
84
+ verbose : {'full', 'progress', None}
85
+ If details about epoch should be printed ('full'), just a progress bar ('progress'),
86
+ or nothing (None)
87
+ classes : Tensor, Optional
88
+ Unique classes of shape (C) and type int/float, where C is the number of classes, if
89
+ using class classification
90
+ loss_func : BaseLoss | None, Optional
91
+ Loss function for the encoder, if None MSELoss will be used if classes is None, else
92
+ CrossEntropyLoss will be used
93
+ transform : BaseTransform, Optional
94
+ Transformation of the low-dimensional data
95
+ in_transform : BaseTransform, Optional
96
+ Transformation for the input data
97
+ optimiser_kwargs : dict[str, Any] | None, Optional
98
+ Optional keyword arguments to pass to init_optimiser
99
+ scheduler_kwargs : dict[str, Any] | None, Optional
100
+ Optional keyword arguments to pass to init_scheduler
101
+ """
102
+ super().__init__(
103
+ save_num,
104
+ states_dir,
105
+ net,
106
+ overwrite=overwrite,
107
+ mix_precision=mix_precision,
108
+ learning_rate=learning_rate,
109
+ description=description,
110
+ verbose=verbose,
111
+ transform=transform,
112
+ in_transform=in_transform,
113
+ optimiser_kwargs=optimiser_kwargs,
114
+ scheduler_kwargs=scheduler_kwargs,
115
+ )
116
+ self._loss_func_: BaseLoss
117
+ self.classes: Tensor | None = classes
118
+
119
+ if self.classes is None:
120
+ self._loss_func_ = loss_func or MSELoss()
121
+ else:
122
+ self.classes = self.classes.to(self._device)
123
+ self._loss_func_ = loss_func or CrossEntropyLoss()
124
+
125
+ def __getstate__(self) -> dict[str, Any]:
126
+ return super().__getstate__() | {'classes': self.classes, 'loss_func': self._loss_func_}
127
+
128
+ def __setstate__(self, state: dict[str, Any]) -> None:
129
+ super().__setstate__(state)
130
+ self.classes = state['classes']
131
+
132
+ if 'loss_func' in state:
133
+ self._loss_func_ = state['loss_func']
134
+ else:
135
+ warn(
136
+ f'{self.__class__.__name__} is saved in old non-weights safe format and is '
137
+ 'deprecated, please resave the architecture in the new format using '
138
+ 'BaseArchitecture.save()',
139
+ DeprecationWarning,
140
+ stacklevel=2,
141
+ )
142
+
143
+ if self.classes is None:
144
+ self._loss_func_ = MSELoss()
145
+ else:
146
+ self._loss_func_ = CrossEntropyLoss()
147
+
148
+ def _loss_tensor(
149
+ self,
150
+ in_data: TensorListLike,
151
+ target: TensorListLike,
152
+ _: Any) -> dict[str, Tensor] | Tensor:
153
+ """
154
+ Calculates the loss from the architecture's predictions.
155
+
156
+ Parameters
157
+ ----------
158
+ in_data : TensorListLike
159
+ Input high dimensional data of shape (N, ...) and type float, where N is the batch size
160
+ target : TensorListLike
161
+ Target low dimensional data of shape (N, ...) and type float
162
+
163
+ Returns
164
+ -------
165
+ dict[str, Tensor] | Tensor
166
+ Loss from the architecture's predictions
167
+ """
168
+ # Default shape is (N, L), but cross entropy expects (N)
169
+ if self.classes is not None and isinstance(target, Tensor):
170
+ target = label_change(target.squeeze(), self.classes)
171
+ return self._loss_func_(self.net(in_data), target)
172
+
173
+ def get_hyperparams(self) -> dict[str, Any]:
174
+ """
175
+ Get the hyperparameters of the encoder.
176
+
177
+ Returns
178
+ -------
179
+ dict[str, Any]
180
+ Hyperparameters of the encoder
181
+ """
182
+ return super().get_hyperparams() | {
183
+ 'classes': int(self.classes.size(0)) if self.classes is not None else None,
184
+ 'loss_func': self._loss_func_.__class__.__name__,
185
+ }
186
+
187
+ def extra_repr(self) -> str:
188
+ """
189
+ Additional representation of the architecture.
190
+
191
+ Returns
192
+ -------
193
+ str
194
+ Architecture specific representation
195
+ """
196
+ return f'loss_func: {self._loss_func_}'
197
+
198
+ def to(self, *args: Any, **kwargs: Any) -> Self:
199
+ super().to(*args, **kwargs)
200
+
201
+ if self.classes is not None:
202
+ self.classes = self.classes.to(self._device)
203
+ return self
204
+
205
+
206
+ class Autoencoder(BaseArchitecture):
207
+ """
208
+ Architecture handler for autoencoder type architectures.
209
+
210
+ Attributes
211
+ ----------
212
+ description : str
213
+ Description of the architecture
214
+ losses : tuple[list[LossCT], list[LossCT]]
215
+ Architecture training and validation losses as a float or dictionary of losses for each loss
216
+ function
217
+ transforms : dict[str, list[BaseTransform] | BaseTransform | None]
218
+ Keys for the output data from predict and corresponding transforms
219
+ idxs: ndarray | None
220
+ Training data indices with shape (N) and type int, where N is the number of elements in the
221
+ training dataset
222
+ optimiser : Optimizer
223
+ Architecture optimiser
224
+ scheduler : LRScheduler
225
+ Optimiser scheduler
226
+ net : Network | CompatibleNetwork
227
+ Neural network
228
+ reconstruct_func : BaseLoss
229
+ Loss function for the reconstruction loss
230
+ latent_func : BaseLoss
231
+ Loss function for the latent loss
232
+ """
233
+ def __init__(
234
+ self,
235
+ save_num: int | str,
236
+ states_dir: str,
237
+ net: nn.Module | Network | CompatibleNetwork,
238
+ *,
239
+ overwrite: bool = False,
240
+ mix_precision: bool = False,
241
+ learning_rate: float = 1e-3,
242
+ description: str = '',
243
+ verbose: Literal['epoch', 'full', 'plot', 'progress', None] = 'epoch',
244
+ transform: list[BaseTransform] | BaseTransform | None = None,
245
+ latent_transform: list[BaseTransform] | BaseTransform | None = None,
246
+ optimiser_kwargs: dict[str, Any] | None = None,
247
+ scheduler_kwargs: dict[str, Any] | None = None) -> None:
248
+ """
249
+ Parameters
250
+ ----------
251
+ save_num : int | str
252
+ File number or name to save the architecture
253
+ states_dir : str
254
+ Directory to save the architecture
255
+ net : Module | Network | CompatibleNetwork
256
+ Network to predict low-dimensional data
257
+ overwrite : bool, Optional
258
+ If saving can overwrite an existing save file, if True and file with the same name
259
+ exists, an error will be raised, default = False
260
+ mix_precision: bool, Optional
261
+ If mixed precision should be used, default = False
262
+ learning_rate : float, Optional
263
+ Optimiser initial learning rate, default = 1e-3
264
+ description : str, Optional
265
+ Description of the architecture training
266
+ verbose : {'full', 'progress', None}
267
+ If details about epoch should be printed ('full'), just a progress bar ('progress'),
268
+ or nothing (None)
269
+ transform : BaseTransform, Optional
270
+ Transformation applied to the input data
271
+ latent_transform : BaseTransform, Optional
272
+ Transformation applied to the latent space
273
+ optimiser_kwargs : dict[str, Any] | None, Optional
274
+ Optional keyword arguments to pass to init_optimiser
275
+ scheduler_kwargs : dict[str, Any] | None, Optional
276
+ Optional keyword arguments to pass to init_scheduler
277
+ """
278
+ super().__init__(
279
+ save_num,
280
+ states_dir,
281
+ net,
282
+ overwrite=overwrite,
283
+ mix_precision=mix_precision,
284
+ learning_rate=learning_rate,
285
+ description=description,
286
+ verbose=verbose,
287
+ transform=transform,
288
+ in_transform=transform,
289
+ optimiser_kwargs=optimiser_kwargs,
290
+ scheduler_kwargs=scheduler_kwargs,
291
+ )
292
+ self.reconstruct_func: BaseLoss = MSELoss()
293
+ self.latent_func: BaseLoss = MSELoss()
294
+
295
+ self._loss_weights = {'reconstruct': 1, 'latent': 1e-2, 'bound': 1e-3, 'kl': 1e-1}
296
+ self.transforms['latent'] = latent_transform
297
+ self.transforms['targets'] = latent_transform
298
+
299
+ def __getstate__(self) -> dict[str, Any]:
300
+ return super().__getstate__() | {
301
+ 'reconstruct_func': self.reconstruct_func,
302
+ 'latent_func': self.latent_func,
303
+ }
304
+
305
+ def __setstate__(self, state: dict[str, Any]) -> None:
306
+ super().__setstate__(state)
307
+ self._loss_weights = state.get('loss_weights', {
308
+ 'reconstruct': state.get('reconstruct_loss', 1),
309
+ 'latent': state.get('latent_loss', 1),
310
+ 'bound': state.get('bound_loss', 1),
311
+ 'kl': state.get('kl_loss', 1),
312
+ })
313
+ self.reconstruct_func = state['reconstruct_func']
314
+ self.latent_func = state['latent_func']
315
+
316
+ def __getattr__(self, item: str) -> Any:
317
+ if item in {'reconstruct_loss', 'latent_loss', 'bound_loss', 'kl_loss'}:
318
+ warn(
319
+ 'Accessing loss weights directly is deprecated, please use '
320
+ 'Autoencoder.get_loss_weights() instead',
321
+ DeprecationWarning,
322
+ stacklevel=2,
323
+ )
324
+ return self._loss_weights[item.replace('_loss', '')]
325
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
326
+
327
+ def _loss_tensor(
328
+ self,
329
+ in_data: TensorListLike,
330
+ target: TensorListLike,
331
+ _: Any) -> dict[str, Tensor]:
332
+ """
333
+ Calculates the loss from the autoencoder's predictions.
334
+
335
+ Parameters
336
+ ----------
337
+ in_data : TensorListLike
338
+ Input high dimensional data of shape (N, ...) and type float, where N is the batch size
339
+ target : TensorListLike
340
+ Latent target low dimensional data of shape (N, ...) and type float
341
+
342
+ Returns
343
+ -------
344
+ dict[str, Tensor]
345
+ Loss function terms from the autoencoder's predictions
346
+ """
347
+ loss: dict[str, Tensor] = {}
348
+ latent: Tensor | None = None
349
+ bounds: Tensor = torch.tensor([0., 1.]).to(self._device)
350
+ output: Tensor = self.net(in_data)
351
+
352
+ if self.net.checkpoints and isinstance(self.net.checkpoints[-1], DataList):
353
+ raise ValueError(f'Autoencoder networks cannot have multiple latent space tensors '
354
+ f'({len(self.net.checkpoints[-1])})')
355
+ if self.net.checkpoints:
356
+ latent = cast(Tensor, self.net.checkpoints[-1])
357
+
358
+ if self.get_loss_weights('reconstruct'):
359
+ loss['reconstruct'] = self.reconstruct_func(output, in_data)
360
+
361
+ if self.get_loss_weights('latent') and latent is not None:
362
+ loss['latent'] = self.latent_func(latent, target)
363
+
364
+ if self.get_loss_weights('bound') and latent is not None:
365
+ loss['bound'] = torch.mean(torch.cat((
366
+ (bounds[0] - latent) ** 2 * (latent < bounds[0]),
367
+ (latent - bounds[1]) ** 2 * (latent > bounds[1]),
368
+ )))
369
+
370
+ if self.get_loss_weights('kl'):
371
+ loss['kl'] = self.net.kl_loss
372
+ return loss
373
+
374
+ def batch_predict(self, data: TensorListLike, **_: Any) -> tuple[NDArrayListLike | None, ...]:
375
+ """
376
+ Generates predictions for the given data batch.
377
+
378
+ Parameters
379
+ ----------
380
+ data : TensorListLike
381
+ Data to generate predictions for of shape (N, ...) and type float, where N is the batch
382
+ size
383
+
384
+ Returns
385
+ -------
386
+ tuple[NDArrayListLike | None, ...]
387
+ Predictions of shape (N, ...) and type float for the given data
388
+ """
389
+ output: NDArrayListLike = self.net(data).detach().cpu().numpy()
390
+ return (
391
+ output,
392
+ cast(NDArrayListLike, self.net.checkpoints[-1].detach().cpu().numpy()),
393
+ cast(NDArrayListLike, data.detach().cpu().numpy()),
394
+ )
395
+
396
+ def extra_repr(self) -> str:
397
+ """
398
+ Additional representation of the architecture.
399
+
400
+ Returns
401
+ -------
402
+ str
403
+ Architecture specific representation
404
+ """
405
+ return f'reconstruct_func: {self.reconstruct_func}, latent_func: {self.latent_func}'
406
+
407
+ def get_hyperparams(self) -> dict[str, Any]:
408
+ """
409
+ Get the hyperparameters of the autoencoder.
410
+
411
+ Returns
412
+ -------
413
+ dict[str, Any]
414
+ Hyperparameters of the autoencoder
415
+ """
416
+ return super().get_hyperparams() | {
417
+ 'reconstruct_func': self.reconstruct_func.__class__.__name__,
418
+ 'latent_func': self.latent_func.__class__.__name__,
419
+ }
420
+
421
+
422
+ class Decoder(BaseArchitecture):
423
+ """
424
+ Decoder architecture for predicting high-dimensional data from low-dimensional inputs.
425
+
426
+ Attributes
427
+ ----------
428
+ description : str
429
+ Description of the architecture
430
+ losses : tuple[list[LossCT], list[LossCT]]
431
+ Architecture training and validation losses as a float or dictionary of losses for each loss
432
+ function
433
+ transforms : dict[str, list[BaseTransform] | BaseTransform | None]
434
+ Keys for the output data from predict and corresponding transforms
435
+ loss_func : BaseLoss
436
+ Loss function for the reconstructions
437
+ idxs: ndarray | None
438
+ Training data indices with shape (N) and type int, where N is the number of elements in the
439
+ training dataset
440
+ optimiser : Optimizer
441
+ Architecture optimiser
442
+ scheduler : LRScheduler
443
+ Optimiser scheduler
444
+ net : Network | CompatibleNetwork
445
+ Neural network
446
+ """
447
+ def __init__(
448
+ self,
449
+ save_num: int | str,
450
+ states_dir: str,
451
+ net: nn.Module | Network | CompatibleNetwork,
452
+ *,
453
+ overwrite: bool = False,
454
+ mix_precision: bool = False,
455
+ learning_rate: float = 1e-3,
456
+ description: str = '',
457
+ verbose: Literal['epoch', 'full', 'plot', 'progress', None] = 'epoch',
458
+ transform: BaseTransform | None = None,
459
+ in_transform: BaseTransform | None = None,
460
+ optimiser_kwargs: dict[str, Any] | None = None,
461
+ scheduler_kwargs: dict[str, Any] | None = None) -> None:
462
+ """
463
+ Parameters
464
+ ----------
465
+ save_num : int | str
466
+ File number or name to save the architecture
467
+ states_dir : str
468
+ Directory to save the architecture
469
+ net : Module | Network | CompatibleNetwork
470
+ Network to predict low-dimensional data
471
+ overwrite : bool, Optional
472
+ If saving can overwrite an existing save file, if True and file with the same name
473
+ exists, an error will be raised, default = False
474
+ mix_precision: bool, Optional
475
+ If mixed precision should be used, default = False
476
+ learning_rate : float, Optional
477
+ Optimiser initial learning rate, if None, no optimiser or scheduler will be set,
478
+ default = 1e-3
479
+ description : str, Optional
480
+ Description of the architecture
481
+ verbose : {'epoch', 'full', 'progress', None}
482
+ If details about each epoch should be printed ('epoch'), details about epoch and epoch
483
+ progress (full), just total progress ('progress'), or nothing (None)
484
+ transform : BaseTransform, Optional
485
+ Transformation of the architecture's output
486
+ in_transform : BaseTransform, Optional
487
+ Transformation for the input data
488
+ optimiser_kwargs : dict[str, Any] | None, Optional
489
+ Optional keyword arguments to pass to init_optimiser
490
+ scheduler_kwargs : dict[str, Any] | None, Optional
491
+ Optional keyword arguments to pass to init_scheduler
492
+ """
493
+ super().__init__(
494
+ save_num,
495
+ states_dir,
496
+ net,
497
+ overwrite=overwrite,
498
+ mix_precision=mix_precision,
499
+ learning_rate=learning_rate,
500
+ description=description,
501
+ verbose=verbose,
502
+ transform=transform,
503
+ in_transform=in_transform,
504
+ optimiser_kwargs=optimiser_kwargs,
505
+ scheduler_kwargs=scheduler_kwargs,
506
+ )
507
+ self.loss_func: BaseLoss = MSELoss()
508
+
509
+ def __getstate__(self) -> dict[str, Any]:
510
+ return super().__getstate__() | {'loss_func': self.loss_func}
511
+
512
+ def __setstate__(self, state: dict[str, Any]) -> None:
513
+ super().__setstate__(state)
514
+ self.loss_func = state['loss_func']
515
+
516
+ @staticmethod
517
+ def _data_loader_translation(low_dim: TensorT, high_dim: TensorT) -> tuple[TensorT, TensorT]:
518
+ """
519
+ Orders low and high dimensional tensors from the data loader as inputs and targets for the
520
+ network.
521
+
522
+ Parameters
523
+ ----------
524
+ low_dim : TensorT
525
+ Low dimensional tensor from the data loader of shape (N,...) and type float, where N is
526
+ the batch size
527
+ high_dim : TensorT
528
+ High dimensional tensor from the data loader of shape (N,...) and type float
529
+
530
+ Returns
531
+ -------
532
+ tuple[TensorT, TensorT]
533
+ Input and output target tensors of shape (N,...) and type float
534
+ """
535
+ return low_dim, high_dim
536
+
537
+ def _loss_tensor(
538
+ self,
539
+ in_data: TensorListLike,
540
+ target: TensorListLike,
541
+ _: Any) -> dict[str, Tensor] | Tensor:
542
+ """
543
+ Calculates the loss from the architecture's predictions.
544
+
545
+ Parameters
546
+ ----------
547
+ in_data : TensorListLike
548
+ Input low dimensional data of shape (N, ...) and type float, where N is the batch size
549
+ target : TensorListLike
550
+ Target high dimensional data of shape (N, ...) and type float
551
+
552
+ Returns
553
+ -------
554
+ dict[str, Tensor] | Tensor
555
+ Loss from the architecture's predictions
556
+ """
557
+ return self.loss_func(self.net(in_data), target)
558
+
559
+ def extra_repr(self) -> str:
560
+ """
561
+ Additional representation of the architecture.
562
+
563
+ Returns
564
+ -------
565
+ str
566
+ Architecture specific representation
567
+ """
568
+ return f'loss_func: {self.loss_func}'
569
+
570
+ def get_hyperparams(self) -> dict[str, Any]:
571
+ """
572
+ Get the hyperparameters of the decoder.
573
+
574
+ Returns
575
+ -------
576
+ dict[str, Any]
577
+ Hyperparameters of the decoder
578
+ """
579
+ return super().get_hyperparams() | {'loss_func': self.loss_func.__class__.__name__}
580
+
581
+
582
+ class Encoder(BaseEncoder):
583
+ """
584
+ Encoder architecture for predicting low-dimensional data from high-dimensional inputs.
585
+
586
+ Attributes
587
+ ----------
588
+ description : str
589
+ Description of the architecture
590
+ losses : tuple[list[LossCT], list[LossCT]]
591
+ Architecture training and validation losses as a float or dictionary of losses for each loss
592
+ function
593
+ transforms : dict[str, list[BaseTransform] | BaseTransform | None]
594
+ Keys for the output data from predict and corresponding transforms
595
+ idxs: ndarray | None
596
+ Training data indices with shape (N) and type int, where N is the number of elements in the
597
+ training dataset
598
+ classes : Tensor | None
599
+ Unique classes of shape (C) and type int/float, where C is the number of classes
600
+ optimiser : Optimizer
601
+ Architecture optimiser
602
+ scheduler : LRScheduler
603
+ Optimiser scheduler
604
+ net : Network | CompatibleNetwork
605
+ Neural network
606
+ """
607
+ def batch_predict(self, data: TensorListLike, **_: Any) -> tuple[NDArrayListLike | None, ...]:
608
+ """
609
+ Generates predictions for the given data.
610
+
611
+ Parameters
612
+ ----------
613
+ data : TensorListLike
614
+ Data to generate predictions for of shape (N, ...) and type float, where N is the batch
615
+ size
616
+
617
+ Returns
618
+ -------
619
+ tuple[NDArrayListLike | None, ...]
620
+ Predictions of shape (N,...) and type float for the given data
621
+ """
622
+ output: NDArrayListLike | None = super().batch_predict(data)[0]
623
+ assert isinstance(output, ndarray)
624
+
625
+ if isinstance(self._loss_func_, nn.CrossEntropyLoss):
626
+ output = np.argmax(output, axis=-1, keepdims=True)
627
+ return (output,)
628
+
629
+
630
+ __all__ = ['BaseEncoder', 'Autoencoder', 'Decoder', 'Encoder']