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,207 @@
1
+ import torch
2
+
3
+ from ._RegressionTree import RegressionTree
4
+ from ....DeepLearning.Layers.Activations import Sigmoid, SoftMax
5
+ from ....DeepLearning.Losses import BCE, CCE, Exponential
6
+ from ....Data.Preprocessing import OneHotEncoder
7
+ from ....Exceptions import NotFittedError
8
+ from ....Data.Metrics import calculate_metrics, prob_to_pred
9
+
10
+
11
+
12
+ class GradientBoostingClassifier:
13
+ """
14
+ GradientBoostingClassifier implements a classification algorithm fitting many consecutive :class:`RegressionTrees <DLL.MachineLearning.SupervisedLearning.Trees.RegressionTree>` to residuals of the model.
15
+
16
+ Args:
17
+ n_trees (int, optional): The number of trees used for predicting. Defaults to 10. Must be a positive integer.
18
+ learning_rate (float, optional): The number multiplied to each additional trees residuals. Must be a real number in range (0, 1). Defaults to 0.5.
19
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 25. Must be a positive integer.
20
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
21
+ loss (string, optional): The loss function used in calculations of the residuals. Must be one of "log_loss" or "exponential". Defaults to "log_loss". "exponential" can only be used for binary classification.
22
+ Attributes:
23
+ n_features (int): The number of features. Available after fitting.
24
+ n_classes (int): The number of classes. 2 for binary classification. Available after fitting.
25
+ """
26
+ def __init__(self, n_trees=10, learning_rate=0.5, max_depth=25, min_samples_split=2, loss="log_loss"):
27
+ if not isinstance(n_trees, int) or n_trees < 1:
28
+ raise ValueError("n_trees must be a positive integer.")
29
+ if not isinstance(learning_rate, float) or learning_rate <= 0 or learning_rate >= 1:
30
+ raise ValueError("learning_rate must be a float in range (0, 1).")
31
+ if not isinstance(max_depth, int) or max_depth < 1:
32
+ raise ValueError("max_depth must be a positive integer.")
33
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
34
+ raise ValueError("min_samples_split must be a positive integer.")
35
+ if loss not in ["log_loss", "exponential"]:
36
+ raise ValueError('loss must be one of ["log_loss", "exponential"]')
37
+ self.n_trees = n_trees
38
+ self.learning_rate = learning_rate
39
+ self.max_depth = max_depth
40
+ self.min_samples_split = min_samples_split
41
+ self.trees = None
42
+ self.loss_ = loss
43
+
44
+ def _get_activation_and_loss(self, classes):
45
+ self.n_classes = len(classes)
46
+ if self.loss_ == "log_loss":
47
+ if self.n_classes == 2:
48
+ self.loss = BCE(reduction="sum")
49
+ self.activation = Sigmoid()
50
+ else:
51
+ self.loss = CCE(reduction="sum")
52
+ self.activation = SoftMax()
53
+ elif self.loss_ == "exponential":
54
+ if self.n_classes != 2:
55
+ raise ValueError("The exponential loss is only applicable in binary classification. Use log_loss for multiclass classification instead.")
56
+ self.loss = Exponential(reduction="sum")
57
+ self.activation = Sigmoid()
58
+
59
+ def fit(self, X, y, metrics=["loss"]):
60
+ """
61
+ Fits the GradientBoostingClassifier model to the input data by fitting trees to the errors made by previous trees.
62
+
63
+ Args:
64
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
65
+ y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
66
+ metrics (dict[str, torch.Tensor]): Contains the metrics that will be calculated between fitting each tree and returned. Only available for binary classification.
67
+ Returns:
68
+ metrics if binary classification else None
69
+ Raises:
70
+ TypeError: If the input matrix or the label vector is not a PyTorch tensor or if the problem is binary and metrics is not a list or a tuple.
71
+ ValueError: If the input matrix or the label vector is not the correct shape or the label vector contains wrong values.
72
+ """
73
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
74
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
75
+ if X.ndim != 2:
76
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
77
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
78
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
79
+ vals = torch.unique(y).numpy()
80
+ if set(vals) != {*range(len(vals))}:
81
+ raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
82
+
83
+ self._get_activation_and_loss(torch.unique(y))
84
+ y = y.to(X.dtype)
85
+ self.n_features = X.shape[1]
86
+
87
+ if self.n_classes == 2:
88
+ if not isinstance(metrics, list | tuple):
89
+ raise ValueError("metrics must be a list or tuple containing the shorthand names of each wanted metric.")
90
+
91
+ return self._binary_fit(X, y, metrics=metrics)
92
+ else:
93
+ self._multi_fit(X, y)
94
+
95
+ def _binary_fit(self, X, y, metrics=["loss"]):
96
+ positive_ratio = y.mean()
97
+ self.initial_log_odds = torch.log(positive_ratio / (1 - positive_ratio))
98
+ pred = torch.full(y.shape, self.initial_log_odds)
99
+ trees = []
100
+ history = {metric: torch.zeros(self.n_trees) for metric in metrics}
101
+
102
+ for i in range(self.n_trees):
103
+ prob = self.activation.forward(pred)
104
+ residual = -self.activation.backward(self.loss.gradient(prob, y))
105
+
106
+ tree = RegressionTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
107
+ tree.fit(X, residual)
108
+ prediction = tree.predict(X)
109
+ pred += self.learning_rate * prediction
110
+
111
+ trees.append(tree)
112
+
113
+ values = calculate_metrics(data=(self.activation.forward(pred), y), metrics=metrics, loss=self.loss.loss)
114
+ for metric, value in values.items():
115
+ history[metric][i] = value
116
+
117
+ self.trees = trees
118
+ return history
119
+
120
+ def _multi_fit(self, X, y):
121
+ encoder = OneHotEncoder()
122
+ y = encoder.fit_encode(y)
123
+
124
+ self.initial_log_odds = 0.0
125
+ pred = torch.full(y.shape, self.initial_log_odds)
126
+ trees = []
127
+
128
+ for class_index in range(self.n_classes):
129
+ class_trees = []
130
+ for _ in range(self.n_trees):
131
+ prob = self.activation.forward(pred)
132
+ residual = -self.activation.backward(self.loss.gradient(prob, y))[:, class_index]
133
+
134
+ tree = RegressionTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
135
+ tree.fit(X, residual)
136
+ prediction = tree.predict(X)
137
+ pred[:, class_index] += self.learning_rate * prediction
138
+
139
+ class_trees.append(tree)
140
+ trees.append(class_trees)
141
+
142
+ self.trees = trees
143
+
144
+ def predict_proba(self, X):
145
+ """
146
+ Applies the fitted GradientBoostingClassifier model to the input data, predicting the probabilities of each class.
147
+
148
+ Args:
149
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
150
+ Returns:
151
+ probabilities (torch.Tensor of shape (n_samples, n_classes) or for binary classification (n_samples,)): The predicted probabilities corresponding to each sample.
152
+ Raises:
153
+ NotFittedError: If the GradientBoostingClassifier model has not been fitted before predicting.
154
+ TypeError: If the input matrix is not a PyTorch tensor.
155
+ ValueError: If the input matrix is not the correct shape.
156
+ """
157
+ if not hasattr(self, "initial_log_odds"):
158
+ raise NotFittedError("GradientBoostingClassifier.fit() must be called before predicting.")
159
+ if not isinstance(X, torch.Tensor):
160
+ raise TypeError("The input matrix must be a PyTorch tensor.")
161
+ if X.ndim != 2 or X.shape[1] != self.n_features:
162
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
163
+
164
+ if self.n_classes > 2:
165
+ return self._multi_predict_proba(X)
166
+
167
+ pred = torch.full((X.shape[0],), self.initial_log_odds)
168
+
169
+ for tree in self.trees:
170
+ prediction = tree.predict(X)
171
+ pred += self.learning_rate * prediction
172
+
173
+ return self.activation.forward(pred)
174
+
175
+ def predict(self, X):
176
+ """
177
+ Applies the fitted GradientBoostingClassifier model to the input data, predicting the correct classes.
178
+
179
+ Args:
180
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
181
+ Returns:
182
+ labels (torch.Tensor of shape (n_samples,)): The predicted labels corresponding to each sample.
183
+ Raises:
184
+ NotFittedError: If the GradientBoostingClassifier model has not been fitted before predicting.
185
+ TypeError: If the input matrix is not a PyTorch tensor.
186
+ ValueError: If the input matrix is not the correct shape.
187
+ """
188
+ if not hasattr(self, "initial_log_odds"):
189
+ raise NotFittedError("GradientBoostingClassifier.fit() must be called before predicting.")
190
+ if not isinstance(X, torch.Tensor):
191
+ raise TypeError("The input matrix must be a PyTorch tensor.")
192
+ if X.ndim != 2 or X.shape[1] != self.n_features:
193
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
194
+
195
+ prob = self.predict_proba(X)
196
+ return prob_to_pred(prob)
197
+
198
+ def _multi_predict_proba(self, X):
199
+ pred = torch.full((X.shape[0], self.n_classes), self.initial_log_odds)
200
+
201
+ for i in range(self.n_classes):
202
+ class_trees = self.trees[i]
203
+ for tree in class_trees:
204
+ pred[:, i] += self.learning_rate * tree.predict(X)
205
+
206
+ return self.activation.forward(pred)
207
+
@@ -0,0 +1,122 @@
1
+ import torch
2
+
3
+ from ._RegressionTree import RegressionTree
4
+ from ....DeepLearning.Losses import MSE, MAE, Huber
5
+ from ....Exceptions import NotFittedError
6
+ from ....Data.Metrics import calculate_metrics
7
+
8
+
9
+ class GradientBoostingRegressor:
10
+ """
11
+ GradientBoostingRegressor implements a regression algorithm fitting many consecutive :class:`RegressionTrees <DLL.MachineLearning.SupervisedLearning.Trees.RegressionTree>` to residuals of the model.
12
+
13
+ Args:
14
+ n_trees (int, optional): The number of trees used for predicting. Defaults to 50. Must be a positive integer.
15
+ learning_rate (float, optional): The number multiplied to each additional trees residuals. Must be a real number in range (0, 1). Defaults to 0.5.
16
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 3. Must be a positive integer.
17
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
18
+ loss (string, optional): The loss function used in calculations of the gradients. Must be one of "squared", "absolute" or "huber". Defaults to "squared".
19
+ huber_delta (float | int, optional): The delta parameter for the possibly used huber loss. If loss is not "huber", this parameter is ignored.
20
+ Attributes:
21
+ n_features (int): The number of features. Available after fitting.
22
+ """
23
+ def __init__(self, n_trees=50, learning_rate=0.5, max_depth=3, min_samples_split=2, loss="squared", huber_delta=1):
24
+ if not isinstance(n_trees, int) or n_trees < 1:
25
+ raise ValueError("n_trees must be a positive integer.")
26
+ if not isinstance(learning_rate, float) or learning_rate <= 0 or learning_rate >= 1:
27
+ raise ValueError("learning_rate must be a float in range (0, 1).")
28
+ if not isinstance(max_depth, int) or max_depth < 1:
29
+ raise ValueError("max_depth must be a positive integer.")
30
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
31
+ raise ValueError("min_samples_split must be a positive integer.")
32
+ if loss not in ["squared", "absolute", "huber"]:
33
+ raise ValueError('loss must be one in ["squared", "absolute", "huber"].')
34
+ if not isinstance(huber_delta, int | float) or huber_delta <= 0:
35
+ raise ValueError("huber_delta must be a positive real number.")
36
+
37
+ self.n_trees = n_trees
38
+ self.learning_rate = learning_rate
39
+ self.max_depth = max_depth
40
+ self.min_samples_split = min_samples_split
41
+ self.trees = None
42
+ self.loss = self._choose_loss(loss, huber_delta)
43
+
44
+ def _choose_loss(self, loss, huber_delta):
45
+ if loss == "squared":
46
+ return MSE(reduction="sum")
47
+ elif loss == "absolute":
48
+ return MAE(reduction="sum")
49
+ elif loss == "huber":
50
+ return Huber(reduction="sum", delta=huber_delta)
51
+
52
+ def fit(self, X, y, metrics=["loss"]):
53
+ """
54
+ Fits the GradientBoostingRegressor model to the input data by fitting trees to the errors made by previous trees.
55
+
56
+ Args:
57
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
58
+ y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
59
+ metrics (dict[str, torch.Tensor]): Contains the metrics that will be calculated between fitting each tree and returned.
60
+ Returns:
61
+ metrics (dict[str, torch.Tensor]): The calculated metrics.
62
+ Raises:
63
+ TypeError: If the input matrix or the target vector is not a PyTorch tensor.
64
+ ValueError: If the input matrix or the target vector is not the correct shape.
65
+ """
66
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
67
+ raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
68
+ if X.ndim != 2:
69
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
70
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
71
+ raise ValueError("The target must be 1 dimensional with the same number of samples as the input data")
72
+ if not isinstance(metrics, list | tuple):
73
+ raise ValueError("metrics must be a list or tuple containing the shorthand names of each wanted metric.")
74
+
75
+ self.n_features = X.shape[1]
76
+ self.initial_pred = y.mean()
77
+ pred = torch.full(y.shape, self.initial_pred)
78
+ trees = []
79
+ history = {metric: torch.zeros(self.n_trees) for metric in metrics}
80
+
81
+ for i in range(self.n_trees):
82
+ residual = -self.loss.gradient(pred, y)
83
+
84
+ tree = RegressionTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
85
+ tree.fit(X, residual)
86
+ prediction = tree.predict(X)
87
+
88
+ pred += self.learning_rate * prediction
89
+ trees.append(tree)
90
+
91
+ values = calculate_metrics(data=(pred, y), metrics=metrics, loss=self.loss.loss)
92
+ for metric, value in values.items():
93
+ history[metric][i] = value
94
+
95
+ self.trees = trees
96
+ return history
97
+
98
+ def predict(self, X):
99
+ """
100
+ Applies the fitted GradientBoostingRegressor model to the input data, predicting the target values.
101
+
102
+ Args:
103
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
104
+ Returns:
105
+ targets (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
106
+ Raises:
107
+ NotFittedError: If the GradientBoostingRegressor model has not been fitted before predicting.
108
+ TypeError: If the input matrix is not a PyTorch tensor.
109
+ ValueError: If the input matrix is not the correct shape.
110
+ """
111
+ if not hasattr(self, "initial_pred"):
112
+ raise NotFittedError("GradientBoostingRegressor.fit() must be called before predicting.")
113
+ if not isinstance(X, torch.Tensor):
114
+ raise TypeError("The input matrix must be a PyTorch tensor.")
115
+ if X.ndim != 2 or X.shape[1] != self.n_features:
116
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
117
+
118
+ pred = torch.full((X.shape[0],), self.initial_pred)
119
+
120
+ for tree in self.trees:
121
+ pred += self.learning_rate * tree.predict(X)
122
+ return pred
@@ -0,0 +1,288 @@
1
+ import torch
2
+
3
+
4
+ from ._LGBMTree import _LGBMTree, _ExclusiveFeatureBundling
5
+ from ....DeepLearning.Layers.Activations import Sigmoid, SoftMax
6
+ from ....DeepLearning.Losses import BCE, CCE, Exponential
7
+ from ....Data.Preprocessing import OneHotEncoder
8
+ from ....Exceptions import NotFittedError
9
+ from ....Data.Metrics import calculate_metrics, prob_to_pred
10
+
11
+
12
+ class LGBMClassifier:
13
+ """
14
+ LGBMClassifier implements a classification algorithm fitting many consecutive trees to gradients and hessians of the predictions.
15
+
16
+ Args:
17
+ n_trees (int, optional): The number of trees used for predicting. Defaults to 10. Must be a positive integer.
18
+ learning_rate (float, optional): The number multiplied to each additional trees residuals. Must be a real number in range (0, 1). Defaults to 0.5.
19
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 25. Must be a positive integer.
20
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
21
+ n_bins (int, optional): The number of bins used to find the optimal split of data. Must be greater than 1. Defaults to 30.
22
+ reg_lambda (float | int, optional): The regularisation parameter used in fitting the trees. The larger the parameter, the smaller the trees. Must be a positive real number. Defaults to 1.
23
+ gamma (float | int, optional): The minimum gain to make a split. Must be a non-negative real number. Defaults to 0.
24
+ large_error_proportion (float, optional): The proportion of the whole data with the largest error, which is always used to train the next weak learner. Defaults to 0.3.
25
+ small_error_proportion (float, optional): The proportion of data randomly selected from the remaining (1 - large_error_proportion) percent of data to train the next weak learner. Defaults to 0.2.
26
+ loss (string, optional): The loss function used in calculations of the residuals. Must be one of "log_loss" or "exponential". Defaults to "log_loss". "exponential" can only be used for binary classification.
27
+ max_conflict_rate (float, optional): The proportion of samples, which are allowed to be nonzero without featuers being bundled. Is ignored if use_efb=False. Defaults to 0.0.
28
+ use_efb (bool, optional): Determines if the exclusive feature bundling algorithm is used. Defaults to True.
29
+ Attributes:
30
+ n_features (int): The number of features. Available after fitting.
31
+ n_classes (int): The number of classes. 2 for binary classification. Available after fitting.
32
+ """
33
+ def __init__(self, n_trees=10, learning_rate=0.5, max_depth=25, min_samples_split=2, n_bins=30, reg_lambda=1, gamma=0, large_error_proportion=0.3, small_error_proportion=0.2, loss="log_loss", max_conflict_rate=0.0, use_efb=True):
34
+ if not isinstance(n_trees, int) or n_trees < 1:
35
+ raise ValueError("n_trees must be a positive integer.")
36
+ if not isinstance(learning_rate, float) or learning_rate <= 0 or learning_rate >= 1:
37
+ raise ValueError("learning_rate must be a float in range (0, 1).")
38
+ if not isinstance(max_depth, int) or max_depth < 1:
39
+ raise ValueError("max_depth must be a positive integer.")
40
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
41
+ raise ValueError("min_samples_split must be a positive integer.")
42
+ if not isinstance(n_bins, int) or n_bins <= 1:
43
+ raise ValueError("n_bins must be an integer greater than 1.")
44
+ if not isinstance(reg_lambda, int | float) or reg_lambda <= 0:
45
+ raise ValueError("reg_lambda must be a positive real number.")
46
+ if not isinstance(gamma, int | float) or gamma < 0:
47
+ raise ValueError("gamma must be a non-negative real number.")
48
+ if not isinstance(large_error_proportion, float) or large_error_proportion <= 0 or large_error_proportion >= 1:
49
+ raise ValueError("large_error_proportion must be a float in range (0, 1).")
50
+ if not isinstance(small_error_proportion, float) or small_error_proportion <= 0 or small_error_proportion >= 1:
51
+ raise ValueError("small_error_proportion must be a float in range (0, 1).")
52
+ if loss not in ["log_loss", "exponential"]:
53
+ raise ValueError('loss must be one of ["log_loss", "exponential"]')
54
+ if not isinstance(max_conflict_rate, float) or max_conflict_rate < 0 or max_conflict_rate >= 1:
55
+ raise ValueError("max_conflict_rate must be a float in range [0, 1).")
56
+ if not isinstance(use_efb, bool):
57
+ raise TypeError("use_efb must be a boolean.")
58
+
59
+ self.n_trees = n_trees
60
+ self.learning_rate = learning_rate
61
+ self.max_depth = max_depth
62
+ self.min_samples_split = min_samples_split
63
+ self.n_bins = n_bins
64
+ self.reg_lambda = reg_lambda
65
+ self.gamma = gamma
66
+ self.trees = None
67
+ self.large_error_proportion = large_error_proportion
68
+ self.small_error_proportion = small_error_proportion
69
+ self.loss_ = loss
70
+ self.use_efb = use_efb
71
+ if self.use_efb: self.efb = _ExclusiveFeatureBundling(max_conflict_rate=max_conflict_rate, n_bins=n_bins)
72
+
73
+ def _get_activation_and_loss(self, classes):
74
+ self.n_classes = len(classes)
75
+ if self.loss_ == "log_loss":
76
+ if self.n_classes == 2:
77
+ self.loss = BCE(reduction="sum")
78
+ self.activation = Sigmoid()
79
+ else:
80
+ self.loss = CCE(reduction="sum")
81
+ self.activation = SoftMax()
82
+ elif self.loss_ == "exponential":
83
+ if self.n_classes != 2:
84
+ raise ValueError("The exponential loss is only applicable in binary classification. Use log_loss for multiclass classification instead.")
85
+ self.loss = Exponential(reduction="sum")
86
+ self.activation = Sigmoid()
87
+
88
+ def fit(self, X, y, metrics=["loss"]):
89
+ """
90
+ Fits the LGBMClassifier model to the input data by fitting trees to the errors made by previous trees.
91
+
92
+ Args:
93
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
94
+ y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
95
+ metrics (dict[str, torch.Tensor]): Contains the metrics that will be calculated between fitting each tree and returned. Only available for binary classification.
96
+ Returns:
97
+ metrics if binary classification else None
98
+ Raises:
99
+ TypeError: If the input matrix or the label vector is not a PyTorch tensor or if the problem is binary and metrics is not a list or a tuple.
100
+ ValueError: If the input matrix or the label vector is not the correct shape or the label vector contains wrong values.
101
+ """
102
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
103
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
104
+ if X.ndim != 2:
105
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
106
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
107
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
108
+ vals = torch.unique(y).numpy()
109
+ if set(vals) != {*range(len(vals))}:
110
+ raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
111
+
112
+ self._get_activation_and_loss(vals)
113
+ y = y.to(X.dtype)
114
+ self.n_features = X.shape[1]
115
+
116
+ if self.use_efb: X = self.efb.fit_transform(X)
117
+
118
+ if self.n_classes == 2:
119
+ if not isinstance(metrics, list | tuple):
120
+ raise ValueError("metrics must be a list or tuple containing the shorthand names of each wanted metric.")
121
+
122
+ return self._binary_fit(X, y, metrics=metrics)
123
+ else:
124
+ self._multi_fit(X, y)
125
+
126
+ def _binary_hessian_diag(self, prob, true_output):
127
+ loss_gradient = self.loss.gradient(prob, true_output)
128
+ loss_hessian = self.loss.hessian(prob, true_output)
129
+ sigmoid_value = prob
130
+ sigmoid_gradient = self.activation.backward(loss_gradient)
131
+
132
+ first_term = sigmoid_gradient * (1 - sigmoid_value) * loss_gradient
133
+ second_term = -sigmoid_value * sigmoid_gradient * loss_gradient
134
+ third_term = sigmoid_value * (1 - sigmoid_value) * loss_hessian # is the same as self.activation.backward(loss_hessian)
135
+ return first_term + second_term + third_term
136
+
137
+ def _multi_hessian_diag(self, prob, true_output):
138
+ loss_gradient = self.loss.gradient(prob, true_output)
139
+ loss_hessian = self.loss.hessian(prob, true_output)
140
+ softmax_value = prob
141
+ softmax_gradient = self.activation.backward(loss_gradient)
142
+
143
+ softmax_hessian = softmax_value * (1 - softmax_value) * (1 - 2 * softmax_value)
144
+
145
+ # From Mathematica: D[f[g[x]], {x, 2}] = Derivative[1][g][x]^2 (f^\[Prime]\[Prime])[g[x]] + Derivative[1][f][g[x]] (g^\[Prime]\[Prime])[x]
146
+ return softmax_gradient ** 2 * loss_hessian + loss_gradient * softmax_hessian
147
+
148
+ def _binary_fit(self, X, y, metrics=["loss"]):
149
+ positive_ratio = y.mean()
150
+ self.initial_log_odds = torch.log(positive_ratio / (1 - positive_ratio))
151
+ pred = torch.full(y.shape, self.initial_log_odds)
152
+ trees = []
153
+ history = {metric: torch.zeros(self.n_trees) for metric in metrics}
154
+
155
+ top_n = int(len(y) * self.large_error_proportion)
156
+ rand_n = int(len(y) * self.small_error_proportion)
157
+ fact = (1 - self.large_error_proportion) / self.small_error_proportion
158
+
159
+ for i in range(self.n_trees):
160
+ prob = self.activation.forward(pred)
161
+ gradient = self.activation.backward(self.loss.gradient(prob, y))
162
+ hessian = self._binary_hessian_diag(prob, y)
163
+
164
+ # GOSS (gradient-based one-sided sampling)
165
+ indicies = torch.argsort(gradient, descending=True)
166
+ large_error_indicies = indicies[:top_n]
167
+ small_error_indicies = indicies[torch.randperm(len(indicies) - len(large_error_indicies))[:rand_n] + top_n]
168
+ train_indicies = torch.cat([large_error_indicies, small_error_indicies])
169
+ hessian[small_error_indicies] *= fact
170
+
171
+ tree = _LGBMTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split, n_bins=self.n_bins, reg_lambda=self.reg_lambda, gamma=self.gamma)
172
+ tree.fit(X[train_indicies], gradient[train_indicies], hessian[train_indicies])
173
+ prediction = tree.predict(X)
174
+ pred += self.learning_rate * prediction
175
+
176
+ trees.append(tree)
177
+
178
+ values = calculate_metrics(data=(self.activation.forward(pred), y), metrics=metrics, loss=self.loss.loss)
179
+ for metric, value in values.items():
180
+ history[metric][i] = value
181
+
182
+ self.trees = trees
183
+ return history
184
+
185
+ def _multi_fit(self, X, y):
186
+ encoder = OneHotEncoder()
187
+ y = encoder.fit_encode(y)
188
+
189
+ self.initial_log_odds = 0.0
190
+ pred = torch.full(y.shape, self.initial_log_odds)
191
+ trees = []
192
+
193
+ top_n = int(len(y) * self.large_error_proportion)
194
+ rand_n = int(len(y) * self.small_error_proportion)
195
+ fact = (1 - self.large_error_proportion) / self.small_error_proportion
196
+
197
+ for class_index in range(self.n_classes):
198
+ class_trees = []
199
+ for _ in range(self.n_trees):
200
+ prob = self.activation.forward(pred)
201
+ gradient = self.activation.backward(self.loss.gradient(prob, y))[:, class_index]
202
+ hessian = self._multi_hessian_diag(prob, y)[:, class_index]
203
+
204
+ # GOSS (gradient-based one-sided sampling)
205
+ indicies = torch.argsort(gradient, descending=True)
206
+ large_error_indicies = indicies[:top_n]
207
+ small_error_indicies = indicies[torch.randperm(len(indicies) - len(large_error_indicies))[:rand_n] + top_n]
208
+ train_indicies = torch.cat([large_error_indicies, small_error_indicies])
209
+ hessian[small_error_indicies] *= fact
210
+
211
+ tree = _LGBMTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split, n_bins=self.n_bins, reg_lambda=self.reg_lambda, gamma=self.gamma)
212
+ tree.fit(X[train_indicies], gradient[train_indicies], hessian[train_indicies])
213
+ prediction = tree.predict(X)
214
+ pred[:, class_index] += self.learning_rate * prediction
215
+
216
+ class_trees.append(tree)
217
+ trees.append(class_trees)
218
+
219
+ self.trees = trees
220
+
221
+ def predict_proba(self, X):
222
+ """
223
+ Applies the fitted LGBMClassifier model to the input data, predicting the probabilities of each class.
224
+
225
+ Args:
226
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
227
+ Returns:
228
+ probabilities (torch.Tensor of shape (n_samples, n_classes) or for binary classification (n_samples,)): The predicted probabilities corresponding to each sample.
229
+ Raises:
230
+ NotFittedError: If the LGBMClassifier model has not been fitted before predicting.
231
+ TypeError: If the input matrix is not a PyTorch tensor.
232
+ ValueError: If the input matrix is not the correct shape.
233
+ """
234
+ if not hasattr(self, "initial_log_odds"):
235
+ raise NotFittedError("LGBMClassifier.fit() must be called before predicting.")
236
+ if not isinstance(X, torch.Tensor):
237
+ raise TypeError("The input matrix must be a PyTorch tensor.")
238
+ if X.ndim != 2 or X.shape[1] != self.n_features:
239
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
240
+
241
+ if self.use_efb: X = self.efb.fit_transform(X)
242
+
243
+ if self.n_classes > 2:
244
+ return self._multi_predict_proba(X)
245
+
246
+ pred = torch.full((X.shape[0],), self.initial_log_odds)
247
+
248
+ for tree in self.trees:
249
+ prediction = tree.predict(X)
250
+ pred += self.learning_rate * prediction
251
+
252
+ return self.activation.forward(pred)
253
+
254
+ def predict(self, X):
255
+ """
256
+ Applies the fitted LGBMClassifier model to the input data, predicting the correct classes.
257
+
258
+ Args:
259
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
260
+ Returns:
261
+ labels (torch.Tensor of shape (n_samples,)): The predicted labels corresponding to each sample.
262
+ Raises:
263
+ NotFittedError: If the LGBMClassifier model has not been fitted before predicting.
264
+ TypeError: If the input matrix is not a PyTorch tensor.
265
+ ValueError: If the input matrix is not the correct shape.
266
+ """
267
+ if not hasattr(self, "initial_log_odds"):
268
+ raise NotFittedError("LGBMClassifier.fit() must be called before predicting.")
269
+ if not isinstance(X, torch.Tensor):
270
+ raise TypeError("The input matrix must be a PyTorch tensor.")
271
+ if X.ndim != 2 or X.shape[1] != self.n_features:
272
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
273
+
274
+ if self.use_efb: X = self.efb.fit_transform(X)
275
+
276
+ prob = self.predict_proba(X)
277
+ return prob_to_pred(prob)
278
+
279
+ def _multi_predict_proba(self, X):
280
+ pred = torch.full((X.shape[0], self.n_classes), self.initial_log_odds)
281
+
282
+ for i in range(self.n_classes):
283
+ class_trees = self.trees[i]
284
+ for tree in class_trees:
285
+ pred[:, i] += self.learning_rate * tree.predict(X)
286
+
287
+ return self.activation.forward(pred)
288
+