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,261 @@
|
|
|
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
|
+
from ...Exceptions import NotCompiledError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LSTM(BaseLayer):
|
|
12
|
+
"""
|
|
13
|
+
The long short-term memory layer for neural networks.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
output_shape (tuple[int]): The ouput shape by the forward method. Must be tuple containing non-negative ints. Based on the length of the tuple and the return_last parameter, the returned tensor is of shape (n_samples,), (n_samples, sequence_length), (n_samples, n_features) or (n_samples, sequence_length, n_features).
|
|
17
|
+
hidden_size (int): The number of features in the hidden state vector. Must be a positive integer.
|
|
18
|
+
return_last (bool): Determines if only the last element or the whole sequence is returned.
|
|
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 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, hidden_size, return_last=True, initialiser=Xavier_Uniform(), activation=None, normalisation=None, **kwargs):
|
|
24
|
+
if not isinstance(hidden_size, int) or hidden_size <= 0:
|
|
25
|
+
raise ValueError("hidden_size must be a positive integer.")
|
|
26
|
+
if not isinstance(return_last, bool):
|
|
27
|
+
raise TypeError("return_last must be a boolean.")
|
|
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
|
+
if return_last and len(output_shape) == 2:
|
|
35
|
+
raise ValueError("return_last should not be True when the output_shape is (seq_len, n_features)")
|
|
36
|
+
|
|
37
|
+
super().__init__(output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
38
|
+
self.name = "LSTM"
|
|
39
|
+
self.hidden_size = hidden_size
|
|
40
|
+
self.return_last = return_last
|
|
41
|
+
self.initialiser = initialiser
|
|
42
|
+
|
|
43
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
44
|
+
"""
|
|
45
|
+
:meta private:
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(input_shape, tuple | list) or len(input_shape) != 2:
|
|
48
|
+
raise ValueError("input_shape must be a tuple of length 2.")
|
|
49
|
+
if not isinstance(data_type, torch.dtype):
|
|
50
|
+
raise TypeError("data_type must be an instance of torch.dtype")
|
|
51
|
+
if not isinstance(device, torch.device):
|
|
52
|
+
raise TypeError('device must be one of torch.device("cpu") or torch.device("cuda")')
|
|
53
|
+
|
|
54
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
55
|
+
|
|
56
|
+
input_size = self.input_shape[1]
|
|
57
|
+
output_size = 1 if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last) else self.output_shape[-1]
|
|
58
|
+
|
|
59
|
+
self.wf = self.initialiser.initialise((input_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
60
|
+
self.uf = self.initialiser.initialise((self.hidden_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
61
|
+
self.bf = torch.zeros((self.hidden_size), dtype=self.data_type, device=self.device)
|
|
62
|
+
self.wi = self.initialiser.initialise((input_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
63
|
+
self.ui = self.initialiser.initialise((self.hidden_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
64
|
+
self.bi = torch.zeros((self.hidden_size), dtype=self.data_type, device=self.device)
|
|
65
|
+
self.wc = self.initialiser.initialise((input_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
66
|
+
self.uc = self.initialiser.initialise((self.hidden_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
67
|
+
self.bc = torch.zeros((self.hidden_size), dtype=self.data_type, device=self.device)
|
|
68
|
+
self.wo = self.initialiser.initialise((input_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
69
|
+
self.uo = self.initialiser.initialise((self.hidden_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
70
|
+
self.bo = torch.zeros((self.hidden_size), dtype=self.data_type, device=self.device)
|
|
71
|
+
self.wy = self.initialiser.initialise((self.hidden_size, output_size), data_type=self.data_type, device=self.device)
|
|
72
|
+
self.by = torch.zeros((output_size), dtype=self.data_type, device=self.device)
|
|
73
|
+
|
|
74
|
+
self.nparams = output_size + self.hidden_size * output_size + 4 * input_size * self.hidden_size + 4 * self.hidden_size + 4 * self.hidden_size ** 2
|
|
75
|
+
|
|
76
|
+
def forward(self, input, training=False, **kwargs):
|
|
77
|
+
"""
|
|
78
|
+
Calculates the forward propagation of the model using the equations
|
|
79
|
+
|
|
80
|
+
.. math::
|
|
81
|
+
|
|
82
|
+
\\begin{align*}
|
|
83
|
+
f_t &= \\sigma(W_fx_t + U_fh_{t - 1} + b_f),\\\\
|
|
84
|
+
i_t &= \\sigma(W_ix_t + U_ih_{t - 1} + b_i),\\\\
|
|
85
|
+
o_t &= \\sigma(W_ox_t + U_oh_{t - 1} + b_o),\\\\
|
|
86
|
+
\\widetilde{c}_t &= \\text{tanh}(W_cx_t + U_ch_{t - 1} + b_c),\\\\
|
|
87
|
+
c_t &= f_t\\odot c_{t - 1} + i_t\\odot\\widetilde{c}_t,\\\\
|
|
88
|
+
h_t &= o_t\\odot\\text{tanh}(c_t),\\\\
|
|
89
|
+
y_t &= W_yh_t + b_y,\\\\
|
|
90
|
+
y_{reg} &= f(y) \\text{ or } f(y_\\text{sequence_length}),\\\\
|
|
91
|
+
y_{activ} &= g(y_{reg}),
|
|
92
|
+
\\end{align*}
|
|
93
|
+
|
|
94
|
+
where :math:`t\in[1,\dots, \\text{sequence_length}]`, :math:`x` is the input, :math:`h_t` is the hidden state, :math:`W` and :math:`U` are the weight matricies, :math:`b` are the biases, :math:`f` is the possible regularisation function and :math:`g` is the possible activation function. Also :math:`\\odot` represents the hadamard or the element-wise product and :math:`\\sigma` represents the sigmoid function.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
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.
|
|
98
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
torch.Tensor: The output tensor after the transformations with the spesified shape.
|
|
102
|
+
|
|
103
|
+
.. list-table:: The return shapes of the method depending on the parameters.
|
|
104
|
+
:widths: 10 25
|
|
105
|
+
:header-rows: 1
|
|
106
|
+
|
|
107
|
+
* - Parameter
|
|
108
|
+
- Return Shape
|
|
109
|
+
* - len(LSTM.output_shape) == 0 and LSTM.return_last
|
|
110
|
+
- (n_samples,)
|
|
111
|
+
* - len(LSTM.output_shape) == 1 and LSTM.return_last
|
|
112
|
+
- (n_samples, LSTM.output_shape[1])
|
|
113
|
+
* - len(LSTM.output_shape) == 1 and not LSTM.return_last
|
|
114
|
+
- (n_samples, sequence_length)
|
|
115
|
+
* - len(LSTM.output_shape) == 2 and not LSTM.return_last
|
|
116
|
+
- (n_samples, sequence_length, LSTM.output_shape[1])
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(input, torch.Tensor):
|
|
119
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
120
|
+
if input.shape[1:] != self.input_shape:
|
|
121
|
+
raise ValueError(f"Input shape {input.shape[1:]} does not match the expected shape {self.input_shape}.")
|
|
122
|
+
if not isinstance(training, bool):
|
|
123
|
+
raise TypeError("training must be a boolean.")
|
|
124
|
+
|
|
125
|
+
batch_size, seq_len, _ = input.size()
|
|
126
|
+
self.input = input
|
|
127
|
+
self.forget_gates = [0] * seq_len
|
|
128
|
+
self.input_gates = [0] * seq_len
|
|
129
|
+
self.candidate_gates = [0] * seq_len
|
|
130
|
+
self.output_gates = [0] * seq_len
|
|
131
|
+
|
|
132
|
+
self.cell_states = {-1: torch.zeros((batch_size, self.hidden_size), dtype=input.dtype, device=input.device)}
|
|
133
|
+
self.hidden_states = {-1: torch.zeros((batch_size, self.hidden_size), dtype=input.dtype, device=input.device)}
|
|
134
|
+
output_dim = 1 if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last) else self.output_shape[-1]
|
|
135
|
+
if not self.return_last: self.output = torch.zeros((batch_size, seq_len, output_dim), dtype=input.dtype, device=input.device)
|
|
136
|
+
|
|
137
|
+
for t in range(seq_len):
|
|
138
|
+
x_t = input[:, t]
|
|
139
|
+
h_t_prev = self.hidden_states[t - 1]
|
|
140
|
+
|
|
141
|
+
self.forget_gates[t] = self._sigmoid(x_t @ self.wf + h_t_prev @ self.uf + self.bf)
|
|
142
|
+
self.input_gates[t] = self._sigmoid(x_t @ self.wi + h_t_prev @ self.ui + self.bi)
|
|
143
|
+
self.candidate_gates[t] = self._tanh(x_t @ self.wc + h_t_prev @ self.uc + self.bc)
|
|
144
|
+
self.output_gates[t] = self._sigmoid(x_t @ self.wo + h_t_prev @ self.uo + self.bo)
|
|
145
|
+
|
|
146
|
+
self.cell_states[t] = self.forget_gates[t] * self.cell_states[t - 1] + self.input_gates[t] * self.candidate_gates[t]
|
|
147
|
+
self.hidden_states[t] = self.output_gates[t] * self._tanh(self.cell_states[t])
|
|
148
|
+
|
|
149
|
+
if not self.return_last: self.output[:, t] = self.hidden_states[t] @ self.wy + self.by
|
|
150
|
+
|
|
151
|
+
if self.return_last: self.output = self.hidden_states[seq_len - 1] @ self.wy + self.by
|
|
152
|
+
if self.normalisation: self.output = self.normalisation.forward(self.output, training=training)
|
|
153
|
+
if self.activation: self.output = self.activation.forward(self.output)
|
|
154
|
+
if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last): output = output.squeeze(dim=-1)
|
|
155
|
+
return self.output
|
|
156
|
+
|
|
157
|
+
def backward(self, dCdy, **kwargs):
|
|
158
|
+
"""
|
|
159
|
+
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.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
torch.Tensor of shape (batch_size, sequence_length, input_size): The new gradient after backpropagation through the layer.
|
|
166
|
+
"""
|
|
167
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
168
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
169
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
170
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
171
|
+
|
|
172
|
+
if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last): dCdy = dCdy.unsqueeze(dim=-1)
|
|
173
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
174
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
175
|
+
|
|
176
|
+
dCdh_next = torch.zeros_like(self.hidden_states[0], dtype=dCdy.dtype, device=dCdy.device) if not self.return_last else dCdy @ self.wy.T
|
|
177
|
+
dCdc_next = torch.zeros_like(self.cell_states[0], dtype=dCdy.dtype, device=dCdy.device)
|
|
178
|
+
dCdx = torch.zeros_like(self.input, dtype=dCdy.dtype, device=dCdy.device)
|
|
179
|
+
_, seq_len, _ = self.input.size()
|
|
180
|
+
|
|
181
|
+
if self.return_last: self.wy.grad += self.hidden_states[seq_len - 1].T @ dCdy
|
|
182
|
+
if self.return_last: self.by.grad += torch.mean(dCdy, axis=0)
|
|
183
|
+
|
|
184
|
+
for t in reversed(range(seq_len)):
|
|
185
|
+
if not self.return_last: self.wy.grad += self.hidden_states[t].T @ dCdy[:, t]
|
|
186
|
+
if not self.return_last: self.by.grad += torch.mean(dCdy[:, t], axis=0)
|
|
187
|
+
dCdh_t = dCdh_next + dCdy[:, t] @ self.wy.T if not self.return_last else dCdh_next # hidden state
|
|
188
|
+
|
|
189
|
+
dCdo = self._tanh(self.cell_states[t]) * dCdh_t # output
|
|
190
|
+
# dCdc_t = self._tanh(self.cell_states[t], derivative = True) * self.output_gates[t] * dCdh_t + dCdc_next # cell state
|
|
191
|
+
dCdc_t = self._tanh(self._tanh(self.cell_states[t]), derivative = True) * self.output_gates[t] * dCdh_t + dCdc_next # cell state
|
|
192
|
+
|
|
193
|
+
dCdc_next = dCdc_t * self.forget_gates[t] # next cell state
|
|
194
|
+
dCdf = dCdc_t * self.cell_states[t - 1] # forget
|
|
195
|
+
dCdi = dCdc_t * self.candidate_gates[t] # input
|
|
196
|
+
dCdc = dCdc_t * self.input_gates[t] # candidate
|
|
197
|
+
|
|
198
|
+
# activation derivatives
|
|
199
|
+
dCdsig_o = dCdo * self._sigmoid(self.output_gates[t], derivative=True)
|
|
200
|
+
dCdsig_c = dCdc * self._tanh(self.candidate_gates[t], derivative=True)
|
|
201
|
+
dCdsig_i = dCdi * self._sigmoid(self.input_gates[t], derivative=True)
|
|
202
|
+
dCdsig_f = dCdf * self._sigmoid(self.forget_gates[t], derivative=True)
|
|
203
|
+
|
|
204
|
+
# next hidden state derivative
|
|
205
|
+
dCdh_next = dCdsig_o @ self.uo.T # batch_size, hidden_size --- (hidden_size, hidden_size).T
|
|
206
|
+
dCdh_next += dCdsig_c @ self.uc.T
|
|
207
|
+
dCdh_next += dCdsig_i @ self.ui.T
|
|
208
|
+
dCdh_next += dCdsig_f @ self.uf.T
|
|
209
|
+
|
|
210
|
+
# output derivatives
|
|
211
|
+
dCdx[:, t] += dCdsig_o @ self.wo.T # batch_size, hidden_size --- (input_size, hidden_size).T
|
|
212
|
+
dCdx[:, t] += dCdsig_c @ self.wc.T
|
|
213
|
+
dCdx[:, t] += dCdsig_i @ self.wi.T
|
|
214
|
+
dCdx[:, t] += dCdsig_f @ self.wf.T
|
|
215
|
+
|
|
216
|
+
# parameter updates
|
|
217
|
+
self.wo.grad += self.input[:, t].T @ dCdsig_o # (batch_size, input_size).T --- batch_size, hidden_size
|
|
218
|
+
self.wc.grad += self.input[:, t].T @ dCdsig_c
|
|
219
|
+
self.wi.grad += self.input[:, t].T @ dCdsig_i
|
|
220
|
+
self.wf.grad += self.input[:, t].T @ dCdsig_f
|
|
221
|
+
|
|
222
|
+
self.uo.grad += self.hidden_states[t - 1].T @ dCdsig_o # (batch_size, hidden_size) --- batch_size, hidden_size
|
|
223
|
+
self.uc.grad += self.hidden_states[t - 1].T @ dCdsig_c
|
|
224
|
+
self.ui.grad += self.hidden_states[t - 1].T @ dCdsig_i
|
|
225
|
+
self.uf.grad += self.hidden_states[t - 1].T @ dCdsig_f
|
|
226
|
+
|
|
227
|
+
self.bo.grad += torch.mean(dCdsig_o, dim=0)
|
|
228
|
+
self.bc.grad += torch.mean(dCdsig_c, dim=0)
|
|
229
|
+
self.bi.grad += torch.mean(dCdsig_i, dim=0)
|
|
230
|
+
self.bf.grad += torch.mean(dCdsig_f, dim=0)
|
|
231
|
+
return dCdx
|
|
232
|
+
|
|
233
|
+
def _sigmoid(self, input, derivative=False):
|
|
234
|
+
if derivative:
|
|
235
|
+
return input * (1 - input)
|
|
236
|
+
return 1 / (1 + torch.exp(-input))
|
|
237
|
+
|
|
238
|
+
def _tanh(self, input, derivative=False):
|
|
239
|
+
if derivative:
|
|
240
|
+
return 1 - input ** 2
|
|
241
|
+
return torch.tanh(input)
|
|
242
|
+
|
|
243
|
+
def get_parameters(self):
|
|
244
|
+
"""
|
|
245
|
+
:meta private:
|
|
246
|
+
"""
|
|
247
|
+
return (self.wy, self.wo, self.wc, self.wi, self.wf,
|
|
248
|
+
self.uo, self.uc, self.ui, self.uf,
|
|
249
|
+
self.by, self.bo, self.bc, self.bi, self.bf, *super().get_parameters())
|
|
250
|
+
|
|
251
|
+
# def summary(self, offset=""):
|
|
252
|
+
# if not hasattr(self, "input_shape"):
|
|
253
|
+
# raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
254
|
+
|
|
255
|
+
# input_shape = "(seq_len, " + str(self.input_shape[0]) + ")"
|
|
256
|
+
# output_shape = str(self.output_shape[0]) if self.return_last else "(seq_len, " + str(self.output_shape[0]) + ")"
|
|
257
|
+
# params_summary = " - Parameters: " + str(self.nparams) if self.nparams > 0 else ""
|
|
258
|
+
# sublayer_offset = offset + " "
|
|
259
|
+
# normalisation_summary = ("\n" + self.normalisation.summary(sublayer_offset)) if self.normalisation else ""
|
|
260
|
+
# activation_summary = ("\n" + self.activation.summary(sublayer_offset)) if self.activation else ""
|
|
261
|
+
# return offset + f"{self.name} - (Input, Output): ({input_shape}, {output_shape})" + params_summary + normalisation_summary + activation_summary
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseLayer import BaseLayer
|
|
4
|
+
from ...Exceptions import NotCompiledError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LayerList(BaseLayer):
|
|
8
|
+
"""
|
|
9
|
+
The list of consecutive layers.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
*args (DLL.DeepLearning.Layers.BaseLayer objects): An arbitrary amount of consecutive layers.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
for layer in args:
|
|
16
|
+
if not isinstance(layer, BaseLayer):
|
|
17
|
+
raise TypeError("layers must be an instances of DLL.DeepLearning.Layers.BaseLayer")
|
|
18
|
+
if kwargs.get("normalisation", None) is not None or kwargs.get("activation", None) is not None:
|
|
19
|
+
raise ValueError("LayerList cannot have normalisation or activation functions directly. Include these functions in the last layer passed to LayerList.")
|
|
20
|
+
|
|
21
|
+
super().__init__(None, **kwargs)
|
|
22
|
+
self.name = "Layer list"
|
|
23
|
+
self.layers = args
|
|
24
|
+
|
|
25
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
26
|
+
"""
|
|
27
|
+
:meta private:
|
|
28
|
+
"""
|
|
29
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
30
|
+
for layer in self.layers:
|
|
31
|
+
layer.initialise_layer(input_shape=input_shape, data_type=data_type, device=device)
|
|
32
|
+
input_shape = layer.output_shape
|
|
33
|
+
self.output_shape = self.layers[-1].output_shape # all possible layers (even activation layers) now know their output_shapes
|
|
34
|
+
|
|
35
|
+
def forward(self, input, training=False, **kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Computes the forward values of the input layers.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
input (torch.Tensor): The input to the layer. Must be a torch.Tensor of the spesified shape given by layer.input_shape.
|
|
41
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
torch.Tensor: The output tensor after the layers with the spesified shape.
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(input, torch.Tensor):
|
|
47
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
48
|
+
if input.shape[1:] != self.input_shape:
|
|
49
|
+
raise ValueError(f"Input shape {input.shape[1:]} does not match the expected shape {self.input_shape}.")
|
|
50
|
+
if not isinstance(training, bool):
|
|
51
|
+
raise TypeError("training must be a boolean.")
|
|
52
|
+
|
|
53
|
+
self.output = input
|
|
54
|
+
for layer in self.layers:
|
|
55
|
+
self.output = layer.forward(self.output, training=training)
|
|
56
|
+
return self.output
|
|
57
|
+
|
|
58
|
+
def backward(self, dCdy, **kwargs):
|
|
59
|
+
"""
|
|
60
|
+
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.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
torch.Tensor of the spesified shape: The new gradient after backpropagation through the layer.
|
|
67
|
+
"""
|
|
68
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
69
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
70
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
71
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
72
|
+
|
|
73
|
+
dCdx = dCdy
|
|
74
|
+
for layer in reversed(self.layers):
|
|
75
|
+
dCdx = layer.backward(dCdx)
|
|
76
|
+
return dCdx
|
|
77
|
+
|
|
78
|
+
def get_parameters(self):
|
|
79
|
+
"""
|
|
80
|
+
:meta private:
|
|
81
|
+
"""
|
|
82
|
+
return tuple(param for layer in self.layers for param in layer.get_parameters())
|
|
83
|
+
|
|
84
|
+
def get_nparams(self):
|
|
85
|
+
n_params = 0
|
|
86
|
+
for layer in self.layers:
|
|
87
|
+
n_params += layer.get_nparams()
|
|
88
|
+
return n_params
|
|
89
|
+
|
|
90
|
+
def summary(self, offset=""):
|
|
91
|
+
if not hasattr(self, "input_shape"):
|
|
92
|
+
raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
93
|
+
|
|
94
|
+
summary = super().summary(offset)
|
|
95
|
+
sublayer_offset = offset + " "
|
|
96
|
+
for layer in self.layers:
|
|
97
|
+
summary += "\n" + offset + layer.summary(sublayer_offset)
|
|
98
|
+
return summary
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from .Activations._Activation import Activation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Pooling layer
|
|
8
|
+
|
|
9
|
+
input.shape = (batch_size, depth, input_height, input_width)
|
|
10
|
+
output.shape = (batch_size, depth, input_height // self.kernel_size, input_width // self.kernel_size)
|
|
11
|
+
"""
|
|
12
|
+
class MaxPooling2D(Activation):
|
|
13
|
+
"""
|
|
14
|
+
The max pooling layer for a neural network.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
pool_size (int): The pooling size used for the model. The pooling kernel is automatically square. Must be a positive integer.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, pool_size, **kwargs):
|
|
20
|
+
if not isinstance(pool_size, int) or pool_size <= 0:
|
|
21
|
+
raise ValueError(f"pool_size must be a positive integer. Currently {pool_size}.")
|
|
22
|
+
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
self.pool_size = pool_size
|
|
25
|
+
self.name = "MaxPooling2D"
|
|
26
|
+
|
|
27
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
28
|
+
"""
|
|
29
|
+
:meta private:
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(input_shape, tuple | list) or len(input_shape) != 3:
|
|
32
|
+
raise ValueError("input_shape must be a tuple of length 3.")
|
|
33
|
+
if not isinstance(data_type, torch.dtype):
|
|
34
|
+
raise TypeError("data_type must be an instance of torch.dtype")
|
|
35
|
+
if not isinstance(device, torch.device):
|
|
36
|
+
raise TypeError('device must be one of torch.device("cpu") or torch.device("cuda")')
|
|
37
|
+
|
|
38
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
39
|
+
input_depth, input_height, input_width = self.input_shape
|
|
40
|
+
self.output_shape = (input_depth, input_height // self.pool_size, input_width // self.pool_size)
|
|
41
|
+
|
|
42
|
+
def generate_sections(self, image_batch):
|
|
43
|
+
"""
|
|
44
|
+
:meta private:
|
|
45
|
+
"""
|
|
46
|
+
if image_batch.ndim != 4:
|
|
47
|
+
raise ValueError("image_batch should be 4 dimensional.")
|
|
48
|
+
|
|
49
|
+
height, width = image_batch.shape[2] // self.pool_size, image_batch.shape[3] // self.pool_size
|
|
50
|
+
for h in range(height):
|
|
51
|
+
for w in range(width):
|
|
52
|
+
slice = image_batch[:, :, (h * self.pool_size):(h * self.pool_size + self.pool_size), (w * self.pool_size):(w * self.pool_size + self.pool_size)]
|
|
53
|
+
yield slice, h, w
|
|
54
|
+
|
|
55
|
+
def forward(self, input, **kwargs):
|
|
56
|
+
"""
|
|
57
|
+
Applies the max pooling transformation.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
input (torch.Tensor of shape (n_samples, input_depth, height, width)): The input to the layer. Must be a torch.Tensor of the spesified shape.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
torch.Tensor of shape (n_samples, output_depth, height // layer.pool_size, width // layer.pool_size): The output tensor after the transformations with the spesified shape.
|
|
64
|
+
"""
|
|
65
|
+
if not isinstance(input, torch.Tensor):
|
|
66
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
67
|
+
if input.shape[1:] != self.input_shape:
|
|
68
|
+
raise ValueError(f"input is not the same shape as the spesified input_shape ({input.shape[1:], self.input_shape}).")
|
|
69
|
+
|
|
70
|
+
self.input = input
|
|
71
|
+
batch_size, depth, widht, height = self.input.shape
|
|
72
|
+
self.output = torch.zeros(size=(batch_size, depth, widht // self.pool_size, height // self.pool_size), device=input.device, dtype=input.dtype)
|
|
73
|
+
for slice, h, w in self.generate_sections(input):
|
|
74
|
+
self.output[:, :, h, w] = torch.amax(slice, dim=(2, 3))
|
|
75
|
+
return self.output
|
|
76
|
+
|
|
77
|
+
def backward(self, dCdy, **kwargs):
|
|
78
|
+
"""
|
|
79
|
+
Calculates the gradient of the loss function with respect to the input of the layer.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
dCdy (torch.Tensor of shape (n_samples, output_depth, output_height, output_width) : The gradient given by the next layer.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
torch.Tensor of shape (n_samples, input_depth, input_height, input_width): The new gradient after backpropagation through the layer.
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
88
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
89
|
+
if dCdy.shape[1:] != self.output_shape:
|
|
90
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output_shape}).")
|
|
91
|
+
|
|
92
|
+
dCdx = torch.zeros_like(self.input, device=dCdy.device, dtype=dCdy.dtype)
|
|
93
|
+
sums = torch.ones_like(self.input, device=dCdy.device, dtype=dCdy.dtype)
|
|
94
|
+
for slice, h, w in self.generate_sections(self.input):
|
|
95
|
+
derivative_slice = dCdx[:, :, h * self.pool_size:h * self.pool_size + self.pool_size, w * self.pool_size:w * self.pool_size + self.pool_size]
|
|
96
|
+
max_vals = self.output[:, :, h, w].unsqueeze(-1).unsqueeze(-1)
|
|
97
|
+
selector = torch.eq(max_vals.repeat(1, 1, self.pool_size, self.pool_size), slice)
|
|
98
|
+
sums[:, :, h * self.pool_size:h * self.pool_size + self.pool_size, w * self.pool_size:w * self.pool_size + self.pool_size] = torch.sum(selector, dim=(2, 3), keepdim=True).repeat(1, 1, self.pool_size, self.pool_size)
|
|
99
|
+
derivatives = dCdy[:, :, h, w].unsqueeze(-1).unsqueeze(-1).repeat(1, 1, self.pool_size, self.pool_size)
|
|
100
|
+
dCdx[:, :, h * self.pool_size:h * self.pool_size + self.pool_size, w * self.pool_size:w * self.pool_size + self.pool_size] = torch.where(selector, derivatives, derivative_slice)
|
|
101
|
+
return dCdx / sums
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ._BaseLayer import BaseLayer
|
|
4
|
+
from ..Initialisers import Xavier_Uniform
|
|
5
|
+
from ..Initialisers._Initialiser import Initialiser
|
|
6
|
+
from ...Exceptions import NotCompiledError
|
|
7
|
+
from .Activations._Activation import Activation
|
|
8
|
+
from .Regularisation._BaseRegularisation import BaseRegularisation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RNN(BaseLayer):
|
|
12
|
+
"""
|
|
13
|
+
The recurrent neural network layer.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
output_shape (tuple[int]): The ouput shape by the forward method. Must be tuple containing non-negative ints. Based on the length of the tuple and the return_last parameter, the returned tensor is of shape (n_samples,), (n_samples, sequence_length), (n_samples, n_features) or (n_samples, sequence_length, n_features).
|
|
17
|
+
hidden_size (int): The number of features in the hidden state vector. Must be a positive integer.
|
|
18
|
+
return_last (bool): Determines if only the last element or the whole sequence is returned.
|
|
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 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, hidden_size, return_last=True, initialiser=Xavier_Uniform(), activation=None, normalisation=None, **kwargs):
|
|
24
|
+
if not isinstance(hidden_size, int) or hidden_size <= 0:
|
|
25
|
+
raise ValueError("hidden_size must be a positive integer.")
|
|
26
|
+
if not isinstance(return_last, bool):
|
|
27
|
+
raise TypeError("return_last must be a boolean.")
|
|
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
|
+
if return_last and len(output_shape) == 2:
|
|
35
|
+
raise ValueError("return_last should not be True when the output_shape is (seq_len, n_features)")
|
|
36
|
+
|
|
37
|
+
super().__init__(output_shape, activation=activation, normalisation=normalisation, **kwargs)
|
|
38
|
+
self.name = "RNN"
|
|
39
|
+
self.hidden_size = hidden_size
|
|
40
|
+
self.return_last = return_last
|
|
41
|
+
self.initialiser = initialiser
|
|
42
|
+
|
|
43
|
+
def initialise_layer(self, input_shape, data_type, device):
|
|
44
|
+
"""
|
|
45
|
+
:meta private:
|
|
46
|
+
"""
|
|
47
|
+
if not isinstance(input_shape, tuple | list) or len(input_shape) != 2:
|
|
48
|
+
raise ValueError("input_shape must be a tuple of length 2.")
|
|
49
|
+
if not isinstance(data_type, torch.dtype):
|
|
50
|
+
raise TypeError("data_type must be an instance of torch.dtype")
|
|
51
|
+
if not isinstance(device, torch.device):
|
|
52
|
+
raise TypeError('device must be one of torch.device("cpu") or torch.device("cuda")')
|
|
53
|
+
|
|
54
|
+
super().initialise_layer(input_shape, data_type, device)
|
|
55
|
+
|
|
56
|
+
output_dim = 1 if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last) else self.output_shape[-1]
|
|
57
|
+
self.ih = self.initialiser.initialise((self.hidden_size, self.input_shape[1]), data_type=self.data_type, device=self.device)
|
|
58
|
+
self.hh = self.initialiser.initialise((self.hidden_size, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
59
|
+
self.ho = self.initialiser.initialise((output_dim, self.hidden_size), data_type=self.data_type, device=self.device)
|
|
60
|
+
self.bh = torch.zeros(self.hidden_size, dtype=self.data_type, device=self.device)
|
|
61
|
+
self.bo = torch.zeros(output_dim, dtype=self.data_type, device=self.device)
|
|
62
|
+
self.nparams = self.hidden_size * self.hidden_size + self.hidden_size * output_dim + self.hidden_size * self.input_shape[1] + self.hidden_size + output_dim
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
input.shape = (batch_size, sequence_length, input_size)
|
|
66
|
+
output.shape = (batch_size, sequence_length, output_size) or (batch_size, output_size) or (batch_size, sequence_length) or (batch_size,)
|
|
67
|
+
"""
|
|
68
|
+
def forward(self, input, training=False, **kwargs):
|
|
69
|
+
"""
|
|
70
|
+
Calculates the forward propagation of the model using the equation
|
|
71
|
+
|
|
72
|
+
.. math::
|
|
73
|
+
|
|
74
|
+
\\begin{align*}
|
|
75
|
+
h_t &= \\text{tanh}(x_tW_{ih}^T + h_{t - 1}W_{hh}^T + b_h),\\\\
|
|
76
|
+
y_{t} &= h_tW_o^T + b_o,\\\\
|
|
77
|
+
y_{reg} &= f(y) \\text{ or } f(y_\\text{sequence_length}),\\\\
|
|
78
|
+
y_{activ} &= g(y_{reg}),
|
|
79
|
+
\\end{align*}
|
|
80
|
+
|
|
81
|
+
where :math:`t\in[1,\dots, \\text{sequence_length}]`, :math:`x` is the input, :math:`h_t` is the hidden state, :math:`W_{ih}` is the input to hidden weights, :math:`W_{hh}` is the hidden to hidden weights, :math:`b_h` is the hidden bias, :math:`W_o` is the output weights, :math:`b_o` is the output bias, :math:`f` is the possible regularisation function and :math:`g` is the possible activation function.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
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.
|
|
85
|
+
training (bool, optional): The boolean flag deciding if the model is in training mode. Defaults to False.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
torch.Tensor: The output tensor after the transformations with the spesified shape.
|
|
89
|
+
|
|
90
|
+
.. list-table:: The return shapes of the method depending on the parameters.
|
|
91
|
+
:widths: 10 25
|
|
92
|
+
:header-rows: 1
|
|
93
|
+
|
|
94
|
+
* - Parameter
|
|
95
|
+
- Return Shape
|
|
96
|
+
* - len(RNN.output_shape) == 0 and RNN.return_last
|
|
97
|
+
- (n_samples,)
|
|
98
|
+
* - len(RNN.output_shape) == 1 and RNN.return_last
|
|
99
|
+
- (n_samples, RNN.output_shape[1])
|
|
100
|
+
* - len(RNN.output_shape) == 1 and not RNN.return_last
|
|
101
|
+
- (n_samples, sequence_length)
|
|
102
|
+
* - len(RNN.output_shape) == 2 and not RNN.return_last
|
|
103
|
+
- (n_samples, sequence_length, RNN.output_shape[1])
|
|
104
|
+
"""
|
|
105
|
+
if not isinstance(input, torch.Tensor):
|
|
106
|
+
raise TypeError("input must be a torch.Tensor.")
|
|
107
|
+
if input.shape[1:] != self.input_shape:
|
|
108
|
+
raise ValueError(f"Input shape {input.shape[1:]} does not match the expected shape {self.input_shape}.")
|
|
109
|
+
if not isinstance(training, bool):
|
|
110
|
+
raise TypeError("training must be a boolean.")
|
|
111
|
+
|
|
112
|
+
self.input = input
|
|
113
|
+
batch_size, seq_len, _ = input.size()
|
|
114
|
+
self.hiddens = [torch.zeros(batch_size, self.hidden_size, dtype=input.dtype, device=input.device)]
|
|
115
|
+
output_dim = 1 if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last) else self.output_shape[-1]
|
|
116
|
+
if not self.return_last: self.output = torch.zeros(batch_size, seq_len, output_dim, dtype=input.dtype, device=input.device)
|
|
117
|
+
|
|
118
|
+
for t in range(seq_len):
|
|
119
|
+
self.hiddens.append(torch.tanh(self.input[:, t] @ self.ih.T + self.hiddens[-1] @ self.hh.T + self.bh))
|
|
120
|
+
if not self.return_last: self.output[:, t] = self.hiddens[-1] @ self.ho.T + self.bo
|
|
121
|
+
|
|
122
|
+
if self.return_last: self.output = self.hiddens[-1] @ self.ho.T + self.bo
|
|
123
|
+
if self.normalisation: self.output = self.normalisation.forward(self.output, training=training)
|
|
124
|
+
if self.activation: self.output = self.activation.forward(self.output)
|
|
125
|
+
if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last): output = output.squeeze(dim=-1)
|
|
126
|
+
return self.output
|
|
127
|
+
|
|
128
|
+
def backward(self, dCdy, **kwargs):
|
|
129
|
+
"""
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
dCdy (torch.Tensor of the same shape as returned from the forward method): The gradient given by the next layer.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
torch.Tensor of shape (n_samples, sequence_length, input_size): The new gradient after backpropagation through the layer.
|
|
137
|
+
"""
|
|
138
|
+
if not isinstance(dCdy, torch.Tensor):
|
|
139
|
+
raise TypeError("dCdy must be a torch.Tensor.")
|
|
140
|
+
if dCdy.shape[1:] != self.output.shape[1:]:
|
|
141
|
+
raise ValueError(f"dCdy is not the same shape as the spesified output_shape ({dCdy.shape[1:], self.output.shape[1:]}).")
|
|
142
|
+
|
|
143
|
+
if len(self.output_shape) == 0 or (len(self.output_shape) == 1 and not self.return_last): dCdy = dCdy.unsqueeze(dim=-1)
|
|
144
|
+
if self.activation: dCdy = self.activation.backward(dCdy)
|
|
145
|
+
if self.normalisation: dCdy = self.normalisation.backward(dCdy)
|
|
146
|
+
|
|
147
|
+
_, seq_len, _ = self.input.size()
|
|
148
|
+
dCdh_next = torch.zeros_like(self.hiddens[0], dtype=dCdy.dtype, device=dCdy.device) if not self.return_last else dCdy @ self.ho
|
|
149
|
+
dCdx = torch.zeros_like(self.input, dtype=dCdy.dtype, device=dCdy.device)
|
|
150
|
+
|
|
151
|
+
if self.return_last: self.ho.grad += dCdy.T @ self.hiddens[-1] # batch_size, output_size --- batch_size, self.hidden_size
|
|
152
|
+
if self.return_last: self.bo.grad += torch.sum(dCdy, axis=0)
|
|
153
|
+
for t in reversed(range(seq_len)):
|
|
154
|
+
if not self.return_last: self.ho.grad += dCdy[:, t].T @ self.hiddens[t + 1]
|
|
155
|
+
if not self.return_last: self.bo.grad += torch.sum(dCdy[:, t], axis=0)
|
|
156
|
+
|
|
157
|
+
dCdh_t = dCdh_next + dCdy[:, t] @ self.ho if not self.return_last else dCdh_next # batch_size, self.hidden_size + batch_size, output_size --- self.output_shape, self.hidden_size
|
|
158
|
+
|
|
159
|
+
dCdtanh = (1 - self.hiddens[t + 1] ** 2) * dCdh_t # batch_size, self.hidden_size
|
|
160
|
+
|
|
161
|
+
self.bh.grad += torch.sum(dCdtanh, axis=0)
|
|
162
|
+
self.ih.grad += dCdtanh.T @ self.input[:, t] # batch_size, self.hidden_size --- batch_size, input_size
|
|
163
|
+
self.hh.grad += dCdtanh.T @ self.hiddens[t] # batch_size, self.hidden_size --- batch_size, self.hidden_size
|
|
164
|
+
|
|
165
|
+
dCdh_next = dCdtanh @ self.hh # self.hidden_size, self.hidden_size --- batch_size, self.hidden_size
|
|
166
|
+
dCdx[:, t] = dCdtanh @ self.ih # batch_size, self.hidden_size --- self.hidden_size, self.input_shape
|
|
167
|
+
return dCdx
|
|
168
|
+
|
|
169
|
+
def get_parameters(self):
|
|
170
|
+
"""
|
|
171
|
+
:meta private:
|
|
172
|
+
"""
|
|
173
|
+
return (self.hh, self.ih, self.ho, self.bh, self.bo, *super().get_parameters())
|
|
174
|
+
|
|
175
|
+
# def summary(self, offset=""):
|
|
176
|
+
# if not hasattr(self, "input_shape"):
|
|
177
|
+
# raise NotCompiledError("layer must be initialized correctly before calling layer.summary().")
|
|
178
|
+
|
|
179
|
+
# input_shape = "(seq_len, " + str(self.input_shape[0]) + ")"
|
|
180
|
+
# output_shape = str(self.output_shape[0]) if self.return_last else "(seq_len, " + str(self.output_shape[0]) + ")"
|
|
181
|
+
# params_summary = " - Parameters: " + str(self.nparams) if self.nparams > 0 else ""
|
|
182
|
+
# sublayer_offset = offset + " "
|
|
183
|
+
# normalisation_summary = ("\n" + self.normalisation.summary(sublayer_offset)) if self.normalisation else ""
|
|
184
|
+
# activation_summary = ("\n" + self.activation.summary(sublayer_offset)) if self.activation else ""
|
|
185
|
+
# return offset + f"{self.name} - (Input, Output): ({input_shape}, {output_shape})" + params_summary + normalisation_summary + activation_summary
|