PyDLLib 1.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.
- DLL/Data/Metrics.py +626 -0
- DLL/Data/Preprocessing.py +384 -0
- DLL/Data/_DataReader.py +55 -0
- DLL/Data/__init__.py +1 -0
- DLL/DeepLearning/Callbacks/_BackUp.py +71 -0
- DLL/DeepLearning/Callbacks/_BaseCallback.py +43 -0
- DLL/DeepLearning/Callbacks/_EarlyStopping.py +102 -0
- DLL/DeepLearning/Callbacks/_ReduceLROnPlateau.py +90 -0
- DLL/DeepLearning/Callbacks/__init__.py +4 -0
- DLL/DeepLearning/Initialisers/_Initialiser.py +24 -0
- DLL/DeepLearning/Initialisers/_Kaiming_He.py +55 -0
- DLL/DeepLearning/Initialisers/_Xavier_Glorot.py +39 -0
- DLL/DeepLearning/Initialisers/__init__.py +2 -0
- DLL/DeepLearning/Layers/Activations/_Activation.py +23 -0
- DLL/DeepLearning/Layers/Activations/_ReLU.py +51 -0
- DLL/DeepLearning/Layers/Activations/_Sigmoid.py +51 -0
- DLL/DeepLearning/Layers/Activations/_SoftMax.py +83 -0
- DLL/DeepLearning/Layers/Activations/_Tanh.py +47 -0
- DLL/DeepLearning/Layers/Activations/__init__.py +4 -0
- DLL/DeepLearning/Layers/Regularisation/_BaseRegularisation.py +12 -0
- DLL/DeepLearning/Layers/Regularisation/_BatchNormalisation.py +106 -0
- DLL/DeepLearning/Layers/Regularisation/_Dropout.py +79 -0
- DLL/DeepLearning/Layers/Regularisation/_GroupNormalisation.py +112 -0
- DLL/DeepLearning/Layers/Regularisation/_InstanceNormalisation.py +50 -0
- DLL/DeepLearning/Layers/Regularisation/_LayerNormalisation.py +37 -0
- DLL/DeepLearning/Layers/Regularisation/__init__.py +5 -0
- DLL/DeepLearning/Layers/_Add.py +109 -0
- DLL/DeepLearning/Layers/_Attention.py +161 -0
- DLL/DeepLearning/Layers/_BaseLayer.py +87 -0
- DLL/DeepLearning/Layers/_Bidirectional.py +113 -0
- DLL/DeepLearning/Layers/_Conv2D.py +133 -0
- DLL/DeepLearning/Layers/_Dense.py +109 -0
- DLL/DeepLearning/Layers/_DenseKAN.py +184 -0
- DLL/DeepLearning/Layers/_Flatten.py +55 -0
- DLL/DeepLearning/Layers/_Identity.py +50 -0
- DLL/DeepLearning/Layers/_Input.py +11 -0
- DLL/DeepLearning/Layers/_LSTM.py +261 -0
- DLL/DeepLearning/Layers/_LayerList.py +98 -0
- DLL/DeepLearning/Layers/_MaxPooling2D.py +101 -0
- DLL/DeepLearning/Layers/_RNN.py +185 -0
- DLL/DeepLearning/Layers/_Reshape.py +52 -0
- DLL/DeepLearning/Layers/__init__.py +14 -0
- DLL/DeepLearning/Losses/_BCE.py +96 -0
- DLL/DeepLearning/Losses/_BaseLoss.py +18 -0
- DLL/DeepLearning/Losses/_CCE.py +87 -0
- DLL/DeepLearning/Losses/_Exponential.py +101 -0
- DLL/DeepLearning/Losses/_Huber.py +104 -0
- DLL/DeepLearning/Losses/_MAE.py +85 -0
- DLL/DeepLearning/Losses/_MSE.py +86 -0
- DLL/DeepLearning/Losses/__init__.py +6 -0
- DLL/DeepLearning/Model.py +265 -0
- DLL/DeepLearning/Optimisers/_ADADELTA.py +53 -0
- DLL/DeepLearning/Optimisers/_ADAGRAD.py +51 -0
- DLL/DeepLearning/Optimisers/_ADAM.py +65 -0
- DLL/DeepLearning/Optimisers/_BaseOptimiser.py +20 -0
- DLL/DeepLearning/Optimisers/_LBFGS.py +120 -0
- DLL/DeepLearning/Optimisers/_RMSPROP.py +62 -0
- DLL/DeepLearning/Optimisers/_SGD.py +44 -0
- DLL/DeepLearning/Optimisers/__init__.py +6 -0
- DLL/DeepLearning/__init__.py +0 -0
- DLL/Exceptions/_Exceptions.py +19 -0
- DLL/Exceptions/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_CalibratedClassifier.py +88 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_IsotonicRegression.py +114 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/_GaussianProcessRegressor.py +190 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/__init__.py +1 -0
- DLL/MachineLearning/SupervisedLearning/Kernels.py +851 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/TimeSeries.py +147 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_Elasticnet.py +161 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LassoRegression.py +157 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LinearRegression.py +81 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LogisticRegression.py +182 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RANSAC.py +97 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RidgeRegression.py +78 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/__init__.py +6 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_BernoulliNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_GaussianNaiveBayes.py +111 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_MultinomialNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/__init__.py +3 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsClassifier.py +115 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsRegressor.py +90 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVC.py +293 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVR.py +100 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostClassifier.py +179 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostRegressor.py +141 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_DecisionTree.py +219 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingClassifier.py +207 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingRegressor.py +122 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMClassifier.py +288 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMRegressor.py +168 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMTree.py +199 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RandomForest.py +96 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionForest.py +75 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionTree.py +148 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostClassifier.py +239 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostRegressor.py +131 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostTree.py +105 -0
- DLL/MachineLearning/SupervisedLearning/Trees/__init__.py +12 -0
- DLL/MachineLearning/SupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_DBScan.py +87 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_GaussianMixtureModels.py +121 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_KMeans.py +120 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_SpectralClustering.py +105 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/__init__.py +4 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_LDA.py +118 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_PCA.py +101 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_QDA.py +69 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_RobustPCA.py +143 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_TSNE.py +151 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_UMAP.py +176 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/__init__.py +6 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/_IsolationForest.py +175 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/__init__.py +1 -0
- DLL/MachineLearning/UnsupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/__init__.py +0 -0
- DLL/__init__.py +0 -0
- pydllib-1.0.dist-info/METADATA +104 -0
- pydllib-1.0.dist-info/RECORD +124 -0
- pydllib-1.0.dist-info/WHEEL +5 -0
- pydllib-1.0.dist-info/licenses/LICENSE +21 -0
- pydllib-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseRegularisation import BaseRegularisation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Dropout(BaseRegularisation):
|
|
7
|
+
"""
|
|
8
|
+
The dropout layer for neural networks.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
p (float, optional): The probability of a node being dropped out. Must be strictly between 0 and 1. Defaults to 0.5.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, p=0.5, **kwargs):
|
|
14
|
+
if p <= 0 or 1 <= p:
|
|
15
|
+
raise ValueError("p must be strictly between 0 and 1.")
|
|
16
|
+
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
self.p = 1 - p
|
|
19
|
+
self.name = "Dropout"
|
|
20
|
+
|
|
21
|
+
def initialise_layer(self, **kwargs):
|
|
22
|
+
"""
|
|
23
|
+
:meta private:
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
super().initialise_layer(**kwargs)
|
|
27
|
+
|
|
28
|
+
def forward(self, input, training=False, **kwargs):
|
|
29
|
+
"""
|
|
30
|
+
Sets some values of the input to zero with probability p.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
input (torch.Tensor of shape (batch_size, channels, ...)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
34
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
torch.Tensor: The output tensor after the transformation with the same shape as the input.
|
|
38
|
+
"""
|
|
39
|
+
if not isinstance(input, torch.Tensor):
|
|
40
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
41
|
+
if input.shape[-len(self.input_shape):] != self.input_shape:
|
|
42
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[-len(self.input_shape):], self.input_shape}).")
|
|
43
|
+
if not isinstance(training, bool):
|
|
44
|
+
raise TypeError("training must be a boolean.")
|
|
45
|
+
|
|
46
|
+
self.input = input
|
|
47
|
+
if training:
|
|
48
|
+
self.mask = torch.rand(size=self.input.shape, dtype=self.data_type, device=self.device) < self.p
|
|
49
|
+
self.output = self.input * self.mask / self.p
|
|
50
|
+
else:
|
|
51
|
+
self.output = self.input
|
|
52
|
+
return self.output
|
|
53
|
+
|
|
54
|
+
def backward(self, dCdy, **kwargs):
|
|
55
|
+
"""
|
|
56
|
+
Calculates the gradient of the loss function with respect to the input of the layer.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
torch.Tensor of shape (batch_size, channels, ...): The new gradient after backpropagation through the layer.
|
|
63
|
+
"""
|
|
64
|
+
if not hasattr(self, "mask"):
|
|
65
|
+
raise ValueError("Forward method should be called before backward.")
|
|
66
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
67
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
68
|
+
if dCdy.shape != self.output.shape:
|
|
69
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape, self.output.shape}).")
|
|
70
|
+
|
|
71
|
+
dCdx = dCdy * self.mask / self.p
|
|
72
|
+
return dCdx
|
|
73
|
+
|
|
74
|
+
def summary(self, offset=""):
|
|
75
|
+
"""
|
|
76
|
+
:meta private:
|
|
77
|
+
"""
|
|
78
|
+
output_shape = self.output_shape[0] if len(self.output_shape) == 1 else self.output_shape
|
|
79
|
+
return offset + f"{self.name} - Output: ({output_shape}) - Keep probability: {self.p}"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseRegularisation import BaseRegularisation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GroupNorm(BaseRegularisation):
|
|
7
|
+
"""
|
|
8
|
+
The group normalisation layer for neural networks. Computes the group norm of a batch along axis=1
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
num_groups (int, optional): The number of groups used in the normalisation. Must be a positive integer. Defaults to 32. The number of channels must be evenly divisible by num_groups. If is set to 1, is identical to layer normalisation and if batch_size, is identical to the instance normalisation.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, num_groups=32, **kwargs):
|
|
14
|
+
if num_groups is not None and (not isinstance(num_groups, int) or num_groups <= 0):
|
|
15
|
+
raise ValueError("num_groups must be a positive integer.")
|
|
16
|
+
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
self.num_groups = num_groups
|
|
19
|
+
self.epsilon = 1e-6
|
|
20
|
+
self.name = "Group normalisation"
|
|
21
|
+
|
|
22
|
+
def initialise_layer(self, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
:meta private:
|
|
25
|
+
"""
|
|
26
|
+
super().initialise_layer(**kwargs)
|
|
27
|
+
|
|
28
|
+
if self.output_shape[0] % self.num_groups != 0:
|
|
29
|
+
raise ValueError("The number of channels must be evenly divisible by num_groups.")
|
|
30
|
+
|
|
31
|
+
self.gamma = torch.ones(self.output_shape, device=self.device, dtype=self.data_type)
|
|
32
|
+
self.beta = torch.zeros(self.output_shape, device=self.device, dtype=self.data_type)
|
|
33
|
+
self.nparams = 2 * self.output_shape[0]
|
|
34
|
+
|
|
35
|
+
def forward(self, input, **kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Normalises the input to have zero mean and one variance accross self.num_groups groups accross the channel dimension with the following equation:
|
|
38
|
+
|
|
39
|
+
.. math::
|
|
40
|
+
y = \\gamma\\frac{x - \\mathbb{E}[x]}{\\sqrt{\\text{var}(x) + \\epsilon}} + \\beta,
|
|
41
|
+
|
|
42
|
+
where :math:`x` is the input, :math:`\\mathbb{E}[x]` is the expected value or the mean accross each group, :math:`\\text{var}(x)` is the variance accross the variance accross each group, :math:`\\epsilon` is a small constant and :math:`\\gamma` and :math:`\\beta` are trainable parameters.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
input (torch.Tensor of shape (batch_size, channels, ...)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
46
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
torch.Tensor: The output tensor after the normalisation with the same shape as the input.
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(input, torch.Tensor):
|
|
52
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
53
|
+
if input.shape[1:] != self.input_shape:
|
|
54
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
55
|
+
|
|
56
|
+
elements_per_group = input.shape[1] // self.num_groups
|
|
57
|
+
self.input = input
|
|
58
|
+
batch_size = input.shape[0]
|
|
59
|
+
self.input = input
|
|
60
|
+
self.input_reshaped = self.input.view(batch_size, self.num_groups, elements_per_group, *input.shape[2:])
|
|
61
|
+
mean = 1.0 / elements_per_group * self.input_reshaped.sum(2, keepdim=True)
|
|
62
|
+
|
|
63
|
+
self.x_centered = self.input_reshaped - mean
|
|
64
|
+
self.x_centered_squared = self.x_centered ** 2
|
|
65
|
+
self.var = 1.0 / elements_per_group * self.x_centered_squared.sum(2, keepdim=True) # biased variance
|
|
66
|
+
|
|
67
|
+
self.inv_std = (self.var + self.epsilon) ** -0.5
|
|
68
|
+
self.x_norm = self.x_centered * self.inv_std
|
|
69
|
+
self.x_reshaped = self.x_norm.view(self.input.shape)
|
|
70
|
+
self.output = self.x_reshaped * self.gamma + self.beta
|
|
71
|
+
return self.output
|
|
72
|
+
|
|
73
|
+
def backward(self, dCdy, **kwargs):
|
|
74
|
+
"""
|
|
75
|
+
Calculates the gradient of the loss function with respect to the input of the layer. Also calculates the gradients of the loss function with respect to the model parameters.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
torch.Tensor of shape (batch_size, channels, ...): The new gradient after backpropagation through the layer.
|
|
82
|
+
"""
|
|
83
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
84
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
85
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
86
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
87
|
+
|
|
88
|
+
batch_size = dCdy.shape[0]
|
|
89
|
+
elements_per_group = self.output.shape[1] // self.num_groups
|
|
90
|
+
dCdx_reshaped = dCdy * self.gamma
|
|
91
|
+
dCdgamma = (dCdy * self.x_reshaped).mean(axis=0)
|
|
92
|
+
dCdbeta = dCdy.mean(axis=0)
|
|
93
|
+
self.gamma.grad += dCdgamma
|
|
94
|
+
self.beta.grad += dCdbeta
|
|
95
|
+
|
|
96
|
+
dCdx_norm = dCdx_reshaped.view(batch_size, self.num_groups, elements_per_group, *self.output.shape[2:])
|
|
97
|
+
dCdx_centered = dCdx_norm * self.inv_std
|
|
98
|
+
dCdinv_std = (dCdx_norm * self.x_centered).sum(2, keepdim=True)
|
|
99
|
+
dCdvar = -0.5 * ((self.var + self.epsilon) ** -1.5) * dCdinv_std
|
|
100
|
+
dCdx_centered_squared = 1.0 / elements_per_group * torch.ones_like(self.x_centered_squared, device=self.device, dtype=self.data_type) * dCdvar
|
|
101
|
+
dCdx_centered += 2 * self.x_centered * dCdx_centered_squared
|
|
102
|
+
dCdinput_reshaped = dCdx_centered.clone()
|
|
103
|
+
dCdmean = -(dCdx_centered).sum(2, keepdim=True)
|
|
104
|
+
dCdinput_reshaped += 1.0 / elements_per_group * torch.ones_like(self.input_reshaped, device=self.device, dtype=self.data_type) * dCdmean
|
|
105
|
+
dCdx = dCdinput_reshaped.view(self.output.shape)
|
|
106
|
+
return dCdx.view(self.output.shape)
|
|
107
|
+
|
|
108
|
+
def get_parameters(self):
|
|
109
|
+
"""
|
|
110
|
+
:meta private:
|
|
111
|
+
"""
|
|
112
|
+
return (self.gamma, self.beta)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from ._GroupNormalisation import GroupNorm
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InstanceNorm(GroupNorm):
|
|
5
|
+
"""
|
|
6
|
+
The instance normalisation layer for neural networks. Computes the group norm of a batch along axis=1 with the same number of groups as channels.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, **kwargs):
|
|
9
|
+
super().__init__(None, **kwargs)
|
|
10
|
+
self.name = "Instance normalisation"
|
|
11
|
+
|
|
12
|
+
def initialise_layer(self, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
:meta private:
|
|
15
|
+
"""
|
|
16
|
+
if "input_shape" not in kwargs or not isinstance(kwargs.get("input_shape"), tuple | list):
|
|
17
|
+
raise ValueError(f"input_shape must be passed as a key word argument to layer.initialise_layer as a tuple or a list. Currently {kwargs.get('input_shape', None)}.")
|
|
18
|
+
|
|
19
|
+
self.num_groups = kwargs.get("input_shape")[0]
|
|
20
|
+
super().initialise_layer(**kwargs)
|
|
21
|
+
|
|
22
|
+
def forward(self, input, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
Normalises the input to have zero mean and one variance accross the channel dimension with the following equation:
|
|
25
|
+
|
|
26
|
+
.. math::
|
|
27
|
+
y = \\gamma\\frac{x - \\mathbb{E}[x]}{\\sqrt{\\text{var}(x) + \\epsilon}} + \\beta,
|
|
28
|
+
|
|
29
|
+
where :math:`x` is the input, :math:`\\mathbb{E}[x]` is the expected value or the mean accross the channel dimension, :math:`\\text{var}(x)` is the variance accross the variance accross the channel dimension, :math:`\\epsilon` is a small constant and :math:`\\gamma` and :math:`\\beta` are trainable parameters.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
input (torch.Tensor of shape (batch_size, channels, ...)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
torch.Tensor: The output tensor after the normalisation with the same shape as the input.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
return super().forward(input, **kwargs)
|
|
39
|
+
|
|
40
|
+
def backward(self, dCdy, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Calculates the gradient of the loss function with respect to the input of the layer. Also calculates the gradients of the loss function with respect to the model parameters.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
torch.Tensor of shape (batch_size, channels, ...): The new gradient after backpropagation through the layer.
|
|
49
|
+
"""
|
|
50
|
+
return super().backward(dCdy, **kwargs)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from ._GroupNormalisation import GroupNorm
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LayerNorm(GroupNorm):
|
|
5
|
+
def __init__(self, **kwargs):
|
|
6
|
+
super().__init__(1, **kwargs)
|
|
7
|
+
self.name = "Layer normalisation"
|
|
8
|
+
|
|
9
|
+
def forward(self, input, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Normalises the input to have zero mean and one variance accross the channel dimension with the following equation:
|
|
12
|
+
|
|
13
|
+
.. math::
|
|
14
|
+
y = \\gamma\\frac{x - \\mathbb{E}[x]}{\\sqrt{\\text{var}(x) + \\epsilon}} + \\beta,
|
|
15
|
+
|
|
16
|
+
where :math:`x` is the input, :math:`\\mathbb{E}[x]` is the expected value or the mean accross the channel dimension, :math:`\\text{var}(x)` is the variance accross the variance accross the channel dimension, :math:`\\epsilon` is a small constant and :math:`\\gamma` and :math:`\\beta` are trainable parameters.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
input (torch.Tensor of shape (batch_size, channels, ...)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
torch.Tensor: The output tensor after the normalisation with the same shape as the input.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
return super().forward(input, **kwargs)
|
|
26
|
+
|
|
27
|
+
def backward(self, dCdy, **kwargs):
|
|
28
|
+
"""
|
|
29
|
+
Calculates the gradient of the loss function with respect to the input of the layer. Also calculates the gradients of the loss function with respect to the model parameters.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
torch.Tensor of shape (batch_size, channels, ...): The new gradient after backpropagation through the layer.
|
|
36
|
+
"""
|
|
37
|
+
return super().backward(dCdy, **kwargs)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseLayer import BaseLayer
|
|
4
|
+
from ...Exceptions import NotCompiledError
|
|
5
|
+
from .Activations._Activation import Activation
|
|
6
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Add(BaseLayer):
|
|
10
|
+
"""
|
|
11
|
+
The addition layer.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
layer1 (DLL.DeepLearning.Layers.BaseLayer object): The first layer the input is passed to. The results of each layer are added together. The input and outpput shapes of the layers must be the same.
|
|
15
|
+
layer2 (DLL.DeepLearning.Layers.BaseLayer object): The second layer the input is passed to. The results of each layer are added together. The input and outpput shapes of the layers must be the same.
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, layer1, layer2, activation=None, normalisation=None, **kwargs):
|
|
18
|
+
if not isinstance(layer1, BaseLayer) or not isinstance(layer2, BaseLayer):
|
|
19
|
+
raise TypeError("layers must be an instances of DLL.DeepLearning.Layers.BaseLayer")
|
|
20
|
+
if layer1.output_shape != layer2.output_shape and layer1.output_shape is not None and layer2.output_shape is not None:
|
|
21
|
+
raise ValueError("Layers must have the same output shape.")
|
|
22
|
+
if not isinstance(activation, Activation) and activation is not None:
|
|
23
|
+
raise ValueError("activation must be from DLL.DeepLearning.Layers.Activations or None.")
|
|
24
|
+
if not isinstance(normalisation, BaseRegularisation) and normalisation is not None:
|
|
25
|
+
raise ValueError("normalisation must be from DLL.DeepLearning.Layers.Regularisation or None.")
|
|
26
|
+
|
|
27
|
+
super().__init__(layer1.output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
28
|
+
self.name = "Add"
|
|
29
|
+
self.layer1 = layer1
|
|
30
|
+
self.layer2 = layer2
|
|
31
|
+
|
|
32
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
33
|
+
"""
|
|
34
|
+
:meta private:
|
|
35
|
+
"""
|
|
36
|
+
if self.layer1.output_shape is None and input_shape != self.layer2.output_shape:
|
|
37
|
+
raise ValueError(f"Layers must have the same output shape {input_shape} vs {self.layer2.output_shape}.")
|
|
38
|
+
if self.layer2.output_shape is None and input_shape != self.layer1.output_shape:
|
|
39
|
+
raise ValueError(f"Layers must have the same output shape {input_shape} vs {self.layer1.output_shape}.")
|
|
40
|
+
|
|
41
|
+
self.layer1.initialise_layer(input_shape, data_type, device)
|
|
42
|
+
self.layer2.initialise_layer(input_shape, data_type, device)
|
|
43
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
44
|
+
|
|
45
|
+
def forward(self, input, training=False, **kwargs):
|
|
46
|
+
"""
|
|
47
|
+
Computes the forward values of the input layers and adds them together.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
input (torch.Tensor): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
51
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
torch.Tensor: The output tensor after the transformations with the spesified shape.
|
|
55
|
+
"""
|
|
56
|
+
if not isinstance(input, torch.Tensor):
|
|
57
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
58
|
+
if input.shape[1:] != self.input_shape:
|
|
59
|
+
raise ValueError(f"Input shape {input.shape[1:]} does not match the expected shape {self.input_shape}.")
|
|
60
|
+
if not isinstance(training, bool):
|
|
61
|
+
raise TypeError("training must be a boolean.")
|
|
62
|
+
|
|
63
|
+
val1 = self.layer1.forward(input, training=training)
|
|
64
|
+
val2 = self.layer2.forward(input.flip(1), training=training)
|
|
65
|
+
self.output = val1 + val2
|
|
66
|
+
if self.normalisation: self.output = self.normalisation.forward(self.output, training=training)
|
|
67
|
+
if self.activation: self.output = self.activation.forward(self.output)
|
|
68
|
+
return self.output
|
|
69
|
+
|
|
70
|
+
def backward(self, dCdy, **kwargs):
|
|
71
|
+
"""
|
|
72
|
+
Calculates the gradient of the loss function with respect to the input of the layers. Also calculates the gradients of the loss function with respect to the model parameters.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
torch.Tensor of the spesified shape: The new gradient after backpropagation through the layer.
|
|
79
|
+
"""
|
|
80
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
81
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
82
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
83
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
84
|
+
|
|
85
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
86
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
87
|
+
dCdx1 = self.layer1.backward(dCdy, **kwargs)
|
|
88
|
+
dCdx2 = self.layer2.backward(dCdy, **kwargs)
|
|
89
|
+
|
|
90
|
+
dCdx = dCdx1 + dCdx2
|
|
91
|
+
return dCdx
|
|
92
|
+
|
|
93
|
+
def get_parameters(self):
|
|
94
|
+
"""
|
|
95
|
+
:meta private:
|
|
96
|
+
"""
|
|
97
|
+
return (*self.layer1.get_parameters(), *self.layer2.get_parameters(), *super().get_parameters())
|
|
98
|
+
|
|
99
|
+
def get_nparams(self):
|
|
100
|
+
return self.layer1.get_nparams() + self.layer2.get_nparams()
|
|
101
|
+
|
|
102
|
+
def summary(self, offset=""):
|
|
103
|
+
if not hasattr(self, "input_shape"):
|
|
104
|
+
raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
105
|
+
|
|
106
|
+
sublayer_offset = offset + " "
|
|
107
|
+
summary1 = "\n" + self.layer1.summary(sublayer_offset)
|
|
108
|
+
summary2 = "\n" + self.layer2.summary(sublayer_offset)
|
|
109
|
+
return super().summary(offset) + summary1 + summary2
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseLayer import BaseLayer
|
|
4
|
+
from . import Dense
|
|
5
|
+
from .Activations import SoftMax
|
|
6
|
+
from .Regularisation import Dropout
|
|
7
|
+
from .Activations._Activation import Activation
|
|
8
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MultiHeadAttention(BaseLayer):
|
|
12
|
+
"""
|
|
13
|
+
The multi head attention layer.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
output_shape (tuple[int]): The output_shape of the model not containing the batch_size. Must be a tuple of positive integers. The returned tensor is of shape (n_samples, seq_len, output_shape) if len(output_shape) == 2 else (n_samples, seq_len).
|
|
17
|
+
n_heads (int): The number of heads used in the layer. The output dimension must be divisible by n_heads.
|
|
18
|
+
use_mask (bool): Determines if a mask is used to make the model only consider past tokens. Must be a boolean.
|
|
19
|
+
dropout (float): The probability of a node being dropped out. Must be in range [0, 1). Defaults to 0.0.
|
|
20
|
+
activation (:ref:`activations_section_label` | None, optional): The activation used after this layer. If is set to None, no activation is used. Defaults to None. If both activation and regularisation is used, the regularisation is performed first in the forward propagation.
|
|
21
|
+
normalisation (:ref:`regularisation_layers_section_label` | None, optional): The regularisation layer used after this layer. If is set to None, no regularisation is used. Defaults to None. If both activation and regularisation is used, the regularisation is performed first in the forward propagation.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self, output_shape, n_heads=1, use_mask=True, dropout=0.0, activation=None, normalisation=None, **kwargs):
|
|
24
|
+
if not isinstance(activation, Activation) and activation is not None:
|
|
25
|
+
raise ValueError("activation must be from DLL.DeepLearning.Layers.Activations or None.")
|
|
26
|
+
if not isinstance(normalisation, BaseRegularisation) and normalisation is not None:
|
|
27
|
+
raise ValueError("normalisation must be from DLL.DeepLearning.Layers.Regularisation or None.")
|
|
28
|
+
if not isinstance(use_mask, bool):
|
|
29
|
+
raise TypeError("use_mask must be a boolean.")
|
|
30
|
+
if dropout < 0 or 1 <= dropout:
|
|
31
|
+
raise ValueError("dropout must be in range [0, 1).")
|
|
32
|
+
if len(output_shape) == 1 and n_heads != 1 or output_shape[1] % n_heads != 0:
|
|
33
|
+
raise ValueError("output_dimension must be divisible by n_heads")
|
|
34
|
+
|
|
35
|
+
super().__init__(output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
36
|
+
self.name = "MultiHeadAttention"
|
|
37
|
+
self.use_mask = use_mask
|
|
38
|
+
self.mask = None
|
|
39
|
+
self.dropout = dropout
|
|
40
|
+
self.n_heads = n_heads
|
|
41
|
+
|
|
42
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
43
|
+
"""
|
|
44
|
+
:meta private:
|
|
45
|
+
"""
|
|
46
|
+
output_dim = self.output_shape[1] if len(self.output_shape) == 2 else 1
|
|
47
|
+
if input_shape[1] != output_dim:
|
|
48
|
+
raise ValueError("input_shape must be the same as the output_shape.")
|
|
49
|
+
if input_shape[0] != self.output_shape[0]:
|
|
50
|
+
raise ValueError("Input_shape must have the same seq_len as the output_shape.")
|
|
51
|
+
|
|
52
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
53
|
+
self.softmax = SoftMax()
|
|
54
|
+
seq_len = input_shape[0]
|
|
55
|
+
|
|
56
|
+
self.input_linear = Dense((seq_len, 3 * output_dim), bias=False)
|
|
57
|
+
self.input_linear.initialise_layer(input_shape, data_type, device)
|
|
58
|
+
|
|
59
|
+
if self.use_mask:
|
|
60
|
+
self.mask = torch.tril(torch.ones(seq_len, seq_len)).view(1, 1, seq_len, seq_len) == 0
|
|
61
|
+
if self.dropout > 0:
|
|
62
|
+
self.dropout = Dropout(self.dropout)
|
|
63
|
+
self.dropout.initialise_layer(input_shape=(self.n_heads, seq_len, seq_len), data_type=self.data_type, device=self.device)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def forward(self, input, training=False, **kwargs):
|
|
67
|
+
"""
|
|
68
|
+
Applies the attention mechanism on multiple heads.
|
|
69
|
+
|
|
70
|
+
.. math::
|
|
71
|
+
|
|
72
|
+
\\begin{align*}
|
|
73
|
+
y_{\\text{MultiHead}} &= \\text{Concat}(head_1, \\dots, head_{\\text{n_heads}}),\\\\
|
|
74
|
+
y_{reg} &= f(y_{\\text{MultiHead}}),\\\\
|
|
75
|
+
y_{activ} &= g(y_{reg}),
|
|
76
|
+
\\end{align*}
|
|
77
|
+
|
|
78
|
+
where :math:`head_i = \\text{Attention}(Q, K, V) = \\text{softmax}(\\frac{QK^T}{\\sqrt{\\text{output_dim}}})`, :math:`f` is the possible regularisation function and :math:`g` is the possible activation function. :math:`Q, K` and :math:`V` are the query, key and value matricies, which taken from the input by transforming it by a linear layer and splitting the result on the feature axis.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
input (torch.Tensor of shape (n_samples, seq_len, output_shape)): The input to the dense layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
82
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
torch.Tensor of shape (n_samples, seq_len) if output_shape == 0 else (n_samples, seq_len, output_shape): The output tensor after the transformations with the spesified shape.
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(input, torch.Tensor):
|
|
88
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
89
|
+
if input.shape[1:] != self.input_shape:
|
|
90
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
91
|
+
if not isinstance(training, bool):
|
|
92
|
+
raise TypeError("training must be a boolean.")
|
|
93
|
+
|
|
94
|
+
self.input = input
|
|
95
|
+
batch_size, seq_len, _ = input.size()
|
|
96
|
+
|
|
97
|
+
# split the input to the key, query and the value matricies
|
|
98
|
+
output_dim = self.output_shape[1] if len(self.output_shape) == 2 else 1
|
|
99
|
+
input = self.input_linear.forward(input, training=training) # project the input into key, query and value matricies
|
|
100
|
+
Q, K, V = input.chunk(3, dim=2)
|
|
101
|
+
self.Q = Q.reshape(batch_size, seq_len, self.n_heads, output_dim // self.n_heads).transpose(1, 2)
|
|
102
|
+
self.K = K.reshape(batch_size, seq_len, self.n_heads, output_dim // self.n_heads).transpose(1, 2)
|
|
103
|
+
self.V = V.reshape(batch_size, seq_len, self.n_heads, output_dim // self.n_heads).transpose(1, 2)
|
|
104
|
+
|
|
105
|
+
output = (self.Q @ self.K.transpose(-2, -1)) * output_dim ** (-0.5)
|
|
106
|
+
if self.mask is not None: output = output.masked_fill(self.mask, float('-inf'))
|
|
107
|
+
output = self.softmax.forward(output, training=training)
|
|
108
|
+
if not isinstance(self.dropout, float | int): output = self.dropout.forward(output, training=training)
|
|
109
|
+
self.attention_scores = output
|
|
110
|
+
output = output @ self.V
|
|
111
|
+
output = output.transpose(1, 2).contiguous().reshape(batch_size, seq_len, output_dim)
|
|
112
|
+
|
|
113
|
+
if self.normalisation: output = self.normalisation.forward(output, training=training)
|
|
114
|
+
if self.activation: output = self.activation.forward(output, training=training)
|
|
115
|
+
if len(self.output_shape) == 1: output = output.squeeze(dim=2) # If the output_shape is a 2d tensor, remove the last dimension
|
|
116
|
+
return output
|
|
117
|
+
|
|
118
|
+
def backward(self, dCdy, **kwargs):
|
|
119
|
+
"""
|
|
120
|
+
Calculates the gradient of the loss function with respect to the input of the layer.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
dCdy (torch.Tensor of shape (n_samples, seq_len, output_dim) if len(output_shape[0]) != 0 else (n_samples, seq_len)): The gradient given by the next layer.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
torch.Tensor of shape (n_samples, seq_len, output_dim): The new gradient after backpropagation through the layer.
|
|
127
|
+
"""
|
|
128
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
129
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
130
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
131
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
132
|
+
|
|
133
|
+
if len(self.output_shape) == 1: dCdy = dCdy.unsqueeze(dim=2) # If the output shape was a 2d tensor, add an extra dimension to the end
|
|
134
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
135
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
136
|
+
|
|
137
|
+
batch_size, seq_len, output_dim = dCdy.size()
|
|
138
|
+
dCdy = dCdy.reshape(batch_size, seq_len, self.n_heads, output_dim // self.n_heads).transpose(1, 2)
|
|
139
|
+
dCdV = self.attention_scores.transpose(-1, -2) @ dCdy
|
|
140
|
+
dCdy = dCdy @ self.V.transpose(-1, -2)
|
|
141
|
+
if not isinstance(self.dropout, float | int): dCdy = self.dropout.backward(dCdy)
|
|
142
|
+
dCdy = self.softmax.backward(dCdy)
|
|
143
|
+
if self.mask is not None: dCdy = dCdy * (~self.mask)
|
|
144
|
+
|
|
145
|
+
dCdy = dCdy * output_dim ** (-0.5)
|
|
146
|
+
dCdQ = dCdy @ self.K
|
|
147
|
+
dCdK = dCdy.transpose(-2, -1) @ self.Q
|
|
148
|
+
|
|
149
|
+
dCdQ = dCdQ.transpose(1, 2).reshape(batch_size, seq_len, output_dim)
|
|
150
|
+
dCdK = dCdK.transpose(1, 2).reshape(batch_size, seq_len, output_dim)
|
|
151
|
+
dCdV = dCdV.transpose(1, 2).reshape(batch_size, seq_len, output_dim)
|
|
152
|
+
dCdx = torch.cat([dCdQ, dCdK, dCdV], dim=2)
|
|
153
|
+
dCdx = self.input_linear.backward(dCdx)
|
|
154
|
+
|
|
155
|
+
return dCdx
|
|
156
|
+
|
|
157
|
+
def get_parameters(self):
|
|
158
|
+
"""
|
|
159
|
+
:meta private:
|
|
160
|
+
"""
|
|
161
|
+
return (*self.input_linear.get_parameters(), *super().get_parameters())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from ...Exceptions import NotCompiledError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseLayer:
|
|
8
|
+
def __init__(self, output_shape, input_shape=None, activation=None, normalisation=None, data_type=torch.float32, device=torch.device("cpu")):
|
|
9
|
+
if output_shape == 0: output_shape = tuple()
|
|
10
|
+
output_shape = (output_shape,) if isinstance(output_shape, int) else output_shape
|
|
11
|
+
if not isinstance(output_shape, tuple) and output_shape is not None:
|
|
12
|
+
raise TypeError(f"output_shape must be a tuple. Currently {output_shape}.")
|
|
13
|
+
if not isinstance(input_shape, tuple) and input_shape is not None:
|
|
14
|
+
raise ValueError(f"input_shape must be a tuple or None. Currently {input_shape}.")
|
|
15
|
+
if not isinstance(data_type, torch.dtype):
|
|
16
|
+
raise TypeError(f"data_type must be an instance of torch.dtype.")
|
|
17
|
+
if not isinstance(device, torch.device):
|
|
18
|
+
raise ValueError('device must be one of torch.device("cpu") or torch.device("cuda").')
|
|
19
|
+
|
|
20
|
+
self.input_shape = input_shape
|
|
21
|
+
self.output_shape = output_shape
|
|
22
|
+
self.output = None
|
|
23
|
+
self.input = None
|
|
24
|
+
self.nparams = 0
|
|
25
|
+
self.name = "base"
|
|
26
|
+
self.activation = activation
|
|
27
|
+
self.normalisation = normalisation
|
|
28
|
+
self.device = device
|
|
29
|
+
self.data_type = data_type
|
|
30
|
+
|
|
31
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
32
|
+
if not isinstance(input_shape, tuple):
|
|
33
|
+
raise ValueError(f"input_shape must be a tuple or None. Currently {input_shape}.")
|
|
34
|
+
if not isinstance(data_type, torch.dtype):
|
|
35
|
+
raise TypeError("data_type must be an instance of torch.dtype.")
|
|
36
|
+
if not isinstance(data_type, torch.dtype):
|
|
37
|
+
raise TypeError("data_type must be an instance of torch.dtype.")
|
|
38
|
+
|
|
39
|
+
self.input_shape = input_shape
|
|
40
|
+
self.data_type = data_type
|
|
41
|
+
self.device = device
|
|
42
|
+
|
|
43
|
+
if self.activation:
|
|
44
|
+
self.activation.initialise_layer(input_shape=self.output_shape, data_type=data_type, device=device)
|
|
45
|
+
if self.normalisation:
|
|
46
|
+
self.normalisation.initialise_layer(input_shape=self.output_shape, data_type=data_type, device=device)
|
|
47
|
+
|
|
48
|
+
def summary(self, offset=""):
|
|
49
|
+
if not hasattr(self, "input_shape"):
|
|
50
|
+
raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
51
|
+
|
|
52
|
+
input_shape = self.input_shape[0] if len(self.input_shape) == 1 else self.input_shape
|
|
53
|
+
output_shape = self.output_shape[0] if len(self.output_shape) == 1 else (self.output_shape if len(self.output_shape) != 0 else 1)
|
|
54
|
+
params_summary = " - Parameters: " + str(self.nparams) if self.nparams > 0 else ""
|
|
55
|
+
sublayer_offset = offset + " "
|
|
56
|
+
normalisation_summary = ("\n" + self.normalisation.summary(sublayer_offset)) if self.normalisation else ""
|
|
57
|
+
activation_summary = ("\n" + self.activation.summary(sublayer_offset)) if self.activation else ""
|
|
58
|
+
return offset + f"{self.name} - (Input, Output): ({input_shape}, {output_shape})" + params_summary + normalisation_summary + activation_summary
|
|
59
|
+
|
|
60
|
+
def forward(self, input, **kwargs):
|
|
61
|
+
if not isinstance(input, torch.Tensor):
|
|
62
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
63
|
+
if input.shape[1:] != self.input_shape:
|
|
64
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
65
|
+
|
|
66
|
+
self.input = input
|
|
67
|
+
return self.input
|
|
68
|
+
|
|
69
|
+
def backward(self, dCdy, **kwargs):
|
|
70
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
71
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
72
|
+
if dCdy.shape[1:] != self.output_shape and not (dCdy.ndim == 1 and self.output_shape[0] == 0):
|
|
73
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
74
|
+
|
|
75
|
+
return dCdy
|
|
76
|
+
|
|
77
|
+
def get_nparams(self):
|
|
78
|
+
if not hasattr(self, "nparams"):
|
|
79
|
+
raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
80
|
+
|
|
81
|
+
return self.nparams + (self.normalisation.nparams if self.normalisation else 0)
|
|
82
|
+
|
|
83
|
+
def get_parameters(self):
|
|
84
|
+
return tuple() if self.normalisation is None else self.normalisation.get_parameters()
|
|
85
|
+
|
|
86
|
+
def clone(self):
|
|
87
|
+
return deepcopy(self)
|