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,265 @@
1
+ import torch
2
+ from copy import deepcopy
3
+ import pickle
4
+
5
+
6
+ class Model:
7
+ """
8
+ The base model for a sequantial deep learning model. Uses a linear stack of layers to do forward- and backpropagation.
9
+
10
+ Args:
11
+ input_shape (tuple[int] | int): A tuple or an int containing the input shape of the model. The batch size should not be given as the first member of the tuple. For instance, if the input is of shape (n_sample, n_features), the input_shape should be n_features or if the input is of shape (n_samples, n_channels, width, heigth), the input_shape should be (n_channels, width, heigth).
12
+ data_type (torch.dtype, optional): The data type used by the model. Defaults to torch.float32.
13
+ device (torch.device, optional): The device of the model. Determines if the computation is made using the gpu or the cpu. Defaults to torch.device("cpu").
14
+ """
15
+ def __init__(self, input_shape, data_type=torch.float32, device=torch.device("cpu")):
16
+ if not isinstance(input_shape, tuple | int):
17
+ raise TypeError("input_shape must be a tuple of ints or an int. See documentation for examples.")
18
+ if not isinstance(data_type, torch.dtype):
19
+ raise TypeError("data_type must be an instance of torch.dtype.")
20
+ if not isinstance(device, torch.device):
21
+ raise TypeError('device must be either torch.device("cpu") or torch.device("cuda").')
22
+
23
+ from .Layers import Input
24
+ input_shape = (input_shape,) if isinstance(input_shape, int) else input_shape
25
+ self.layers = [Input(input_shape, device=device, data_type=data_type)]
26
+ self.optimiser = None
27
+ self.loss = None
28
+ self.data_type = data_type
29
+ self.device = device
30
+ self.training = False
31
+
32
+ def compile(self, optimiser=None, loss=None, metrics=("loss",), callbacks=tuple()):
33
+ """
34
+ Configures the model for training. Sets the optimiser and the loss function.
35
+
36
+ Args:
37
+ optimiser (:ref:`optimisers_section_label` | None, optional): The optimiser used for training the model. If None, the ADAM optimiser is used.
38
+ loss (:ref:`losses_section_label` | None, optional): The loss function used for training the model. If None, the MSE loss is used.
39
+ metrics (tuple[str], optional): The metrics that will be tracked during training. Defaults to ("loss").
40
+ callbacks (tuple[:ref:`callbacks_section_label`], optional): The callbacks used by the model. Defaults to ().
41
+ Raises:
42
+ TypeError: If the optimiser is not from DLL.DeepLearning.Optimisers, the loss is not from DLL.DeepLearning.Losses or the metrics is not a tuple or a list of strings.
43
+ """
44
+
45
+ from .Losses._BaseLoss import BaseLoss
46
+ from .Optimisers._BaseOptimiser import BaseOptimiser
47
+ from .Callbacks import Callback
48
+ from .Losses import MSE
49
+ from .Optimisers import ADAM
50
+
51
+ if not isinstance(optimiser, BaseOptimiser) and optimiser is not None:
52
+ raise TypeError("optimiser must be from DLL.DeepLearning.Optimisers")
53
+ if not isinstance(loss, BaseLoss) and loss is not None:
54
+ raise TypeError("loss must be from DLL.DeepLearning.Losses")
55
+ if not isinstance(metrics, list | tuple):
56
+ raise TypeError("metrics must be a list or a tuple containing the strings of wanted metrics.")
57
+ if any([not isinstance(callback, Callback) for callback in callbacks]):
58
+ raise TypeError("callbacks must be a tuple of callback objects.")
59
+
60
+ self.optimiser = optimiser if optimiser is not None else ADAM()
61
+ parameters = [parameter for layer in self.layers for parameter in layer.get_parameters()]
62
+ self.optimiser.initialise_parameters(parameters)
63
+ self.loss = loss if loss is not None else MSE()
64
+ self.metrics = metrics
65
+ self.callbacks = callbacks
66
+
67
+ def clone(self):
68
+ """
69
+ Returns a copy of the same model.
70
+ """
71
+ return deepcopy(self)
72
+
73
+ def add(self, layer):
74
+ """
75
+ Adds and initializes a layer to the model.
76
+
77
+ Args:
78
+ layer (:ref:`layers_section_label`): The layer that is added to the model.
79
+ """
80
+ from .Layers._BaseLayer import BaseLayer
81
+ if not isinstance(layer, BaseLayer):
82
+ raise TypeError("layer must be from DLL.Deeplearning.Layers")
83
+
84
+ layer.initialise_layer(input_shape=self.layers[-1].output_shape, data_type=self.data_type, device=self.device)
85
+ self.layers.append(layer)
86
+
87
+ def train(self):
88
+ """
89
+ Change the state of the model to training state. Effects some layers, but other layers remain the same.
90
+ """
91
+ self.training = True
92
+
93
+ def eval(self):
94
+ """
95
+ Change the state of the model to evaluation state. Effects some layers, but other layers remain the same.
96
+ """
97
+ self.training = False
98
+
99
+ def summary(self):
100
+ """
101
+ Prints the summary of the model containing its architecture and the number of parameters of the model.
102
+ """
103
+ print("Model summary:")
104
+ total_params = 0
105
+ for layer in self.layers:
106
+ print(layer.summary())
107
+ total_params += layer.get_nparams()
108
+ print(f"Total number of parameters: {total_params}")
109
+
110
+ def __str__(self):
111
+ message = "Model summary:"
112
+ total_params = 0
113
+ for layer in self.layers:
114
+ message += layer.summary() + "\n"
115
+ total_params += layer.get_nparams()
116
+ message += f"Total number of parameters: {total_params}"
117
+ return message
118
+
119
+ def predict(self, X):
120
+ """
121
+ Applies the fitted Model to the input data, predicting wanted values by forward propagation.
122
+
123
+ Args:
124
+ X (torch.Tensor of shape (n_samples, *input_shape)): The input data that goes through the model by forward propagation.
125
+
126
+ Raises:
127
+ NotCompiledError: If the Model has not been compiled before predicting.
128
+
129
+ Returns:
130
+ torch.Tensor of shape (n_samples, *last_layer.output_shape)): The predictions made by the model.
131
+ """
132
+ from ..Exceptions import NotCompiledError
133
+ if self.optimiser is None:
134
+ raise NotCompiledError("Model.compile() must be called before predicting.")
135
+
136
+ current = X
137
+ for layer in self.layers:
138
+ current = layer.forward(current, training=self.training)
139
+ return current
140
+
141
+ def backward(self, initial_gradient):
142
+ """
143
+ :meta private:
144
+ """
145
+ reversedLayers = reversed(self.layers)
146
+ gradient = initial_gradient
147
+ for layer in reversedLayers:
148
+ gradient = layer.backward(gradient, training=self.training)
149
+ return gradient
150
+
151
+ def fit(self, X, Y, val_data=None, epochs=10, callback_frequency=1, batch_size=None, shuffle_every_epoch=True, shuffle_data=True, verbose=False):
152
+ """
153
+ Fits the LogisticRegression model to the input data by minimizing the cross entropy loss (logistic loss).
154
+
155
+ Args:
156
+ X (torch.Tensor of shape (n_samples, *first_layer.input_shape)): The input data, of correct shape determined by the input_shape of the model.
157
+ y (torch.Tensor): The targets corresponding to each sample.
158
+ val_data (tuple[X_val, y_val] | None, optional): Optional validation samples. Must have the same remaining dimensions than X and y apart from n_samples. If None, no validation data is used. Defaults to None.
159
+ epochs (int, optional): The number of training iterations. Must be a positive integer. Defaults to 10.
160
+ callback_frequency (int, optional): The number of iterations between printing info from training. Must be a positive integer. Defaults to 1, which means that every iteration, info is printed assuming verbose=True.
161
+ batch_size (int | None, optional): The batch size used in training. Must be a positive integer. If None, every sample is used for every gradient calculation. Defaults to None.
162
+ shuffle_every_epoch (bool, optional): If True, shuffles the order of the samples every epoch. Defaults to True.
163
+ shuffle_data (bool, optional): If True, shuffles data before the training.
164
+ verbose (bool, optional): If True, prints info of the chosen metrics during training. Defaults to False.
165
+ Returns:
166
+ history (dict[str, list]): A dictionary tracking the evolution of selected metrics at intervals defined by callback_frequency. If training was not stopped early, each metric is floor(epochs / callback_frequency) long.
167
+ Raises:
168
+ TypeError: If the input matrix or the target matrix is not a PyTorch tensor or if other parameters are of wrong type.
169
+ ValueError: If the input matrix or the target matrix is not the correct shape or if other parameters have incorrect values.
170
+ """
171
+ if not isinstance(X, torch.Tensor) or not isinstance(Y, torch.Tensor):
172
+ raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
173
+ if X.shape[1:] != self.layers[0].input_shape:
174
+ raise ValueError("The input matrix must have the same shape as input_shape.")
175
+ if len(Y) != len(X) or Y.shape[1:] != self.layers[-1].output_shape:
176
+ raise ValueError(f"The targets must have the same shape as the output_shape of the last layer with the same number of samples as the input data {Y.shape[1:], self.layers[-1].output_shape}.")
177
+ if not isinstance(val_data, list | tuple) and val_data is not None:
178
+ raise TypeError("val_data must either be a tuple containing validation samples or None.")
179
+ if isinstance(val_data, list | tuple) and len(val_data) != 2:
180
+ raise ValueError("val_data must contain both X_val and y_val.")
181
+ if isinstance(val_data, list | tuple) and len(val_data) == 2 and (val_data[0].shape[1:] != X.shape[1:] or val_data[1].shape[1:] != Y.shape[1:] or len(val_data[0]) != len(val_data[1])):
182
+ raise ValueError("X_val and y_val must be of correct shape.")
183
+ if not isinstance(epochs, int) or epochs <= 0:
184
+ raise ValueError("epochs must be a positive integer.")
185
+ if not isinstance(callback_frequency, int) or callback_frequency <= 0:
186
+ raise ValueError("callback_frequency must be a positive integer.")
187
+ if (not isinstance(batch_size, int) or batch_size <= 0) and batch_size is not None:
188
+ raise ValueError("batch_size must be a positive integer.")
189
+ if not isinstance(shuffle_every_epoch, bool):
190
+ raise TypeError("shuffle_every_epoch must be a boolean.")
191
+ if not isinstance(shuffle_data, bool):
192
+ raise TypeError("shuffle_data must be a boolean.")
193
+ if not isinstance(verbose, bool):
194
+ raise TypeError("verbose must be a boolean.")
195
+
196
+ from ..Data import DataReader
197
+ from ..Data.Metrics import calculate_metrics, _round_dictionary
198
+
199
+ history = {metric: [] for metric in self.metrics}
200
+ batch_size = len(X) if batch_size is None else batch_size
201
+ data_reader = DataReader(X, Y, batch_size=batch_size, shuffle=shuffle_data, shuffle_every_epoch=shuffle_every_epoch)
202
+
203
+ train_metrics = [metric for metric in self.metrics if metric[:4] != "val_"]
204
+ val_metrics = [metric for metric in self.metrics if metric[:4] == "val_"]
205
+
206
+ self.stop_training = False # boolean for callbacks to stop the training
207
+ for callback in self.callbacks:
208
+ callback.set_model(self)
209
+ callback.on_train_start()
210
+
211
+ for epoch in range(epochs):
212
+ if self.stop_training:
213
+ break
214
+ self.train()
215
+ for x, y in data_reader.get_data():
216
+ self.optimiser.zero_grad()
217
+ predictions = self.predict(x)
218
+ initial_gradient = self.loss.gradient(predictions, y)
219
+ self.backward(initial_gradient)
220
+ self.optimiser.update_parameters()
221
+ for callback in self.callbacks:
222
+ callback.on_batch_end(epoch)
223
+
224
+ self.eval()
225
+ if epoch % callback_frequency == 0:
226
+ values = calculate_metrics(data=(self.predict(X), Y), metrics=train_metrics, loss=self.loss.loss)
227
+ if val_data is not None:
228
+ val_values = calculate_metrics(data=(self.predict(val_data[0]), val_data[1]), metrics=val_metrics, loss=self.loss.loss, validation=True)
229
+ values |= val_values
230
+ for metric, value in values.items():
231
+ history[metric].append(value)
232
+ if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
233
+
234
+ for callback in self.callbacks:
235
+ callback.on_epoch_end(epoch, values)
236
+
237
+ for callback in self.callbacks:
238
+ callback.on_train_end()
239
+
240
+ return history
241
+
242
+
243
+ def save_model(model, filepath="./model.pkl"):
244
+ """Saves a model using pickle serialization."""
245
+ try:
246
+ with open(filepath, "wb") as f:
247
+ pickle.dump(model, f)
248
+ except TypeError as e:
249
+ print(f"Error: The model is not serializable. Saving failed.")
250
+ raise
251
+ except OSError as e:
252
+ print(f"File error while saving the model.")
253
+ raise
254
+
255
+ def load_model(filepath="./model.pkl"):
256
+ """Loads a model from a pickle file."""
257
+ try:
258
+ with open(filepath, "rb") as f:
259
+ return pickle.load(f)
260
+ except (pickle.UnpicklingError, EOFError, AttributeError, ModuleNotFoundError, TypeError) as e:
261
+ print(f"Error loading the model file.")
262
+ raise
263
+ except OSError as e:
264
+ print(f"File error while loading the model.")
265
+ raise
@@ -0,0 +1,53 @@
1
+ import torch
2
+
3
+ from ._BaseOptimiser import BaseOptimiser
4
+
5
+
6
+ class ADADELTA(BaseOptimiser):
7
+ """
8
+ The adadelta optimiser. A first order method and therefore does not use information on second gradients, i.e. the hessian matrix. Hence, does not require a lot of memory.
9
+
10
+ Args:
11
+ learning_rate (float, optional): The learning rate of the optimiser. Must be positive. Defaults to 0.001.
12
+ rho (float, optional): Determines how long the previous gradients affect the current step direction. Must be in range [0, 1). Defaults to 0.9.
13
+ weight_decay (float, optional): Determines if regularisation should be applied to the weights. Must be in range [0, 1). Defaults to 0.
14
+ """
15
+ def __init__(self, learning_rate=0.001, rho=0.9, weight_decay=0):
16
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
17
+ raise ValueError("learning_rate must be a positive real number.")
18
+ if not isinstance(rho, int | float) or rho < 0 or rho >= 1:
19
+ raise ValueError("rho must be in range [0, 1).")
20
+ if not isinstance(weight_decay, int | float) or weight_decay < 0 or weight_decay >= 1:
21
+ raise ValueError("weight_decay must be in range [0, 1).")
22
+
23
+ self.learning_rate = learning_rate
24
+ self.rho = rho
25
+ self.weight_decay = weight_decay
26
+
27
+ def initialise_parameters(self, model_parameters):
28
+ """
29
+ Initialises the optimiser with the parameters that need to be optimised.
30
+
31
+ Args:
32
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
33
+ """
34
+
35
+ if not isinstance(model_parameters, list | tuple):
36
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
37
+
38
+ self.model_parameters = model_parameters
39
+ self.square_avg = [torch.zeros_like(param) for param in model_parameters]
40
+ self.accumulate_variables = [torch.zeros_like(param) for param in model_parameters]
41
+ self.t = 0
42
+
43
+ def update_parameters(self):
44
+ """
45
+ Takes a step towards the optimum for each parameter.
46
+ """
47
+ self.t += 1
48
+ for i, parameter in enumerate(self.model_parameters):
49
+ if self.weight_decay > 0: parameter.grad += self.weight_decay * parameter
50
+ self.square_avg[i] = self.square_avg[i] * self.rho + parameter.grad ** 2 * (1 - self.rho)
51
+ delta_x = parameter.grad * torch.sqrt(self.accumulate_variables[i] + 1e-10) / torch.sqrt(self.square_avg[i] + 1e-10)
52
+ self.accumulate_variables[i] = self.accumulate_variables[i] * self.rho + delta_x ** 2 * (1 - self.rho)
53
+ parameter -= self.learning_rate * delta_x
@@ -0,0 +1,51 @@
1
+ import torch
2
+
3
+ from ._BaseOptimiser import BaseOptimiser
4
+
5
+
6
+ class ADAGRAD(BaseOptimiser):
7
+ """
8
+ The adaptive gradient optimiser. A first order method and therefore does not use information on second gradients, i.e. the hessian matrix. Hence, does not require a lot of memory.
9
+
10
+ Args:
11
+ learning_rate (float, optional): The learning rate of the optimiser. Must be positive. Defaults to 0.001.
12
+ lr_decay (float, optional): Determines how fast the learning rate decreases. Must be positive. Defaults to 0.
13
+ weight_decay (float, optional): Determines if regularisation should be applied to the weights. Must be in range [0, 1). Defaults to 0.
14
+ """
15
+ def __init__(self, learning_rate=0.001, lr_decay=0, weight_decay=0):
16
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
17
+ raise ValueError("learning_rate must be a positive real number.")
18
+ if not isinstance(lr_decay, int | float) or lr_decay < 0:
19
+ raise ValueError("lr_decay must be positive.")
20
+ if not isinstance(weight_decay, int | float) or weight_decay < 0 or weight_decay >= 1:
21
+ raise ValueError("weight_decay must be in range [0, 1).")
22
+
23
+ self.learning_rate = learning_rate
24
+ self.lr_decay = lr_decay
25
+ self.weight_decay = weight_decay
26
+
27
+ def initialise_parameters(self, model_parameters):
28
+ """
29
+ Initialises the optimiser with the parameters that need to be optimised.
30
+
31
+ Args:
32
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
33
+ """
34
+
35
+ if not isinstance(model_parameters, list | tuple):
36
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
37
+
38
+ self.model_parameters = model_parameters
39
+ self.state_sum = [torch.zeros_like(param) for param in model_parameters]
40
+ self.t = 0
41
+
42
+ def update_parameters(self):
43
+ """
44
+ Takes a step towards the optimum for each parameter.
45
+ """
46
+ self.t += 1
47
+ for i, parameter in enumerate(self.model_parameters):
48
+ learning_rate = self.learning_rate / (1 + (self.t - 1) * self.lr_decay)
49
+ if self.weight_decay > 0: parameter.grad += self.weight_decay * parameter
50
+ self.state_sum[i] += parameter.grad ** 2
51
+ parameter -= learning_rate * parameter.grad / (torch.sqrt(self.state_sum[i]) + 1e-10)
@@ -0,0 +1,65 @@
1
+ import torch
2
+
3
+ from ._BaseOptimiser import BaseOptimiser
4
+
5
+
6
+ class ADAM(BaseOptimiser):
7
+ """
8
+ The adaptive moment estimation optimiser. Is very robust and does not require a lot of tuning it's hyperparameters. A first order method and therefore does not use information on second gradients, i.e. the hessian matrix. Hence, does not require a lot of memory. Is based on algorithm 1 on `this paper <https://arxiv.org/pdf/1412.6980>`_.
9
+
10
+ Args:
11
+ learning_rate (float, optional): The learning rate of the optimiser. Must be positive. Defaults to 0.001.
12
+ beta1 (float, optional): Determines how long the previous gradients affect the current step direction. Must be in range [0, 1). Defaults to 0.9.
13
+ beta2 (float, optional): Determines how long the previous squared gradients affect the current step direction. Must be in range [0, 1). Defaults to 0.999.
14
+ weight_decay (float, optional): Determines if regularisation should be applied to the weights. Must be in range [0, 1). Defaults to 0.
15
+ amsgrad (bool, optional): Determines if a modified version of the algorithm is used. Defaults to False.
16
+ """
17
+ def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, weight_decay=0, amsgrad=False):
18
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
19
+ raise ValueError("learning_rate must be a positive real number.")
20
+ if not isinstance(beta1, int | float) or beta1 < 0 or beta1 >= 1:
21
+ raise ValueError("momentum must be in range [0, 1).")
22
+ if not isinstance(beta2, int | float) or beta2 < 0 or beta2 >= 1:
23
+ raise ValueError("momentum must be in range [0, 1).")
24
+ if not isinstance(weight_decay, int | float) or weight_decay < 0 or weight_decay >= 1:
25
+ raise ValueError("weight_decay must be in range [0, 1).")
26
+
27
+ self.learning_rate = learning_rate
28
+ self.beta1 = beta1
29
+ self.beta2 = beta2
30
+ self.weight_decay = weight_decay
31
+ self.amsgrad = amsgrad
32
+
33
+ def initialise_parameters(self, model_parameters):
34
+ """
35
+ Initialises the optimiser with the parameters that need to be optimised.
36
+
37
+ Args:
38
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
39
+ """
40
+
41
+ if not isinstance(model_parameters, list | tuple):
42
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
43
+
44
+ self.model_parameters = model_parameters
45
+ self.m = [torch.zeros_like(parameter) for parameter in self.model_parameters]
46
+ self.v = [torch.zeros_like(parameter) for parameter in self.model_parameters]
47
+ self.v_max = [torch.zeros_like(parameter) for parameter in self.model_parameters]
48
+ self.t = 0
49
+
50
+ def update_parameters(self):
51
+ """
52
+ Takes a step towards the optimum for each parameter.
53
+ """
54
+ self.t += 1
55
+ for i, parameter in enumerate(self.model_parameters):
56
+ if self.weight_decay > 0: parameter.grad += self.weight_decay * parameter
57
+ self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * parameter.grad
58
+ self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * parameter.grad ** 2
59
+ m_hat = self.m[i] / (1 - self.beta1 ** self.t)
60
+ v_hat = self.v[i] / (1 - self.beta2 ** self.t)
61
+ if self.amsgrad:
62
+ self.v_max[i] = torch.maximum(v_hat, self.v_max[i])
63
+ parameter -= self.learning_rate * m_hat / (torch.sqrt(self.v_max[i]) + 1e-10)
64
+ else:
65
+ parameter -= self.learning_rate * m_hat / (torch.sqrt(v_hat) + 1e-10)
@@ -0,0 +1,20 @@
1
+ import torch
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class BaseOptimiser(ABC):
7
+ """
8
+ :meta private:
9
+ """
10
+ @abstractmethod
11
+ def initialise_parameters(self, model_parameters):
12
+ pass
13
+
14
+ @abstractmethod
15
+ def update_parameters(self):
16
+ pass
17
+
18
+ def zero_grad(self):
19
+ for param in self.model_parameters:
20
+ param.grad = torch.zeros_like(param)
@@ -0,0 +1,120 @@
1
+ import torch
2
+ from collections import deque
3
+
4
+ from ._BaseOptimiser import BaseOptimiser
5
+
6
+
7
+ class LBFGS(BaseOptimiser):
8
+ """
9
+ Limited-memory Broyden-Fletcher-Goldfarb-Shanno optimiser. A second order method and approximates the hessian matrix using changes in position and gradient. Hence, requires more memory than first order methods.
10
+
11
+ Args:
12
+ loss (Callable[[], float]): The target function. For a deep learning model, one could use eg. lambda: model.loss.loss(model.predict(x_train), y_train).
13
+ learning_rate (float, optional): The learning rate of the optimiser. Must be positive. Defaults to 0.001.
14
+ history_size (int, optional): The number of old changes in position and gradient stored. Must be a non-negative integer. Defaults to 10.
15
+ maxiterls (int, optional): The maximum number of iterations in the line search. Must be a non-negative integer. Defaults to 20.
16
+ """
17
+ def __init__(self, loss, learning_rate=0.001, history_size=10, maxiterls=20):
18
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
19
+ raise ValueError("learning_rate must be a positive real number.")
20
+ if not isinstance(history_size, int) or history_size < 0:
21
+ raise ValueError("history_size must be a non-negative integer.")
22
+ if not isinstance(maxiterls, int) or maxiterls < 0:
23
+ raise ValueError("maxiterls must be a non-negative integer.")
24
+
25
+ self.loss = loss
26
+ self.learning_rate = learning_rate
27
+ self.history_size = history_size
28
+ self.maxiterls = maxiterls
29
+
30
+ def initialise_parameters(self, model_parameters):
31
+ """
32
+ Initialises the optimiser with the parameters that need to be optimised.
33
+
34
+ Args:
35
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
36
+ """
37
+
38
+ if not isinstance(model_parameters, list | tuple):
39
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
40
+
41
+ self.model_parameters = model_parameters
42
+ self.s_history = [deque([], maxlen=self.history_size) for _ in model_parameters]
43
+ self.y_history = [deque([], maxlen=self.history_size) for _ in model_parameters]
44
+ self.prevs = [param.clone() for param in model_parameters]
45
+ self.prev_Bs = [torch.ones_like(param.flatten()) for param in model_parameters]
46
+
47
+ def update_parameters(self):
48
+ """
49
+ Takes a step towards the optimum for each parameter.
50
+ """
51
+
52
+ for i, param in enumerate(self.model_parameters):
53
+ if param.grad is None:
54
+ continue
55
+
56
+ current = param.clone()
57
+ current.grad = param.grad.clone()
58
+ s = current - self.prevs[i]
59
+ y = current.grad - self.prevs[i].grad if self.prevs[i].grad is not None else torch.zeros_like(current.grad)
60
+
61
+ if s.flatten() @ y.flatten() > 1e-8:
62
+ self.s_history[i].append(s) # automatically pops the oldest values as maxlen is spesified for the deque
63
+ self.y_history[i].append(y) # automatically pops the oldest values as maxlen is spesified for the deque
64
+
65
+ Bs = self.learning_rate * self._recursion_two_loops(i)
66
+
67
+ learning_rate = self._line_search(param, Bs)
68
+ param -= learning_rate * Bs.view_as(param)
69
+
70
+ self.prevs[i] = current
71
+ self.prev_Bs[i] = Bs
72
+
73
+ def _recursion_two_loops(self, i):
74
+ q = self.model_parameters[i].grad.clone().flatten()
75
+ alphas = torch.zeros(self.history_size)
76
+ rhos = torch.tensor([1.0 / (self.y_history[i][j].flatten() @ self.s_history[i][j].flatten()) for j in range(self.history_size) if len(self.s_history[i]) > j])
77
+
78
+ for j, (s, y) in enumerate(zip(reversed(self.s_history[i]), reversed(self.y_history[i]))):
79
+ j = len(self.s_history[i]) - j - 1
80
+ s, y = s.flatten(), y.flatten()
81
+ alphas[j] = rhos[j] * (s @ q)
82
+ q -= alphas[j] * y
83
+
84
+ if len(self.s_history[i]) > 0:
85
+ last_s, last_y = self.s_history[i][-1].flatten(), self.y_history[i][-1].flatten()
86
+ Hk_0 = (last_s @ last_y) / last_y @ last_y
87
+ else:
88
+ Hk_0 = 1
89
+ r = Hk_0 * q
90
+
91
+ for j, (s, y) in enumerate(zip(self.s_history[i], self.y_history[i])):
92
+ s, y = s.flatten(), y.flatten()
93
+ beta = rhos[j] * (y @ r)
94
+ r += (alphas[j] - beta) * s
95
+ return r
96
+
97
+ def _line_search(self, param, direction):
98
+ step = 1.0
99
+ initial_func_value = self.loss()
100
+ grad_dot_direction = torch.sum(param.grad.flatten() * direction)
101
+ orig_param = param.clone()
102
+ c = 1e-4
103
+
104
+ iter = 0
105
+ while iter < self.maxiterls:
106
+ new_param_value = orig_param - step * direction.view_as(orig_param)
107
+ param.data = new_param_value
108
+ new_func_value = self.loss()
109
+
110
+ # Does not include the other wolfe condition as it requires us to calculate the gradient of the parameter
111
+ # at the new point, which is only possible to calculate using finite differences with current architecture.
112
+ if new_func_value <= initial_func_value + c * step * grad_dot_direction:
113
+ break
114
+ else:
115
+ step *= 0.5
116
+
117
+ iter += 1
118
+
119
+ param.data = orig_param.data
120
+ return step
@@ -0,0 +1,62 @@
1
+ import torch
2
+
3
+ from ._BaseOptimiser import BaseOptimiser
4
+
5
+
6
+ class RMSPROP(BaseOptimiser):
7
+ """
8
+ The adaptive moment estimation optimiser. Is very robust and does not require a lot of tuning it's hyperparameters. A first order method and therefore does not use information on second gradients, i.e. the hessian matrix. Hence, does not require a lot of memory.
9
+
10
+ Args:
11
+ learning_rate (float, optional): The learning rate of the optimiser. Must be positive. Defaults to 0.001.
12
+ alpha (float, optional): A smoothing constant. Defaults to 0.99.
13
+ momentum (float, optional): Determines how long the previous gradients affect the current direction. Must be in range [0, 1). Defaults to 0.
14
+ weight_decay (float, optional): Determines if regularisation should be applied to the weights. Must be in range [0, 1). Defaults to 0.
15
+ centered (bool, optional): Determines if a centered version of the algorithm is used. Defaults to False.
16
+ """
17
+ def __init__(self, learning_rate=0.001, alpha=0.99, momentum=0, weight_decay=0, centered=False):
18
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
19
+ raise ValueError("learning_rate must be a positive real number.")
20
+ if not isinstance(momentum, int | float) or momentum < 0 or momentum >= 1:
21
+ raise ValueError("momentum must be in range [0, 1).")
22
+ if not isinstance(weight_decay, int | float) or weight_decay < 0 or weight_decay >= 1:
23
+ raise ValueError("weight_decay must be in range [0, 1).")
24
+
25
+ self.learning_rate = learning_rate
26
+ self.alpha = alpha
27
+ self.momentum = momentum
28
+ self.weight_decay = weight_decay
29
+ self.centered = centered
30
+
31
+ def initialise_parameters(self, model_parameters):
32
+ """
33
+ Initialises the optimiser with the parameters that need to be optimised.
34
+
35
+ Args:
36
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
37
+ """
38
+
39
+ if not isinstance(model_parameters, list | tuple):
40
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
41
+
42
+ self.model_parameters = model_parameters
43
+ self.square_average = [torch.zeros_like(parameter) for parameter in self.model_parameters]
44
+ self.buffer = [torch.zeros_like(parameter) for parameter in self.model_parameters]
45
+ self.g_average = [torch.zeros_like(parameter) for parameter in self.model_parameters]
46
+
47
+ def update_parameters(self):
48
+ """
49
+ Takes a step towards the optimum for each parameter.
50
+ """
51
+ for i, parameter in enumerate(self.model_parameters):
52
+ if self.weight_decay > 0: parameter.grad += self.weight_decay * parameter
53
+ self.square_average[i] = self.alpha * self.square_average[i] + (1 - self.alpha) * parameter.grad ** 2
54
+ v_tilde = self.square_average[i]
55
+ if self.centered:
56
+ self.g_average[i] = self.alpha * self.g_average[i] + (1 - self.alpha) * parameter.grad
57
+ v_tilde -= self.g_average[i] ** 2
58
+ if self.momentum > 0:
59
+ self.buffer[i] = self.momentum * self.buffer[i] + parameter.grad / (torch.sqrt(v_tilde) + 1e-10)
60
+ parameter -= self.learning_rate * self.buffer[i]
61
+ else:
62
+ parameter -= self.learning_rate * parameter.grad / (torch.sqrt(v_tilde) + 1e-10)