PyDLLib 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DLL/Data/Metrics.py +626 -0
- DLL/Data/Preprocessing.py +384 -0
- DLL/Data/_DataReader.py +55 -0
- DLL/Data/__init__.py +1 -0
- DLL/DeepLearning/Callbacks/_BackUp.py +71 -0
- DLL/DeepLearning/Callbacks/_BaseCallback.py +43 -0
- DLL/DeepLearning/Callbacks/_EarlyStopping.py +102 -0
- DLL/DeepLearning/Callbacks/_ReduceLROnPlateau.py +90 -0
- DLL/DeepLearning/Callbacks/__init__.py +4 -0
- DLL/DeepLearning/Initialisers/_Initialiser.py +24 -0
- DLL/DeepLearning/Initialisers/_Kaiming_He.py +55 -0
- DLL/DeepLearning/Initialisers/_Xavier_Glorot.py +39 -0
- DLL/DeepLearning/Initialisers/__init__.py +2 -0
- DLL/DeepLearning/Layers/Activations/_Activation.py +23 -0
- DLL/DeepLearning/Layers/Activations/_ReLU.py +51 -0
- DLL/DeepLearning/Layers/Activations/_Sigmoid.py +51 -0
- DLL/DeepLearning/Layers/Activations/_SoftMax.py +83 -0
- DLL/DeepLearning/Layers/Activations/_Tanh.py +47 -0
- DLL/DeepLearning/Layers/Activations/__init__.py +4 -0
- DLL/DeepLearning/Layers/Regularisation/_BaseRegularisation.py +12 -0
- DLL/DeepLearning/Layers/Regularisation/_BatchNormalisation.py +106 -0
- DLL/DeepLearning/Layers/Regularisation/_Dropout.py +79 -0
- DLL/DeepLearning/Layers/Regularisation/_GroupNormalisation.py +112 -0
- DLL/DeepLearning/Layers/Regularisation/_InstanceNormalisation.py +50 -0
- DLL/DeepLearning/Layers/Regularisation/_LayerNormalisation.py +37 -0
- DLL/DeepLearning/Layers/Regularisation/__init__.py +5 -0
- DLL/DeepLearning/Layers/_Add.py +109 -0
- DLL/DeepLearning/Layers/_Attention.py +161 -0
- DLL/DeepLearning/Layers/_BaseLayer.py +87 -0
- DLL/DeepLearning/Layers/_Bidirectional.py +113 -0
- DLL/DeepLearning/Layers/_Conv2D.py +133 -0
- DLL/DeepLearning/Layers/_Dense.py +109 -0
- DLL/DeepLearning/Layers/_DenseKAN.py +184 -0
- DLL/DeepLearning/Layers/_Flatten.py +55 -0
- DLL/DeepLearning/Layers/_Identity.py +50 -0
- DLL/DeepLearning/Layers/_Input.py +11 -0
- DLL/DeepLearning/Layers/_LSTM.py +261 -0
- DLL/DeepLearning/Layers/_LayerList.py +98 -0
- DLL/DeepLearning/Layers/_MaxPooling2D.py +101 -0
- DLL/DeepLearning/Layers/_RNN.py +185 -0
- DLL/DeepLearning/Layers/_Reshape.py +52 -0
- DLL/DeepLearning/Layers/__init__.py +14 -0
- DLL/DeepLearning/Losses/_BCE.py +96 -0
- DLL/DeepLearning/Losses/_BaseLoss.py +18 -0
- DLL/DeepLearning/Losses/_CCE.py +87 -0
- DLL/DeepLearning/Losses/_Exponential.py +101 -0
- DLL/DeepLearning/Losses/_Huber.py +104 -0
- DLL/DeepLearning/Losses/_MAE.py +85 -0
- DLL/DeepLearning/Losses/_MSE.py +86 -0
- DLL/DeepLearning/Losses/__init__.py +6 -0
- DLL/DeepLearning/Model.py +265 -0
- DLL/DeepLearning/Optimisers/_ADADELTA.py +53 -0
- DLL/DeepLearning/Optimisers/_ADAGRAD.py +51 -0
- DLL/DeepLearning/Optimisers/_ADAM.py +65 -0
- DLL/DeepLearning/Optimisers/_BaseOptimiser.py +20 -0
- DLL/DeepLearning/Optimisers/_LBFGS.py +120 -0
- DLL/DeepLearning/Optimisers/_RMSPROP.py +62 -0
- DLL/DeepLearning/Optimisers/_SGD.py +44 -0
- DLL/DeepLearning/Optimisers/__init__.py +6 -0
- DLL/DeepLearning/__init__.py +0 -0
- DLL/Exceptions/_Exceptions.py +19 -0
- DLL/Exceptions/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_CalibratedClassifier.py +88 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/_IsotonicRegression.py +114 -0
- DLL/MachineLearning/SupervisedLearning/Calibration/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/_GaussianProcessRegressor.py +190 -0
- DLL/MachineLearning/SupervisedLearning/GaussianProcesses/__init__.py +1 -0
- DLL/MachineLearning/SupervisedLearning/Kernels.py +851 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/TimeSeries.py +147 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_Elasticnet.py +161 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LassoRegression.py +157 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LinearRegression.py +81 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_LogisticRegression.py +182 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RANSAC.py +97 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/_RidgeRegression.py +78 -0
- DLL/MachineLearning/SupervisedLearning/LinearModels/__init__.py +6 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_BernoulliNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_GaussianNaiveBayes.py +111 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/_MultinomialNaiveBayes.py +115 -0
- DLL/MachineLearning/SupervisedLearning/NaiveBayes/__init__.py +3 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsClassifier.py +115 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsRegressor.py +90 -0
- DLL/MachineLearning/SupervisedLearning/Neighbors/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVC.py +293 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVR.py +100 -0
- DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/__init__.py +2 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostClassifier.py +179 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostRegressor.py +141 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_DecisionTree.py +219 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingClassifier.py +207 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingRegressor.py +122 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMClassifier.py +288 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMRegressor.py +168 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_LGBMTree.py +199 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RandomForest.py +96 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionForest.py +75 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_RegressionTree.py +148 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostClassifier.py +239 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostRegressor.py +131 -0
- DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostTree.py +105 -0
- DLL/MachineLearning/SupervisedLearning/Trees/__init__.py +12 -0
- DLL/MachineLearning/SupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_DBScan.py +87 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_GaussianMixtureModels.py +121 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_KMeans.py +120 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/_SpectralClustering.py +105 -0
- DLL/MachineLearning/UnsupervisedLearning/Clustering/__init__.py +4 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_LDA.py +118 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_PCA.py +101 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_QDA.py +69 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_RobustPCA.py +143 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_TSNE.py +151 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_UMAP.py +176 -0
- DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/__init__.py +6 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/_IsolationForest.py +175 -0
- DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/__init__.py +1 -0
- DLL/MachineLearning/UnsupervisedLearning/__init__.py +0 -0
- DLL/MachineLearning/__init__.py +0 -0
- DLL/__init__.py +0 -0
- pydllib-1.0.dist-info/METADATA +104 -0
- pydllib-1.0.dist-info/RECORD +124 -0
- pydllib-1.0.dist-info/WHEEL +5 -0
- pydllib-1.0.dist-info/licenses/LICENSE +21 -0
- pydllib-1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from math import floor
|
|
3
|
+
|
|
4
|
+
from ....DeepLearning.Losses import BCE, CCE
|
|
5
|
+
from ....DeepLearning.Layers.Activations import Sigmoid, SoftMax
|
|
6
|
+
from ....Data.Metrics import calculate_metrics, _round_dictionary, prob_to_pred
|
|
7
|
+
from ....Data import DataReader
|
|
8
|
+
from ....DeepLearning.Optimisers import ADAM
|
|
9
|
+
from ....DeepLearning.Optimisers._BaseOptimiser import BaseOptimiser
|
|
10
|
+
from ....Exceptions import NotFittedError
|
|
11
|
+
from ....Data.Preprocessing import OneHotEncoder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LogisticRegression:
|
|
15
|
+
"""
|
|
16
|
+
Implements a logistic regression model for binary and multi-class classification.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
learning_rate (float, optional): The step size towards the negative gradient. Must be a positive real number. Defaults to 0.01.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
n_features (int): The number of features. Available after fitting.
|
|
23
|
+
weights (torch.Tensor of shape (n_features,)): The weights of the logistic regression model. Available after fitting.
|
|
24
|
+
bias (torch.Tensor of shape (1,)): The constant of the model. Available after fitting.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, learning_rate=0.001):
|
|
27
|
+
if not isinstance(learning_rate, float) or learning_rate <= 0:
|
|
28
|
+
raise ValueError("learning_rate must be a positive real number.")
|
|
29
|
+
|
|
30
|
+
self.learning_rate = learning_rate
|
|
31
|
+
|
|
32
|
+
def fit(self, X, y, sample_weight=None, val_data=None, epochs=100, optimiser=None, callback_frequency=1, metrics=["loss"], batch_size=None, shuffle_every_epoch=True, shuffle_data=True, verbose=False):
|
|
33
|
+
"""
|
|
34
|
+
Fits the LogisticRegression model to the input data by minimizing the cross entropy loss (logistic loss).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
38
|
+
y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
|
|
39
|
+
val_data (tuple[X_val, y_val] | None, optional): Optional validation samples. If None, no validation data is used. Defaults to None.
|
|
40
|
+
epochs (int, optional): The number of training iterations. Must be a positive integer. Defaults to 100.
|
|
41
|
+
optimiser (:ref:`optimisers_section_label` | None, optional): The optimiser used for training the model. If None, the Adam optimiser is used.
|
|
42
|
+
callback_frequency (int, optional): The number of iterations between printing info from training. Must be a positive integer. Defaults to 1, which means that every iteration, info is printed assuming verbose=True.
|
|
43
|
+
metrics (list[str], optional): The metrics that will be tracked during training. Defaults to ["loss"].
|
|
44
|
+
batch_size (int | None, optional): The batch size used in training. Must be a positive integer. If None, every sample is used for every gradient calculation. Defaults to None.
|
|
45
|
+
shuffle_every_epoch (bool, optional): If True, shuffles the order of the samples every epoch. Defaults to True.
|
|
46
|
+
shuffle_data (bool, optional): If True, shuffles data before the training.
|
|
47
|
+
verbose (bool, optional): If True, prints info of the chosen metrics during training. Defaults to False.
|
|
48
|
+
Returns:
|
|
49
|
+
history (dict[str, torch.Tensor], each tensor is floor(epochs / callback_frequency) long.): A dictionary tracking the evolution of selected metrics at intervals defined by callback_frequency.
|
|
50
|
+
Raises:
|
|
51
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor or if other parameters are of wrong type.
|
|
52
|
+
ValueError: If the input matrix or the target matrix is not the correct shape or if other parameters have incorrect values.
|
|
53
|
+
"""
|
|
54
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
55
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
56
|
+
if X.ndim != 2:
|
|
57
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
58
|
+
if y.ndim != 1 or len(y) != len(X):
|
|
59
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
60
|
+
if not isinstance(val_data, list | tuple) and val_data is not None:
|
|
61
|
+
raise TypeError("val_data must either be a tuple containing validation samples or None.")
|
|
62
|
+
if isinstance(val_data, list | tuple) and len(val_data) != 2:
|
|
63
|
+
raise ValueError("val_data must contain both X_val and y_val.")
|
|
64
|
+
if isinstance(val_data, list | tuple) and len(val_data) == 2 and (val_data[0].ndim != 2 or val_data[1].ndim != 1 or val_data[0].shape[1] != X.shape[1] or len(val_data[0]) != len(val_data[1])):
|
|
65
|
+
raise ValueError("X_val and y_val must be of correct shape.")
|
|
66
|
+
if not isinstance(epochs, int) or epochs <= 0:
|
|
67
|
+
raise ValueError("epochs must be a positive integer.")
|
|
68
|
+
if not isinstance(optimiser, BaseOptimiser) and optimiser is not None:
|
|
69
|
+
raise TypeError("optimiser must be from DLL.DeepLearning.Optimisers")
|
|
70
|
+
if not isinstance(callback_frequency, int) or callback_frequency <= 0:
|
|
71
|
+
raise ValueError("callback_frequency must be a positive integer.")
|
|
72
|
+
if not isinstance(metrics, list | tuple):
|
|
73
|
+
raise TypeError("metrics must be a list or a tuple containing the strings of wanted metrics.")
|
|
74
|
+
if (not isinstance(batch_size, int) or batch_size <= 0) and batch_size is not None:
|
|
75
|
+
raise ValueError("batch_size must be a positive integer.")
|
|
76
|
+
if not isinstance(shuffle_every_epoch, bool):
|
|
77
|
+
raise TypeError("shuffle_every_epoch must be a boolean.")
|
|
78
|
+
if not isinstance(shuffle_data, bool):
|
|
79
|
+
raise TypeError("shuffle_data must be a boolean.")
|
|
80
|
+
if not isinstance(verbose, bool):
|
|
81
|
+
raise TypeError("verbose must be a boolean.")
|
|
82
|
+
vals = torch.unique(y).numpy()
|
|
83
|
+
if set(vals) != {*range(len(vals))}:
|
|
84
|
+
raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
|
|
85
|
+
if not isinstance(sample_weight, torch.Tensor) and sample_weight is not None:
|
|
86
|
+
raise TypeError("sample_weight must be torch.Tensor or None.")
|
|
87
|
+
if isinstance(sample_weight, torch.Tensor) and (sample_weight.ndim != 1 or len(X) != len(sample_weight)):
|
|
88
|
+
raise ValueError("sample_weight must be of shape (n_samples,)")
|
|
89
|
+
|
|
90
|
+
n_samples, self.n_features = X.shape
|
|
91
|
+
if len(vals) == 2:
|
|
92
|
+
# normal logistic regression
|
|
93
|
+
self.loss = BCE()
|
|
94
|
+
self.activation = Sigmoid()
|
|
95
|
+
weight_shape = (self.n_features,)
|
|
96
|
+
self.multiclass = False
|
|
97
|
+
elif len(vals) > 2:
|
|
98
|
+
# multiple logistic regression
|
|
99
|
+
encoder = OneHotEncoder()
|
|
100
|
+
y = encoder.fit_encode(y)
|
|
101
|
+
self.multiclass = True
|
|
102
|
+
self.loss = CCE()
|
|
103
|
+
self.activation = SoftMax()
|
|
104
|
+
weight_shape = (self.n_features, y.shape[1])
|
|
105
|
+
|
|
106
|
+
history = {metric: torch.zeros(floor(epochs / callback_frequency)) for metric in metrics}
|
|
107
|
+
batch_size = len(X) if batch_size is None else n_samples
|
|
108
|
+
data_reader = DataReader(X, y, batch_size=batch_size, shuffle=shuffle_data, shuffle_every_epoch=shuffle_every_epoch)
|
|
109
|
+
|
|
110
|
+
if sample_weight is None:
|
|
111
|
+
sample_weight = torch.ones(n_samples)
|
|
112
|
+
sample_weight = sample_weight / torch.sum(sample_weight)
|
|
113
|
+
|
|
114
|
+
self.weights = torch.randn(weight_shape)
|
|
115
|
+
self.bias = torch.zeros((1,)) if len(weight_shape) == 1 else torch.zeros(y.shape[1])
|
|
116
|
+
optimiser = ADAM() if optimiser is None else optimiser
|
|
117
|
+
optimiser.learning_rate = self.learning_rate
|
|
118
|
+
optimiser.initialise_parameters([self.weights, self.bias])
|
|
119
|
+
|
|
120
|
+
for epoch in range(epochs):
|
|
121
|
+
for x_batch, y_batch in data_reader.get_data():
|
|
122
|
+
y_linear = x_batch @ self.weights + self.bias
|
|
123
|
+
predictions = self.activation.forward(y_linear)
|
|
124
|
+
bce_derivative = self.loss.gradient(predictions, y_batch)
|
|
125
|
+
dCdy = self.activation.backward(bce_derivative)
|
|
126
|
+
self.weights.grad = x_batch.T @ dCdy
|
|
127
|
+
self.bias.grad = torch.mean(dCdy, dim=0, keepdim=(y.ndim == 1))
|
|
128
|
+
optimiser.update_parameters()
|
|
129
|
+
if epoch % callback_frequency == 0:
|
|
130
|
+
values = calculate_metrics(data=(self.predict_proba(X), y), metrics=metrics, loss=self.loss.loss)
|
|
131
|
+
if val_data is not None:
|
|
132
|
+
val_values = calculate_metrics(data=(self.predict_proba(val_data[0]), val_data[1]), metrics=metrics, loss=self.loss.loss, validation=True)
|
|
133
|
+
values |= val_values
|
|
134
|
+
for metric, value in values.items():
|
|
135
|
+
history[metric][int(epoch / callback_frequency)] = value
|
|
136
|
+
if verbose: print(f"Epoch: {epoch + 1} - Metrics: {_round_dictionary(values)}")
|
|
137
|
+
return history
|
|
138
|
+
|
|
139
|
+
def predict(self, X):
|
|
140
|
+
"""
|
|
141
|
+
Applies the fitted LogisticRegression model to the input data, predicting the labels.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
145
|
+
Returns:
|
|
146
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
147
|
+
Raises:
|
|
148
|
+
NotFittedError: If the LogisticRegression model has not been fitted before predicting.
|
|
149
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
150
|
+
ValueError: If the input matrix is not the correct shape.
|
|
151
|
+
"""
|
|
152
|
+
if not hasattr(self, "weights"):
|
|
153
|
+
raise NotFittedError("LogisticRegression.fit() must be called before predicting.")
|
|
154
|
+
if not isinstance(X, torch.Tensor):
|
|
155
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
156
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
157
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
158
|
+
|
|
159
|
+
probs = self.activation.forward(X @ self.weights + self.bias)
|
|
160
|
+
return prob_to_pred(probs)
|
|
161
|
+
|
|
162
|
+
def predict_proba(self, X):
|
|
163
|
+
"""
|
|
164
|
+
Applies the fitted LogisticRegression model to the input data, predicting the probabilities of each class.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
168
|
+
Returns:
|
|
169
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
170
|
+
Raises:
|
|
171
|
+
NotFittedError: If the LogisticRegression model has not been fitted before predicting.
|
|
172
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
173
|
+
ValueError: If the input matrix is not the correct shape.
|
|
174
|
+
"""
|
|
175
|
+
if not hasattr(self, "weights"):
|
|
176
|
+
raise NotFittedError("LogisticRegression.fit() must be called before predicting.")
|
|
177
|
+
if not isinstance(X, torch.Tensor):
|
|
178
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
179
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
180
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
181
|
+
|
|
182
|
+
return self.activation.forward(X @ self.weights + self.bias)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from math import ceil
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
|
|
5
|
+
from . import LinearRegression
|
|
6
|
+
from ....Exceptions import NotFittedError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RANSACRegression:
|
|
10
|
+
"""
|
|
11
|
+
Implements the random sample consensus (RANSAC) regression model.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
estimator (A regression model with fit and predict methods): A base model which is fit to random samples of the data. Defaults to LinearRegression.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
best_estimator (estimator): The best model. Available after fitting.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, estimator=LinearRegression()):
|
|
20
|
+
if not hasattr(estimator, "fit") or not hasattr(estimator, "predict"):
|
|
21
|
+
raise TypeError("estimator must have fit and predict functions.")
|
|
22
|
+
|
|
23
|
+
self.estimator = estimator
|
|
24
|
+
|
|
25
|
+
def fit(self, X, y, min_samples=None, residual_threshold=None, max_trials=100, stop_inliers_prob=1, **kwargs):
|
|
26
|
+
"""
|
|
27
|
+
Samples random subsamples of the datapoints and fits base estimators to the subsamples.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
31
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
32
|
+
min_samples (int | float | None, optional): The number of samples used to fit the base estimators. If float, ceil(n_samples * min_samples) is used and if None, n_features + 1 is used. Defaults to None
|
|
33
|
+
residual_threshold (int | float | None, optional): The threshold for which larger absolute errors are considered outliers. If None, the median absolute deviation of y is used. Defaults to None.
|
|
34
|
+
max_trials (int, optional): The number of tries to sample the data. Must be a positive integer. Defaults to 100.
|
|
35
|
+
stop_inliers_prob (int | float, optional): If the proportion of inliers on an iteration exceeds this value, the random sampling is stopped early. Defaults to 1, i.e. the process is never stopped early as the max(n_inliers / number_of_samples_in_subsample) == 1.
|
|
36
|
+
kwargs: Other parameters are passed to estimator.fit()
|
|
37
|
+
"""
|
|
38
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
39
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
40
|
+
if X.ndim != 2:
|
|
41
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
42
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
43
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
44
|
+
if min_samples is not None and not isinstance(min_samples, float) and not isinstance(min_samples, int):
|
|
45
|
+
raise TypeError("min_samples must be one of None, float or int.")
|
|
46
|
+
if isinstance(min_samples, float) and (min_samples < 0 or min_samples > 1):
|
|
47
|
+
raise ValueError("If min_samples is a float, it must be in range [0, 1].")
|
|
48
|
+
if residual_threshold is not None and not isinstance(residual_threshold, float) and not isinstance(residual_threshold, int):
|
|
49
|
+
raise TypeError("residual_threshold must be one of None, float or int.")
|
|
50
|
+
if not isinstance(max_trials, int) or max_trials < 1:
|
|
51
|
+
raise ValueError("max_trials must be a positive integer.")
|
|
52
|
+
if not isinstance(stop_inliers_prob, float | int) or (stop_inliers_prob < 0 or stop_inliers_prob > 1):
|
|
53
|
+
raise ValueError("stop_inliers_prob must be a float in range [0, 1].")
|
|
54
|
+
|
|
55
|
+
n_samples, self.n_features = X.shape
|
|
56
|
+
|
|
57
|
+
if isinstance(min_samples, float):
|
|
58
|
+
min_samples = ceil(n_samples * min_samples)
|
|
59
|
+
min_samples = X.shape[1] + 1 if min_samples is None else min_samples
|
|
60
|
+
|
|
61
|
+
residual_threshold = torch.median(torch.abs(y - torch.median(y))) if residual_threshold is None else residual_threshold
|
|
62
|
+
|
|
63
|
+
max_inliers = -1
|
|
64
|
+
|
|
65
|
+
for _ in range(max_trials):
|
|
66
|
+
indicies = torch.randperm(n_samples)[:min_samples]
|
|
67
|
+
X_ = X[indicies]
|
|
68
|
+
y_ = y[indicies]
|
|
69
|
+
|
|
70
|
+
self.estimator.fit(X_, y_, **kwargs)
|
|
71
|
+
preds = self.estimator.predict(X_)
|
|
72
|
+
n_inliers = torch.sum(torch.abs(y_ - preds) < residual_threshold)
|
|
73
|
+
if n_inliers > max_inliers:
|
|
74
|
+
max_inliers = n_inliers
|
|
75
|
+
best_model = deepcopy(self.estimator)
|
|
76
|
+
|
|
77
|
+
if n_inliers / min_samples > stop_inliers_prob:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
self.best_estimator = best_model
|
|
81
|
+
|
|
82
|
+
def predict(self, X, **kwargs):
|
|
83
|
+
"""
|
|
84
|
+
Predicts the values of the samples using the best estimator determined in the fit method.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
88
|
+
kwargs: Other parameters are passed to estimator.predict()
|
|
89
|
+
"""
|
|
90
|
+
if not hasattr(self, "best_estimator"):
|
|
91
|
+
raise NotFittedError("RANSACRegression.predict() must be called before predicting.")
|
|
92
|
+
if not isinstance(X, torch.Tensor):
|
|
93
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
94
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
95
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
96
|
+
|
|
97
|
+
return self.best_estimator.predict(X, **kwargs)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ....Exceptions import NotFittedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RidgeRegression:
|
|
7
|
+
"""
|
|
8
|
+
Implements a linear regression model with L2 regularization.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
alpha (int | float, optional): The regularization parameter. Larger alpha will force the l2 norm of the weights to be lower. Must be a positive real number. Defaults to 1.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
n_features (int): The number of features. Available after fitting.
|
|
15
|
+
beta (torch.Tensor of shape (n_features + 1,)): The weights of the linear regression model. Available after fitting.
|
|
16
|
+
residuals (torch.Tensor of shape (n_samples,)): The residuals of the fitted model. For a good fit, the residuals should be normally distributed with zero mean and constant variance. Available after fitting.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, alpha=1.0):
|
|
19
|
+
if not isinstance(alpha, int | float) or alpha <= 0:
|
|
20
|
+
raise ValueError("Alpha must be a positive integer.")
|
|
21
|
+
|
|
22
|
+
self.alpha = alpha
|
|
23
|
+
|
|
24
|
+
def fit(self, X, y, sample_weight=None):
|
|
25
|
+
"""
|
|
26
|
+
Fits the RidgeRegression model to the input data by minimizing the squared error.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
30
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
31
|
+
sample_weight (torch.Tensor of shape (n_samples,) or None): A weight given to each sample in the regression. If None, this parameter is ignored.
|
|
32
|
+
Returns:
|
|
33
|
+
None
|
|
34
|
+
Raises:
|
|
35
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor.
|
|
36
|
+
ValueError: If the input matrix or the target matrix is not the correct shape.
|
|
37
|
+
"""
|
|
38
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
39
|
+
raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.")
|
|
40
|
+
if X.ndim != 2:
|
|
41
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
42
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
43
|
+
raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data")
|
|
44
|
+
if not isinstance(sample_weight, torch.Tensor) and sample_weight is not None:
|
|
45
|
+
raise TypeError("sample_weight must be torch.Tensor or None.")
|
|
46
|
+
if isinstance(sample_weight, torch.Tensor) and (sample_weight.ndim != 1 or len(X) != len(sample_weight)):
|
|
47
|
+
raise ValueError("sample_weight must be of shape (n_samples,)")
|
|
48
|
+
|
|
49
|
+
self.n_features = X.shape[1]
|
|
50
|
+
X_with_const = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
51
|
+
identity = torch.eye(X_with_const.shape[1])
|
|
52
|
+
# identity[0, 0] = 0 # Should regularisation be applied to the constant as well?
|
|
53
|
+
sample_weight = torch.diag(sample_weight) if sample_weight is not None else torch.eye(len(X))
|
|
54
|
+
self.beta = torch.linalg.lstsq(X_with_const.T @ sample_weight @ X_with_const + self.alpha * identity, X_with_const.T @ sample_weight @ y).solution
|
|
55
|
+
self.residuals = y - self.predict(X)
|
|
56
|
+
|
|
57
|
+
def predict(self, X):
|
|
58
|
+
"""
|
|
59
|
+
Applies the fitted RidgeRegression model to the input data, predicting the correct values.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
|
|
63
|
+
Returns:
|
|
64
|
+
target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample.
|
|
65
|
+
Raises:
|
|
66
|
+
NotFittedError: If the RidgeRegression model has not been fitted before predicting.
|
|
67
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
68
|
+
ValueError: If the input matrix is not the correct shape.
|
|
69
|
+
"""
|
|
70
|
+
if not hasattr(self, "beta"):
|
|
71
|
+
raise NotFittedError("RidgeRegression.fit() must be called before predicting.")
|
|
72
|
+
if not isinstance(X, torch.Tensor):
|
|
73
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
74
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
75
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
76
|
+
|
|
77
|
+
X = torch.cat((torch.ones(size=(X.shape[0], 1)), X), dim=1)
|
|
78
|
+
return X @ self.beta
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from ._Elasticnet import ElasticNetRegression
|
|
2
|
+
from ._LassoRegression import LASSORegression
|
|
3
|
+
from ._LinearRegression import LinearRegression
|
|
4
|
+
from ._RidgeRegression import RidgeRegression
|
|
5
|
+
from ._LogisticRegression import LogisticRegression
|
|
6
|
+
from ._RANSAC import RANSACRegression
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ....Exceptions import NotFittedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BernoulliNaiveBayes:
|
|
7
|
+
"""
|
|
8
|
+
The BernoulliNaiveBayes classifier model. Applies the Bayes theorem to classify samples with binary features.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
n_features (int): The number of features. Available after fitting.
|
|
12
|
+
n_classes (int): The number of classes. Available after fitting.
|
|
13
|
+
"""
|
|
14
|
+
def fit(self, X, y, alpha=1):
|
|
15
|
+
"""
|
|
16
|
+
Fits the BernoulliNaiveBayes model to the input data by calculating the prior probabilities and likelihoods.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature. Must contain only binary values.
|
|
20
|
+
y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
|
|
21
|
+
alpha (float | int): Laplacian smoothing parameter. Must be non-negative. For no smoothing, alpha is set to zero. Defaults to 1.
|
|
22
|
+
Returns:
|
|
23
|
+
None
|
|
24
|
+
Raises:
|
|
25
|
+
TypeError: If the input matrix or the label matrix is not a PyTorch tensor.
|
|
26
|
+
ValueError: If the input matrix or the label matrix is not the correct shape.
|
|
27
|
+
"""
|
|
28
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
29
|
+
raise TypeError("The input matrix and the label matrix must be PyTorch tensors.")
|
|
30
|
+
if X.ndim != 2:
|
|
31
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
32
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
33
|
+
raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
|
|
34
|
+
vals = torch.unique(y).numpy()
|
|
35
|
+
if set(vals) != {*range(len(vals))}:
|
|
36
|
+
raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
|
|
37
|
+
if set(torch.unique(X).numpy()) != {0, 1}:
|
|
38
|
+
raise ValueError("The features must be labeled 0 and 1. If your features are not binary, consider other NaiveBayes models.")
|
|
39
|
+
if not isinstance(alpha, int | float) or alpha < 0:
|
|
40
|
+
raise ValueError("alpha must be a non-negative real number.")
|
|
41
|
+
|
|
42
|
+
self.n_features = X.shape[1]
|
|
43
|
+
self.n_classes = len(vals)
|
|
44
|
+
self.priors = torch.zeros((self.n_classes,), dtype=torch.float32)
|
|
45
|
+
self.likelihoods = torch.zeros((self.n_classes, X.shape[1]), dtype=torch.float32)
|
|
46
|
+
|
|
47
|
+
for i in range(self.n_classes):
|
|
48
|
+
X_cls = X[y == i]
|
|
49
|
+
self.priors[i] = len(X_cls) / len(y)
|
|
50
|
+
self.likelihoods[i] = (X_cls.sum(dim=0) + alpha) / (len(X_cls) + alpha * X.shape[1]) # laplace smoothing
|
|
51
|
+
|
|
52
|
+
def predict(self, X):
|
|
53
|
+
"""
|
|
54
|
+
Applies the fitted BernoulliNaiveBayes model to the input data, predicting the labels.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
58
|
+
Returns:
|
|
59
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
60
|
+
Raises:
|
|
61
|
+
NotFittedError: If the BernoulliNaiveBayes model has not been fitted before predicting.
|
|
62
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
63
|
+
ValueError: If the input matrix is not the correct shape.
|
|
64
|
+
"""
|
|
65
|
+
if not hasattr(self, "priors"):
|
|
66
|
+
raise NotFittedError("BernoulliNaiveBayes.fit() must be called before predicting.")
|
|
67
|
+
if not isinstance(X, torch.Tensor):
|
|
68
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
69
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
70
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
71
|
+
if set(torch.unique(X).numpy()) != {0, 1}:
|
|
72
|
+
raise ValueError("The features must be labeled 0 and 1. If your features are not binary, consider other NaiveBayes models.")
|
|
73
|
+
|
|
74
|
+
posteriors = torch.zeros((self.n_classes, len(X)), dtype=torch.float32)
|
|
75
|
+
|
|
76
|
+
for i in range(self.n_classes):
|
|
77
|
+
prior = torch.log(self.priors[i])
|
|
78
|
+
posterior = (X * torch.log(self.likelihoods[i]) + (1 - X) * torch.log(1 - self.likelihoods[i])).sum(dim=1) + prior
|
|
79
|
+
posteriors[i] = posterior
|
|
80
|
+
return torch.argmax(posteriors, dim=0)
|
|
81
|
+
|
|
82
|
+
def predict_proba(self, X):
|
|
83
|
+
"""
|
|
84
|
+
Applies the fitted BernoulliNaiveBayes model to the input data, predicting the labels.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
88
|
+
Returns:
|
|
89
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
90
|
+
Raises:
|
|
91
|
+
NotFittedError: If the BernoulliNaiveBayes model has not been fitted before predicting.
|
|
92
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
93
|
+
ValueError: If the input matrix is not the correct shape.
|
|
94
|
+
"""
|
|
95
|
+
if not hasattr(self, "priors"):
|
|
96
|
+
raise NotFittedError("BernoulliNaiveBayes.fit() must be called before predicting.")
|
|
97
|
+
if not isinstance(X, torch.Tensor):
|
|
98
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
99
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
100
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
101
|
+
if set(torch.unique(X).numpy()) != {0, 1}:
|
|
102
|
+
raise ValueError("The features must be labeled 0 and 1. If your features are not binary, consider other NaiveBayes models.")
|
|
103
|
+
|
|
104
|
+
posteriors = torch.zeros((self.n_classes, len(X)), dtype=torch.float32)
|
|
105
|
+
|
|
106
|
+
for i in range(self.n_classes):
|
|
107
|
+
prior = torch.log(self.priors[i])
|
|
108
|
+
posterior = (X * torch.log(self.likelihoods[i]) + (1 - X) * torch.log(1 - self.likelihoods[i])).sum(dim=1) + prior
|
|
109
|
+
posteriors[i] = posterior
|
|
110
|
+
prob_normalizers = torch.logsumexp(posteriors, dim=0)
|
|
111
|
+
log_probs = posteriors - prob_normalizers
|
|
112
|
+
probs = torch.exp(log_probs).T
|
|
113
|
+
if self.n_classes == 2: # binary classification
|
|
114
|
+
probs = probs[:, 1]
|
|
115
|
+
return probs
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ....Exceptions import NotFittedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GaussianNaiveBayes:
|
|
7
|
+
"""
|
|
8
|
+
The GaussianNaiveBayes classifier model. Applies the Bayes theorem to classify samples with real number features.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
n_features (int): The number of features. Available after fitting.
|
|
12
|
+
n_classes (int): The number of classes. Available after fitting.
|
|
13
|
+
"""
|
|
14
|
+
def fit(self, X, y):
|
|
15
|
+
"""
|
|
16
|
+
Fits the GaussianNaiveBayes model to the input data by calculating the prior probabilities and likelihoods.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
20
|
+
y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
|
|
21
|
+
Returns:
|
|
22
|
+
None
|
|
23
|
+
Raises:
|
|
24
|
+
TypeError: If the input matrix or the label matrix is not a PyTorch tensor.
|
|
25
|
+
ValueError: If the input matrix or the label matrix is not the correct shape.
|
|
26
|
+
"""
|
|
27
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
28
|
+
raise TypeError("The input matrix and the label matrix must be PyTorch tensors.")
|
|
29
|
+
if X.ndim != 2:
|
|
30
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
31
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
32
|
+
raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
|
|
33
|
+
vals = torch.unique(y).numpy()
|
|
34
|
+
if set(vals) != {*range(len(vals))}:
|
|
35
|
+
raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
|
|
36
|
+
|
|
37
|
+
n, self.n_features = X.shape
|
|
38
|
+
self.n_classes = len(vals)
|
|
39
|
+
self.means = torch.zeros((self.n_classes, self.n_features), dtype=X.dtype)
|
|
40
|
+
self.vars = torch.zeros_like(self.means, dtype=X.dtype)
|
|
41
|
+
self.priors = torch.zeros((self.n_classes,), dtype=X.dtype)
|
|
42
|
+
|
|
43
|
+
for i in range(self.n_classes):
|
|
44
|
+
X_cls = X[y == i]
|
|
45
|
+
self.means[i] = X_cls.mean(dim=0)
|
|
46
|
+
self.vars[i] = X_cls.var(dim=0)
|
|
47
|
+
self.priors[i] = len(X_cls) / n
|
|
48
|
+
|
|
49
|
+
def predict(self, X):
|
|
50
|
+
"""
|
|
51
|
+
Applies the fitted GaussianNaiveBayes model to the input data, predicting the labels.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
55
|
+
Returns:
|
|
56
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
57
|
+
Raises:
|
|
58
|
+
NotFittedError: If the GaussianNaiveBayes model has not been fitted before predicting.
|
|
59
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
60
|
+
ValueError: If the input matrix is not the correct shape.
|
|
61
|
+
"""
|
|
62
|
+
if not hasattr(self, "priors"):
|
|
63
|
+
raise NotFittedError("GaussianNaiveBayes.fit() must be called before predicting.")
|
|
64
|
+
if not isinstance(X, torch.Tensor):
|
|
65
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
66
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
67
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
68
|
+
|
|
69
|
+
posteriors = torch.zeros((self.n_classes, len(X)), dtype=X.dtype)
|
|
70
|
+
|
|
71
|
+
for i in range(self.n_classes):
|
|
72
|
+
prior = torch.log(self.priors[i])
|
|
73
|
+
posterior = torch.log(self._pdf(X, self.means[i], self.vars[i])).sum(dim=1) + prior
|
|
74
|
+
posteriors[i] = posterior
|
|
75
|
+
return torch.argmax(posteriors, dim=0)
|
|
76
|
+
|
|
77
|
+
def predict_proba(self, X):
|
|
78
|
+
"""
|
|
79
|
+
Applies the fitted GaussianNaiveBayes model to the input data, predicting the probabilities.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
83
|
+
Returns:
|
|
84
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
85
|
+
Raises:
|
|
86
|
+
NotFittedError: If the GaussianNaiveBayes model has not been fitted before predicting.
|
|
87
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
88
|
+
ValueError: If the input matrix is not the correct shape.
|
|
89
|
+
"""
|
|
90
|
+
if not hasattr(self, "priors"):
|
|
91
|
+
raise NotFittedError("GaussianNaiveBayes.fit() must be called before predicting.")
|
|
92
|
+
if not isinstance(X, torch.Tensor):
|
|
93
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
94
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
95
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
96
|
+
|
|
97
|
+
posteriors = torch.zeros((self.n_classes, len(X)), dtype=X.dtype)
|
|
98
|
+
|
|
99
|
+
for i in range(self.n_classes):
|
|
100
|
+
prior = torch.log(self.priors[i])
|
|
101
|
+
posterior = torch.log(self._pdf(X, self.means[i], self.vars[i])).sum(dim=1) + prior
|
|
102
|
+
posteriors[i] = posterior
|
|
103
|
+
prob_normalizers = torch.logsumexp(posteriors, dim=0)
|
|
104
|
+
log_probs = posteriors - prob_normalizers
|
|
105
|
+
probs = torch.exp(log_probs).T
|
|
106
|
+
if self.n_classes == 2: # binary classification
|
|
107
|
+
probs = probs[:, 1]
|
|
108
|
+
return probs
|
|
109
|
+
|
|
110
|
+
def _pdf(self, x, mean, var):
|
|
111
|
+
return torch.exp(-(x - mean) ** 2 / (2 * var)) / torch.sqrt(2 * torch.pi * var)
|