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,113 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
|
|
4
|
+
from ._BaseLayer import BaseLayer
|
|
5
|
+
from . import RNN, LSTM
|
|
6
|
+
from ...Exceptions import NotCompiledError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Bidirectional(BaseLayer):
|
|
10
|
+
"""
|
|
11
|
+
The bidirectional wrapper for LSTM or RNN layers.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
layer (DLL.DeepLearning.Layers.RNN or LSTM object): The input is passed to this layer in forward and reverse. The results of each layer are concatanated together along the feature axis.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, layer, **kwargs):
|
|
17
|
+
if not isinstance(layer, RNN) and not isinstance(layer, LSTM):
|
|
18
|
+
raise TypeError("layer must be an instance of DLL.DeepLearning.Layers.RNN or LSTM")
|
|
19
|
+
|
|
20
|
+
# Change the layers output shape to have at least 1 dimensional features.
|
|
21
|
+
if len(layer.output_shape) == 0 or (len(layer.output_shape) == 1 and not layer.return_last):
|
|
22
|
+
layer.output_shape = (*layer.output_shape, 1)
|
|
23
|
+
output_shape = (*layer.output_shape[:-1], 2 * layer.output_shape[-1])
|
|
24
|
+
|
|
25
|
+
super().__init__(output_shape, **kwargs)
|
|
26
|
+
self.name = "Bidirectional"
|
|
27
|
+
self.forward_layer = layer
|
|
28
|
+
self.backward_layer = deepcopy(layer)
|
|
29
|
+
|
|
30
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
31
|
+
"""
|
|
32
|
+
:meta private:
|
|
33
|
+
"""
|
|
34
|
+
if not isinstance(input_shape, tuple | list) or len(input_shape) != 2:
|
|
35
|
+
raise ValueError("input_shape must be a tuple of length 2.")
|
|
36
|
+
if not isinstance(data_type, torch.dtype):
|
|
37
|
+
raise TypeError("data_type must be an instance of torch.dtype")
|
|
38
|
+
if not isinstance(device, torch.device):
|
|
39
|
+
raise TypeError('device must be one of torch.device("cpu") or torch.device("cuda")')
|
|
40
|
+
|
|
41
|
+
self.forward_layer.initialise_layer(input_shape, data_type, device)
|
|
42
|
+
self.backward_layer.initialise_layer(input_shape, data_type, device)
|
|
43
|
+
|
|
44
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
45
|
+
|
|
46
|
+
def forward(self, input, training=False, **kwargs):
|
|
47
|
+
"""
|
|
48
|
+
Computes the forward values of the RNN or LSTM layer for both normal input and reverse input and concatanates the results along the feature axis.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
input (torch.Tensor of shape (batch_size, sequence_length, input_size)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
52
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
torch.Tensor of shape (n_samples, 2 * RNN.output_shape[-1]) or (n_samples, sequence_length, 2 * RNN.output_shape[-1]): The output tensor after the transformations with the spesified shape.
|
|
56
|
+
"""
|
|
57
|
+
if not isinstance(input, torch.Tensor):
|
|
58
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
59
|
+
if input.shape[1:] != self.input_shape:
|
|
60
|
+
raise ValueError(f"Input shape {input.shape[1:]} does not match the expected shape {self.input_shape}.")
|
|
61
|
+
if not isinstance(training, bool):
|
|
62
|
+
raise TypeError("training must be a boolean.")
|
|
63
|
+
|
|
64
|
+
forward_val = self.forward_layer.forward(input, training=training)
|
|
65
|
+
backward_val = self.backward_layer.forward(input.flip(1), training=training)
|
|
66
|
+
if not self.backward_layer.return_last:
|
|
67
|
+
backward_val = backward_val.flip(1)
|
|
68
|
+
self.output = torch.cat((forward_val, backward_val), dim=-1)
|
|
69
|
+
return self.output
|
|
70
|
+
|
|
71
|
+
def backward(self, dCdy, **kwargs):
|
|
72
|
+
"""
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
torch.Tensor of shape (n_samples, sequence_length, input_size): The new gradient after backpropagation through the layer.
|
|
80
|
+
"""
|
|
81
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
82
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
83
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
84
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
85
|
+
|
|
86
|
+
forward_grad = dCdy[..., :self.output_shape[-1] // 2]
|
|
87
|
+
backward_grad = dCdy[..., self.output_shape[-1] // 2:]
|
|
88
|
+
dCdx_forward = self.forward_layer.backward(forward_grad, **kwargs)
|
|
89
|
+
dCdx_backward = self.backward_layer.backward(backward_grad.flip(1), **kwargs)
|
|
90
|
+
if not self.backward_layer.return_last:
|
|
91
|
+
dCdx_backward = dCdx_backward.flip(1)
|
|
92
|
+
|
|
93
|
+
dCdx = dCdx_backward + dCdx_forward
|
|
94
|
+
return dCdx
|
|
95
|
+
|
|
96
|
+
def get_parameters(self):
|
|
97
|
+
"""
|
|
98
|
+
:meta private:
|
|
99
|
+
"""
|
|
100
|
+
return (*self.forward_layer.get_parameters(), *self.backward_layer.get_parameters(), *super().get_parameters())
|
|
101
|
+
|
|
102
|
+
def get_nparams(self):
|
|
103
|
+
return self.forward_layer.get_nparams() + self.backward_layer.get_nparams()
|
|
104
|
+
|
|
105
|
+
def summary(self, offset=""):
|
|
106
|
+
if not hasattr(self, "input_shape"):
|
|
107
|
+
raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
108
|
+
|
|
109
|
+
super_summary = offset + f"{self.name} - (Input, Output): ({self.input_shape}, {self.output_shape})"
|
|
110
|
+
sublayer_offset = offset + " "
|
|
111
|
+
forward_summary = "\n" + offset + self.forward_layer.summary(sublayer_offset)
|
|
112
|
+
backward_summary = "\n" + offset + self.backward_layer.summary(sublayer_offset)
|
|
113
|
+
return super_summary + forward_summary + backward_summary
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn.functional as F
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ._BaseLayer import BaseLayer
|
|
6
|
+
from .Activations._Activation import Activation
|
|
7
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
8
|
+
from ..Initialisers import Xavier_Uniform
|
|
9
|
+
from ..Initialisers._Initialiser import Initialiser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Conv2D(BaseLayer):
|
|
13
|
+
"""
|
|
14
|
+
The convolutional layer for a neural network.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
kernel_size (int): The kernel size used for the model. The kernel is automatically square. Must be a positive integer.
|
|
18
|
+
output_depth (int): The output depth of the layer. Must be a positive integer.
|
|
19
|
+
initialiser (:ref:`initialisers_section_label`, optional): The initialisation method for models weights. Defaults to Xavier_uniform.
|
|
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 fter 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, kernel_size, output_depth, initialiser=Xavier_Uniform(), activation=None, normalisation=None, **kwargs):
|
|
24
|
+
if not isinstance(kernel_size, int) or kernel_size <= 0:
|
|
25
|
+
raise ValueError(f"kernel_size must be a positive integer. Currently {kernel_size}.")
|
|
26
|
+
if not isinstance(output_depth, int) or output_depth <= 0:
|
|
27
|
+
raise ValueError(f"output_depth must be a positive integer. Currently {output_depth}.")
|
|
28
|
+
if not isinstance(initialiser, Initialiser):
|
|
29
|
+
raise ValueError('initialiser must be an instance of DLL.DeepLearning.Initialisers')
|
|
30
|
+
if not isinstance(activation, Activation) and activation is not None:
|
|
31
|
+
raise ValueError("activation must be from DLL.DeepLearning.Layers.Activations or None.")
|
|
32
|
+
if not isinstance(normalisation, BaseRegularisation) and normalisation is not None:
|
|
33
|
+
raise ValueError("normalisation must be from DLL.DeepLearning.Layers.Regularisation or None.")
|
|
34
|
+
|
|
35
|
+
super().__init__(output_shape=None, activation=activation, normalisation=normalisation, **kwargs)
|
|
36
|
+
self.output_depth = output_depth
|
|
37
|
+
self.kernel_size = kernel_size
|
|
38
|
+
self.initialiser = initialiser
|
|
39
|
+
self.name = "Conv2D"
|
|
40
|
+
|
|
41
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
42
|
+
"""
|
|
43
|
+
:meta private:
|
|
44
|
+
"""
|
|
45
|
+
input_depth, input_height, input_width = input_shape
|
|
46
|
+
self.input_depth = input_depth
|
|
47
|
+
self.output_shape = (self.output_depth, input_height - self.kernel_size + 1, input_width - self.kernel_size + 1)
|
|
48
|
+
self.kernels_shape = (self.output_depth, input_depth, self.kernel_size, self.kernel_size)
|
|
49
|
+
self.nparams = np.prod(self.kernels_shape) + np.prod(self.output_shape)
|
|
50
|
+
|
|
51
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
52
|
+
|
|
53
|
+
self.kernels = self.initialiser.initialise(self.kernels_shape, data_type=self.data_type, device=self.device)
|
|
54
|
+
self.biases = torch.zeros(self.output_shape)
|
|
55
|
+
|
|
56
|
+
if self.activation:
|
|
57
|
+
self.activation.initialise_layer(self.output_shape, self.data_type, self.device)
|
|
58
|
+
if self.normalisation:
|
|
59
|
+
self.normalisation.initialise_layer(self.output_shape, self.data_type, self.device)
|
|
60
|
+
|
|
61
|
+
def forward(self, input, training=False, **kwargs):
|
|
62
|
+
"""
|
|
63
|
+
Applies the convolutional transformation.
|
|
64
|
+
|
|
65
|
+
.. math::
|
|
66
|
+
\\begin{align*}
|
|
67
|
+
y_{i, j} &= \\text{bias}_j + \\sum_{k = 1}^{\\text{d_in}} \\text{kernel}(j, k) \star \\text{input}(i, k),\\\\
|
|
68
|
+
y_{reg_{i, j}} &= f(y_{i, j}),\\\\
|
|
69
|
+
y_{activ_{i, j}} &= g(y_{reg}),
|
|
70
|
+
\\end{align*}
|
|
71
|
+
|
|
72
|
+
where :math:`\star` is the cross-correlation operator, :math:`\\text{d_in}` is the input_depth, :math:`i\in [1,\dots, \\text{batch_size}]`, :math:`j\in[1,\dots, \\text{output_depth}]`, :math:`f` is the possible regularisation function and :math:`g` is the possible activation function.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
input (torch.Tensor of shape (n_samples, input_depth, input_height, input_width)): The input to the layer. Must be a torch.Tensor of the spesified shape.
|
|
76
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
torch.Tensor of shape (n_samples, output_depth, height - kernel_size + 1, width - kernel_size + 1): The output tensor after the transformations with the spesified shape.
|
|
80
|
+
"""
|
|
81
|
+
if not isinstance(input, torch.Tensor):
|
|
82
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
83
|
+
if input.shape[1:] != self.input_shape:
|
|
84
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
85
|
+
if not isinstance(training, bool):
|
|
86
|
+
raise TypeError("training must be a boolean.")
|
|
87
|
+
|
|
88
|
+
batch_size = input.shape[0]
|
|
89
|
+
self.input = input
|
|
90
|
+
self.output = self.biases.clone().repeat(batch_size, 1, 1, 1)
|
|
91
|
+
for i in range(self.output_depth):
|
|
92
|
+
for j in range(self.input_depth):
|
|
93
|
+
conv_output = F.conv2d(input[:, j:j+1, :, :], self.kernels[i:i+1, j:j+1, :, :], padding="valid")
|
|
94
|
+
self.output[:, i, :, :] += conv_output[:, 0, :, :]
|
|
95
|
+
|
|
96
|
+
if self.normalisation: self.output = self.normalisation.forward(self.output, training=training)
|
|
97
|
+
if self.activation: self.output = self.activation.forward(self.output)
|
|
98
|
+
return self.output
|
|
99
|
+
|
|
100
|
+
def backward(self, dCdy, **kwargs):
|
|
101
|
+
"""
|
|
102
|
+
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.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
dCdy (torch.Tensor of shape (n_samples, output_depth, output_height, output_width) : The gradient given by the next layer.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
torch.Tensor of shape (n_samples, input_depth, input_height, input_width): The new gradient after backpropagation through the layer.
|
|
109
|
+
"""
|
|
110
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
111
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
112
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
113
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
114
|
+
|
|
115
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
116
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
117
|
+
kernel_gradient = torch.zeros_like(self.kernels, device=self.device, dtype=self.data_type)
|
|
118
|
+
dCdx = torch.zeros_like(self.input, device=self.device, dtype=self.data_type)
|
|
119
|
+
batch_size = self.input.shape[0]
|
|
120
|
+
for i in range(self.output_depth):
|
|
121
|
+
for j in range(self.input_depth):
|
|
122
|
+
kernel_gradient[i, j] = F.conv2d(self.input[:, j:j+1, :, :], dCdy[:, i:i+1, :, :], padding="valid")[0, 0, :, :]
|
|
123
|
+
dCdx[:, j] += F.conv2d(dCdy[:, i:i+1, :, :], torch.flip(self.kernels[i:i+1, j:j+1, :, :], dims=(2, 3)), padding=[self.kernel_size - 1, self.kernel_size - 1])[0, 0, :, :]
|
|
124
|
+
|
|
125
|
+
self.biases.grad += dCdy.mean(dim=0)
|
|
126
|
+
self.kernels.grad += kernel_gradient / batch_size
|
|
127
|
+
return dCdx
|
|
128
|
+
|
|
129
|
+
def get_parameters(self):
|
|
130
|
+
"""
|
|
131
|
+
:meta private:
|
|
132
|
+
"""
|
|
133
|
+
return (self.kernels, self.biases, *super().get_parameters())
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseLayer import BaseLayer
|
|
4
|
+
from .Activations._Activation import Activation
|
|
5
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
6
|
+
from ..Initialisers import Xavier_Uniform
|
|
7
|
+
from ..Initialisers._Initialiser import Initialiser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Dense(BaseLayer):
|
|
11
|
+
"""
|
|
12
|
+
The basic dense linear layer.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
output_shape (tuple[int] or int): The output_shape of the model not containing the batch_size dimension. Must contain non-negative integers. If is int, returned shape is (n_samples, int). If is the length is zero, the returned tensor is of shape (n_samples,). Otherwise the returned tensor is of shape (n_samples, *output_shape).
|
|
16
|
+
initialiser (:ref:`initialisers_section_label`, optional): The initialisation method for models weights. Defaults to Xavier_uniform.
|
|
17
|
+
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.
|
|
18
|
+
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.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, output_shape, bias=True, initialiser=Xavier_Uniform(), activation=None, normalisation=None, **kwargs):
|
|
21
|
+
if not isinstance(initialiser, Initialiser):
|
|
22
|
+
raise ValueError('initialiser must be an instance of DLL.DeepLearning.Initialisers')
|
|
23
|
+
if not isinstance(activation, Activation) and activation is not None:
|
|
24
|
+
raise ValueError("activation must be from DLL.DeepLearning.Layers.Activations or None.")
|
|
25
|
+
if not isinstance(normalisation, BaseRegularisation) and normalisation is not None:
|
|
26
|
+
raise ValueError("normalisation must be from DLL.DeepLearning.Layers.Regularisation or None.")
|
|
27
|
+
|
|
28
|
+
super().__init__(output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
29
|
+
self.name = "Dense"
|
|
30
|
+
self.initialiser = initialiser
|
|
31
|
+
self.bias = bias
|
|
32
|
+
|
|
33
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
34
|
+
"""
|
|
35
|
+
:meta private:
|
|
36
|
+
"""
|
|
37
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
38
|
+
|
|
39
|
+
input_dim = input_shape[-1]
|
|
40
|
+
output_dim = 1 if len(self.output_shape) == 0 else self.output_shape[-1]
|
|
41
|
+
|
|
42
|
+
self.weights = self.initialiser.initialise((input_dim, output_dim), data_type=self.data_type, device=self.device)
|
|
43
|
+
self.biases = torch.zeros(output_dim, dtype=self.data_type, device=self.device)
|
|
44
|
+
self.nparams = output_dim * input_dim + output_dim
|
|
45
|
+
|
|
46
|
+
def forward(self, input, training=False, **kwargs):
|
|
47
|
+
"""
|
|
48
|
+
Applies the basic linear transformation
|
|
49
|
+
|
|
50
|
+
.. math::
|
|
51
|
+
|
|
52
|
+
\\begin{align*}
|
|
53
|
+
y_{lin} = xW + b,\\\\
|
|
54
|
+
y_{reg} = f(y_{lin}),\\\\
|
|
55
|
+
y_{activ} = g(y_{reg}),
|
|
56
|
+
\\end{align*}
|
|
57
|
+
|
|
58
|
+
where :math:`f` is the possible regularisation function and :math:`g` is the possible activation function.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
input (torch.Tensor of shape (n_samples, n_features)): The input to the dense layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
62
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
torch.Tensor of shape (n_samples,) if len(layer.output_shape) == 0 else (n_samples, output_shape): The output tensor after the transformations with the spesified shape.
|
|
66
|
+
"""
|
|
67
|
+
if not isinstance(input, torch.Tensor):
|
|
68
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
69
|
+
if input.shape[1:] != self.input_shape:
|
|
70
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
71
|
+
if not isinstance(training, bool):
|
|
72
|
+
raise TypeError("training must be a boolean.")
|
|
73
|
+
|
|
74
|
+
self.input = input
|
|
75
|
+
output = self.input @ self.weights + self.biases
|
|
76
|
+
if self.normalisation: output = self.normalisation.forward(output, training=training)
|
|
77
|
+
if self.activation: output = self.activation.forward(output)
|
|
78
|
+
if len(self.output_shape) == 0: output = output.squeeze(dim=1) # If the output_shape is a 1d tensor, remove the last dimension
|
|
79
|
+
return output
|
|
80
|
+
|
|
81
|
+
def backward(self, dCdy, **kwargs):
|
|
82
|
+
"""
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
dCdy (torch.Tensor of shape (n_samples,) if len(layer.output_shape) == 0 else (n_samples, output_shape)): The gradient given by the next layer.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
torch.Tensor of shape (n_samples, layer.input_shape[0]): The new gradient after backpropagation through the layer.
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
92
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
93
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
94
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
95
|
+
|
|
96
|
+
if len(self.output_shape) == 0: dCdy = dCdy.unsqueeze(dim=1) # If the output shape was a 1d tensor, add an extra dimension to the end
|
|
97
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
98
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
99
|
+
dCdx = dCdy @ self.weights.T
|
|
100
|
+
dCdW = self.input.transpose(-1, -2) @ dCdy
|
|
101
|
+
self.weights.grad += torch.mean(dCdW, dim=tuple(range(self.input.ndim - 2))) if self.input.ndim > 2 else dCdW
|
|
102
|
+
self.biases.grad += torch.mean(dCdy, dim=tuple(range(self.input.ndim - 1))) if self.bias else torch.zeros_like(self.biases)
|
|
103
|
+
return dCdx
|
|
104
|
+
|
|
105
|
+
def get_parameters(self):
|
|
106
|
+
"""
|
|
107
|
+
:meta private:
|
|
108
|
+
"""
|
|
109
|
+
return (self.weights, self.biases, *super().get_parameters())
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from functools import lru_cache, partial
|
|
3
|
+
|
|
4
|
+
from ._BaseLayer import BaseLayer
|
|
5
|
+
from .Activations._Activation import Activation
|
|
6
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
7
|
+
from ..Initialisers import Xavier_Uniform
|
|
8
|
+
from ..Initialisers._Initialiser import Initialiser
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@lru_cache
|
|
12
|
+
def _bspline_basis(i, n_fun, knots, x):
|
|
13
|
+
if n_fun == 0:
|
|
14
|
+
return ((knots[i] <= x) & (x < knots[i + 1])).float()
|
|
15
|
+
|
|
16
|
+
denom1 = knots[i + n_fun] - knots[i]
|
|
17
|
+
denom2 = knots[i + n_fun + 1] - knots[i + 1]
|
|
18
|
+
|
|
19
|
+
term1 = (x - knots[i]) / denom1 * _bspline_basis(i, n_fun - 1, knots, x) if denom1 > 0 else torch.zeros_like(x)
|
|
20
|
+
term2 = (knots[i + n_fun + 1] - x) / denom2 * _bspline_basis(i + 1, n_fun - 1, knots, x) if denom2 > 0 else torch.zeros_like(x)
|
|
21
|
+
return term1 + term2
|
|
22
|
+
|
|
23
|
+
@lru_cache
|
|
24
|
+
def _bspline_basis_derivative(i, n_fun, knots, x):
|
|
25
|
+
if n_fun == 0:
|
|
26
|
+
return torch.zeros(x)
|
|
27
|
+
|
|
28
|
+
denom1 = knots[i + n_fun] - knots[i]
|
|
29
|
+
denom2 = knots[i + n_fun + 1] - knots[i + 1]
|
|
30
|
+
|
|
31
|
+
term1 = _bspline_basis(i, n_fun - 1, knots, x) / denom1 if denom1 > 0 else torch.zeros_like(x)
|
|
32
|
+
term2 = _bspline_basis(i + 1, n_fun - 1, knots, x) / denom2 if denom2 > 0 else torch.zeros_like(x)
|
|
33
|
+
|
|
34
|
+
return n_fun * (term1 - term2)
|
|
35
|
+
|
|
36
|
+
def _SiLU(x):
|
|
37
|
+
return x / (1 + torch.exp(-x))
|
|
38
|
+
|
|
39
|
+
def _SiLU_der(x):
|
|
40
|
+
return (1 + torch.exp(-x) + x * torch.exp(-x)) / (1 + torch.exp(-x)) ** 2
|
|
41
|
+
|
|
42
|
+
def make_basis_func(i, degree, t, x):
|
|
43
|
+
return _bspline_basis(i, degree, t, x)
|
|
44
|
+
|
|
45
|
+
def make_basis_func_der(i, degree, t, x):
|
|
46
|
+
return _bspline_basis_derivative(i, degree, t, x)
|
|
47
|
+
|
|
48
|
+
def _get_basis_functions(n_fun, degree=3, bounds=(-1, 1)):
|
|
49
|
+
grid_len = n_fun - degree + 1
|
|
50
|
+
step = (bounds[1] - bounds[0]) / (grid_len - 1)
|
|
51
|
+
edge_funcs, edge_func_ders = [], []
|
|
52
|
+
|
|
53
|
+
# SiLU bias function
|
|
54
|
+
edge_funcs.append(_SiLU)
|
|
55
|
+
edge_func_ders.append(_SiLU_der)
|
|
56
|
+
|
|
57
|
+
# B-splines
|
|
58
|
+
t = torch.linspace(bounds[0] - degree * step, bounds[1] + degree * step, grid_len + 2 * degree)
|
|
59
|
+
t[degree], t[-degree - 1] = bounds[0], bounds[1]
|
|
60
|
+
for ind_spline in range(n_fun - 1):
|
|
61
|
+
edge_funcs.append(partial(make_basis_func, ind_spline, degree, t))
|
|
62
|
+
edge_func_ders.append(partial(make_basis_func_der, ind_spline, degree, t))
|
|
63
|
+
return edge_funcs, edge_func_ders
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class _NeuronKAN:
|
|
67
|
+
def __init__(self, input_length, n_basis_funcs, bounds, initialiser, data_type, device, basis_func_degree=3):
|
|
68
|
+
self.edge_fun, self.edge_fun_der = _get_basis_functions(n_fun=n_basis_funcs, degree=basis_func_degree, bounds=bounds)
|
|
69
|
+
self.weights = initialiser.initialise((n_basis_funcs, input_length), data_type=data_type, device=device)
|
|
70
|
+
|
|
71
|
+
def forward(self, input, **kwargs):
|
|
72
|
+
self.input = input
|
|
73
|
+
self.edge_func_values = torch.stack([func(input) for func in self.edge_fun], dim=1) # (n, n_basis_funcs, input_length)
|
|
74
|
+
output = (self.weights.unsqueeze(0) * self.edge_func_values).sum(dim=(1, 2))
|
|
75
|
+
return output
|
|
76
|
+
|
|
77
|
+
def backward(self, dCdy, **kwargs):
|
|
78
|
+
dCdy = dCdy.view(len(dCdy), 1, 1) # (n, 1, 1)
|
|
79
|
+
self.weights.grad += torch.sum(dCdy * self.edge_func_values, dim=0)
|
|
80
|
+
dCdx = dCdy * self.weights.unsqueeze(0) # (n, n_basis_funcs, input_length)
|
|
81
|
+
edge_func_derivatives = torch.stack([func(self.input) for func in self.edge_fun_der], dim=1)
|
|
82
|
+
dCdx = torch.sum(dCdx * edge_func_derivatives, dim=1)
|
|
83
|
+
return dCdx
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DenseKAN(BaseLayer):
|
|
87
|
+
"""
|
|
88
|
+
The dense Kolmogorov-Arnold network layer. The implementation is based on `this paper <https://arxiv.org/pdf/2404.19756>`_ and `this article <https://mlwithouttears.com/2024/05/15/a-from-scratch-implementation-of-kolmogorov-arnold-networks-kan/>`_.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
output_shape (tuple[int] or int): The output_shape of the model not containing the batch_size dimension. Must contain non-negative integers. If is int, returned shape is (n_samples, int). If is the length is zero, the returned tensor is of shape (n_samples,). Otherwise the returned tensor is of shape (n_samples, *output_shape).
|
|
92
|
+
n_basis_funcs (int, optional): The number of basis functions used for fitting. If 1, only SiLU is used. Otherwise 1 SiLU and n_basis_funcs - 1 Bsplines basis functions are used. Must be a positive integer. Defaults to 10.
|
|
93
|
+
bounds (tuple[int], optional): The theoretical min and max of the data that will be passed to the forward method. Must be a tuple containing two integers. Defaults to (-1, 1).
|
|
94
|
+
basis_func_degree (int, optional): The degree of the Bspline basis functions. Must be positive. Defaults to 3.
|
|
95
|
+
initialiser (:ref:`initialisers_section_label`, optional): The initialisation method for models weights. Defaults to Xavier_uniform.
|
|
96
|
+
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.
|
|
97
|
+
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.
|
|
98
|
+
Note:
|
|
99
|
+
n_basis_funcs and basis_func_degree should be different to avoid certain errors.
|
|
100
|
+
"""
|
|
101
|
+
def __init__(self, output_shape, n_basis_funcs=10, bounds=(-1, 1), basis_func_degree=3, initialiser=Xavier_Uniform(), activation=None, normalisation=None, **kwargs):
|
|
102
|
+
if not isinstance(n_basis_funcs, int) or n_basis_funcs <= 0:
|
|
103
|
+
raise ValueError("n_basis_funcs must be a positive integer.")
|
|
104
|
+
if not isinstance(bounds, tuple) or len(bounds) != 2 or not isinstance(bounds[0], int) or not isinstance(bounds[1], int) or bounds[0] >= bounds[1]:
|
|
105
|
+
raise ValueError("bounds should be a tuple of length 2 containing the theoretical minimum and maximum of the data that will be passed in.")
|
|
106
|
+
if not isinstance(basis_func_degree, int) or basis_func_degree <= 0:
|
|
107
|
+
raise ValueError("basis_func_degree must be a positive integer.")
|
|
108
|
+
if n_basis_funcs == basis_func_degree:
|
|
109
|
+
raise ValueError("n_basis_funcs and basis_func_degree should be different.")
|
|
110
|
+
if not isinstance(initialiser, Initialiser):
|
|
111
|
+
raise ValueError('initialiser must be an instance of DLL.DeepLearning.Initialisers')
|
|
112
|
+
if not isinstance(activation, Activation) and activation is not None:
|
|
113
|
+
raise ValueError("activation must be from DLL.DeepLearning.Layers.Activations or None.")
|
|
114
|
+
if not isinstance(normalisation, BaseRegularisation) and normalisation is not None:
|
|
115
|
+
raise ValueError("normalisation must be from DLL.DeepLearning.Layers.Regularisation or None.")
|
|
116
|
+
|
|
117
|
+
super().__init__(output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
118
|
+
self.name = "DenseKAN"
|
|
119
|
+
self.initialiser = initialiser
|
|
120
|
+
self.n_basis_funcs = n_basis_funcs
|
|
121
|
+
|
|
122
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
123
|
+
"""
|
|
124
|
+
:meta private:
|
|
125
|
+
"""
|
|
126
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
127
|
+
|
|
128
|
+
input_dim = input_shape[-1]
|
|
129
|
+
output_dim = 1 if len(self.output_shape) == 0 else self.output_shape[-1]
|
|
130
|
+
|
|
131
|
+
self.neurons = [_NeuronKAN(input_dim, self.n_basis_funcs, (-1, 1), self.initialiser, data_type, device, 3) for _ in range(output_dim)] # TODO: Change the hard coded values to actual parameters
|
|
132
|
+
self.nparams = self.n_basis_funcs * output_dim * input_dim
|
|
133
|
+
|
|
134
|
+
def forward(self, input, training=False, **kwargs):
|
|
135
|
+
"""
|
|
136
|
+
Applies the forward equation of the Kolmogorov-Arnold network.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
input (torch.Tensor of shape (n_samples, n_features)): The input to the dense layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
140
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
torch.Tensor of shape (n_samples,) if len(layer.output_shape) == 0 else (n_samples, output_shape): The output tensor after the transformations with the spesified shape.
|
|
144
|
+
"""
|
|
145
|
+
if not isinstance(input, torch.Tensor):
|
|
146
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
147
|
+
if input.shape[1:] != self.input_shape:
|
|
148
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
149
|
+
if not isinstance(training, bool):
|
|
150
|
+
raise TypeError("training must be a boolean.")
|
|
151
|
+
|
|
152
|
+
self.input = input
|
|
153
|
+
output = torch.stack([neuron.forward(input) for neuron in self.neurons], dim=1)
|
|
154
|
+
if self.normalisation: output = self.normalisation.forward(output, training=training)
|
|
155
|
+
if self.activation: output = self.activation.forward(output)
|
|
156
|
+
if len(self.output_shape) == 0: output = output.squeeze(dim=1) # If the output_shape is a 1d tensor, remove the last dimension
|
|
157
|
+
return output
|
|
158
|
+
|
|
159
|
+
def backward(self, dCdy, **kwargs):
|
|
160
|
+
"""
|
|
161
|
+
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.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
dCdy (torch.Tensor of shape (n_samples,) if len(layer.output_shape) == 0 else (n_samples, output_shape)): The gradient given by the next layer.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
torch.Tensor of shape (n_samples, layer.input_shape[0]): The new gradient after backpropagation through the layer.
|
|
168
|
+
"""
|
|
169
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
170
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
171
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
172
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
173
|
+
|
|
174
|
+
if len(self.output_shape) == 0: dCdy = dCdy.unsqueeze(dim=1) # If the output shape was a 1d tensor, add an extra dimension to the end
|
|
175
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
176
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
177
|
+
dCdx = sum([neuron.backward(dCdy[:, i]) for i, neuron in enumerate(self.neurons)])
|
|
178
|
+
return dCdx
|
|
179
|
+
|
|
180
|
+
def get_parameters(self):
|
|
181
|
+
"""
|
|
182
|
+
:meta private:
|
|
183
|
+
"""
|
|
184
|
+
return (*(neuron.weights for neuron in self.neurons), *super().get_parameters())
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from ._BaseLayer import BaseLayer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Flatten(BaseLayer):
|
|
8
|
+
"""
|
|
9
|
+
The flattening layer.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, **kwargs):
|
|
12
|
+
super().__init__(None, **kwargs)
|
|
13
|
+
self.name = "Flatten"
|
|
14
|
+
|
|
15
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
16
|
+
"""
|
|
17
|
+
:meta private:
|
|
18
|
+
"""
|
|
19
|
+
self.output_shape = (np.prod(input_shape),)
|
|
20
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
21
|
+
|
|
22
|
+
def forward(self, input, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
Flattens the input tensor into a 2 dimensional tensor.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
input (torch.Tensor of shape (n_samples, ...)): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
torch.Tensor of shape (n_samples, product_of_other_dimensions): The output tensor after flattening the input tensor.
|
|
31
|
+
"""
|
|
32
|
+
if not isinstance(input, torch.Tensor):
|
|
33
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
34
|
+
if input.shape[-len(self.input_shape):] != self.input_shape:
|
|
35
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
36
|
+
|
|
37
|
+
self.input = input
|
|
38
|
+
return input.reshape(input.shape[0], -1)
|
|
39
|
+
|
|
40
|
+
def backward(self, dCdy, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Reshapes the gradient to the original shape.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
dCdy (torch.Tensor of shape (n_samples, product_of_other_dimensions): The gradient given by the next layer.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
torch.Tensor of shape (n_samples, *layer.input_shape): The reshaped gradient after backpropagation through the layer.
|
|
49
|
+
"""
|
|
50
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
51
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
52
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
53
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
54
|
+
|
|
55
|
+
return dCdy.reshape(*self.input.shape)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from .Activations._Activation import Activation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Identity(Activation):
|
|
7
|
+
"""
|
|
8
|
+
The identity layer.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, **kwargs):
|
|
11
|
+
|
|
12
|
+
super().__init__(**kwargs)
|
|
13
|
+
self.name = "Identity"
|
|
14
|
+
|
|
15
|
+
def forward(self, input, training=False, **kwargs):
|
|
16
|
+
"""
|
|
17
|
+
Returns the input.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
input (torch.Tensor of shape (n_samples, n_features)): The input to the dense layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
21
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
torch.Tensor: The same tensor as the input
|
|
25
|
+
"""
|
|
26
|
+
if not isinstance(input, torch.Tensor):
|
|
27
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
28
|
+
if input.shape[1:] != self.input_shape:
|
|
29
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
30
|
+
if not isinstance(training, bool):
|
|
31
|
+
raise TypeError("training must be a boolean.")
|
|
32
|
+
|
|
33
|
+
return input
|
|
34
|
+
|
|
35
|
+
def backward(self, dCdy, **kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Returns the gradient.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
dCdy (torch.Tensor): The gradient given by the next layer.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
torch.Tensor: The same tensor as the input gradient
|
|
44
|
+
"""
|
|
45
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
46
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
47
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
48
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
49
|
+
|
|
50
|
+
return dCdy
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from ._BaseLayer import BaseLayer
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Input(BaseLayer):
|
|
5
|
+
def __init__(self, output_shape, **kwargs):
|
|
6
|
+
super().__init__(output_shape=output_shape, input_shape=output_shape, **kwargs)
|
|
7
|
+
self.name = "Input"
|
|
8
|
+
|
|
9
|
+
def summary(self, offset=""):
|
|
10
|
+
output_shape = self.output_shape[0] if len(self.output_shape) == 1 else self.output_shape
|
|
11
|
+
return offset + f"{self.name} - Output: ({output_shape})"
|