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,879 @@
1
+ """
2
+ Miscellaneous network layers
3
+ """
4
+ from copy import deepcopy
5
+ from warnings import warn
6
+ from typing import Any, Self, cast, overload
7
+
8
+ import torch
9
+ import numpy as np
10
+ from torch import nn, Tensor
11
+
12
+ from netloader.utils import Shapes
13
+ from netloader.data import DataList
14
+ from netloader.layers import multi_layer
15
+ from netloader.utils.types import TensorListLike
16
+ from netloader.layers.base import BaseLayer, BaseSingleLayer
17
+
18
+
19
+ class BatchNorm(BaseSingleLayer):
20
+ """
21
+ Batch Normalisation layer
22
+
23
+ Attributes
24
+ ----------
25
+ group : int
26
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
27
+ Networks
28
+ description : str
29
+ Description of the layer
30
+ layers : Sequential
31
+ Layers to loop through in the forward pass
32
+ """
33
+ def __init__(self, shapes: Shapes, **kwargs: Any) -> None:
34
+ """
35
+ Parameters
36
+ ----------
37
+ shapes : Shapes
38
+ Shape of the outputs from each layer
39
+
40
+ **kwargs
41
+ Leftover parameters to pass to base layer for checking
42
+ """
43
+ super().__init__(**kwargs)
44
+ self._check_shape((1, 4), shapes[-1])
45
+ self.layers.add_module(
46
+ 'BatchNorm',
47
+ [
48
+ nn.BatchNorm1d,
49
+ nn.BatchNorm2d,
50
+ nn.BatchNorm3d,
51
+ ][max(0, len(shapes[-1]) - 2)](shapes[-1][0]),
52
+ )
53
+ shapes.append(shapes[-1].copy())
54
+
55
+
56
+ class Checkpoint(BaseLayer):
57
+ """
58
+ Constructs a layer to create a checkpoint for using the output from the previous layer in
59
+ future layers.
60
+
61
+ Attributes
62
+ ----------
63
+ group : int
64
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
65
+ Networks
66
+ description : str
67
+ Description of the layer
68
+ """
69
+ def __init__(
70
+ self,
71
+ shapes: Shapes,
72
+ check_shapes: Shapes,
73
+ **kwargs: Any) -> None:
74
+ """
75
+ Parameters
76
+ ----------
77
+ shapes : Shapes
78
+ Shape of the outputs from each layer
79
+ check_shapes : Shapes
80
+ Shape of the outputs from each checkpoint
81
+ **kwargs
82
+ Leftover parameters to pass to base layer for checking
83
+ """
84
+ super().__init__(**kwargs)
85
+ self._check_num: int = len(check_shapes)
86
+ shapes.append(deepcopy(shapes.get(-1, True)))
87
+ check_shapes.append(deepcopy(shapes.get(-1, True)))
88
+
89
+ def forward(
90
+ self,
91
+ x: TensorListLike,
92
+ checkpoints: list[TensorListLike],
93
+ *_: Any,
94
+ **__: Any) -> TensorListLike:
95
+ """
96
+ Forward pass of the checkpoint layer.
97
+
98
+ Parameters
99
+ ----------
100
+ x : TensorListLike
101
+ Input with tensors of shape (N,...) and type float, where N is the batch size
102
+ checkpoints : list[TensorListLike]
103
+ List of checkpoint values with tensors of shape (N,...) and type float
104
+
105
+ Returns
106
+ -------
107
+ TensorListLike
108
+ Output with tensors of shape (N,...) and type float
109
+ """
110
+ checkpoints.append(x.clone())
111
+ return x
112
+
113
+ def extra_repr(self) -> str:
114
+ """
115
+ Displays layer parameters when printing the network
116
+
117
+ Returns
118
+ -------
119
+ str
120
+ Layer parameters
121
+ """
122
+ return f'checkpoint_num={self._check_num}'
123
+
124
+
125
+ class Dropout(BaseSingleLayer):
126
+ """
127
+ Constructs a dropout layer to randomly zero out elements of the input tensor.
128
+
129
+ Attributes
130
+ ----------
131
+ group : int
132
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
133
+ Networks
134
+ description : str
135
+ Description of the layer
136
+ layers : Sequential
137
+ Layers to loop through in the forward pass
138
+ """
139
+ def __init__(
140
+ self,
141
+ prob: float,
142
+ *,
143
+ shapes: Shapes | None = None,
144
+ **kwargs: Any) -> None:
145
+ """
146
+ Parameters
147
+ ----------
148
+ prob : float
149
+ Probability of dropout
150
+ shapes : Shapes | None, Optional
151
+ Shape of the outputs from each layer, only required if tracking layer outputs is
152
+ necessary
153
+ **kwargs
154
+ Leftover parameters to pass to base layer for checking
155
+ """
156
+ super().__init__(**kwargs)
157
+ self.layers.add_module('Dropout', nn.Dropout(prob))
158
+
159
+ # If not used as a layer in a network
160
+ if not shapes:
161
+ return
162
+
163
+ shapes.append(shapes[-1].copy())
164
+
165
+ def __getstate__(self) -> dict[str, Any]:
166
+ return super().__getstate__() | {'prob': self.layers.Dropout.p} # type: ignore[union-attr]
167
+
168
+
169
+ class DropPath(BaseLayer):
170
+ """
171
+ Constructs a drop path layer to drop samples in a batch.
172
+
173
+ See `FractalNet: Ultra-Deep Neural Networks without Residuals
174
+ <https://arxiv.org/abs/1605.07648>`_ by Larsson et al. (2016) for details.
175
+
176
+ Custom version of implementation by `timm
177
+ <https://github.com/pprp/timm/blob/master/timm/layers/drop.py#L157>`_.
178
+
179
+ Attributes
180
+ ----------
181
+ group : int
182
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
183
+ Networks
184
+ description : str
185
+ Description of the layer
186
+ """
187
+ def __init__(
188
+ self,
189
+ prob: float,
190
+ *,
191
+ shapes: Shapes | None = None,
192
+ **kwargs: Any) -> None:
193
+ """
194
+ Parameters
195
+ ----------
196
+ prob : float
197
+ Probability of dropout
198
+ shapes : Shapes | None, Optional
199
+ Shape of the outputs from each layer, only required if tracking layer outputs is
200
+ necessary
201
+ **kwargs
202
+ Leftover parameters to pass to base layer for checking
203
+ """
204
+ super().__init__(**kwargs)
205
+ self._keep_prob: float = 1 - prob
206
+
207
+ # If not used as a layer in a network
208
+ if not shapes:
209
+ return
210
+
211
+ shapes.append(shapes[-1].copy())
212
+
213
+ def __getstate__(self) -> dict[str, Any]:
214
+ return super().__getstate__() | {'prob': 1 - self._keep_prob}
215
+
216
+ def forward(self, x: Tensor, *_: Any, **__: Any) -> Tensor:
217
+ """
218
+ Forward pass of the drop path layer.
219
+
220
+ Parameters
221
+ ----------
222
+ x : Tensor
223
+ Input tensor of shape (N,...) and type float, where N is the batch size
224
+
225
+ Returns
226
+ -------
227
+ Tensor
228
+ Output tensor of shape (N,...) and type float
229
+ """
230
+ if not self.training or self._keep_prob == 1:
231
+ return x
232
+
233
+ mask: Tensor = x.new_empty(x.shape[0:1] + (1,) * (x.ndim - 1)).bernoulli_(self._keep_prob)
234
+
235
+ if self._keep_prob:
236
+ mask.div_(self._keep_prob)
237
+
238
+ return x * mask
239
+
240
+ def extra_repr(self) -> str:
241
+ """
242
+ Displays layer parameters when printing the network
243
+
244
+ Returns
245
+ -------
246
+ str
247
+ Layer parameters
248
+ """
249
+ return f'prob={1 - self._keep_prob}'
250
+
251
+
252
+ class Index(BaseLayer):
253
+ """
254
+ Constructs a layer to slice the last dimension from the output from the previous layer.
255
+
256
+ Attributes
257
+ ----------
258
+ group : int
259
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
260
+ Networks
261
+ description : str
262
+ Description of the layer
263
+ """
264
+ @overload
265
+ def __init__(
266
+ self,
267
+ *,
268
+ index: int | list[int | None] | tuple[int | None, ...] | slice,
269
+ map_: bool = ...,
270
+ shapes: Shapes | None = ...,
271
+ **kwargs: Any) -> None: ...
272
+
273
+ @overload
274
+ def __init__(
275
+ self,
276
+ number: int,
277
+ *,
278
+ map_: bool = ...,
279
+ greater: bool = ...,
280
+ shapes: Shapes | None = ...,
281
+ **kwargs: Any) -> None: ...
282
+
283
+ def __init__(
284
+ self,
285
+ *args: int,
286
+ map_: bool = True,
287
+ index: int | list[int | None] | tuple[int | None, ...] | slice | None = None,
288
+ shapes: Shapes | None = None,
289
+ **kwargs: Any) -> None:
290
+ r"""
291
+ Parameters
292
+ ----------
293
+ index : int | list[int | None] | tuple[int | None, ...] | slice
294
+ Index or slice to apply to the last dimension of the input tensor(s) or if map\_ is
295
+ False, the DataList itself
296
+ map\_ : bool, Optional
297
+ If slicing should be applied to each tensor in a DataList, or if the DataList should be
298
+ sliced itself, only used if input is a DataList, default = True
299
+ shapes : Shapes | None, Optional
300
+ Shape of the outputs from each layer, only required if tracking layer outputs is
301
+ necessary
302
+ **kwargs
303
+ Leftover parameters to pass to base layer for checking
304
+ """
305
+ super().__init__(**kwargs)
306
+ number: int
307
+ shape: list[int]
308
+ in_shape: list[int] | list[list[int]]
309
+ self._map: bool = map_
310
+ self._idx: int | slice
311
+
312
+ if args or 'number' in kwargs:
313
+ warn(
314
+ 'number argument and greater keyword argument are deprecated, please use '
315
+ 'index instead',
316
+ DeprecationWarning,
317
+ stacklevel=2,
318
+ )
319
+ number = kwargs.pop('number', args[0])
320
+ self._idx = slice(number, None) if kwargs.pop('greater', True) else slice(number)
321
+ elif index is None and 'idx' in kwargs:
322
+ warn(
323
+ 'idx argument is deprecated, please use index instead',
324
+ DeprecationWarning,
325
+ stacklevel=2,
326
+ )
327
+ self._idx = kwargs['idx']
328
+ else:
329
+ assert index is not None
330
+ self._idx = slice(*index) if isinstance(index, (tuple, list)) else index
331
+
332
+ # If not used as a layer in a network
333
+ if not shapes:
334
+ return
335
+
336
+ in_shape = deepcopy(shapes.get(-1, True))
337
+
338
+ # Length of slice
339
+ if isinstance(in_shape[0], int):
340
+ in_shape = self._shape_index(cast(list[int], in_shape))
341
+ elif isinstance(in_shape[0], list) and self._map:
342
+ in_shape = [self._shape_index(shape) for shape in cast(list[list[int]], in_shape)]
343
+ else:
344
+ in_shape = cast(list[list[int]], in_shape)[self._idx]
345
+
346
+ shapes.append(in_shape)
347
+
348
+ def __getstate__(self) -> dict[str, Any]:
349
+ return super().__getstate__() | {'map_': self._map, 'index': self._idx}
350
+
351
+ def _shape_index(self, shape: list[int]) -> list[int]:
352
+ """
353
+ Returns the output shape after indexing.
354
+
355
+ Parameters
356
+ ----------
357
+ shape : list[int]
358
+ Input shape
359
+
360
+ Returns
361
+ -------
362
+ list[int]
363
+ Output shape
364
+ """
365
+ out_shape: list[int] = shape.copy()
366
+ idx: tuple[int, int, int]
367
+
368
+ if isinstance(self._idx, int):
369
+ out_shape[-1] = 1
370
+ else:
371
+ idx = self._idx.indices(shape[-1])
372
+ out_shape[-1] = (idx[1] - idx[0] + (idx[2] - 1)) // idx[2]
373
+ return out_shape
374
+
375
+ def forward(self, x: TensorListLike, *_: Any, **__: Any) -> TensorListLike:
376
+ """
377
+ Forward pass of the indexing layer.
378
+
379
+ Parameters
380
+ ----------
381
+ x : Tensor
382
+ Input tensor of shape (N,...) and type float, where N is the batch size
383
+
384
+ Returns
385
+ -------
386
+ Tensor
387
+ Output tensor of shape (N,...) and type float
388
+ """
389
+ if isinstance(x, DataList) and not self._map:
390
+ return x.get(self._idx, list_=True) if isinstance(self._idx, int) else (
391
+ DataList(x.get(self._idx, list_=True)))
392
+ return x[..., self._idx] if isinstance(x, Tensor) else x[self._idx]
393
+
394
+ def extra_repr(self) -> str:
395
+ """
396
+ Displays layer parameters when printing the network.
397
+
398
+ Returns
399
+ -------
400
+ str
401
+ Layer parameters
402
+ """
403
+ return f'map={bool(self._map)}, idx={self._idx}'
404
+
405
+
406
+ class LayerNorm(BaseLayer):
407
+ """
408
+ Constructs a layer normalisation layer that is the same as PyTorch's LayerNorm, except
409
+ normalisation priority is the first dimension after batch dimension to follow ConvNeXt
410
+ implementation.
411
+
412
+ Attributes
413
+ ----------
414
+ group : int
415
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
416
+ Networks
417
+ description : str
418
+ Description of the layer
419
+ """
420
+ def __init__(
421
+ self,
422
+ *,
423
+ dims: int | None = None,
424
+ shape: list[int] | None = None,
425
+ shapes: Shapes | None = None,
426
+ **kwargs: Any) -> None:
427
+ """
428
+ Parameters
429
+ ----------
430
+ dims : int | None, Optional
431
+ Number of dimensions to normalise starting with the first dimension, ignoring batch
432
+ dimension, requires shapes argument, won't be used if shape is provided
433
+ shape : list[int] | None, Optional
434
+ Input shape or shape of the first dimension to normalise, will be used if provided, else
435
+ dims will be used
436
+ shapes : Shapes | None, Optional
437
+ Shape of the outputs from each layer, only required if tracking layer outputs is
438
+ necessary
439
+ **kwargs
440
+ Leftover parameters to pass to base layer for checking
441
+ """
442
+ super().__init__(**kwargs)
443
+ self._layer: nn.LayerNorm
444
+
445
+ if not shape and (dims and shapes):
446
+ shape = shapes[-1][:dims]
447
+ elif not shape:
448
+ raise ValueError(f'Either shape ({shape}) or dims ({dims}) and shapes must be provided')
449
+
450
+ self._layer = nn.LayerNorm(shape[::-1])
451
+
452
+ # If not used as a layer in a network
453
+ if not shapes:
454
+ return
455
+
456
+ shapes.append(shapes[-1].copy())
457
+
458
+ def __getstate__(self) -> dict[str, Any]:
459
+ return super().__getstate__() | {'shape': self._layer.normalized_shape}
460
+
461
+ def forward(self, x: Tensor, *_: Any, **__: Any) -> Tensor:
462
+ """
463
+ Forward pass of the layer normalisation layer.
464
+
465
+ Parameters
466
+ ----------
467
+ x : Tensor
468
+ Input tensor of shape (N,...) and type float, where N is the batch size
469
+
470
+ Returns
471
+ -------
472
+ Tensor
473
+ Output Tensor of shape (N,...) and type float
474
+ """
475
+ permutation: list[int] = list(range(x.ndim - 1, 0, -1))
476
+ return self._layer(x.permute(0, *permutation)).permute(0, *permutation)
477
+
478
+
479
+ class Pad(BaseLayer):
480
+ """
481
+ Constructs a padding layer to pad the output from the previous layer.
482
+
483
+ Attributes
484
+ ----------
485
+ group : int
486
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
487
+ Networks
488
+ description : str
489
+ Description of the layer
490
+
491
+ Methods
492
+ -------
493
+ forward(x) -> Tensor
494
+ Forward pass of the padding layer
495
+ extra_repr() -> str
496
+ Displays layer parameters when printing the network
497
+ """
498
+ @overload
499
+ def __init__(
500
+ self,
501
+ pad: list[int] | list[tuple[int, int]] | tuple[int, ...],
502
+ *,
503
+ shapes: Shapes | None = ...,
504
+ pad_kwargs: dict[str, Any] | None = ...,
505
+ **kwargs: Any) -> None: ...
506
+
507
+ @overload
508
+ def __init__(
509
+ self,
510
+ pad: int,
511
+ *,
512
+ dims: int = ...,
513
+ shapes: Shapes = ...,
514
+ pad_kwargs: dict[str, Any] | None = ...,
515
+ **kwargs: Any) -> None: ...
516
+
517
+ def __init__(
518
+ self,
519
+ pad: int | list[int] | list[tuple[int, int]] | tuple[int, ...],
520
+ *,
521
+ dims: int = -1,
522
+ shapes: Shapes | None = None,
523
+ pad_kwargs: dict[str, Any] | None = None,
524
+ **kwargs: Any) -> None:
525
+ """
526
+ Parameters
527
+ ----------
528
+ pad : int | list[int] | list[tuple[int, int]] | tuple[int, ...]
529
+ Amount of padding to apply to each dimension of the input tensor, ignoring batch
530
+ dimension and from right to left, if int is provided, same padding will be applied to
531
+ left and right of all dimensions according to dims, if list of integers is provided,
532
+ each integer will be applied to left and right of the corresponding dimension, if list
533
+ of tuple of integers is provided, then asymmetric (left, right) padding will be applied,
534
+ if tuple of integers is provided, each pair of integers will be applied to left and
535
+ right of the corresponding dimension
536
+ dims : int, Optional
537
+ Number of dimensions to pad starting with the last dimension, ignoring batch dimension,
538
+ if shapes is not provided, dims must be greater than 0, if negative, excludes the first
539
+ abs(dims) dimensions, if 0 pads all dimensions, won't be used if padding is a list of
540
+ integers or list of tuple of integers, default = -1
541
+ shapes : Shapes | None, Optional
542
+ Shape of the outputs from each layer, only required if tracking layer outputs is
543
+ necessary
544
+ pad_kwargs : dict[str, Any] | None, Optional
545
+ Additional keyword arguments to pass to the padding function
546
+ **kwargs
547
+ Leftover parameters to pass to base layer for checking
548
+ """
549
+ super().__init__(**kwargs)
550
+ self._pad: tuple[int, ...]
551
+ self._kwargs: dict[str, Any] = pad_kwargs or {}
552
+ pad_: list[int]
553
+ sub_shape: list[int]
554
+ shape: list[int] | list[list[int]]
555
+
556
+ if isinstance(pad, int) and dims <= 0 and shapes is None:
557
+ raise ValueError(f'Dims ({dims}) must be greater than 0 if pad ({pad}) is an integer '
558
+ f'and shapes is not provided')
559
+
560
+ if shapes is None and dims <= 0:
561
+ dims = 1
562
+
563
+ if isinstance(pad, tuple) and len(pad) % 2 != 0:
564
+ raise ValueError(f'If pad is a tuple, it must contain an even number of integers '
565
+ f'({len(pad)})')
566
+
567
+ if dims < 0 and shapes:
568
+ dims = len(shapes[-1]) + dims
569
+ elif dims == 0 and shapes:
570
+ len(shapes[-1])
571
+
572
+ if isinstance(pad, int):
573
+ self._pad = tuple([pad] * 2 * dims)
574
+ elif isinstance(pad, list) and isinstance(pad[0], int):
575
+ self._pad = tuple(np.repeat(pad, 2).tolist())
576
+ elif isinstance(pad, list):
577
+ self._pad = tuple(np.array(pad).flatten().tolist())
578
+ else:
579
+ self._pad = pad
580
+
581
+ if shapes is None:
582
+ return
583
+
584
+ shape = shapes.get(-1, True)
585
+ pad_ = ([0] * (len(shape) - len(self._pad) // 2) +
586
+ [sum(self._pad[i - 2:i]) for i in range(len(self._pad), 0, -2)])
587
+
588
+ if isinstance(shape[0], list):
589
+ shapes.append([
590
+ self._pad_shape(cast(list[int], sub_shape), pad_) for sub_shape in shape
591
+ ])
592
+ else:
593
+ shapes.append(self._pad_shape(cast(list[int], shape), pad_))
594
+
595
+ def __getstate__(self) -> dict[str, Any]:
596
+ return super().__getstate__() | {
597
+ 'pad': self._pad,
598
+ 'pad_kwargs': self._kwargs,
599
+ }
600
+
601
+ @staticmethod
602
+ def _pad_shape(shape: list[int], dim_pads: list[int]) -> list[int]:
603
+ """
604
+ Returns the output shape after padding.
605
+
606
+ Parameters
607
+ ----------
608
+ shape : list[int]
609
+ Input shape
610
+ dim_pads : list[int]
611
+ Amount of padding applied to each dimension
612
+
613
+ Returns
614
+ -------
615
+ list[int]
616
+ Output shape
617
+ """
618
+ dim: int
619
+ dim_pad: int
620
+ return [dim + dim_pad for dim, dim_pad in zip(shape, dim_pads)]
621
+
622
+ def extra_repr(self) -> str:
623
+ """
624
+ Displays layer parameters when printing the network.
625
+
626
+ Returns
627
+ -------
628
+ str
629
+ Layer parameters
630
+ """
631
+ return f'pad={list(zip(self._pad[::2], self._pad[1::2]))}, pad_kwargs={self._kwargs}'
632
+
633
+ def forward(self, x: TensorListLike, *_: Any, **__: Any) -> TensorListLike:
634
+ """
635
+ Forward pass of the padding layer.
636
+
637
+ Parameters
638
+ ----------
639
+ x : TensorListLike
640
+ Input with tensor(s) of shape (N,...) and type float, where N is the batch size
641
+
642
+ Returns
643
+ -------
644
+ TensorListLike
645
+ Output with tensor(s) of shape (N,...) and type float
646
+ """
647
+ if isinstance(x, Tensor):
648
+ return nn.functional.pad(x, self._pad, **self._kwargs)
649
+ return x.apply(nn.functional.pad, self._pad, **self._kwargs)
650
+
651
+
652
+ class Reshape(BaseLayer):
653
+ """
654
+ Constructs a reshaping layer to change the data dimensions.
655
+
656
+ Attributes
657
+ ----------
658
+ group : int
659
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
660
+ Networks
661
+ description : str
662
+ Description of the layer
663
+ """
664
+ def __init__(
665
+ self,
666
+ shape: list[int],
667
+ *,
668
+ factor: bool = False,
669
+ layer: int | None = None,
670
+ net_out: list[int] | None = None,
671
+ shapes: Shapes | None = None,
672
+ **kwargs: Any) -> None:
673
+ """
674
+ Parameters
675
+ ----------
676
+ shape : list[int]
677
+ Desired shape of the output tensor, ignoring first dimension
678
+ factor : bool, Optional
679
+ If reshape should be relative to the network output shape, or if layer is provided,
680
+ which layer to be relative to, requires tracking layer outputs, default = False
681
+ layer : int | None, Optional
682
+ If factor is True, which layer for factor to be relative to, if None, network output
683
+ will be used
684
+ net_out : list[int] | None, Optional
685
+ Shape of the network's output, required only if factor is True
686
+ shapes : Shapes | None, Optional
687
+ Shape of the outputs from each layer, only required if tracking layer outputs is
688
+ necessary
689
+ **kwargs
690
+ Leftover parameters to pass to base layer for checking
691
+ """
692
+ super().__init__(**kwargs)
693
+ self._shape: list[int] = shape
694
+ prod: int
695
+ elm: int
696
+ target: list[int]
697
+
698
+ # If not used as a layer in a network
699
+ if shapes is None:
700
+ return
701
+
702
+ if factor and shapes is not None and net_out is not None:
703
+ target = shapes[layer] if layer is not None else net_out
704
+ self._shape = [
705
+ int(elm * length) if elm != -1 else -1 for elm, length in zip(self._shape, target)
706
+ ]
707
+ if factor and net_out is None:
708
+ raise ValueError('factor requires net_out to not be None')
709
+
710
+ # If -1 in output shape, calculate the dimension length from the input dimensions
711
+ if -1 not in self._shape:
712
+ shapes.append(self._shape)
713
+ elif self._shape.count(-1) == 1:
714
+ shape = self._shape.copy()
715
+ prod = np.prod(np.array(shape)[np.array(shape) != -1])
716
+ shape[shape.index(-1)] = int(np.prod(shapes[-1]) // prod)
717
+ shapes.append(shape)
718
+ else:
719
+ raise ValueError(f'Cannot infer output shape as -1 occurs more than once in '
720
+ f'{self._shape}')
721
+
722
+ # If input tensor cannot be reshaped into output shape
723
+ self._check_reshape(shapes[-2], shapes[-1])
724
+
725
+ def __getstate__(self) -> dict[str, Any]:
726
+ return super().__getstate__() | {'shape': self._shape}
727
+
728
+ @staticmethod
729
+ def _check_reshape(in_shape: list[int], out_shape: list[int]) -> None:
730
+ """
731
+ Checks if the input tensor can be reshaped into the output tensor.
732
+
733
+ Parameters
734
+ ----------
735
+ in_shape : list[int]
736
+ Input shape
737
+ out_shape : list[int]
738
+ Output shape
739
+ """
740
+ if np.prod(out_shape) != np.prod(in_shape):
741
+ raise ValueError(f'Input size does not match output size for input shape {in_shape} '
742
+ f'& output shape {out_shape}')
743
+
744
+ def forward(self, x: Tensor, *_: Any, **__: Any) -> Tensor:
745
+ """
746
+ Forward pass of reshaping tensors.
747
+
748
+ Parameters
749
+ ----------
750
+ x : Tensor
751
+ Input tensor of shape (N,...) and type float, where N is the batch size
752
+
753
+ Returns
754
+ -------
755
+ Tensor
756
+ Output tensor of shape (N,...) and type float
757
+ """
758
+ return x.contiguous().view(x.size(0), *self._shape)
759
+
760
+ def extra_repr(self) -> str:
761
+ """
762
+ Displays layer parameters when printing the network
763
+
764
+ Returns
765
+ -------
766
+ str
767
+ Layer parameters
768
+ """
769
+ return f'output={self._shape}'
770
+
771
+
772
+ class Scale(BaseLayer):
773
+ """
774
+ Constructs a scaling layer that scales the output by a learnable tensor.
775
+
776
+ Attributes
777
+ ----------
778
+ group : int
779
+ Layer group, if 0 it will always be used, else it will only be used if its group matches the
780
+ Networks
781
+ description : str
782
+ Description of the layer
783
+ """
784
+ def __init__(
785
+ self,
786
+ dims: int,
787
+ scale: float,
788
+ shapes: Shapes,
789
+ *,
790
+ first: bool = True,
791
+ **kwargs: Any) -> None:
792
+ """
793
+ Parameters
794
+ ----------
795
+ dims : int
796
+ Number of dimensions to have individual scales for
797
+ scale : float
798
+ Initial scale factor
799
+ shapes : Shapes
800
+ Shape of the outputs from each layer
801
+ first : bool, Optional
802
+ If dims should count from the first dimension after the batch dimension, or from the
803
+ final dimension backwards, default = True
804
+ **kwargs
805
+ Leftover parameters to pass to base layer for checking
806
+ """
807
+ super().__init__(**kwargs)
808
+ self._first: bool = first
809
+ self._scale: Tensor
810
+ shape: list[int]
811
+
812
+ if self._first:
813
+ shape = shapes[-1][:dims]
814
+ else:
815
+ shape = shapes[-1][-dims:]
816
+
817
+ self._scale = nn.Parameter(scale * torch.ones(*shape), requires_grad=True).to(self._device)
818
+
819
+ shapes.append(shapes[-1].copy())
820
+
821
+ def __getstate__(self) -> dict[str, Any]:
822
+ return super().__getstate__() | {
823
+ 'first': self._first,
824
+ 'dims': self._scale.ndim,
825
+ 'scale': self._scale.detach().flatten().cpu().tolist()[0],
826
+ }
827
+
828
+ def extra_repr(self) -> str:
829
+ return (f'first={self._first}, dims={self._scale.ndim}, '
830
+ f'scale={self._scale.detach().flatten().cpu().tolist()[0]}')
831
+
832
+ def forward(self, x: Tensor, *_: Any, **__: Any) -> Tensor:
833
+ """
834
+ Forward pass of the scale layer.
835
+
836
+ Parameters
837
+ ----------
838
+ x : Tensor
839
+ Input tensor of shape (N,...) and type float, where N is the batch size
840
+
841
+ Returns
842
+ -------
843
+ Tensor
844
+ Output tensor of shape (N,...) and type float
845
+ """
846
+ if self._first:
847
+ permutation = np.arange(x.ndim - 1, 0, -1)
848
+ return (self._scale * x.permute(0, *permutation)).permute(0, *permutation)
849
+ return self._scale * x
850
+
851
+ def to(self, *args: Any, **kwargs: Any) -> Self:
852
+ super().to(*args, **kwargs)
853
+ self._scale = self._scale.to(*args, **kwargs)
854
+ return self
855
+
856
+
857
+ __all__ = [
858
+ 'BatchNorm',
859
+ 'Checkpoint',
860
+ 'Dropout',
861
+ 'DropPath',
862
+ 'Index',
863
+ 'LayerNorm',
864
+ 'Pad',
865
+ 'Reshape',
866
+ 'Scale',
867
+ ]
868
+
869
+
870
+ def __getattr__(name: str) -> Any:
871
+ if hasattr(multi_layer, name):
872
+ warn(
873
+ f'Accessing {name} from layers.misc is deprecated, please use layers or '
874
+ f'layers.multi_layer instead',
875
+ DeprecationWarning,
876
+ stacklevel=2,
877
+ )
878
+ return getattr(multi_layer, name)
879
+ raise AttributeError(f"module {__name__} has no attribute {name}")