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.
- netloader/__init__.py +61 -0
- netloader/architectures/__init__.py +48 -0
- netloader/architectures/base.py +1350 -0
- netloader/architectures/encoder_decoder.py +630 -0
- netloader/architectures/flows.py +501 -0
- netloader/architectures/utils.py +179 -0
- netloader/data.py +1092 -0
- netloader/layers/__init__.py +88 -0
- netloader/layers/base.py +367 -0
- netloader/layers/blocks.py +145 -0
- netloader/layers/convolutional.py +1008 -0
- netloader/layers/flows.py +123 -0
- netloader/layers/linear.py +445 -0
- netloader/layers/misc.py +879 -0
- netloader/layers/multi_layer.py +470 -0
- netloader/layers/pooling.py +298 -0
- netloader/layers/recurrent.py +163 -0
- netloader/layers/utils.py +168 -0
- netloader/loss_funcs.py +201 -0
- netloader/models/__init__.py +22 -0
- netloader/models/convnext.py +582 -0
- netloader/models/misc.py +209 -0
- netloader/network.py +895 -0
- netloader/networks/__init__.py +48 -0
- netloader/schedulers.py +409 -0
- netloader/transforms.py +847 -0
- netloader/utils/__init__.py +46 -0
- netloader/utils/configs.py +241 -0
- netloader/utils/transforms.py +22 -0
- netloader/utils/types.py +130 -0
- netloader/utils/utils.py +677 -0
- netloader-3.11.0.dist-info/METADATA +59 -0
- netloader-3.11.0.dist-info/RECORD +36 -0
- netloader-3.11.0.dist-info/WHEEL +5 -0
- netloader-3.11.0.dist-info/licenses/LICENSE.txt +21 -0
- netloader-3.11.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classes that contain normalising flow-based architectures
|
|
3
|
+
"""
|
|
4
|
+
from typing import Any, Literal, cast
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy import ndarray
|
|
9
|
+
from torch import optim, nn, Tensor
|
|
10
|
+
from torch.utils.data import DataLoader
|
|
11
|
+
from torch.optim.optimizer import ParamsT
|
|
12
|
+
from zuko.distributions import NormalizingFlow
|
|
13
|
+
|
|
14
|
+
from netloader.data import Data
|
|
15
|
+
from netloader.utils import label_change
|
|
16
|
+
from netloader.loss_funcs import BaseLoss
|
|
17
|
+
from netloader.transforms import BaseTransform
|
|
18
|
+
from netloader.network import Network, CompatibleNetwork
|
|
19
|
+
from netloader.architectures.base import BaseArchitecture
|
|
20
|
+
from netloader.architectures.encoder_decoder import BaseEncoder
|
|
21
|
+
from netloader.utils.types import NDArrayLike, TensorListLike, NDArrayListLike, TensorT
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NormFlow(BaseArchitecture):
|
|
25
|
+
"""
|
|
26
|
+
Transforms a simple distribution into a distribution that reflects the input data
|
|
27
|
+
|
|
28
|
+
Requires last layer to be a normalising flow and will not pass input data through the network.
|
|
29
|
+
|
|
30
|
+
Attributes
|
|
31
|
+
----------
|
|
32
|
+
net : Network | CompatibleNetwork
|
|
33
|
+
Neural spline flow
|
|
34
|
+
description : str
|
|
35
|
+
Description of the architecture
|
|
36
|
+
losses : tuple[list[LossCT], list[LossCT]]
|
|
37
|
+
Architecture training and validation losses as a float or dictionary of losses for each loss
|
|
38
|
+
function
|
|
39
|
+
transforms : dict[str, list[BaseTransform] | BaseTransform | None]
|
|
40
|
+
Keys for the output data from predict and corresponding transforms
|
|
41
|
+
idxs: ndarray | None
|
|
42
|
+
Training data indices with shape (N) and type int, where N is the number of elements in the
|
|
43
|
+
training dataset
|
|
44
|
+
optimiser : Optimizer
|
|
45
|
+
Architecture optimiser
|
|
46
|
+
scheduler : LRScheduler
|
|
47
|
+
Optimiser scheduler
|
|
48
|
+
"""
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _data_loader_translation(low_dim: TensorT, high_dim: TensorT) -> tuple[TensorT, TensorT]:
|
|
51
|
+
"""
|
|
52
|
+
Orders low and high dimensional tensors from the data loader as inputs and targets for the
|
|
53
|
+
network.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
low_dim : TensorT
|
|
58
|
+
Low dimensional tensor from the data loader of shape (N, ...) and type float, where N is
|
|
59
|
+
the batch size
|
|
60
|
+
high_dim : TensorT
|
|
61
|
+
High dimensional tensor from the data loader of shape (N, ...) and type float
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
tuple[TensorT, TensorT]
|
|
66
|
+
Input and output target tensors of shape (N, ...) and type float
|
|
67
|
+
"""
|
|
68
|
+
return low_dim, high_dim
|
|
69
|
+
|
|
70
|
+
def _loss_tensor(self, _: Any, target: TensorListLike, __: Any) -> Tensor:
|
|
71
|
+
"""
|
|
72
|
+
Calculates the loss from the flow's predictions.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
target : TensorListLike
|
|
77
|
+
Target high dimensional data of shape (N, ...) and type float, where N is the batch size
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
Tensor
|
|
82
|
+
Loss from the flow's predictions
|
|
83
|
+
"""
|
|
84
|
+
return -self.net().log_prob(target).mean()
|
|
85
|
+
|
|
86
|
+
def predict(
|
|
87
|
+
self,
|
|
88
|
+
loader: DataLoader[Any] | None = None,
|
|
89
|
+
*,
|
|
90
|
+
path: str = '',
|
|
91
|
+
num: list[int] | None = None,
|
|
92
|
+
**__: Any) -> dict[str, NDArrayLike]:
|
|
93
|
+
"""
|
|
94
|
+
Generates probability distributions for a dataset and can save to a file.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
loader : DataLoader[Any] | None, Optional
|
|
99
|
+
Unused, present for compatibility with BaseArchitecture
|
|
100
|
+
path : str, Optional
|
|
101
|
+
Path as pkl file to save the predictions if they should be saved
|
|
102
|
+
num : list[int] | None, Optional
|
|
103
|
+
Number of samples, S, to generate, default = [1e3]
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
dict[str, NDArrayLike]
|
|
108
|
+
Predicted distribution, with shape (N,...,S) and type float for dataset of size N
|
|
109
|
+
"""
|
|
110
|
+
assert isinstance(self.transforms['preds'], (BaseTransform, type(None)))
|
|
111
|
+
data: dict[str, NDArrayLike]
|
|
112
|
+
samples: ndarray
|
|
113
|
+
transform: BaseTransform | None = self.transforms['preds']
|
|
114
|
+
|
|
115
|
+
if num is None:
|
|
116
|
+
num = [int(1e3)]
|
|
117
|
+
|
|
118
|
+
samples = self.net().sample(num).moveaxis(0, -1).detach().cpu().numpy()
|
|
119
|
+
data = {'samples': samples if transform is None else transform(samples, back=True)}
|
|
120
|
+
self._save_predictions(path, data)
|
|
121
|
+
return data
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class NormFlowEncoder(BaseEncoder):
|
|
125
|
+
"""
|
|
126
|
+
Calculates the loss for a network and normalising flow that takes high-dimensional data
|
|
127
|
+
and predicts a low-dimensional data distribution.
|
|
128
|
+
|
|
129
|
+
Requires the normalising flow to be the last layer in the network.
|
|
130
|
+
|
|
131
|
+
Attributes
|
|
132
|
+
----------
|
|
133
|
+
description : str
|
|
134
|
+
Description of the architecture
|
|
135
|
+
losses : tuple[list[LossCT], list[LossCT]]
|
|
136
|
+
Architecture training and validation losses as a float or dictionary of losses for each loss
|
|
137
|
+
function
|
|
138
|
+
transforms : dict[str, list[BaseTransform] | BaseTransform | None]
|
|
139
|
+
Keys for the output data from predict and corresponding transforms
|
|
140
|
+
idxs: ndarray | None
|
|
141
|
+
Training data indices with shape (N) and type int, where N is the number of elements in the
|
|
142
|
+
training dataset
|
|
143
|
+
classes : Tensor | None
|
|
144
|
+
Unique classes of shape (C) and type int/float, where C is the number of classes
|
|
145
|
+
optimiser : Optimizer
|
|
146
|
+
Architecture optimiser
|
|
147
|
+
scheduler : LRScheduler
|
|
148
|
+
Optimiser scheduler
|
|
149
|
+
net : Network | CompatibleNetwork
|
|
150
|
+
Neural network
|
|
151
|
+
"""
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
save_num: int | str,
|
|
155
|
+
states_dir: str,
|
|
156
|
+
net: nn.Module | Network | CompatibleNetwork,
|
|
157
|
+
*,
|
|
158
|
+
overwrite: bool = False,
|
|
159
|
+
mix_precision: bool = False,
|
|
160
|
+
net_checkpoint: int | None = None,
|
|
161
|
+
description: str = '',
|
|
162
|
+
verbose: Literal['epoch', 'full', 'plot', 'progress', None] = 'epoch',
|
|
163
|
+
train_epochs: tuple[int, int] = (0, -1),
|
|
164
|
+
learning_rate: tuple[float, float] = (1e-3,) * 2,
|
|
165
|
+
classes: Tensor | None = None,
|
|
166
|
+
loss_func: BaseLoss | None = None,
|
|
167
|
+
transform: list[BaseTransform] | BaseTransform | None = None,
|
|
168
|
+
in_transform: list[BaseTransform] | BaseTransform | None = None,
|
|
169
|
+
optimiser_kwargs: dict[str, Any] | None = None,
|
|
170
|
+
scheduler_kwargs: dict[str, Any] | None = None) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
save_num : int | str
|
|
175
|
+
File number or name to save the flow
|
|
176
|
+
states_dir : str
|
|
177
|
+
Directory to save the architecture and flow
|
|
178
|
+
net : Module | Network | CompatibleNetwork
|
|
179
|
+
Normalising flow to predict low-dimensional data distribution
|
|
180
|
+
overwrite : bool, Optional
|
|
181
|
+
If saving can overwrite an existing save file, if True and file with the same name
|
|
182
|
+
exists, an error will be raised, default = False
|
|
183
|
+
mix_precision: bool, Optional
|
|
184
|
+
If mixed precision should be used, default = False
|
|
185
|
+
net_checkpoint : int, Optional
|
|
186
|
+
Network checkpoint for calculating the encoder's loss, if none, will use output from the
|
|
187
|
+
network if output is a Tensor, else no encoder loss will be used
|
|
188
|
+
description : str, Optional
|
|
189
|
+
Description of the architecture training
|
|
190
|
+
verbose : {'full', 'progress', None}
|
|
191
|
+
If details about epoch should be printed ('full'), just a progress bar ('progress'),
|
|
192
|
+
or nothing (None)
|
|
193
|
+
train_epochs : tuple[int, int], Optional
|
|
194
|
+
Epoch when to start training the normalising flow and epoch when to stop training the
|
|
195
|
+
encoder, if both are zero, then flow will be trained from the beginning and encoder will
|
|
196
|
+
not be trained, if both are -1, then flow will never be trained and encoder will always
|
|
197
|
+
be trained, default = (0,-1)
|
|
198
|
+
learning_rate : tuple[float, float], Optional
|
|
199
|
+
Optimiser initial learning rate for encoder and normalising flow, if None, no optimiser
|
|
200
|
+
or scheduler will be set, default = (1e-3,1e-3)
|
|
201
|
+
classes : Tensor, Optional
|
|
202
|
+
Unique classes of shape (C) and type int/float, where C is the number of classes
|
|
203
|
+
loss_func : BaseLoss | None, Optional
|
|
204
|
+
Loss function for the encoder, if None MSELoss will be used if classes is None, else
|
|
205
|
+
CrossEntropyLoss will be used
|
|
206
|
+
in_transform : BaseTransform, Optional
|
|
207
|
+
Transformation for the input data
|
|
208
|
+
optimiser_kwargs : dict[str, Any] | None, Optional
|
|
209
|
+
Optional keyword arguments to pass to init_optimiser
|
|
210
|
+
scheduler_kwargs : dict[str, Any] | None, Optional
|
|
211
|
+
Optional keyword arguments to pass to init_scheduler
|
|
212
|
+
"""
|
|
213
|
+
super().__init__(
|
|
214
|
+
save_num,
|
|
215
|
+
states_dir,
|
|
216
|
+
net,
|
|
217
|
+
overwrite=overwrite,
|
|
218
|
+
mix_precision=mix_precision,
|
|
219
|
+
learning_rate=0,
|
|
220
|
+
description=description,
|
|
221
|
+
verbose=verbose,
|
|
222
|
+
classes=classes,
|
|
223
|
+
loss_func=loss_func,
|
|
224
|
+
transform=transform,
|
|
225
|
+
in_transform=in_transform,
|
|
226
|
+
optimiser_kwargs=optimiser_kwargs,
|
|
227
|
+
scheduler_kwargs=scheduler_kwargs,
|
|
228
|
+
)
|
|
229
|
+
self._train_flow: bool
|
|
230
|
+
self._train_encoder: bool
|
|
231
|
+
self._checkpoint: int | None = net_checkpoint
|
|
232
|
+
self._epochs: tuple[int, int] = train_epochs
|
|
233
|
+
|
|
234
|
+
self._train_flow = not self._epochs[0]
|
|
235
|
+
self._train_encoder = bool(self._epochs[-1])
|
|
236
|
+
self._loss_weights = {'flow': 1, 'encoder': 1}
|
|
237
|
+
self.transforms |= {
|
|
238
|
+
'distributions': transform,
|
|
239
|
+
'probs': None,
|
|
240
|
+
'max': transform,
|
|
241
|
+
'meds': transform,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if not self._train_encoder:
|
|
245
|
+
self.net.layers[:-1].requires_grad_(False)
|
|
246
|
+
|
|
247
|
+
if not self._train_flow:
|
|
248
|
+
self.net.layers[-1].requires_grad_(False)
|
|
249
|
+
|
|
250
|
+
self.optimiser = self.init_optimiser(
|
|
251
|
+
self.get_param_groups(learning_rate),
|
|
252
|
+
**optimiser_kwargs or {},
|
|
253
|
+
)
|
|
254
|
+
self.scheduler = self.init_scheduler(self.optimiser, **scheduler_kwargs or {})
|
|
255
|
+
|
|
256
|
+
if isinstance(self.scheduler, optim.lr_scheduler.ReduceLROnPlateau):
|
|
257
|
+
self.scheduler.load_state_dict(
|
|
258
|
+
{'factor': 0.5, 'min_lr': min(learning_rate) * 1e-3} | (scheduler_kwargs or {}),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
262
|
+
return super().__getstate__() | {
|
|
263
|
+
'train_flow': self._train_flow,
|
|
264
|
+
'train_encoder': self._train_encoder,
|
|
265
|
+
'checkpoint': self._checkpoint,
|
|
266
|
+
'epochs': self._epochs,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
270
|
+
super().__setstate__(state)
|
|
271
|
+
self._train_flow = state['train_flow']
|
|
272
|
+
self._train_encoder = state['train_encoder']
|
|
273
|
+
self._checkpoint = state['checkpoint']
|
|
274
|
+
self._epochs = state['epochs']
|
|
275
|
+
self._loss_weights = state.get(
|
|
276
|
+
'loss_weights',
|
|
277
|
+
{'flow': state.get('flow_loss', 1), 'encoder': state.get('encoder_loss', 1)},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def __getattr__(self, item: str) -> Any:
|
|
281
|
+
if item == 'flow_loss':
|
|
282
|
+
return self._loss_weights.get('flow', 0)
|
|
283
|
+
if item == 'encoder_loss':
|
|
284
|
+
return self._loss_weights.get('encoder', 0)
|
|
285
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'")
|
|
286
|
+
|
|
287
|
+
def _loss_tensor(
|
|
288
|
+
self,
|
|
289
|
+
in_data: TensorListLike,
|
|
290
|
+
target: TensorListLike,
|
|
291
|
+
_: Any) -> dict[str, Tensor]:
|
|
292
|
+
"""
|
|
293
|
+
Calculates the loss from the network and flow's predictions.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
in_data : TensorListLike
|
|
298
|
+
Input high dimensional data of shape (N, ...) and type float, where N is the batch size
|
|
299
|
+
target : TensorListLike
|
|
300
|
+
Target low dimensional data of shape (N, ...) and type float
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
dict[str, Tensor]
|
|
305
|
+
Dictionary of losses for encoder and flow
|
|
306
|
+
"""
|
|
307
|
+
loss: dict[str, Tensor] = {}
|
|
308
|
+
output: Tensor | NormalizingFlow = self.net(in_data)
|
|
309
|
+
|
|
310
|
+
if not isinstance(target, Tensor):
|
|
311
|
+
raise ValueError(f'{self.__class__.__name__} requires target to be a Tensor, got '
|
|
312
|
+
f'{type(target)}')
|
|
313
|
+
|
|
314
|
+
if isinstance(output, NormalizingFlow) and self.get_loss_weights('flow'):
|
|
315
|
+
loss['flow'] = -output.log_prob(target).mean()
|
|
316
|
+
|
|
317
|
+
if isinstance(output, NormalizingFlow) and self._checkpoint:
|
|
318
|
+
output = self.net.checkpoints[self._checkpoint]
|
|
319
|
+
|
|
320
|
+
# Default shape is (N, L), but cross entropy expects (N)
|
|
321
|
+
if self.get_loss_weights('encoder') and self.classes is not None and \
|
|
322
|
+
isinstance(output, Tensor):
|
|
323
|
+
target = label_change(target.squeeze(), self.classes)
|
|
324
|
+
|
|
325
|
+
if self.get_loss_weights('encoder') and isinstance(output, Tensor):
|
|
326
|
+
loss['encoder'] = self._loss_func_(output, target)
|
|
327
|
+
return loss
|
|
328
|
+
|
|
329
|
+
def _update_epoch(self) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Updates network and flow epoch if they are being trained
|
|
332
|
+
"""
|
|
333
|
+
super()._update_epoch()
|
|
334
|
+
|
|
335
|
+
if not self._train_flow and self._epoch >= self._epochs[0] != -1:
|
|
336
|
+
self._train_flow = True
|
|
337
|
+
self.net.layers[-1].requires_grad_(True)
|
|
338
|
+
|
|
339
|
+
if self._train_encoder and self._epoch >= self._epochs[-1] != -1:
|
|
340
|
+
self._train_encoder = False
|
|
341
|
+
self.net.layers[:-1].requires_grad_(False)
|
|
342
|
+
|
|
343
|
+
def batch_predict(
|
|
344
|
+
self,
|
|
345
|
+
data: TensorListLike,
|
|
346
|
+
*,
|
|
347
|
+
num: list[int] | None = None,
|
|
348
|
+
**_: Any) -> tuple[NDArrayListLike | None, ndarray | None]:
|
|
349
|
+
"""
|
|
350
|
+
Generates probability distributions for the data batch
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
data : TensorListLike
|
|
355
|
+
Data of shape (N,...) and type float to generate distributions for, where N is the batch
|
|
356
|
+
size
|
|
357
|
+
num : list[int] | None, Optional
|
|
358
|
+
Number of samples, S, to generate, default = [1e3]
|
|
359
|
+
|
|
360
|
+
Returns
|
|
361
|
+
-------
|
|
362
|
+
tuple[NDArrayListLike | None, ndarray | None]
|
|
363
|
+
Architecture output with shape (N,...) and type float and samples of shape (N,S) and type
|
|
364
|
+
float from each probability distribution
|
|
365
|
+
"""
|
|
366
|
+
samples: ndarray | None = None
|
|
367
|
+
output: NormalizingFlow | TensorListLike | None = self.net(data)
|
|
368
|
+
|
|
369
|
+
if num is None:
|
|
370
|
+
num = [int(1e3)]
|
|
371
|
+
|
|
372
|
+
# Generate samples
|
|
373
|
+
if isinstance(output, NormalizingFlow):
|
|
374
|
+
samples = torch.transpose(
|
|
375
|
+
output.sample(num).squeeze(-1),
|
|
376
|
+
0,
|
|
377
|
+
1,
|
|
378
|
+
).detach().cpu().numpy()
|
|
379
|
+
output = None
|
|
380
|
+
|
|
381
|
+
if output is None and self._checkpoint:
|
|
382
|
+
output = self.net.checkpoints[self._checkpoint]
|
|
383
|
+
|
|
384
|
+
assert not isinstance(output, NormalizingFlow)
|
|
385
|
+
return (None if output is None else
|
|
386
|
+
cast(NDArrayListLike, output.detach().cpu().numpy()),
|
|
387
|
+
samples)
|
|
388
|
+
|
|
389
|
+
def extra_repr(self) -> str:
|
|
390
|
+
"""
|
|
391
|
+
Additional representation of the architecture.
|
|
392
|
+
|
|
393
|
+
Returns
|
|
394
|
+
-------
|
|
395
|
+
str
|
|
396
|
+
Architecture specific representation
|
|
397
|
+
"""
|
|
398
|
+
return f'{super().extra_repr()}, train_epochs: {self._epochs}'
|
|
399
|
+
|
|
400
|
+
def get_hyperparams(self) -> dict[str, Any]:
|
|
401
|
+
"""
|
|
402
|
+
Get the hyperparameters of the normalising flow encoder.
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
dict[str, Any]
|
|
407
|
+
Hyperparameters of the normalising flow encoder
|
|
408
|
+
"""
|
|
409
|
+
return super().get_hyperparams() | {
|
|
410
|
+
'checkpoint': self._checkpoint,
|
|
411
|
+
'train_epochs': self._epochs,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
def predict(
|
|
415
|
+
self,
|
|
416
|
+
loader: DataLoader[Any],
|
|
417
|
+
*_: Any,
|
|
418
|
+
inputs: bool = False,
|
|
419
|
+
bin_num: int = 100,
|
|
420
|
+
path: str = '',
|
|
421
|
+
num: list[int] | None = None,
|
|
422
|
+
**__: Any) -> dict[str, NDArrayLike]:
|
|
423
|
+
"""
|
|
424
|
+
Generates probability distributions for a dataset and can save to a file
|
|
425
|
+
|
|
426
|
+
Parameters
|
|
427
|
+
----------
|
|
428
|
+
loader : DataLoader
|
|
429
|
+
Dataset to generate predictions for
|
|
430
|
+
inputs : bool, Optional
|
|
431
|
+
If the input data should be returned and saved, default = False,
|
|
432
|
+
bin_num : int, Optional
|
|
433
|
+
Number of bins for calculating the probability of the target and maximum of the
|
|
434
|
+
distribution, higher is more precise but requires more samples, default = 100
|
|
435
|
+
path : str, Optional
|
|
436
|
+
Path as a pkl file to save the predictions if they should be saved
|
|
437
|
+
num : list[int] | None, Optional
|
|
438
|
+
Number of samples, S, to generate from the predicted distribution, default = [1e3]
|
|
439
|
+
|
|
440
|
+
Returns
|
|
441
|
+
-------
|
|
442
|
+
dict[str, NDArrayLike]
|
|
443
|
+
Prediction IDs of shape (N), Optional inputs, target values, target probability,
|
|
444
|
+
distribution maximum, distribution median of shape (N,...), and predicted distribution
|
|
445
|
+
of shape (N,...,S) and type float for dataset of size N
|
|
446
|
+
"""
|
|
447
|
+
probs: list[ndarray] = []
|
|
448
|
+
maxima: list[ndarray] = []
|
|
449
|
+
data: dict[str, NDArrayLike] = super().predict(loader, inputs=inputs, num=num)
|
|
450
|
+
hist: ndarray
|
|
451
|
+
bins: ndarray
|
|
452
|
+
prob: ndarray
|
|
453
|
+
distribution: ndarray
|
|
454
|
+
target: ndarray
|
|
455
|
+
|
|
456
|
+
if 'distributions' not in data:
|
|
457
|
+
self._save_predictions(path, data)
|
|
458
|
+
return data
|
|
459
|
+
|
|
460
|
+
assert (isinstance(data['distributions'], ndarray) and
|
|
461
|
+
isinstance(data['targets'], (ndarray, Data)))
|
|
462
|
+
|
|
463
|
+
for target, distribution in zip(
|
|
464
|
+
data['targets'] if isinstance(data['targets'], ndarray) else data['targets'].data,
|
|
465
|
+
data['distributions']):
|
|
466
|
+
hist, bins = np.histogram(distribution, bins=bin_num, density=True)
|
|
467
|
+
prob = hist * (bins[1] - bins[0])
|
|
468
|
+
bins[-1] += 1e-6
|
|
469
|
+
probs.append(prob[np.clip(np.digitize(target, bins) - 1, 0, bin_num - 1)])
|
|
470
|
+
maxima.append(bins[np.argmax(hist)])
|
|
471
|
+
|
|
472
|
+
data['probs'] = np.stack(probs)
|
|
473
|
+
data['max'] = np.stack(maxima)
|
|
474
|
+
data['meds'] = np.median(data['distributions'], axis=-1)
|
|
475
|
+
self._save_predictions(path, data)
|
|
476
|
+
return data
|
|
477
|
+
|
|
478
|
+
def get_param_groups(self, learning_rate: tuple[float, float] | None) -> ParamsT:
|
|
479
|
+
"""
|
|
480
|
+
Sets the optimiser for the architecture, by default AdamW.
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
learning_rate : tuple[float, float] | None, Optional
|
|
485
|
+
Learning rate for the encoder and normalising flow, if None, learning rate is set to 0
|
|
486
|
+
**kwargs
|
|
487
|
+
Optional keyword arguments to pass to the optimiser
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
Optimizer
|
|
492
|
+
Architecture optimiser
|
|
493
|
+
"""
|
|
494
|
+
learning_rate = learning_rate or (0,) * 2
|
|
495
|
+
return [
|
|
496
|
+
{'params': self.net.layers[:-1].parameters(), 'lr': learning_rate[0]},
|
|
497
|
+
{'params': self.net.layers[-1:].parameters(), 'lr': learning_rate[1]},
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
__all__ = ['NormFlow', 'NormFlowEncoder']
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility mixin for architecture classes and helper functions for optimisers and schedulers.
|
|
3
|
+
"""
|
|
4
|
+
import pickle
|
|
5
|
+
import logging as log
|
|
6
|
+
from warnings import warn
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from torch import optim, Tensor
|
|
10
|
+
from torch.optim.optimizer import ParamsT
|
|
11
|
+
|
|
12
|
+
from netloader.utils.types import Param, TensorT
|
|
13
|
+
|
|
14
|
+
logger: log.Logger = log.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UtilityMixin:
|
|
18
|
+
"""
|
|
19
|
+
Mixin class providing utility functions for architecture classes.
|
|
20
|
+
"""
|
|
21
|
+
@staticmethod
|
|
22
|
+
def _data_loader_translation(low_dim: TensorT, high_dim: TensorT) -> tuple[TensorT, TensorT]:
|
|
23
|
+
"""
|
|
24
|
+
Orders low and high dimensional tensors from the data loader as inputs and targets for the
|
|
25
|
+
network.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
low_dim : TensorT
|
|
30
|
+
Low dimensional data from the data loader of shape (N,...) and type float, where N is
|
|
31
|
+
the number of elements
|
|
32
|
+
high_dim : TensorT
|
|
33
|
+
High dimensional data from the data loader of shape (N,...) and type float
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
tuple[TensorT, TensorT]
|
|
38
|
+
Input and output target tensors of shape (N,...) and type float
|
|
39
|
+
"""
|
|
40
|
+
return high_dim, low_dim
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _param_device(params: dict[Tensor, Param] | Param, *args: Any, **kwargs: Any) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Sends parameters to the device, such as the parameters in the optimiser.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
params : dict[Tensor, Param] | Param
|
|
50
|
+
Parameters to move/cast
|
|
51
|
+
*args, **kwargs
|
|
52
|
+
Arguments to pass to torch.Tensor.to
|
|
53
|
+
"""
|
|
54
|
+
param: Tensor | Param
|
|
55
|
+
|
|
56
|
+
for param in params.values():
|
|
57
|
+
if isinstance(param, dict):
|
|
58
|
+
UtilityMixin._param_device(param, *args, **kwargs)
|
|
59
|
+
elif isinstance(param, Tensor):
|
|
60
|
+
param.data = param.data.to(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
# pylint: disable=protected-access
|
|
63
|
+
if param._grad is not None:
|
|
64
|
+
param._grad.data = param._grad.data.to(*args, **kwargs)
|
|
65
|
+
# pylint: enable=protected-access
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _save_predictions(path: str, data: dict[str, Any]) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Saves architecture predictions to pickle file if path is provided.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
path : str
|
|
75
|
+
Path to save architecture predictions
|
|
76
|
+
data : dict[str, Any]
|
|
77
|
+
Architecture predictions of shape (N,...) to save
|
|
78
|
+
"""
|
|
79
|
+
if not path:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
with open(path + '' if '.pkl' in path else '.pkl', 'wb') as file:
|
|
83
|
+
pickle.dump(data, file)
|
|
84
|
+
|
|
85
|
+
def _method_override(self, name: str, owner: type) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Checks if a method is overridden in a subclass.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
name : str
|
|
92
|
+
Name of the method to check
|
|
93
|
+
owner : type
|
|
94
|
+
Class to check for method override
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
bool
|
|
99
|
+
True if the method is overridden, False otherwise
|
|
100
|
+
"""
|
|
101
|
+
method: Callable = getattr(self, name, None)
|
|
102
|
+
|
|
103
|
+
if method is None:
|
|
104
|
+
logger.warning(f'Method {name} not found in {type(self).__name__}')
|
|
105
|
+
return False
|
|
106
|
+
return method.__qualname__.split('.')[0] != owner.__name__
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def init_optimiser(parameters: ParamsT, **kwargs: Any) -> optim.Optimizer:
|
|
110
|
+
"""
|
|
111
|
+
Sets the optimiser for the architecture, by default AdamW.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
parameters : ParamsT
|
|
116
|
+
Architecture parameters to optimise
|
|
117
|
+
**kwargs
|
|
118
|
+
Optional keyword arguments to pass to the optimiser
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
Optimizer
|
|
123
|
+
Architecture optimiser
|
|
124
|
+
"""
|
|
125
|
+
return optim.AdamW(parameters, **kwargs)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def init_scheduler(optimiser: optim.Optimizer, **kwargs: Any) -> optim.lr_scheduler.LRScheduler:
|
|
129
|
+
"""
|
|
130
|
+
Initialise the scheduler for the architecture, by default ReduceLROnPlateau.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
optimiser : Optimizer
|
|
135
|
+
Architecture optimiser
|
|
136
|
+
**kwargs
|
|
137
|
+
Optional keyword arguments to pass to the scheduler
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
LRScheduler
|
|
142
|
+
Optimiser scheduler
|
|
143
|
+
"""
|
|
144
|
+
return optim.lr_scheduler.ReduceLROnPlateau(optimiser, **kwargs)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def set_optimiser(parameters: ParamsT, **kwargs: Any) -> optim.Optimizer:
|
|
148
|
+
"""
|
|
149
|
+
Deprecated alias of :meth:`init_optimiser`.
|
|
150
|
+
|
|
151
|
+
.. deprecated:: 3.11.0
|
|
152
|
+
Use :meth:`init_optimiser` instead.
|
|
153
|
+
"""
|
|
154
|
+
warn(
|
|
155
|
+
"UtilityMixin.set_optimiser is deprecated, use UtilityMixin.init_optimiser "
|
|
156
|
+
"instead",
|
|
157
|
+
DeprecationWarning,
|
|
158
|
+
stacklevel=2,
|
|
159
|
+
)
|
|
160
|
+
return UtilityMixin.init_optimiser(parameters, **kwargs)
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def set_scheduler(optimiser: optim.Optimizer, **kwargs: Any) -> optim.lr_scheduler.LRScheduler:
|
|
164
|
+
"""
|
|
165
|
+
Deprecated alias of :meth:`init_scheduler`.
|
|
166
|
+
|
|
167
|
+
.. deprecated:: 3.11.0
|
|
168
|
+
Use :meth:`init_scheduler` instead.
|
|
169
|
+
"""
|
|
170
|
+
warn(
|
|
171
|
+
"UtilityMixin.set_scheduler is deprecated, use UtilityMixin.init_scheduler "
|
|
172
|
+
"instead",
|
|
173
|
+
DeprecationWarning,
|
|
174
|
+
stacklevel=2,
|
|
175
|
+
)
|
|
176
|
+
return UtilityMixin.init_scheduler(optimiser, **kwargs)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
__all__ = ['UtilityMixin']
|