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
DLL/Data/Metrics.py ADDED
@@ -0,0 +1,626 @@
1
+ import torch
2
+ from math import sqrt
3
+
4
+ from ..DeepLearning.Losses import MSE, MAE, BCE, CCE, Huber, Exponential
5
+
6
+
7
+ def calculate_metrics(data, metrics, loss=None, validation=False):
8
+ """
9
+ Calculates the values of different metrics based on training predictions and true values.
10
+
11
+ Args:
12
+ data (tuple[torch.Tensor, torch.Tensor]): A tuple of predictions and the true outputs of a model.
13
+ metrics (tuple | list): A list metric names to calculate. Each element must be in ["loss", "accuracy", "precision", "recall", "f1_score", "rmse", "mae", "mse", "bce", "cce", "huber", "median_absolute"].
14
+ loss (Callable[[predictions, true values], float], optional): The wanted loss function. If Defaults to None.
15
+ validation (bool, optional): Determines if "val_" is appended before each metric. If true, the each element of the metrics must be for instance "val_loss" or "val_mse". Defaults to False.
16
+
17
+ Returns:
18
+ dict[str, float]: A dictionary with metric name as the key and the metric as the value.
19
+ """
20
+ if not isinstance(data, tuple) or len(data) != 2:
21
+ raise TypeError("data must be a tuple of length 2.")
22
+ predictions, true_output = data
23
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
24
+ raise TypeError("The elements of data must be torch tensors.")
25
+ if true_output.shape != predictions.shape:
26
+ raise ValueError("The shapes of the predictions and the output do not match.")
27
+ if true_output.ndim > 2 or predictions.ndim > 2:
28
+ raise ValueError(f"The shapes of the data are not 1 or 2 dimensional. Currently {true_output.ndim, predictions.ndim}.")
29
+ available_metrics = ["loss", "accuracy", "precision", "recall", "f1_score", "rmse", "mae", "mse", "bce", "cce", "huber", "median_absolute"]
30
+ if any([(metric[4:] if validation else metric) not in available_metrics for metric in metrics]):
31
+ raise ValueError(f"Only the following metrics are supported {available_metrics}. Currently {metrics}.")
32
+ if ("loss" in metrics or "val_loss" in metrics) and loss is None:
33
+ raise ValueError("For calculating the loss, the loss function must be passed as an argument.")
34
+
35
+ val = "val_" if validation else ""
36
+ values = {}
37
+ for metric in metrics:
38
+ if metric == (val + "loss") and loss is not None:
39
+ metric_value = loss(predictions, true_output).item()
40
+ elif metric == (val + "accuracy"):
41
+ metric_value = accuracy(predictions, true_output)
42
+ elif metric == (val + "precision"):
43
+ metric_value = precision(predictions, true_output)
44
+ elif metric == (val + "recall"):
45
+ metric_value = recall(predictions, true_output)
46
+ elif metric == (val + "f1_score"):
47
+ metric_value = f1_score(predictions, true_output)
48
+ elif metric == (val + "rmse"):
49
+ metric_value = root_mean_squared_error(predictions, true_output)
50
+ elif metric == (val + "mae"):
51
+ metric_value = mean_absolute_error(predictions, true_output)
52
+ elif metric == (val + "mse"):
53
+ metric_value = mean_squared_error(predictions, true_output)
54
+ elif metric == (val + "bce"):
55
+ metric_value = binary_cross_entropy(predictions, true_output)
56
+ elif metric == (val + "cce"):
57
+ metric_value = categorical_cross_entropy(predictions, true_output)
58
+ elif metric == (val + "huber"):
59
+ metric_value = huber_loss(predictions, true_output)
60
+ elif metric == (val + "median_absolute"):
61
+ metric_value = median_absolute_error(predictions, true_output)
62
+ values[metric] = metric_value
63
+ return values
64
+
65
+ def _round_dictionary(values):
66
+ if not isinstance(values, dict):
67
+ raise TypeError("values must be a dictionary")
68
+
69
+ return {key: "{:0.4f}".format(value) for key, value in values.items()}
70
+
71
+
72
+ # ===============================CLASSIFICATION===============================
73
+ def accuracy(predictions, true_output):
74
+ """
75
+ Calculates the accuracy of predictions.
76
+
77
+ Args:
78
+ predictions (torch.Tensor): A torch tensor of either predicted labels or probabilities. If is given probabilities, calculates the corresponding predictions.
79
+ true_output (torch.Tensor): A torch tensor of either true labels or one-hot encoded values. If is given a one-hot encoded tensor, calculates the corresponding labels.
80
+
81
+ Returns:
82
+ float: the accuracy of the predictions.
83
+ """
84
+ if not isinstance(predictions, torch.Tensor) or predictions.ndim > 2:
85
+ raise ValueError("predictions must be a 1 or 2 dimensional torch tensor.")
86
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 2:
87
+ raise ValueError("true_output must be a 1 or 2 dimensional torch tensor.")
88
+ if len(predictions) != len(true_output):
89
+ raise ValueError("predictions and true_output must have the same number of samples.")
90
+
91
+ if predictions.ndim == 2 or torch.any(torch.bitwise_and(predictions > 0, predictions < 1)):
92
+ predictions = prob_to_pred(predictions)
93
+ if true_output.ndim == 2:
94
+ true_output = prob_to_pred(true_output)
95
+ correct = predictions == true_output
96
+ return correct.to(torch.float32).mean().item()
97
+
98
+ def precision(predictions, true_output):
99
+ """
100
+ Calculates the precision or the positive predictive value of the predictions. The problem must be binary classification to be able to calculate the precision.
101
+
102
+ Args:
103
+ predictions (torch.Tensor of shape (n_samples,)): A torch tensor of either predicted labels or probabilities. If is given probabilities, calculates the corresponding predictions.
104
+ true_output (torch.Tensor of shape (n_samples,)): A torch tensor of true labels.
105
+
106
+ Returns:
107
+ float: the precision of the predictions.
108
+ """
109
+ if not isinstance(predictions, torch.Tensor) or predictions.ndim > 2:
110
+ raise ValueError("predictions must be a 1 or 2 dimensional torch tensor.")
111
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 1:
112
+ raise ValueError("true_output must be a 1 dimensional torch tensor.")
113
+ if len(predictions) != len(true_output):
114
+ raise ValueError("predictions and true_output must have the same number of samples.")
115
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
116
+ raise ValueError("The problem must be binary classification.")
117
+
118
+ if set(torch.unique(predictions).numpy()) != {0, 1}:
119
+ predictions = prob_to_pred(predictions)
120
+ conf_mat = confusion_matrix(predictions, true_output)
121
+ numerator = conf_mat[1, 1]
122
+ denumenator = conf_mat[1, 1] + conf_mat[0, 1]
123
+ return (numerator / denumenator).item()
124
+
125
+ def recall(predictions, true_output):
126
+ """
127
+ Calculates the recall or the sensitivity of the predictions. The problem must be binary classification to be able to calculate the recall.
128
+
129
+ Args:
130
+ predictions (torch.Tensor of shape (n_samples,)): A torch tensor of either predicted labels or probabilities. If is given probabilities, calculates the corresponding predictions.
131
+ true_output (torch.Tensor of shape (n_samples,)): A torch tensor of true labels.
132
+
133
+ Returns:
134
+ float: the recall of the predictions.
135
+ """
136
+ if not isinstance(predictions, torch.Tensor) or predictions.ndim > 2:
137
+ raise ValueError("predictions must be a 1 or 2 dimensional torch tensor.")
138
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 1:
139
+ raise ValueError("true_output must be a 1 dimensional torch tensor.")
140
+ if len(predictions) != len(true_output):
141
+ raise ValueError("predictions and true_output must have the same number of samples.")
142
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
143
+ raise ValueError("The problem must be binary classification.")
144
+
145
+ if set(torch.unique(predictions).numpy()) != {0, 1}:
146
+ predictions = prob_to_pred(predictions)
147
+ conf_mat = confusion_matrix(predictions, true_output)
148
+ numerator = conf_mat[1, 1]
149
+ denumenator = conf_mat[1, 1] + conf_mat[1, 0]
150
+ return (numerator / denumenator).item()
151
+
152
+ def roc_curve(probabilities, true_output, thresholds):
153
+ """
154
+ Calculates receiver operating characteristic curve. The problem must be binary classification.
155
+
156
+ Args:
157
+ probabilities (torch.Tensor of shape (n_samples,)): A torch tensor of probabilities.
158
+ true_output (torch.Tensor of shape (n_samples,)): A torch tensor of true labels.
159
+ thresholds (torch.Tensor of shape (n_thresholds,)): A tensor of thresholds. Every value must be between 0 and 1.
160
+
161
+ Returns:
162
+ tuple[torch.Tensor, torch.Tensor]: A tuple of false-positive-rate and true-positive-rate evaluated at the given thresholds.
163
+ """
164
+ if not isinstance(probabilities, torch.Tensor) or probabilities.ndim > 2:
165
+ raise ValueError("probabilities must be a 1 or 2 dimensional torch tensor.")
166
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 1:
167
+ raise ValueError("true_output must be a 1 dimensional torch tensor.")
168
+ if len(probabilities) != len(true_output):
169
+ raise ValueError("probabilities and true_output must have the same number of samples.")
170
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
171
+ raise ValueError("The problem must be binary classification.")
172
+ if not isinstance(thresholds, torch.Tensor):
173
+ raise TypeError("thresholds must be a torch tensor.")
174
+ if torch.any(torch.bitwise_or(thresholds < 0, thresholds > 1)):
175
+ raise ValueError("thresholds must have every value must be between 0 and 1.")
176
+
177
+ tpr = torch.zeros_like(thresholds)
178
+ fpr = torch.zeros_like(thresholds)
179
+ for i, threshold in enumerate(reversed(thresholds)):
180
+ predictions = _binary_prob_to_prediction(probabilities, threshold)
181
+ conf_mat = confusion_matrix(predictions, true_output)
182
+ tpr[i] = (conf_mat[1, 1] / (conf_mat[1, 1] + conf_mat[1, 0])).item()
183
+ fpr[i] = (conf_mat[0, 1] / (conf_mat[0, 1] + conf_mat[0, 0])).item()
184
+ return fpr, tpr
185
+
186
+ def auc(fpr, tpr):
187
+ """
188
+ Calculates area under the roc curve using the trapezoidal rule. The problem must be binary classification.
189
+
190
+ Args:
191
+ fpr (torch.Tensor of shape (n_thresholds,)): A torch tensor containing the false positive rates.
192
+ tpr (torch.Tensor of shape (n_thresholds,)): A torch tensor containing the true positive rates.
193
+
194
+ Returns:
195
+ float: the area under the roc curve.
196
+ """
197
+ if not isinstance(fpr, torch.Tensor) or fpr.ndim > 1:
198
+ raise ValueError("fpr must be a 1 dimensional torch tensor.")
199
+ if not isinstance(tpr, torch.Tensor) or fpr.ndim > 1:
200
+ raise ValueError("tpr must be a 1 dimensional torch tensor.")
201
+ if len(fpr) != len(tpr):
202
+ raise ValueError("fpr and tpr must have the same number of thresholds.")
203
+
204
+ indicies = torch.argsort(fpr)
205
+ tpr = tpr[indicies]
206
+ fpr = fpr[indicies]
207
+ diffs = fpr[1:] - fpr[:-1]
208
+ return torch.sum(diffs * (tpr[1:] + tpr[:-1]) / 2).item()
209
+
210
+ def roc_auc(probabilities, true_output, thresholds):
211
+ """
212
+ Calculates area under the roc curve. The problem must be binary classification.
213
+
214
+ Args:
215
+ probabilities (torch.Tensor of shape (n_samples,)): A torch tensor of probabilities.
216
+ true_output (torch.Tensor of shape (n_samples,)): A torch tensor of true labels.
217
+ thresholds (torch.Tensor of shape (n_thresholds,)): A tensor of thresholds. Every value must be between 0 and 1.
218
+
219
+ Returns:
220
+ float: the area under the roc curve.
221
+ """
222
+ if not isinstance(probabilities, torch.Tensor) or probabilities.ndim > 2:
223
+ raise ValueError("probabilities must be a 1 or 2 dimensional torch tensor.")
224
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 1:
225
+ raise ValueError("true_output must be a 1 dimensional torch tensor.")
226
+ if len(probabilities) != len(true_output):
227
+ raise ValueError("probabilities and true_output must have the same number of samples.")
228
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
229
+ raise ValueError("The problem must be binary classification.")
230
+ if not isinstance(thresholds, torch.Tensor):
231
+ raise TypeError("thresholds must be a torch tensor.")
232
+ if torch.any(torch.bitwise_or(thresholds < 0, thresholds > 1)):
233
+ raise ValueError("thresholds must have every value must be between 0 and 1.")
234
+
235
+ fpr, tpr = roc_curve(probabilities, true_output, thresholds)
236
+ return auc(fpr, tpr)
237
+
238
+ def _binary_prob_to_prediction(probabilities, threshold=0.5):
239
+ return (probabilities > threshold).to(torch.int32)
240
+
241
+ def _one_hot_to_prediction(probabilities):
242
+ return probabilities.argmax(dim=1)
243
+
244
+ def prob_to_pred(probabilities):
245
+ """
246
+ Converts probabilities to predicted labels.
247
+
248
+ Args:
249
+ probabilities (torch.Tensor with 1 or 2 dimensions): A tensor of probabilities. Must either be 1 or 2 dimensional.
250
+ """
251
+ if not isinstance(probabilities, torch.Tensor) or probabilities.ndim > 2:
252
+ raise ValueError("probabilities must be a 1 or 2 dimensional torch tensor.")
253
+
254
+ if probabilities.ndim == 2:
255
+ return _one_hot_to_prediction(probabilities)
256
+ return _binary_prob_to_prediction(probabilities)
257
+
258
+ """
259
+ Returns the confusion matrix of a problem with the smallest label in the top left corner.
260
+ """
261
+ def confusion_matrix(predictions, true_output):
262
+ """
263
+ The confusion matrix. At the moment, the problem must be binary classification. The element at (i, j) represents the number of observations in class i predicted to be class j.
264
+
265
+ Args:
266
+ predictions (torch.Tensor): A torch tensor of either predicted labels or probabilities. If is given probabilities, calculates the corresponding predictions.
267
+ true_output (torch.Tensor): A torch tensor of either true labels or one-hot encoded values. If is given a one-hot encoded tensor, calculates the corresponding labels.
268
+
269
+ Returns:
270
+ torch.Tensor of shape (n_classes, n_classes): The confusion matrix.
271
+ """
272
+ if not isinstance(predictions, torch.Tensor) or predictions.ndim > 2:
273
+ raise ValueError("predictions must be a 1 or 2 dimensional torch tensor.")
274
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 2:
275
+ raise ValueError("true_output must be a 1 or 2 dimensional torch tensor.")
276
+ if len(predictions) != len(true_output):
277
+ raise ValueError("predictions and true_output must have the same number of samples.")
278
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
279
+ raise ValueError("The problem must be binary classification.")
280
+
281
+ if predictions.ndim == 2 or torch.any(torch.bitwise_and(predictions > 0, predictions < 1)):
282
+ predictions = prob_to_pred(predictions)
283
+ if true_output.ndim == 2:
284
+ true_output = prob_to_pred(true_output)
285
+
286
+ classes = torch.unique(true_output).tolist()
287
+ num_classes = len(classes)
288
+ _confusion_matrix = torch.zeros((num_classes, num_classes))
289
+ for pred, true in zip(predictions, true_output):
290
+ j = classes.index(pred)
291
+ i = classes.index(true)
292
+ _confusion_matrix[i, j] += 1
293
+ return _confusion_matrix
294
+
295
+ def f1_score(predictions, true_output):
296
+ """
297
+ Calculates the f1 score of the predictions. The problem must be binary classification.
298
+
299
+ Args:
300
+ predictions (torch.Tensor of shape (n_samples,)): A torch tensor of either predicted labels or probabilities. If is given probabilities, calculates the corresponding predictions.
301
+ true_output (torch.Tensor of shape (n_samples,)): A torch tensor of true labels.
302
+
303
+ Returns:
304
+ float: the f1 score of the predictions.
305
+ """
306
+ if not isinstance(predictions, torch.Tensor) or predictions.ndim > 2:
307
+ raise ValueError("predictions must be a 1 or 2 dimensional torch tensor.")
308
+ if not isinstance(true_output, torch.Tensor) or true_output.ndim > 1:
309
+ raise ValueError("true_output must be a 1 dimensional torch tensor.")
310
+ if len(predictions) != len(true_output):
311
+ raise ValueError("predictions and true_output must have the same number of samples.")
312
+ if set(torch.unique(predictions).numpy()) != {0, 1}:
313
+ raise ValueError("The problem must be binary classification.")
314
+
315
+ if set(torch.unique(predictions).numpy()) != {0, 1}:
316
+ predictions = prob_to_pred(predictions)
317
+ _precision = precision(predictions, true_output)
318
+ _recall = recall(predictions, true_output)
319
+ return (2 * _precision * _recall / (_precision + _recall))
320
+
321
+ def categorical_cross_entropy(predictions, true_output):
322
+ """
323
+ Calculates the categorical cross entropy of the predictions.
324
+
325
+ Args:
326
+ predictions (torch.Tensor): A tensor of predicted values as a probability distribution. Must be the same shape as the true_output.
327
+ true_output (torch.Tensor): A one-hot encoded tensor of true values. Must be the same shape as the prediction.
328
+
329
+ Returns:
330
+ float: The categorical cross entropy of the predictions.
331
+ """
332
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
333
+ raise TypeError("predictions and true_output must be torch tensors.")
334
+ if predictions.shape != true_output.shape:
335
+ raise ValueError("predictions and true_output must have the same shape.")
336
+ if true_output.ndim != 2:
337
+ raise ValueError("The predictions and the true output must be one-hot encoded.")
338
+
339
+ return CCE().loss(predictions, true_output).item()
340
+
341
+ def binary_cross_entropy(predictions, true_output):
342
+ """
343
+ Calculates the binary cross entropy of the predictions. The problem must be binary classification.
344
+
345
+ Args:
346
+ predictions (torch.Tensor): A tensor of predicted probabilities as a vector. Must be the same shape as the true_output.
347
+ true_output (torch.Tensor): A tensor of true labels as a vector. Must be the same shape as the predictions.
348
+
349
+ Returns:
350
+ float: The binary cross entropy of the predictions.
351
+ """
352
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
353
+ raise TypeError("predictions and true_output must be torch tensors.")
354
+ if predictions.shape != true_output.shape:
355
+ raise ValueError("predictions and true_output must have the same shape.")
356
+ if true_output.ndim != 1:
357
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
358
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
359
+ raise ValueError("The problem must be binary classification.")
360
+
361
+ return BCE().loss(predictions, true_output).item()
362
+
363
+ def exponential_loss(predictions, true_output):
364
+ """
365
+ Calculates the exponential loss of the predictions. The problem must be binary classification.
366
+
367
+ Args:
368
+ predictions (torch.Tensor): A tensor of predicted probabilities as a vector. Must be the same shape as the true_output.
369
+ true_output (torch.Tensor): A tensor of true labels as a vector. Must be the same shape as the predictions.
370
+
371
+ Returns:
372
+ float: The exponential loss of the predictions.
373
+ """
374
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
375
+ raise TypeError("predictions and true_output must be torch tensors.")
376
+ if predictions.shape != true_output.shape:
377
+ raise ValueError("predictions and true_output must have the same shape.")
378
+ if true_output.ndim != 1:
379
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
380
+ if set(torch.unique(true_output).numpy()) != {0, 1}:
381
+ raise ValueError("The problem must be binary classification.")
382
+
383
+ return Exponential().loss(predictions, true_output).item()
384
+
385
+ def calibration_curve(y_true, y_prob, n_bins=5, strategy="quantile"):
386
+ """
387
+ Computes a calibration curve.
388
+
389
+ Parameters:
390
+ y_true (torch.Tensor of shape (n_samples,)): True binary labels.
391
+ y_prob (torch.Tensor of shape (n_samples,)): Predicted probabilities.
392
+ n_bins (int, optional): Number of bins to discretize predictions. Must be a positive integer. Defaults to 5.
393
+ strategy (str, optional): The binning strategy. Must be in ["uniform", "quantile"]. Defaults to "quantile".
394
+
395
+ Returns:
396
+ prob_true, prob_pred (tuple[torch.Tensor, torch.Tensor]): A tuple of the fraction of positives in each bin and the mean predicted probability in each bin.
397
+ """
398
+ if not isinstance(y_true, torch.Tensor) or y_true.ndim != 1:
399
+ raise ValueError("y_true must be a torch.Tensor of shape (n_samples,).")
400
+ if not isinstance(y_prob, torch.Tensor) or y_prob.ndim != 1:
401
+ raise ValueError("y_prob must be a torch.Tensor of shape (n_samples,).")
402
+ if not isinstance(n_bins, int) or n_bins <= 0:
403
+ raise ValueError("n_bins must be a positive integer.")
404
+ if strategy not in ["quantile", "uniform"]:
405
+ raise ValueError('strategy must be in ["uniform", "quantile"].')
406
+
407
+ y_true, y_prob = y_true.float(), y_prob.float()
408
+
409
+ if strategy == 'uniform':
410
+ bins = torch.linspace(0, 1, n_bins + 1)
411
+ elif strategy == 'quantile':
412
+ bins = torch.quantile(y_prob, torch.linspace(0, 1, n_bins + 1))
413
+
414
+ bin_ids = torch.bucketize(y_prob, bins, right=True) - 1
415
+
416
+ prob_true_list = []
417
+ prob_pred_list = []
418
+
419
+ for i in range(n_bins):
420
+ mask = bin_ids == i
421
+ if mask.any():
422
+ prob_true_list.append(y_true[mask].mean().item())
423
+ prob_pred_list.append(y_prob[mask].mean().item())
424
+
425
+ return torch.tensor(prob_true_list), torch.tensor(prob_pred_list)
426
+
427
+
428
+ # ===============================REGRESSION===============================
429
+ def mean_squared_error(predictions, true_output):
430
+ """
431
+ Calculates the mean squared error of the predictions.
432
+
433
+ Args:
434
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
435
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the predictions.
436
+
437
+ Returns:
438
+ float: The mean squared error of the predictions.
439
+ """
440
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
441
+ raise TypeError("predictions and true_output must be torch tensors.")
442
+ if predictions.shape != true_output.shape:
443
+ raise ValueError("predictions and true_output must have the same shape.")
444
+ if true_output.ndim != 1:
445
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
446
+
447
+ return MSE().loss(predictions, true_output).item()
448
+
449
+ def root_mean_squared_error(predictions, true_output):
450
+ """
451
+ Calculates the root mean squared error of the predictions.
452
+
453
+ Args:
454
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
455
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
456
+
457
+ Returns:
458
+ float: The root mean squared error of the predictions.
459
+ """
460
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
461
+ raise TypeError("predictions and true_output must be torch tensors.")
462
+ if predictions.shape != true_output.shape:
463
+ raise ValueError("predictions and true_output must have the same shape.")
464
+ if true_output.ndim != 1:
465
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
466
+
467
+ return sqrt(mean_squared_error(predictions, true_output))
468
+
469
+ def mean_absolute_error(predictions, true_output):
470
+ """
471
+ Calculates the mean absolute error of the predictions.
472
+
473
+ Args:
474
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
475
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
476
+
477
+ Returns:
478
+ float: The mean absolute error of the predictions.
479
+ """
480
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
481
+ raise TypeError("predictions and true_output must be torch tensors.")
482
+ if predictions.shape != true_output.shape:
483
+ raise ValueError("predictions and true_output must have the same shape.")
484
+ if true_output.ndim != 1:
485
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
486
+
487
+ return MAE().loss(predictions, true_output).item()
488
+
489
+ def median_absolute_error(predictions, true_output):
490
+ """
491
+ Calculates the median absoalute error of the predictions.
492
+
493
+ Args:
494
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
495
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
496
+
497
+ Returns:
498
+ float: The median absolute error of the predictions.
499
+ """
500
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
501
+ raise TypeError("predictions and true_output must be torch tensors.")
502
+ if predictions.shape != true_output.shape:
503
+ raise ValueError("predictions and true_output must have the same shape.")
504
+ if true_output.ndim != 1:
505
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
506
+
507
+ return torch.median(torch.abs(predictions - true_output)).item()
508
+
509
+ def huber_loss(predictions, true_output):
510
+ """
511
+ Calculates the huber loss of the predictions.
512
+
513
+ Args:
514
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
515
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
516
+
517
+ Returns:
518
+ float: The huber loss of the predictions.
519
+ """
520
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
521
+ raise TypeError("predictions and true_output must be torch tensors.")
522
+ if predictions.shape != true_output.shape:
523
+ raise ValueError("predictions and true_output must have the same shape.")
524
+ if true_output.ndim != 1:
525
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
526
+
527
+ return Huber().loss(predictions, true_output).item()
528
+
529
+ def r2_score(predictions, true_output):
530
+ """
531
+ Calculates the coefficient of determination of the predictions.
532
+
533
+ Args:
534
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
535
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
536
+
537
+ Returns:
538
+ float: The coefficient of determination of the predictions.
539
+ """
540
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
541
+ raise TypeError("predictions and true_output must be torch tensors.")
542
+ if predictions.shape != true_output.shape:
543
+ raise ValueError("predictions and true_output must have the same shape.")
544
+ if true_output.ndim != 1:
545
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
546
+
547
+ residuals = true_output - predictions
548
+ SSE = torch.sum(residuals ** 2).item()
549
+ SST = torch.sum((true_output - torch.mean(true_output)) ** 2).item()
550
+ r_squared = 1 - SSE / SST
551
+ return r_squared
552
+
553
+ def adjusted_r2_score(predictions, true_output, n_features):
554
+ """
555
+ Calculates the adjusted coefficient of determination of the predictions.
556
+
557
+ Args:
558
+ predictions (torch.Tensor): A tensor of predicted as a vector. Must be the same shape as the true_output.
559
+ true_output (torch.Tensor): A tensor of true values as a vector. Must be the same shape as the prediction.
560
+ n_features (int): The number of features in the original data. Must be a positive integer.
561
+
562
+ Returns:
563
+ float: The adjusted coefficient of determination of the predictions.
564
+ """
565
+ if not isinstance(predictions, torch.Tensor) or not isinstance(true_output, torch.Tensor):
566
+ raise TypeError("predictions and true_output must be torch tensors.")
567
+ if predictions.shape != true_output.shape:
568
+ raise ValueError("predictions and true_output must have the same shape.")
569
+ if true_output.ndim != 1:
570
+ raise ValueError("The predictions and the true output must be a 1 dimensional tensor.")
571
+ n_samples = len(true_output)
572
+ if not isinstance(n_features, int) or n_features <= 0:
573
+ raise ValueError("n_features must be a positive integer.")
574
+ if n_samples - n_features - 1 <= 0:
575
+ raise ValueError("number of samples must be greater than the number of features.")
576
+
577
+ r_squared = r2_score(predictions, true_output)
578
+ adjusted_r_squared = 1 - (1 - r_squared) * (n_samples - 1) / (n_samples - n_features - 1)
579
+ return adjusted_r_squared
580
+
581
+
582
+ # ===============================Clustering===============================
583
+ def silhouette_score(X, y, return_samples=False):
584
+ """
585
+ Computes the silhouette score of a clustering algorithm.
586
+
587
+ Args:
588
+ X (torch.Tensor): The input data of the model.
589
+ y (torch.Tensor): The output classes.
590
+ return_samples (bool, optional): Determines if silhouette score is returned separately or is averaged accross every sample. Defaults to False, i.e. by default returns the average value.
591
+
592
+ Returns:
593
+ float | torch.Tensor: if return_samples, returns a 1 dimensional torch tensor of values and if false, returns the average silhouette score.
594
+ """
595
+ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
596
+ raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
597
+ if X.ndim != 2:
598
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
599
+ if y.ndim != 1 or y.shape[0] != X.shape[0]:
600
+ raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
601
+ vals = torch.unique(y).numpy()
602
+ if set(vals) != {*range(len(vals))}:
603
+ raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
604
+
605
+ dists = torch.cdist(X, X)
606
+ classes = torch.unique(y)
607
+ a = torch.zeros_like(y, dtype=X.dtype)
608
+ b = torch.zeros_like(y, dtype=X.dtype)
609
+
610
+ for i in range(len(X)):
611
+ mask = y == y[i]
612
+ mask[i] = False # remove own sample
613
+ a[i] = torch.mean(dists[i, mask]) if torch.sum(mask) > 1 else 0
614
+ other_distances = []
615
+ for label in classes:
616
+ if label != y[i]:
617
+ mask = y == label
618
+ other_distances.append(torch.mean(dists[i, mask]))
619
+ if len(other_distances) > 0: b[i] = min(other_distances)
620
+
621
+ s = (b - a) / torch.maximum(a, b)
622
+ s[torch.isnan(s)] = 0
623
+
624
+ if return_samples:
625
+ return s
626
+ return torch.mean(s).item()