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,44 @@
1
+ import torch
2
+
3
+ from ._BaseOptimiser import BaseOptimiser
4
+
5
+
6
+ class SGD(BaseOptimiser):
7
+ """
8
+ Stochastic gradient descent optimiser with momentum. 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
+ momentum (float, optional): Determines how long the previous gradients affect the current direction. Must be in range [0, 1). Defaults to 0.9.
13
+ """
14
+ def __init__(self, learning_rate=0.001, momentum=0.9):
15
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
16
+ raise ValueError("learning_rate must be a positive real number.")
17
+ if not isinstance(momentum, int | float) or momentum < 0 or momentum >= 1:
18
+ raise ValueError("momentum must be in range [0, 1).")
19
+
20
+ self.learning_rate = learning_rate
21
+ self.momentum = momentum
22
+
23
+ def initialise_parameters(self, model_parameters):
24
+ """
25
+ Initialises the optimiser with the parameters that need to be optimised.
26
+
27
+ Args:
28
+ model_parameters (list[torch.Tensor]): The parameters that will be optimised. Must be a list or a tuple of torch tensors.
29
+ """
30
+
31
+ if not isinstance(model_parameters, list | tuple):
32
+ raise TypeError("model_parameters must be a list or a tuple of torch tensors.")
33
+
34
+ self.model_parameters = model_parameters
35
+ self.changes = [torch.zeros_like(parameter) for parameter in self.model_parameters]
36
+
37
+ def update_parameters(self):
38
+ """
39
+ Takes a step towards the optimum for each parameter.
40
+ """
41
+ for i, parameter in enumerate(self.model_parameters):
42
+ change = self.learning_rate * parameter.grad + self.momentum * self.changes[i]
43
+ parameter -= change
44
+ self.changes[i] = change
@@ -0,0 +1,6 @@
1
+ from ._ADAM import ADAM
2
+ from ._SGD import SGD
3
+ from ._LBFGS import LBFGS
4
+ from ._ADAGRAD import ADAGRAD
5
+ from ._ADADELTA import ADADELTA
6
+ from ._RMSPROP import RMSPROP
File without changes
@@ -0,0 +1,19 @@
1
+ class NotFittedError(Exception):
2
+ """
3
+ Exception raised when an action is called before the model is fitted.
4
+
5
+ Args:
6
+ message (string): The message which is passed to the Exception class.
7
+ """
8
+ def __init__(self, message):
9
+ super().__init__(message)
10
+
11
+ class NotCompiledError(Exception):
12
+ """
13
+ Exception raised when an action is called before the model is not compiled.
14
+
15
+ Args:
16
+ message (string): The message which is passed to the Exception class.
17
+ """
18
+ def __init__(self, message):
19
+ super().__init__(message)
@@ -0,0 +1,2 @@
1
+ from ._Exceptions import NotFittedError
2
+ from ._Exceptions import NotCompiledError
@@ -0,0 +1,88 @@
1
+ import torch
2
+
3
+ from ....Data.Preprocessing import data_split
4
+ from ....Data.Metrics import prob_to_pred
5
+ from . import IsotonicRegression
6
+ from ..LinearModels import LogisticRegression
7
+
8
+
9
+ class CalibratedClassifier:
10
+ """
11
+ The CalibratedClassifier for probability calibration.
12
+
13
+ Args:
14
+ estimator (DLL.Machinelearning classifier): An estimator to calibrate. The estimator must have the predict_proba method defined.
15
+ method (str, optional): The method used for the calibration. Must be one of "logistic" or "isotonic". Defaults to "logistic".
16
+ learning_rate (float, optional): The learning rate for fitting the logistic regression. Must be a positive real number. If method == "isotonic", this parameter is ignored. Defaults to 0.01.
17
+ """
18
+ def __init__(self, estimator, method="logistic", learning_rate=0.01):
19
+ if not hasattr(estimator, "predict_proba"):
20
+ raise TypeError("The estimator must have the predict_proba method defined.")
21
+ if method not in ["isotonic", "logistic"]:
22
+ raise ValueError('The chosen method be one of "logistic" or "isotonic".')
23
+ if not isinstance(learning_rate, int | float) or learning_rate <= 0:
24
+ raise ValueError("learning_rate must be a positive real number.")
25
+
26
+ self.estimator = estimator
27
+ self.method = method
28
+ self.calibrator = LogisticRegression(learning_rate) if method == "logistic" else IsotonicRegression()
29
+
30
+ def fit(self, X, y, calibration_size=0.2, **kwargs):
31
+ """
32
+ Fits chosen classifier to the input data and the output labels trying to be as accurate as possible about the probabilities of each class. Uses part of the data for the calibration and not for the fitting.
33
+
34
+ Args:
35
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
36
+ y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
37
+ calibration_size (float, optional): The amount of data used for the model calibration. Defaults to 0.2.
38
+ **kwargs: Key word arguments, which are passed to the fit method of the chosen estimator.
39
+ """
40
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
41
+ raise TypeError("The inpu matrix X and the label matrix y must be PyTorch tensors.")
42
+ if X.ndim != 2:
43
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
44
+ if y.ndim != 1 or len(y) != len(X):
45
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
46
+ if not isinstance(calibration_size, int | float) or calibration_size <= 0 or calibration_size >= 1:
47
+ raise ValueError("calibration_size must be in range (0, 1).")
48
+ vals = torch.unique(y).numpy()
49
+ if set(vals) != {*range(len(vals))}:
50
+ raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
51
+
52
+ X_train, y_train, _, _, X_cal, y_cal = data_split(X, y, train_split=1 - calibration_size, validation_split=0.0)
53
+ self.estimator.fit(X_train, y_train, **kwargs)
54
+ probs = self.estimator.predict_proba(X_cal)
55
+ if self.method == "logistic": probs = probs.unsqueeze(1)
56
+ self.calibrator.fit(self._logit(probs), y_cal, epochs=1000) if self.method == "logistic" else self.calibrator.fit(probs, y_cal)
57
+
58
+ def predict_proba(self, X):
59
+ """
60
+ Predicts the calibrated probabilities of the data points.
61
+
62
+ Args:
63
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
64
+
65
+ Returns:
66
+ torch.Tensor of shape (n_samples, n_classes) or (n_samples,): The predicted probabilities.
67
+ """
68
+ probs = self.estimator.predict_proba(X)
69
+ if self.method == "logistic": probs = probs.unsqueeze(1)
70
+ calibrated_probs = self.calibrator.predict_proba(self._logit(probs)) if self.method == "logistic" else self.calibrator.predict(probs)
71
+ return calibrated_probs
72
+
73
+ def predict(self, X):
74
+ """
75
+ Predicts the classes of each data point using the calibrated probabilities.
76
+
77
+ Args:
78
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
79
+
80
+ Returns:
81
+ torch.Tensor of shape (n_samples, n_classes) or (n_samples,): The predicted probabilities.
82
+ """
83
+ return prob_to_pred(self.predict_proba(X))
84
+
85
+ def _logit(self, X):
86
+ X[X < 1e-6] = 1e-6
87
+ X[X > 1 - 1e-6] = 1 - 1e-6
88
+ return torch.log(X / (1 - X))
@@ -0,0 +1,114 @@
1
+ import torch
2
+ from functools import partial
3
+
4
+
5
+ class IsotonicRegression:
6
+ """
7
+ Isotonic regression model.
8
+ """
9
+ def fit(self, X, y, weight=None, increasing=True):
10
+ """
11
+ Fits a monotonic function to the trainin data using the Pool-Adjacent-Violators (PAV) algorithm.
12
+
13
+ Args:
14
+ X (torch.Tensor of shape (n_samples,)): Independent variable (not necessarily sorted).
15
+ y (torch.Tensor of shape (n_samples,)): Dependent variable.
16
+ weight (torch.Tensor of shape (n_samples,) | None, optional): Sample weights. Must be a non-negative torch tensor. Defaults to None, which corresponds to uniform weights.
17
+ increasing (bool, optional): Deterimines if the final predictions should be increasing or decreasing. Must be a boolean. Defaults to True.
18
+ """
19
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
20
+ raise TypeError("The input matrix X and the label matrix y must be PyTorch tensors.")
21
+ if not isinstance(weight, torch.Tensor | None):
22
+ raise TypeError("The weight matrix must be a PyTorch tensor or None.")
23
+ if X.ndim != 1:
24
+ raise ValueError("The input matrix must be a 1 dimensional tensor.")
25
+ if y.ndim != 1 or len(y) != len(X):
26
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data.")
27
+ if weight is not None and (weight.ndim != 1 or len(weight) != len(X)):
28
+ raise ValueError("The weights must be 1 dimensional with the same number of samples as the input data.")
29
+ if not isinstance(increasing, bool):
30
+ raise TypeError("increasing must be a boolean.")
31
+
32
+ sorted_idx = torch.argsort(X)
33
+ X_sorted = X[sorted_idx]
34
+ y_sorted = y[sorted_idx]
35
+ if weight is None:
36
+ weight = torch.ones_like(y_sorted)
37
+ else:
38
+ weight = weight[sorted_idx]
39
+ if not increasing:
40
+ y_sorted = y_sorted.flip(0)
41
+ weight = weight.flip(0)
42
+
43
+ solution, _ = self._pava(y_sorted, weight)
44
+ if not increasing:
45
+ solution = solution.flip(0)
46
+ self._interp_func = partial(self._linear_interpolation, X_sorted, solution)
47
+
48
+ def _pava(self, y, w):
49
+ """
50
+ Adapted from sklearn docs: https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/_isotonic.pyx
51
+ """
52
+ n = len(y)
53
+ y, w = y.clone(), w.clone()
54
+ target = torch.arange(n, dtype=torch.int32)
55
+ i = 0
56
+ while i < n:
57
+ k = target[i].item() + 1
58
+ if k == n:
59
+ break
60
+ if y[i] < y[k]:
61
+ i = k
62
+ continue
63
+ sum_wy = w[i] * y[i]
64
+ sum_w = w[i]
65
+ while True:
66
+ prev_y = y[k]
67
+ sum_wy += w[k] * y[k]
68
+ sum_w += w[k]
69
+ k = target[k].item() + 1
70
+ if k == n or prev_y < y[k]:
71
+ y[i] = sum_wy / sum_w
72
+ w[i] = sum_w
73
+ target[i] = k - 1
74
+ target[k - 1] = i
75
+ if i > 0:
76
+ i = target[i - 1].item()
77
+ break
78
+ i = 0
79
+ while i < n:
80
+ k = target[i] + 1
81
+ y[i + 1:k] = y[i]
82
+ i = k
83
+ return y, w
84
+
85
+ def _linear_interpolation(self, X_fit, y_fit, X_new):
86
+ X_new = X_new.unsqueeze(0) if X_new.dim() == 0 else X_new
87
+ y_interp = torch.zeros_like(X_new).float()
88
+
89
+ for i, x in enumerate(X_new):
90
+ if x <= X_fit[0]:
91
+ y_interp[i] = y_fit[0]
92
+ elif x >= X_fit[-1]:
93
+ y_interp[i] = y_fit[-1]
94
+ else:
95
+ idx = torch.searchsorted(X_fit, x, right=True) - 1
96
+ x0, x1 = X_fit[idx], X_fit[min(idx + 1, len(X_fit) - 1)]
97
+ y0, y1 = y_fit[idx], y_fit[min(idx + 1, len(X_fit) - 1)]
98
+ y_interp[i] = y0 + (y1 - y0) * (x - x0) / (x1 - x0)
99
+ return y_interp
100
+
101
+ def predict(self, X):
102
+ """
103
+ Predicts values using the fitted isotonic regression model with linear interpolation.
104
+
105
+ Parameters:
106
+ X (torch.Tensor): New independent variable values.
107
+
108
+ Returns:
109
+ torch.Tensor: Predicted values.
110
+ """
111
+ if not isinstance(X, torch.Tensor) or X.ndim != 1:
112
+ raise ValueError("The input matrix must be a torch.Tensor of shape (n_samples,).")
113
+
114
+ return self._interp_func(X)
@@ -0,0 +1,2 @@
1
+ from ._IsotonicRegression import IsotonicRegression
2
+ from ._CalibratedClassifier import CalibratedClassifier
@@ -0,0 +1,190 @@
1
+ import torch
2
+ from math import floor
3
+
4
+ from ..Kernels import _Base
5
+ from ....DeepLearning.Optimisers import ADAM
6
+ from ....DeepLearning.Optimisers._BaseOptimiser import BaseOptimiser
7
+ from ....Exceptions import NotFittedError
8
+ from ....Data.Metrics import _round_dictionary
9
+
10
+
11
+ class GaussianProcessRegressor:
12
+ """
13
+ Implements the Gaussian process regression model.
14
+
15
+ Args:
16
+ covariance_function (:ref:`kernel_section_label`, optional): The kernel function expressing how similar are different samples.
17
+ noise (int | float, optional): The artificially added noise to the model. Is added as variance to each sample. Must be non-negative. Defaults to 0.
18
+ epsilon (float, optional): Implemented similarly to noise. Makes sure the covariance matrix is positive definite and hence invertible. Must be positive. Defaults to 1e-5. If one gets a RunTimeError for a matrix not being invertible, one should increase this parameter.
19
+ device (torch.device, optional): The device of all matricies. Defaults to torch.device("cpu").
20
+
21
+ Attributes:
22
+ n_features (int): The number of features. Available after fitting.
23
+ """
24
+ def __init__(self, covariance_function, noise=0, epsilon=1e-5, device=torch.device("cpu")):
25
+ if not isinstance(covariance_function, _Base):
26
+ raise TypeError("covariance_function must be from DLL.MachineLearning.Supervisedlearning.Kernels.")
27
+ if not isinstance(noise, int | float) or noise < 0:
28
+ raise ValueError("noise must b non-negative.")
29
+ if not isinstance(epsilon, int | float) or epsilon <= 0:
30
+ raise ValueError("epsilon must be positive.")
31
+ if not isinstance(device, torch.device):
32
+ raise TypeError("device must be an instance of torch.device.")
33
+
34
+ self.covariance_function = covariance_function
35
+ self.noise = noise
36
+ self.epsilon = epsilon
37
+ self.device = device
38
+
39
+ def _get_covariance_matrix(self, X1, X2):
40
+ return self.covariance_function(X1, X2).to(X1.dtype).to(self.device)
41
+
42
+ def fit(self, X, y):
43
+ """
44
+ Fits the GaussianProcessRegressor model to the input data.
45
+
46
+ Args:
47
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
48
+ y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample. Should be normalized to zero mean and one variance.
49
+ Returns:
50
+ None
51
+ Raises:
52
+ TypeError: If the input matrix or the target matrix is not a PyTorch tensor.
53
+ ValueError: If the input matrix or the target matrix is not the correct shape.
54
+ """
55
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
56
+ raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
57
+ if X.ndim != 2:
58
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
59
+ if y.ndim != 1 or len(y) != len(X):
60
+ raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
61
+ if len(y) < 2:
62
+ raise ValueError("There must be atleast 2 samples.")
63
+ # variance = torch.var(y)
64
+ # mean = torch.mean(y)
65
+ # if not torch.allclose(variance, torch.ones_like(variance)):
66
+ # raise ValueError("y must have one variance. Use DLL.Data.Preprocessing.StandardScaler to scale the data.")
67
+ # if not torch.allclose(mean, torch.zeros_like(mean)):
68
+ # raise ValueError("y must have zero mean. Use DLL.Data.Preprocessing.StandardScaler to scale the data.")
69
+
70
+ self.n_features = X.shape[1]
71
+ self.X = X
72
+ self.Y = y.unsqueeze(dim=1)
73
+ self.prior_covariance_matrix = self._get_covariance_matrix(X, X) + (self.noise + self.epsilon) * torch.eye(len(X), device=self.device)
74
+ self.inverse_prior_covariance_matrix = torch.linalg.inv(self.prior_covariance_matrix)
75
+
76
+ def predict(self, X):
77
+ """
78
+ Applies the fitted GaussianProcessRegressor model to the input data, predicting the correct values.
79
+
80
+ Args:
81
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
82
+ Returns:
83
+ mean, covariance (tuple[torch.Tensor of shape (n_samples,), torch.Tensor of shape (n_samples, n_samples)): A tuple containing the posterior mean and posterior covariance. As the prediction, one should use the mean.
84
+ Raises:
85
+ NotFittedError: If the GaussianProcessRegressor model has not been fitted before predicting.
86
+ TypeError: If the input matrix is not a PyTorch tensor.
87
+ ValueError: If the input matrix is not the correct shape.
88
+ """
89
+ if not hasattr(self, "inverse_prior_covariance_matrix"):
90
+ raise NotFittedError("GaussianProcessRegressor.fit() must be called before predicting.")
91
+ if not isinstance(X, torch.Tensor):
92
+ raise TypeError("The input matrix must be a PyTorch tensor.")
93
+ if X.ndim != 2 or X.shape[1] != self.n_features:
94
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
95
+
96
+ k_1 = self._get_covariance_matrix(self.X, X)
97
+ k_2 = self._get_covariance_matrix(X, X) + (self.noise + self.epsilon) * torch.eye(len(X), device=self.device)
98
+ mean = k_1.T @ self.inverse_prior_covariance_matrix @ self.Y
99
+ posterior_covariance = k_2 - k_1.T @ self.inverse_prior_covariance_matrix @ k_1
100
+ return mean, posterior_covariance
101
+
102
+ def log_marginal_likelihood(self):
103
+ """
104
+ Computes the log marginal likelihood of the current model. This value is used to optimize hyperparameters.
105
+
106
+ Returns:
107
+ log marginal likelihood (float): The log marginal likelihood of the current model.
108
+ """
109
+ if not hasattr(self, "inverse_prior_covariance_matrix"):
110
+ raise NotFittedError("GaussianProcessRegressor.fit() must be called before calculating the log marginal likelihood.")
111
+
112
+ L = torch.linalg.cholesky(self.prior_covariance_matrix)
113
+ alpha = torch.cholesky_solve(self.Y, L)
114
+ lml = -0.5 * self.Y.T @ alpha - torch.sum(torch.log(torch.diagonal(L))) - 0.5 * len(self.Y) * torch.log(torch.tensor(2 * torch.pi, device=self.device))
115
+ return lml.item()
116
+
117
+ def train_kernel(self, epochs=10, optimiser=None, callback_frequency=1, verbose=False):
118
+ """
119
+ Trains the current covariance function parameters by maximizing the log marginal likelihood.
120
+
121
+ Args:
122
+ epochs (int, optional): The number of optimisation rounds. Must be a positive integer. Defaults to 10.
123
+ optimiser (:ref:`optimisers_section_label` | None, optional): The optimiser used for training the model. If None, the Adam optimiser is used.
124
+ 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.
125
+ verbose (bool, optional): If True, prints the log marginal likelihood of the model during training. Defaults to False.
126
+
127
+ Returns:
128
+ history (dict[str, torch.Tensor], the tensor is floor(epochs / callback_frequency) long.): A dictionary tracking the evolution of the log marginal likelihood at intervals defined by callback_frequency. The tensor can be accessed with history["log marginal likelihood"].
129
+
130
+ Raises:
131
+ NotFittedError: If the GaussianProcessRegressor model has not been fitted before training the kernel.
132
+ TypeError: If the parameters are of wrong type.
133
+ ValueError: If epochs is not a positive integer.
134
+ """
135
+ if not hasattr(self, "inverse_prior_covariance_matrix"):
136
+ raise NotFittedError("GaussianProcessRegressor.fit() must be called before calculating the log marginal likelihood.")
137
+ if not isinstance(epochs, int) or epochs <= 0:
138
+ raise ValueError("epochs must be a positive integer.")
139
+ if not isinstance(optimiser, BaseOptimiser) and optimiser is not None:
140
+ raise TypeError("optimiser must be from DLL.DeepLearning.Optimisers")
141
+ if not isinstance(verbose, bool):
142
+ raise TypeError("verbose must be a boolean.")
143
+
144
+ optimiser = ADAM() if optimiser is None else optimiser
145
+ optimiser.initialise_parameters(list(self.covariance_function.parameters().values()))
146
+
147
+ history = {"log marginal likelihood": torch.zeros(floor(epochs / callback_frequency))}
148
+
149
+ for epoch in range(epochs):
150
+ # form the derivative function
151
+ def derivative(parameter_derivative):
152
+ derivative = 0.5 * self.Y.T @ self.inverse_prior_covariance_matrix @ parameter_derivative @ self.inverse_prior_covariance_matrix @ self.Y
153
+
154
+ if parameter_derivative.ndim == 2:
155
+ derivative = derivative.squeeze(1)
156
+ derivative -= 0.5 * torch.trace(self.inverse_prior_covariance_matrix @ parameter_derivative)
157
+ else:
158
+ derivative = derivative.squeeze((1, 2))
159
+ derivative -= 0.5 * (self.inverse_prior_covariance_matrix @ parameter_derivative).diagonal(dim1=1, dim2=2).sum(dim=-1)
160
+ # minus sign, since goal is to maximise the log_marginal_likelihood
161
+ return -derivative
162
+
163
+ # calculate the derivatives
164
+ self.covariance_function.update(derivative, self.X)
165
+
166
+ # update the parameters
167
+ optimiser.update_parameters()
168
+
169
+ self.prior_covariance_matrix = self._get_covariance_matrix(self.X, self.X) + (self.noise + self.epsilon) * torch.eye(len(self.X), device=self.device)
170
+ self.inverse_prior_covariance_matrix = torch.linalg.inv(self.prior_covariance_matrix)
171
+ if epoch % callback_frequency == 0:
172
+ lml = self.log_marginal_likelihood()
173
+ history["log marginal likelihood"][int(epoch / callback_frequency)] = lml
174
+ if verbose:
175
+ params = {}
176
+ for key, param in self.covariance_function.parameters().items():
177
+ if len(param) == 1:
178
+ params[key] = round(param.item(), 3)
179
+ continue
180
+ for i, param_ in enumerate(param):
181
+ params[key + "_" + str(i + 1)] = round(param_.item(), 3)
182
+ print(f"Epoch: {epoch + 1} - Log marginal likelihood: {lml} - Parameters: {params}")
183
+ return history
184
+
185
+ # def is_positive_definite(matrix):
186
+ # print("-----------------")
187
+ # if not torch.allclose(matrix, matrix.T):
188
+ # print(f"Matrix not symmetric: {torch.linalg.norm(matrix.T - matrix)}")
189
+ # eigenvalues = torch.linalg.eigvalsh(matrix)
190
+ # print(f"Minimum eigenvalue: {torch.min(eigenvalues).item()}")
@@ -0,0 +1 @@
1
+ from ._GaussianProcessRegressor import GaussianProcessRegressor