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,100 @@
1
+ import torch
2
+ import cvxopt
3
+ import numpy as np
4
+
5
+ from ..Kernels import RBF, _Base
6
+ from ....Exceptions import NotFittedError
7
+
8
+
9
+ class SVR:
10
+ """
11
+ The support vector machine regressor with a quadratic programming solver.
12
+
13
+ Args:
14
+ kernel (:ref:`kernel_section_label`, optional): The non-linearity function for fitting the model. Defaults to RBF.
15
+ C (float or int, optional): A regularization parameter. Defaults to 1. Must be positive real number.
16
+ epsilon (float or int, optional): The width of the tube of no penalty in epsilon-SVR. Must be a positive real number.
17
+ Attributes:
18
+ n_features (int): The number of features. Available after fitting.
19
+ alpha (torch.Tensor of shape (n_samples,)): The optimized dual coefficients. Available after fitting.
20
+ alpha_star (torch.Tensor of shape (n_samples,)): The optimized dual coefficients. Available after fitting.
21
+ """
22
+ def __init__(self, kernel=RBF(), C=1, epsilon=0.1):
23
+ if not isinstance(kernel, _Base):
24
+ raise ValueError("kernel must be from DLL.MachineLearning.SupervisedLearning.Kernels")
25
+ if not isinstance(C, float | int) or C <= 0:
26
+ raise ValueError("C must be must be positive real number.")
27
+ if not isinstance(epsilon, float | int) or epsilon <= 0:
28
+ raise ValueError("epsilon must be must be positive real number.")
29
+
30
+ self.kernel = kernel
31
+ self.C = C
32
+ self.epsilon = epsilon
33
+
34
+ def _kernel_matrix(self, X1, X2):
35
+ return self.kernel(X1, X2).to(X1.dtype)
36
+
37
+ def fit(self, X, y):
38
+ """
39
+ Fits the SVR model to the input data by finding the hypertube that contains the data with minimum loss.
40
+
41
+ Args:
42
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
43
+ y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
44
+ Returns:
45
+ None
46
+ Raises:
47
+ TypeError: If the input matrix or the target matrix is not a PyTorch tensor.
48
+ ValueError: If the input matrix or the target matrix is not the correct shape.
49
+ """
50
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
51
+ raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
52
+ if X.ndim != 2:
53
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
54
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
55
+ raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
56
+
57
+ self.y = y.reshape((-1, 1)).to(X.dtype)
58
+ self.X = X
59
+ self.n_features = X.shape[1]
60
+ n = X.shape[0]
61
+ K = self._kernel_matrix(X, X).numpy()
62
+
63
+ P = cvxopt.matrix(np.block([[K, -K], [-K, K]]).tolist()) # [[K, -K], [-K, K]]
64
+ q = cvxopt.matrix(np.hstack([self.epsilon - y, self.epsilon + y]).tolist())
65
+ A = cvxopt.matrix(np.vstack([np.ones((n, 1)), -np.ones((n, 1))]).tolist())
66
+ b = cvxopt.matrix(0.0)
67
+ G = cvxopt.matrix(np.hstack([-np.eye(2 * n), np.eye(2 * n)]).tolist())
68
+ h = cvxopt.matrix(np.hstack([np.zeros((2 * n,)), np.ones((2 * n,)) * self.C]).tolist())
69
+
70
+ cvxopt.solvers.options['show_progress'] = False
71
+ sol = cvxopt.solvers.qp(P, q, G, h, A, b)
72
+ alpha = torch.tensor(np.array(sol["x"]), dtype=torch.float64).squeeze(dim=1)
73
+ self.alpha = alpha[:n]
74
+ self.alpha_star = alpha[n:]
75
+
76
+ def predict(self, X):
77
+ """
78
+ Applies the fitted SVR 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
+ target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
84
+ Raises:
85
+ NotFittedError: If the SVR 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, "n_features"):
90
+ raise NotFittedError("SVR.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
+ # Use every datapoint as a support vector, since otherwise the results seem to be bad.
97
+ # This makes no sense to me, but yields the best results.
98
+ bias = (self.y - torch.sum((self.alpha - self.alpha_star) * self._kernel_matrix(self.X, self.X), dim=1)).mean()
99
+ prediction = (self.alpha - self.alpha_star) @ self._kernel_matrix(self.X, X).to(dtype=self.alpha.dtype) + bias
100
+ return prediction
@@ -0,0 +1,2 @@
1
+ from ._SVC import SVC
2
+ from ._SVR import SVR
@@ -0,0 +1,179 @@
1
+ import torch
2
+ from math import log
3
+ from warnings import warn
4
+
5
+ from ._DecisionTree import DecisionTree
6
+ from ....Exceptions import NotFittedError
7
+
8
+
9
+ class AdaBoostClassifier:
10
+ """
11
+ AdaBoostClassifier implements a classification algorithm fitting many consecutive :class:`DecisionTrees <DLL.MachineLearning.SupervisedLearning.Trees.DecisionTree>` to previously missclassified samples.
12
+
13
+ Args:
14
+ n_trees (int, optional): The number of trees used for predicting. Defaults to 10. Must be a positive integer.
15
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 25. Must be a positive integer.
16
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
17
+ criterion (str, optional): The information criterion used to select optimal splits. Must be one of "entropy" or "gini". Defaults to "gini".
18
+ Attributes:
19
+ n_features (int): The number of features. Available after fitting.
20
+ n_classes (int): The number of classes. 2 for binary classification. Available after fitting.
21
+ confidences (torch.tensor of shape (n_trees,)): The confidence on each tree.
22
+ """
23
+ def __init__(self, n_trees=10, max_depth=25, min_samples_split=2, criterion="gini"):
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(max_depth, int) or max_depth < 1:
27
+ raise ValueError("max_depth must be a positive integer.")
28
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
29
+ raise ValueError("min_samples_split must be a positive integer.")
30
+ if criterion not in ["entropy", "gini"]:
31
+ raise ValueError('The chosen criterion must be one of "entropy" or "gini".')
32
+
33
+ self.n_trees = n_trees
34
+ self.max_depth = max_depth
35
+ self.min_samples_split = min_samples_split
36
+ self.criterion = criterion
37
+ self.trees = None
38
+
39
+ def fit(self, X, y, verbose=True):
40
+ """
41
+ Fits the AdaBoostClassifier model to the input data by fitting trees to the errors made by previous trees.
42
+
43
+ Args:
44
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
45
+ y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
46
+ verbose (bool, optional): Determines if warnings are given if the training ends due to a weak learner being worse than random guessing. Defaults to True.
47
+ Returns:
48
+ The average errors after each tree.
49
+ Raises:
50
+ 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.
51
+ ValueError: If the input matrix or the label vector is not the correct shape or the label vector contains wrong values.
52
+ """
53
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
54
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
55
+ if X.ndim != 2:
56
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
57
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
58
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
59
+ vals = torch.unique(y).numpy()
60
+ if set(vals) != {*range(len(vals))}:
61
+ raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
62
+ if not isinstance(verbose, bool):
63
+ raise TypeError("verbose must be a boolean.")
64
+
65
+ self.n_classes = len(vals)
66
+ self.classes = vals
67
+ y = y.to(X.dtype)
68
+ self.n_features = X.shape[1]
69
+
70
+ trees = []
71
+ weights = torch.full_like(y, 1 / len(y))
72
+ self.confidences = []
73
+
74
+ errors = []
75
+
76
+ for i in range(self.n_trees):
77
+ indices = torch.multinomial(weights, len(y), replacement=True)
78
+ X_sample = X[indices]
79
+ y_sample = y[indices]
80
+
81
+ # Make sure atleast 1 datapoint is present from each class
82
+ sample_classes = torch.unique(y_sample)
83
+ if len(sample_classes) < self.n_classes:
84
+ unseen_classes = list(set(self.classes) - set(sample_classes))
85
+ X_new = []
86
+ y_new = []
87
+ for class_ in unseen_classes:
88
+ index = torch.multinomial((y == class_).float(), 1)
89
+ X_new.append(X[index].squeeze(0))
90
+ y_new.append(y[index].squeeze(0))
91
+ X_sample = torch.cat([X_sample, torch.stack(X_new)])
92
+ y_sample = torch.cat([y_sample, torch.stack(y_new)])
93
+
94
+ tree = DecisionTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split, criterion=self.criterion)
95
+ tree.fit(X_sample, y_sample)
96
+ prediction = tree.predict(X)
97
+ incorrect = prediction != y
98
+ eps = torch.sum(incorrect * weights)
99
+ errors.append(eps)
100
+
101
+ # If better than random quessing, continue
102
+ if eps < 1 - 1 / self.n_classes:
103
+ alpha = 0.5 * (torch.log((1 - eps) / (eps + 1e-8)) + log(self.n_classes - 1))
104
+ weights = weights * torch.exp(alpha * incorrect)
105
+ weights /= weights.sum() # keep weights as a probability distribution
106
+ self.confidences.append(alpha)
107
+ else:
108
+ if verbose: warn(f"The latest weak learner is worse than random guessing. The training is stopped to reduce over fitting. Only {i} learners are used.")
109
+ self.n_trees = i
110
+ break
111
+
112
+ trees.append(tree)
113
+
114
+ self.trees = trees
115
+ return errors
116
+
117
+ def predict(self, X):
118
+ """
119
+ Applies the fitted AdaBoostClassifier model to the input data, predicting the correct classes.
120
+
121
+ Args:
122
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
123
+
124
+ Returns:
125
+ labels (torch.Tensor of shape (n_samples,)): The predicted labels corresponding to each sample.
126
+
127
+ Raises:
128
+ NotFittedError: If the AdaBoostClassifier model has not been fitted before predicting.
129
+ TypeError: If the input matrix is not a PyTorch tensor.
130
+ ValueError: If the input matrix is not the correct shape.
131
+ """
132
+ if self.trees is None:
133
+ raise NotFittedError("AdaBoostClassifier.fit() must be called before predicting.")
134
+ if not isinstance(X, torch.Tensor):
135
+ raise TypeError("The input matrix must be a PyTorch tensor.")
136
+ if X.ndim != 2 or X.shape[1] != self.n_features:
137
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
138
+
139
+ preds = torch.zeros((len(X), self.n_classes))
140
+ for alpha, tree in zip(self.confidences, self.trees):
141
+ pred = tree.predict(X).int()
142
+ one_hot_pred = torch.eye(self.n_classes)[pred]
143
+ preds += alpha * one_hot_pred
144
+
145
+ return preds.argmax(dim=1)
146
+
147
+ def predict_proba(self, X):
148
+ """
149
+ Applies the fitted AdaBoostClassifier model to the input data, predicting the probabilities of each class.
150
+
151
+ Args:
152
+ X (torch.Tensor of shape (n_samples, n_features)): The input data for which to predict probabilities.
153
+
154
+ Returns:
155
+ torch.Tensor of shape (n_samples, n_classes): The predicted probabilities for each class.
156
+
157
+ Raises:
158
+ NotFittedError: If the AdaBoostClassifier model has not been fitted before predicting.
159
+ TypeError: If the input matrix is not a PyTorch tensor.
160
+ ValueError: If the input matrix is not the correct shape.
161
+ """
162
+ if self.trees is None:
163
+ raise NotFittedError("AdaBoostClassifier.fit() must be called before predicting.")
164
+ if not isinstance(X, torch.Tensor):
165
+ raise TypeError("The input matrix must be a PyTorch tensor.")
166
+ if X.ndim != 2 or X.shape[1] != self.n_features:
167
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
168
+
169
+ preds = torch.zeros((len(X), self.n_classes))
170
+ for alpha, tree in zip(self.confidences, self.trees):
171
+ pred = tree.predict(X).int()
172
+ one_hot_pred = torch.eye(self.n_classes)[pred]
173
+ preds += alpha * one_hot_pred
174
+
175
+ probs = torch.exp(preds - torch.max(preds, dim=1, keepdim=True).values)
176
+ probs = probs / torch.sum(probs, dim=1, keepdim=True)
177
+ if self.n_classes == 2:
178
+ return probs[:, 1]
179
+ return probs
@@ -0,0 +1,141 @@
1
+ import torch
2
+ from warnings import warn
3
+
4
+ from ._RegressionTree import RegressionTree
5
+ from ....Exceptions import NotFittedError
6
+
7
+
8
+ class AdaBoostRegressor:
9
+ """
10
+ AdaBoostRegressor implements a regression algorithm fitting many consecutive :class:`RegressionTrees <DLL.MachineLearning.SupervisedLearning.Trees.RegressionTree>` to previously incorrectly predicted samples.
11
+
12
+ Args:
13
+ n_trees (int, optional): The number of trees used for predicting. Defaults to 10. Must be a positive integer.
14
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 25. Must be a positive integer.
15
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
16
+ loss (str, optional): The loss function used. Must be in ["linear", "square", "exponential"]. Defaults to "square".
17
+ Attributes:
18
+ n_features (int): The number of features. Available after fitting.
19
+ """
20
+ def __init__(self, n_trees=10, max_depth=25, min_samples_split=2, loss="square"):
21
+ if not isinstance(n_trees, int) or n_trees < 1:
22
+ raise ValueError("n_trees must be a positive integer.")
23
+ if not isinstance(max_depth, int) or max_depth < 1:
24
+ raise ValueError("max_depth must be a positive integer.")
25
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
26
+ raise ValueError("min_samples_split must be a positive integer.")
27
+ if loss not in ["linear", "square", "exponential"]:
28
+ raise ValueError('loss must be in ["linear", "square", "exponential"].')
29
+
30
+ self.n_trees = n_trees
31
+ self.max_depth = max_depth
32
+ self.min_samples_split = min_samples_split
33
+ self.loss = loss
34
+ self.trees = None
35
+
36
+ def fit(self, X, y, verbose=True):
37
+ """
38
+ Fits the AdaBoostRegressor model to the input data by fitting trees to the errors made by previous trees.
39
+
40
+ Args:
41
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
42
+ y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
43
+ verbose (bool, optional): Determines if warnings are given if the training ends due to a weak learner having over 0.5 weighted loss. Defaults to True.
44
+ Returns:
45
+ The average errors after each tree.
46
+ Raises:
47
+ 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.
48
+ ValueError: If the input matrix or the label vector is not the correct shape or the label vector contains wrong values.
49
+ """
50
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
51
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
52
+ if X.ndim != 2:
53
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
54
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
55
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
56
+ if not isinstance(verbose, bool):
57
+ raise TypeError("verbose must be a boolean.")
58
+
59
+ y = y.to(X.dtype)
60
+ self.n_features = X.shape[1]
61
+
62
+ trees = []
63
+ weights = torch.full_like(y, 1 / len(y))
64
+ self.confidences = []
65
+ errors = []
66
+
67
+ for i in range(self.n_trees):
68
+ indices = torch.multinomial(weights, len(y), replacement=True)
69
+ X_sample = X[indices]
70
+ y_sample = y[indices]
71
+
72
+ tree = RegressionTree(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
73
+ tree.fit(X_sample, y_sample)
74
+ prediction = tree.predict(X)
75
+ max_absolute_error = torch.abs(y - prediction).max()
76
+ if self.loss == "linear":
77
+ error = torch.abs(y - prediction) / max_absolute_error
78
+ elif self.loss == "square":
79
+ error = (y - prediction) ** 2 / max_absolute_error ** 2
80
+ elif self.loss == "exponential":
81
+ error = 1 - torch.exp(-torch.abs(y - prediction) / max_absolute_error)
82
+
83
+ average_loss = torch.sum(weights * error)
84
+ if average_loss > 0.5:
85
+ if verbose: warn(f"The average error exceeds 0.5. The training is stopped to reduce over fitting. Only {i} trees are used.")
86
+ self.n_trees = i
87
+ break
88
+
89
+ beta = average_loss / (1 - average_loss)
90
+ weights = weights * beta ** (1 - error)
91
+ weights /= weights.sum()
92
+
93
+ self.confidences.append(torch.log(1 / beta))
94
+ trees.append(tree)
95
+ errors.append(average_loss)
96
+
97
+ self.confidences = torch.stack(self.confidences)
98
+ self.confidences /= self.confidences.sum()
99
+ self.trees = trees
100
+ return errors
101
+
102
+ def predict(self, X, method="average"):
103
+ """
104
+ Applies the fitted AdaBoostRegressor model to the input data, predicting the correct values.
105
+
106
+ Args:
107
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
108
+ method (str, optional): The method for computing the prediction. Must be one of "average" or "weighted_median". Defaults to average.
109
+
110
+ Returns:
111
+ values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
112
+
113
+ Raises:
114
+ NotFittedError: If the AdaBoostRegressor model has not been fitted before predicting.
115
+ TypeError: If the input matrix is not a PyTorch tensor.
116
+ ValueError: If the input matrix is not the correct shape.
117
+ """
118
+ if self.trees is None:
119
+ raise NotFittedError("AdaBoostRegressor.fit() must be called before predicting.")
120
+ if not isinstance(X, torch.Tensor):
121
+ raise TypeError("The input matrix must be a PyTorch tensor.")
122
+ if X.ndim != 2 or X.shape[1] != self.n_features:
123
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
124
+ if method not in ["average", "weighted_median"]:
125
+ raise ValueError('method must be one of "average" or "weighted_median".')
126
+
127
+ if method == "average":
128
+ preds = torch.zeros(len(X))
129
+ for log_beta, tree in zip(self.confidences, self.trees):
130
+ pred = tree.predict(X)
131
+ preds += log_beta * pred
132
+ elif method == "weighted_median":
133
+ tree_preds = torch.stack([tree.predict(X) for tree in self.trees]).T # n_samples, n_trees
134
+ indicies = torch.argsort(tree_preds, dim=1)
135
+ accumulated = torch.cumsum(self.confidences[indicies], dim=1)
136
+ after_middle = accumulated >= 0.5 * accumulated[:, -1]
137
+ middle_indicies = torch.argmax(after_middle, dim=1)
138
+ original_middle_indicies = indicies[:, middle_indicies]
139
+ preds = tree_preds[:, original_middle_indicies]
140
+
141
+ return preds
@@ -0,0 +1,219 @@
1
+ import torch
2
+
3
+ from ....Exceptions import NotFittedError
4
+
5
+
6
+ class Node:
7
+ def __init__(self, left=None, right=None, threshold=None, feature_index=None, value=None, probabilities=None, size=None, depth=None):
8
+ self.left = left
9
+ self.right = right
10
+ self.threshold = threshold
11
+ self.feature_index = feature_index
12
+ self.value = value
13
+ self.probabilities = probabilities
14
+ self.size = size
15
+ self.depth = depth
16
+
17
+ def is_leaf(self):
18
+ return self.value is not None
19
+
20
+
21
+ class DecisionTree:
22
+ """
23
+ DecisionTree implements a classification algorithm splitting the data along features yielding the maximum entropy.
24
+
25
+ Args:
26
+ max_depth (int, optional): The maximum depth of the tree. Defaults to 10. Must be a positive integer.
27
+ min_samples_split (int, optional): The minimum required samples in a leaf to make a split. Defaults to 2. Must be a positive integer.
28
+ criterion (str, optional): The information criterion used to select optimal splits. Must be one of "entropy" or "gini". Defaults to "gini".
29
+ ccp_alpha (non-negative float, optional): Determines how easily subtrees are pruned in cost-complexity pruning. The larger the value, more subtrees are pruned. Defaults to 0.0.
30
+ Attributes:
31
+ n_classes (int): The number of classes. A positive integer available after calling DecisionTree.fit().
32
+ classes (torch.Tensor of shape (n_classes,)): The classes in an arbitrary order. Available after calling DecisionTree.fit().
33
+ """
34
+ def __init__(self, max_depth=10, min_samples_split=2, criterion="gini", ccp_alpha=0.0):
35
+ if not isinstance(max_depth, int) or max_depth < 1:
36
+ raise ValueError("max_depth must be a positive integer.")
37
+ if not isinstance(min_samples_split, int) or min_samples_split < 1:
38
+ raise ValueError("min_samples_split must be a positive integer.")
39
+ if criterion not in ["entropy", "gini"]:
40
+ raise ValueError('The chosen criterion must be one of "entropy" or "gini".')
41
+ if not isinstance(ccp_alpha, int | float) or ccp_alpha < 0:
42
+ raise ValueError("ccp_alpha must be non-negative.")
43
+
44
+ self.max_depth = max_depth
45
+ self.min_samples_split = min_samples_split
46
+ self.criterion = criterion
47
+ self.root = None
48
+ self.ccp_alpha = ccp_alpha
49
+
50
+ def fit(self, X, y):
51
+ """
52
+ Fits the DecisionTree model to the input data by generating a tree, which splits the data appropriately.
53
+
54
+ Args:
55
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
56
+ y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample.
57
+ Returns:
58
+ None
59
+ Raises:
60
+ TypeError: If the input matrix or the label matrix is not a PyTorch tensor.
61
+ ValueError: If the input matrix or the label matrix is not the correct shape.
62
+ """
63
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
64
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
65
+ if X.ndim != 2:
66
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
67
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
68
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
69
+ self.n_samples, self.n_features = X.shape
70
+ self.classes = torch.unique(y)
71
+ self.n_classes = len(self.classes)
72
+ self.root = self._grow_tree(X, y, 0)
73
+ if self.ccp_alpha > 0:
74
+ self.root, _, _ = self._prune(self.root, X, y)
75
+
76
+ def _grow_tree(self, x, y, depth):
77
+ n_samples, n_features = x.size()
78
+ classes, counts = torch.unique(y, return_counts=True)
79
+
80
+ if depth >= self.max_depth or len(classes) == 1 or n_samples < self.min_samples_split:
81
+ largest_class = classes[torch.argmax(counts)]
82
+ probabilities = torch.tensor([(counts[torch.where(classes == _class)[0]] / len(y) if _class in classes else 0) for _class in self.classes])
83
+ return Node(value=largest_class, probabilities=probabilities)
84
+
85
+ feature_indicies = torch.randperm(n_features)[:self.n_features]
86
+ split_threshold, split_index = self._best_split(x, y, feature_indicies)
87
+ # if no split gains more information
88
+ if split_threshold is None:
89
+ largest_class = classes[torch.argmax(counts)]
90
+ probabilities = torch.tensor([(counts[torch.where(classes == _class)[0]] / len(y) if _class in classes else 0) for _class in self.classes])
91
+ return Node(value=largest_class, probabilities=probabilities)
92
+
93
+ left_indicies, right_indicies = self._split(x[:, split_index], split_threshold)
94
+ left = self._grow_tree(x[left_indicies], y[left_indicies], depth + 1)
95
+ right = self._grow_tree(x[right_indicies], y[right_indicies], depth + 1)
96
+ return Node(left, right, split_threshold, split_index)
97
+
98
+ def _best_split(self, x, y, feature_indicies):
99
+ max_entropy_gain = -1
100
+ split_index = None
101
+ split_threshold = None
102
+
103
+ for index in feature_indicies:
104
+ feature_values = x[:, index]
105
+ possible_thresholds = torch.unique(feature_values)
106
+ for threshold in possible_thresholds:
107
+ entropy_gain = self._information_gain(y, feature_values, threshold)
108
+ if entropy_gain > max_entropy_gain:
109
+ max_entropy_gain = entropy_gain
110
+ split_index = index
111
+ split_threshold = threshold
112
+ if max_entropy_gain <= 0: return None, None
113
+ return split_threshold, split_index
114
+
115
+ def _split(self, feature_values, threshold):
116
+ left_indicies = torch.argwhere(feature_values <= threshold).flatten()
117
+ right_indicies = torch.argwhere(feature_values > threshold).flatten()
118
+ return left_indicies, right_indicies
119
+
120
+ def _information_gain(self, y, feature_values, threshold):
121
+ if self.criterion == "gini":
122
+ information_func = self._gini
123
+ elif self.criterion == "entropy":
124
+ information_func = self._entropy
125
+
126
+ left_indicies, right_indicies = self._split(feature_values, threshold)
127
+ if len(left_indicies) == 0 or len(right_indicies) == 0:
128
+ return 0
129
+ n = len(y)
130
+ return information_func(y) - len(left_indicies) / n * information_func(y[left_indicies]) - len(right_indicies) / n * information_func(y[right_indicies])
131
+
132
+ def _entropy(self, values):
133
+ n = len(values)
134
+ data_type = values.dtype
135
+ p = torch.bincount(values.to(dtype=torch.int32)).to(dtype=data_type) / n
136
+ p = p[p != 0] # remove non-zero classes from messing up the calculations
137
+ return -torch.sum(p * torch.log(p))
138
+
139
+ def _gini(self, values):
140
+ n = len(values)
141
+ data_type = values.dtype
142
+ p = torch.bincount(values.to(dtype=torch.int32)).to(dtype=data_type) / n
143
+ return torch.sum(p * (1 - p))
144
+
145
+ def predict(self, X):
146
+ """
147
+ Applies the fitted DecisionTree model to the input data, predicting the correct classes.
148
+
149
+ Args:
150
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
151
+ Returns:
152
+ labels (torch.Tensor of shape (n_samples,)): The predicted labels corresponding to each sample.
153
+ Raises:
154
+ NotFittedError: If the DecisionTree model has not been fitted before predicting.
155
+ TypeError: If the input matrix is not a PyTorch tensor.
156
+ ValueError: If the input matrix is not the correct shape.
157
+ """
158
+ if self.root is None:
159
+ raise NotFittedError("DecisionTree.fit() must be called before predicting.")
160
+ if not isinstance(X, torch.Tensor):
161
+ raise TypeError("The input matrix must be a PyTorch tensor.")
162
+ if X.ndim != 2 or X.shape[1] != self.n_features:
163
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
164
+ return torch.tensor([self._predict_single(datapoint, self.root) for datapoint in X])
165
+
166
+ def _predict_single(self, x, current_node, proba=False):
167
+ if current_node.is_leaf():
168
+ if proba:
169
+ return current_node.probabilities
170
+ return current_node.value
171
+
172
+ if x[current_node.feature_index] <= current_node.threshold:
173
+ return self._predict_single(x, current_node.left, proba=proba)
174
+
175
+ return self._predict_single(x, current_node.right, proba=proba)
176
+
177
+ def predict_proba(self, X):
178
+ """
179
+ Applies the fitted DecisionTree model to the input data, predicting the probabilities of each class.
180
+
181
+ Args:
182
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
183
+ Returns:
184
+ probabilities (torch.Tensor of shape (n_samples, n_classes)): The predicted probabilities corresponding to each sample.
185
+ Raises:
186
+ NotFittedError: If the DecisionTree model has not been fitted before predicting.
187
+ TypeError: If the input matrix is not a PyTorch tensor.
188
+ ValueError: If the input matrix is not the correct shape.
189
+ """
190
+ if self.root is None:
191
+ raise NotFittedError("DecisionTree.fit() must be called before predicting.")
192
+ if not isinstance(X, torch.Tensor):
193
+ raise TypeError("The input matrix must be a PyTorch tensor.")
194
+ if X.ndim != 2 or X.shape[1] != self.n_features:
195
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
196
+ return torch.stack([self._predict_single(datapoint, self.root, proba=True) for datapoint in X])
197
+
198
+ def _prune(self, node, X, y):
199
+ classes, counts = torch.unique(y, return_counts=True)
200
+ m = len(y) - torch.min(counts).item()
201
+ if node.is_leaf():
202
+ return node, m / self.n_samples, 1
203
+
204
+ left_indicies, right_indicies = self._split(X[:, node.feature_index], node.threshold)
205
+ node.left, left_cost, left_leaf_nodes = self._prune(node.left, X[left_indicies], y[left_indicies])
206
+ node.right, right_cost, right_leaf_nodes = self._prune(node.right, X[right_indicies], y[right_indicies])
207
+ subtree_cost = left_cost + right_cost
208
+ total_subleaves = left_leaf_nodes + right_leaf_nodes
209
+
210
+ leaf_miss_classification_error_rate = m / self.n_samples
211
+ new_leaf_nodes = 1
212
+
213
+ subtree_cost_with_n_nodes_penalty = subtree_cost + self.ccp_alpha * total_subleaves
214
+
215
+ if leaf_miss_classification_error_rate + self.ccp_alpha * new_leaf_nodes < subtree_cost_with_n_nodes_penalty:
216
+ largest_class = classes[torch.argmax(counts)]
217
+ probabilities = torch.tensor([(counts[torch.where(classes == _class)[0]] / len(y) if _class in classes else 0) for _class in self.classes])
218
+ return Node(value=largest_class, probabilities=probabilities), leaf_miss_classification_error_rate, 1
219
+ return node, subtree_cost, total_subleaves