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.
Files changed (124) hide show
  1. DLL/Data/Metrics.py +626 -0
  2. DLL/Data/Preprocessing.py +384 -0
  3. DLL/Data/_DataReader.py +55 -0
  4. DLL/Data/__init__.py +1 -0
  5. DLL/DeepLearning/Callbacks/_BackUp.py +71 -0
  6. DLL/DeepLearning/Callbacks/_BaseCallback.py +43 -0
  7. DLL/DeepLearning/Callbacks/_EarlyStopping.py +102 -0
  8. DLL/DeepLearning/Callbacks/_ReduceLROnPlateau.py +90 -0
  9. DLL/DeepLearning/Callbacks/__init__.py +4 -0
  10. DLL/DeepLearning/Initialisers/_Initialiser.py +24 -0
  11. DLL/DeepLearning/Initialisers/_Kaiming_He.py +55 -0
  12. DLL/DeepLearning/Initialisers/_Xavier_Glorot.py +39 -0
  13. DLL/DeepLearning/Initialisers/__init__.py +2 -0
  14. DLL/DeepLearning/Layers/Activations/_Activation.py +23 -0
  15. DLL/DeepLearning/Layers/Activations/_ReLU.py +51 -0
  16. DLL/DeepLearning/Layers/Activations/_Sigmoid.py +51 -0
  17. DLL/DeepLearning/Layers/Activations/_SoftMax.py +83 -0
  18. DLL/DeepLearning/Layers/Activations/_Tanh.py +47 -0
  19. DLL/DeepLearning/Layers/Activations/__init__.py +4 -0
  20. DLL/DeepLearning/Layers/Regularisation/_BaseRegularisation.py +12 -0
  21. DLL/DeepLearning/Layers/Regularisation/_BatchNormalisation.py +106 -0
  22. DLL/DeepLearning/Layers/Regularisation/_Dropout.py +79 -0
  23. DLL/DeepLearning/Layers/Regularisation/_GroupNormalisation.py +112 -0
  24. DLL/DeepLearning/Layers/Regularisation/_InstanceNormalisation.py +50 -0
  25. DLL/DeepLearning/Layers/Regularisation/_LayerNormalisation.py +37 -0
  26. DLL/DeepLearning/Layers/Regularisation/__init__.py +5 -0
  27. DLL/DeepLearning/Layers/_Add.py +109 -0
  28. DLL/DeepLearning/Layers/_Attention.py +161 -0
  29. DLL/DeepLearning/Layers/_BaseLayer.py +87 -0
  30. DLL/DeepLearning/Layers/_Bidirectional.py +113 -0
  31. DLL/DeepLearning/Layers/_Conv2D.py +133 -0
  32. DLL/DeepLearning/Layers/_Dense.py +109 -0
  33. DLL/DeepLearning/Layers/_DenseKAN.py +184 -0
  34. DLL/DeepLearning/Layers/_Flatten.py +55 -0
  35. DLL/DeepLearning/Layers/_Identity.py +50 -0
  36. DLL/DeepLearning/Layers/_Input.py +11 -0
  37. DLL/DeepLearning/Layers/_LSTM.py +261 -0
  38. DLL/DeepLearning/Layers/_LayerList.py +98 -0
  39. DLL/DeepLearning/Layers/_MaxPooling2D.py +101 -0
  40. DLL/DeepLearning/Layers/_RNN.py +185 -0
  41. DLL/DeepLearning/Layers/_Reshape.py +52 -0
  42. DLL/DeepLearning/Layers/__init__.py +14 -0
  43. DLL/DeepLearning/Losses/_BCE.py +96 -0
  44. DLL/DeepLearning/Losses/_BaseLoss.py +18 -0
  45. DLL/DeepLearning/Losses/_CCE.py +87 -0
  46. DLL/DeepLearning/Losses/_Exponential.py +101 -0
  47. DLL/DeepLearning/Losses/_Huber.py +104 -0
  48. DLL/DeepLearning/Losses/_MAE.py +85 -0
  49. DLL/DeepLearning/Losses/_MSE.py +86 -0
  50. DLL/DeepLearning/Losses/__init__.py +6 -0
  51. DLL/DeepLearning/Model.py +265 -0
  52. DLL/DeepLearning/Optimisers/_ADADELTA.py +53 -0
  53. DLL/DeepLearning/Optimisers/_ADAGRAD.py +51 -0
  54. DLL/DeepLearning/Optimisers/_ADAM.py +65 -0
  55. DLL/DeepLearning/Optimisers/_BaseOptimiser.py +20 -0
  56. DLL/DeepLearning/Optimisers/_LBFGS.py +120 -0
  57. DLL/DeepLearning/Optimisers/_RMSPROP.py +62 -0
  58. DLL/DeepLearning/Optimisers/_SGD.py +44 -0
  59. DLL/DeepLearning/Optimisers/__init__.py +6 -0
  60. DLL/DeepLearning/__init__.py +0 -0
  61. DLL/Exceptions/_Exceptions.py +19 -0
  62. DLL/Exceptions/__init__.py +2 -0
  63. DLL/MachineLearning/SupervisedLearning/Calibration/_CalibratedClassifier.py +88 -0
  64. DLL/MachineLearning/SupervisedLearning/Calibration/_IsotonicRegression.py +114 -0
  65. DLL/MachineLearning/SupervisedLearning/Calibration/__init__.py +2 -0
  66. DLL/MachineLearning/SupervisedLearning/GaussianProcesses/_GaussianProcessRegressor.py +190 -0
  67. DLL/MachineLearning/SupervisedLearning/GaussianProcesses/__init__.py +1 -0
  68. DLL/MachineLearning/SupervisedLearning/Kernels.py +851 -0
  69. DLL/MachineLearning/SupervisedLearning/LinearModels/TimeSeries.py +147 -0
  70. DLL/MachineLearning/SupervisedLearning/LinearModels/_Elasticnet.py +161 -0
  71. DLL/MachineLearning/SupervisedLearning/LinearModels/_LassoRegression.py +157 -0
  72. DLL/MachineLearning/SupervisedLearning/LinearModels/_LinearRegression.py +81 -0
  73. DLL/MachineLearning/SupervisedLearning/LinearModels/_LogisticRegression.py +182 -0
  74. DLL/MachineLearning/SupervisedLearning/LinearModels/_RANSAC.py +97 -0
  75. DLL/MachineLearning/SupervisedLearning/LinearModels/_RidgeRegression.py +78 -0
  76. DLL/MachineLearning/SupervisedLearning/LinearModels/__init__.py +6 -0
  77. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_BernoulliNaiveBayes.py +115 -0
  78. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_GaussianNaiveBayes.py +111 -0
  79. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_MultinomialNaiveBayes.py +115 -0
  80. DLL/MachineLearning/SupervisedLearning/NaiveBayes/__init__.py +3 -0
  81. DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsClassifier.py +115 -0
  82. DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsRegressor.py +90 -0
  83. DLL/MachineLearning/SupervisedLearning/Neighbors/__init__.py +2 -0
  84. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVC.py +293 -0
  85. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVR.py +100 -0
  86. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/__init__.py +2 -0
  87. DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostClassifier.py +179 -0
  88. DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostRegressor.py +141 -0
  89. DLL/MachineLearning/SupervisedLearning/Trees/_DecisionTree.py +219 -0
  90. DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingClassifier.py +207 -0
  91. DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingRegressor.py +122 -0
  92. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMClassifier.py +288 -0
  93. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMRegressor.py +168 -0
  94. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMTree.py +199 -0
  95. DLL/MachineLearning/SupervisedLearning/Trees/_RandomForest.py +96 -0
  96. DLL/MachineLearning/SupervisedLearning/Trees/_RegressionForest.py +75 -0
  97. DLL/MachineLearning/SupervisedLearning/Trees/_RegressionTree.py +148 -0
  98. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostClassifier.py +239 -0
  99. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostRegressor.py +131 -0
  100. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostTree.py +105 -0
  101. DLL/MachineLearning/SupervisedLearning/Trees/__init__.py +12 -0
  102. DLL/MachineLearning/SupervisedLearning/__init__.py +0 -0
  103. DLL/MachineLearning/UnsupervisedLearning/Clustering/_DBScan.py +87 -0
  104. DLL/MachineLearning/UnsupervisedLearning/Clustering/_GaussianMixtureModels.py +121 -0
  105. DLL/MachineLearning/UnsupervisedLearning/Clustering/_KMeans.py +120 -0
  106. DLL/MachineLearning/UnsupervisedLearning/Clustering/_SpectralClustering.py +105 -0
  107. DLL/MachineLearning/UnsupervisedLearning/Clustering/__init__.py +4 -0
  108. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_LDA.py +118 -0
  109. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_PCA.py +101 -0
  110. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_QDA.py +69 -0
  111. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_RobustPCA.py +143 -0
  112. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_TSNE.py +151 -0
  113. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_UMAP.py +176 -0
  114. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/__init__.py +6 -0
  115. DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/_IsolationForest.py +175 -0
  116. DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/__init__.py +1 -0
  117. DLL/MachineLearning/UnsupervisedLearning/__init__.py +0 -0
  118. DLL/MachineLearning/__init__.py +0 -0
  119. DLL/__init__.py +0 -0
  120. pydllib-1.0.dist-info/METADATA +104 -0
  121. pydllib-1.0.dist-info/RECORD +124 -0
  122. pydllib-1.0.dist-info/WHEEL +5 -0
  123. pydllib-1.0.dist-info/licenses/LICENSE +21 -0
  124. 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