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,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)
|