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,115 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
|
|
3
|
+
from ....Exceptions import NotFittedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MultinomialNaiveBayes:
|
|
7
|
+
"""
|
|
8
|
+
The MultinomialNaiveBayes classifier model. Applies the Bayes theorem to classify samples with positive integer 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 MultinomialNaiveBayes 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 non-negative integers.
|
|
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 (X.dtype == torch.int or X.dtype == torch.int8 or X.dtype == torch.int16 or X.dtype == torch.int32 or X.dtype == torch.int64) and torch.all(X >= 0):
|
|
38
|
+
raise ValueError("The features must be non-negative integers. If your features are not non-negative integers, 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_classes = len(vals)
|
|
43
|
+
self.n_features = X.shape[1]
|
|
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) / (X_cls.sum() + alpha * X.shape[1]) # laplace smoothing
|
|
51
|
+
|
|
52
|
+
def predict(self, X):
|
|
53
|
+
"""
|
|
54
|
+
Applies the fitted MultinomialNaiveBayes 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 MultinomialNaiveBayes 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("MultinomialNaiveBayes.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 (X.dtype == torch.int or X.dtype == torch.int8 or X.dtype == torch.int16 or X.dtype == torch.int32 or X.dtype == torch.int64) and torch.all(X >= 0):
|
|
72
|
+
raise ValueError("The features must be non-negative integers. If your features are not non-negative integers, 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])).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 MultinomialNaiveBayes 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 MultinomialNaiveBayes 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("MultinomialNaiveBayes.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 (X.dtype == torch.int or X.dtype == torch.int8 or X.dtype == torch.int16 or X.dtype == torch.int32 or X.dtype == torch.int64) and torch.all(X >= 0):
|
|
102
|
+
raise ValueError("The features must be non-negative integers. If your features are not non-negative integers, 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])).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,115 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from ....Exceptions import NotFittedError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KNNClassifier:
|
|
8
|
+
"""
|
|
9
|
+
The k-nearest neighbors classifier model. Looks for the k closest samples with respect to a distance metric and calculates the most common label.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
k (int, optional): The number of closest samples considered for the predictions. Must be a positive integer. Defaults to 3.
|
|
13
|
+
metric (str, optional): A distance metric for the closest points. Must be one of "euclidian" or "manhattan". Defaults to "euclidian".
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, k=3, metric="euclidian"):
|
|
16
|
+
if not isinstance(k, int) or k <= 0:
|
|
17
|
+
raise ValueError("k must be a positive integer.")
|
|
18
|
+
if metric not in ["euclidian", "manhattan"]:
|
|
19
|
+
raise ValueError('metric must be on of "euclidian" or "manhattan".')
|
|
20
|
+
|
|
21
|
+
self.k = k
|
|
22
|
+
self.metric = partial(self._metric, metric)
|
|
23
|
+
|
|
24
|
+
def _metric(self, metric, X1, X2):
|
|
25
|
+
if metric == "euclidian":
|
|
26
|
+
return ((X1 - X2) ** 2).sum(dim=2).sqrt()
|
|
27
|
+
elif metric == "manhattan":
|
|
28
|
+
return torch.abs(X1 - X2).sum(dim=2)
|
|
29
|
+
|
|
30
|
+
def fit(self, X, y):
|
|
31
|
+
"""
|
|
32
|
+
Fits the KNNClassifier model to the input data by storing the input and label matricies.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature. There must be atleast k samples.
|
|
36
|
+
y (torch.Tensor of shape (n_samples,)): The labels corresponding to each sample. Every element must be in [0, ..., n_classes - 1].
|
|
37
|
+
Returns:
|
|
38
|
+
None
|
|
39
|
+
Raises:
|
|
40
|
+
TypeError: If the input matrix or the label matrix is not a PyTorch tensor.
|
|
41
|
+
ValueError: If the input matrix or the label matrix is not the correct shape.
|
|
42
|
+
"""
|
|
43
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
44
|
+
raise TypeError("The input matrix and the label matrix must be PyTorch tensors.")
|
|
45
|
+
if X.ndim != 2:
|
|
46
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
47
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
48
|
+
raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
|
|
49
|
+
if X.shape[0] < self.k:
|
|
50
|
+
raise ValueError("There must be atleast k samples.")
|
|
51
|
+
vals = torch.unique(y).numpy()
|
|
52
|
+
if set(vals) != {*range(len(vals))}:
|
|
53
|
+
raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
|
|
54
|
+
|
|
55
|
+
self.X = X
|
|
56
|
+
self.y = y
|
|
57
|
+
|
|
58
|
+
def predict(self, X):
|
|
59
|
+
"""
|
|
60
|
+
Applies the fitted KNNClassifier model to the input data, predicting the labels.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
64
|
+
Returns:
|
|
65
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
66
|
+
Raises:
|
|
67
|
+
NotFittedError: If the KNNClassifier model has not been fitted before predicting.
|
|
68
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
69
|
+
ValueError: If the input matrix is not the correct shape.
|
|
70
|
+
"""
|
|
71
|
+
if not hasattr(self, "X"):
|
|
72
|
+
raise NotFittedError("KNNClassifier.fit() must be called before predicting.")
|
|
73
|
+
if not isinstance(X, torch.Tensor):
|
|
74
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
75
|
+
if X.ndim != 2 or X.shape[1] != self.X.shape[1]:
|
|
76
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
77
|
+
|
|
78
|
+
distances: torch.Tensor = self.metric(self.X.unsqueeze(0), X.unsqueeze(1)) # (len(X), len(self.X))
|
|
79
|
+
indicies = distances.topk(self.k, largest=False).indices
|
|
80
|
+
k_labels = self.y[indicies]
|
|
81
|
+
return torch.mode(k_labels, dim=1).values
|
|
82
|
+
|
|
83
|
+
def predict_proba(self, X):
|
|
84
|
+
"""
|
|
85
|
+
Applies the fitted KNNClassifier model to the input data, predicting the probabilities of each class.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
89
|
+
Returns:
|
|
90
|
+
labels (torch.Tensor of shape (n_samples,) for binary and (n_samples, n_classes) for multiclass classification): The predicted target probabilities corresponding to each sample.
|
|
91
|
+
Raises:
|
|
92
|
+
NotFittedError: If the KNNClassifier model has not been fitted before predicting.
|
|
93
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
94
|
+
ValueError: If the input matrix is not the correct shape.
|
|
95
|
+
"""
|
|
96
|
+
if not hasattr(self, "X"):
|
|
97
|
+
raise NotFittedError("KNNClassifier.fit() must be called before predicting.")
|
|
98
|
+
if not isinstance(X, torch.Tensor):
|
|
99
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
100
|
+
if X.ndim != 2 or X.shape[1] != self.X.shape[1]:
|
|
101
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
102
|
+
|
|
103
|
+
distances: torch.Tensor = self.metric(self.X.unsqueeze(0), X.unsqueeze(1)) # (len(X), len(self.X))
|
|
104
|
+
indicies = distances.topk(self.k, largest=False).indices
|
|
105
|
+
k_labels = self.y[indicies]
|
|
106
|
+
|
|
107
|
+
all_classes = torch.unique(self.y)
|
|
108
|
+
probs = torch.zeros((len(X), len(all_classes)))
|
|
109
|
+
for i, labels in enumerate(k_labels):
|
|
110
|
+
classes, counts = torch.unique(labels, return_counts=True)
|
|
111
|
+
prob = torch.tensor([(counts[torch.where(classes == _class)[0]] / len(labels) if _class in classes else 0) for _class in all_classes])
|
|
112
|
+
probs[i] = prob
|
|
113
|
+
if len(all_classes) == 2: # binary classification
|
|
114
|
+
probs = probs[:, 1]
|
|
115
|
+
return probs
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
from ....Exceptions import NotFittedError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KNNRegressor:
|
|
8
|
+
"""
|
|
9
|
+
The k-nearest neighbors regressor model. Looks for the k closest samples with respect to a distance metric and calculates the average according to some weight function.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
k (int, optional): The number of closest samples considered for the predictions. Must be a positive integer. Defaults to 3.
|
|
13
|
+
metric (str, optional): A distance metric for the closest points. Must be one of "euclidian" or "manhattan". Defaults to "euclidian".
|
|
14
|
+
weight (str, optional): A weight function that decides how important are the nearest k samples. Must be in ["uniform", "distance", "gaussian"]. Defaults to "gaussian".
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, k=3, metric="euclidian", weight="gaussian"):
|
|
17
|
+
if not isinstance(k, int) or k <= 0:
|
|
18
|
+
raise ValueError("k must be a positive integer.")
|
|
19
|
+
if metric not in ["euclidian", "manhattan"]:
|
|
20
|
+
raise ValueError('metric must be on of "euclidian" or "manhattan".')
|
|
21
|
+
if weight not in ["uniform", "distance", "gaussian"]:
|
|
22
|
+
raise ValueError('weight must be in ["uniform", "distance", "gaussian"].')
|
|
23
|
+
|
|
24
|
+
self.k = k
|
|
25
|
+
self.metric = partial(self._metric, metric)
|
|
26
|
+
self.weight = partial(self._weight, weight)
|
|
27
|
+
|
|
28
|
+
def _metric(self, metric, X1, X2):
|
|
29
|
+
if metric == "euclidian":
|
|
30
|
+
return ((X1 - X2) ** 2).sum(dim=2).sqrt()
|
|
31
|
+
elif metric == "manhattan":
|
|
32
|
+
return torch.abs(X1 - X2).sum(dim=2)
|
|
33
|
+
|
|
34
|
+
def _weight(self, weight, distances):
|
|
35
|
+
if weight == "uniform":
|
|
36
|
+
return torch.ones_like(distances)
|
|
37
|
+
elif weight == "distance":
|
|
38
|
+
return 1 / (distances + 1e-10)
|
|
39
|
+
elif weight == "gaussian":
|
|
40
|
+
return torch.exp(-distances ** 2)
|
|
41
|
+
|
|
42
|
+
def fit(self, X, y):
|
|
43
|
+
"""
|
|
44
|
+
Fits the KNNRegressor model to the input data by storing the input and target matricies.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
|
|
48
|
+
y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample.
|
|
49
|
+
Returns:
|
|
50
|
+
None
|
|
51
|
+
Raises:
|
|
52
|
+
TypeError: If the input matrix or the target matrix is not a PyTorch tensor.
|
|
53
|
+
ValueError: If the input matrix or the target matrix is not the correct shape.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
56
|
+
raise TypeError("The input matrix and the target matrix must be PyTorch tensors.")
|
|
57
|
+
if X.ndim != 2:
|
|
58
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
59
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
60
|
+
raise ValueError("The target values must be 1 dimensional with the same number of samples as the input data")
|
|
61
|
+
|
|
62
|
+
self.X = X
|
|
63
|
+
self.y = y
|
|
64
|
+
|
|
65
|
+
def predict(self, X):
|
|
66
|
+
"""
|
|
67
|
+
Applies the fitted KNNRegressor model to the input data, predicting the target values.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed.
|
|
71
|
+
Returns:
|
|
72
|
+
targets (torch.Tensor of shape (n_samples,)): The predicted target values corresponding to each sample.
|
|
73
|
+
Raises:
|
|
74
|
+
NotFittedError: If the KNNRegressor model has not been fitted before predicting.
|
|
75
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
76
|
+
ValueError: If the input matrix is not the correct shape.
|
|
77
|
+
"""
|
|
78
|
+
if not hasattr(self, "X"):
|
|
79
|
+
raise NotFittedError("KNNRegressor.fit() must be called before predicting.")
|
|
80
|
+
if not isinstance(X, torch.Tensor):
|
|
81
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
82
|
+
if X.ndim != 2 or X.shape[1] != self.X.shape[1]:
|
|
83
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
84
|
+
|
|
85
|
+
distances: torch.Tensor = self.metric(self.X.unsqueeze(0), X.unsqueeze(1)) # (len(X), len(self.X))
|
|
86
|
+
indicies = distances.topk(self.k, largest=False).indices
|
|
87
|
+
k_values = self.y[indicies]
|
|
88
|
+
k_distances = distances.gather(1, indicies)
|
|
89
|
+
k_weights = self.weight(k_distances)
|
|
90
|
+
return (k_values * k_weights).sum(dim=1) / (k_weights.sum(dim=1) + 1e-10)
|
|
@@ -0,0 +1,293 @@
|
|
|
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 SVC:
|
|
10
|
+
"""
|
|
11
|
+
The support vector machine classifier. "cvxopt"-optimization method's implementation is largly based on `this article <https://towardsdatascience.com/implement-multiclass-svm-from-scratch-in-python-b141e43dc084#a603>`_. The "smo"-optimization algorithm is based on `this paper <https://www.microsoft.com/en-us/research/uploads/prod/1998/04/sequential-minimal-optimization.pdf>`_.
|
|
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
|
+
opt_method (str, optional): The method that will be used in the optimization step. Must be in ["cvxopt", "smo", "coord_ascent"]. Defaults to "coord_ascent". For optimal results one should experiment with every optimization method, but as a rule of thumb, "coord_ascent" is the most efficient.
|
|
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
|
+
"""
|
|
21
|
+
def __init__(self, kernel=RBF(), C=1, opt_method="coord_ascent"):
|
|
22
|
+
if not isinstance(kernel, _Base):
|
|
23
|
+
raise ValueError("kernel must be from DLL.MachineLearning.SupervisedLearning.Kernels")
|
|
24
|
+
if not isinstance(C, float | int) or C <= 0:
|
|
25
|
+
raise ValueError("C must be must be positive real number.")
|
|
26
|
+
if opt_method not in ["cvxopt", "smo", "coord_ascent"]:
|
|
27
|
+
raise ValueError('opt_method must be in ["cvxopt", "smo", "coord_ascent"].')
|
|
28
|
+
|
|
29
|
+
self.kernel = kernel
|
|
30
|
+
self.C = C
|
|
31
|
+
self.opt_method = opt_method
|
|
32
|
+
|
|
33
|
+
self._from_multi = False
|
|
34
|
+
self.tol = 1e-5
|
|
35
|
+
|
|
36
|
+
def _kernel_matrix(self, X1, X2):
|
|
37
|
+
return self.kernel(X1, X2).to(X1.dtype)
|
|
38
|
+
|
|
39
|
+
def fit(self, X, y, epochs=10, multi_method="ovr"):
|
|
40
|
+
"""
|
|
41
|
+
Fits the SVC model to the input data by finding the hyperplane that separates the data with maximum margin. It uses the optimization method defined in the constructor of the class.
|
|
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
|
+
epochs (int, optional): The number of iterations the training loop runs. Defaults to 100. Must be a positive integer. Is ignored for opt_method="cvxopt".
|
|
47
|
+
multi_method (str, optional): The method for multi-class classification. Is ignored for binary classification. Must be one of "ovr" or "ovo". Defaults to "ovr".
|
|
48
|
+
Returns:
|
|
49
|
+
None
|
|
50
|
+
Raises:
|
|
51
|
+
TypeError: If the input matrix or the label matrix is not a PyTorch tensor or multi_method is not a string.
|
|
52
|
+
ValueError: If the input matrix or the label matrix is not the correct shape or multi_method is not in allowed methods.
|
|
53
|
+
"""
|
|
54
|
+
if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor):
|
|
55
|
+
raise TypeError("The input matrix and the label matrix must be a PyTorch tensor.")
|
|
56
|
+
if not isinstance(multi_method, str):
|
|
57
|
+
raise TypeError("multi_method must be a string.")
|
|
58
|
+
if X.ndim != 2:
|
|
59
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor.")
|
|
60
|
+
if y.ndim != 1 or y.shape[0] != X.shape[0]:
|
|
61
|
+
raise ValueError("The labels must be 1 dimensional with the same number of samples as the input data")
|
|
62
|
+
if not isinstance(epochs, int) or epochs <= 0:
|
|
63
|
+
raise ValueError("epochs must be a positive integer.")
|
|
64
|
+
if multi_method not in ["ovr", "ovo"]:
|
|
65
|
+
raise ValueError('multi_method must be one of "ovr" or "ovo".')
|
|
66
|
+
vals = torch.unique(y).numpy()
|
|
67
|
+
if set(vals) != {*range(len(vals))}:
|
|
68
|
+
raise ValueError("y must only contain the values in [0, ..., n_classes - 1].")
|
|
69
|
+
|
|
70
|
+
self.n_features = X.shape[1]
|
|
71
|
+
|
|
72
|
+
if len(torch.unique(y)) > 2:
|
|
73
|
+
self.multiclass = True
|
|
74
|
+
return self._multi_fit(X, y, epochs, multi_method)
|
|
75
|
+
|
|
76
|
+
self.multiclass = False
|
|
77
|
+
y = torch.where(y == 0, -1, 1)
|
|
78
|
+
self.y = y
|
|
79
|
+
self.X = X
|
|
80
|
+
n = X.shape[0]
|
|
81
|
+
K = self._kernel_matrix(X, X)
|
|
82
|
+
|
|
83
|
+
if self.opt_method == "cvxopt":
|
|
84
|
+
y_vec = y.reshape((-1, 1)).to(X.dtype)
|
|
85
|
+
|
|
86
|
+
P = cvxopt.matrix((y_vec @ y_vec.T * K).numpy().astype(np.double))
|
|
87
|
+
q = cvxopt.matrix(-np.ones((n, 1)))
|
|
88
|
+
A = cvxopt.matrix((y_vec.T).numpy().astype(np.double))
|
|
89
|
+
b = cvxopt.matrix(np.zeros(1))
|
|
90
|
+
G = cvxopt.matrix(np.vstack((-np.eye(n), np.eye(n))))
|
|
91
|
+
h = cvxopt.matrix(np.vstack((np.zeros((n, 1)), np.ones((n, 1)) * self.C)))
|
|
92
|
+
|
|
93
|
+
cvxopt.solvers.options['show_progress'] = False
|
|
94
|
+
sol = cvxopt.solvers.qp(P, q, G, h, A, b)
|
|
95
|
+
self.alpha = torch.tensor(np.array(sol["x"])).squeeze().to(X.dtype)
|
|
96
|
+
|
|
97
|
+
elif self.opt_method == "coord_ascent":
|
|
98
|
+
alpha = torch.zeros(n, dtype=X.dtype)
|
|
99
|
+
|
|
100
|
+
for _ in range(epochs):
|
|
101
|
+
# process all sample examples sequentially
|
|
102
|
+
for i in range(n):
|
|
103
|
+
new_val = (1 - y[i] * ((alpha * y * K[i]).sum() - alpha[i] * y[i] * K[i, i])) / K[i, i]
|
|
104
|
+
new_val = min(self.C, max(new_val, 0))
|
|
105
|
+
alpha[i] = new_val
|
|
106
|
+
self.alpha = alpha
|
|
107
|
+
|
|
108
|
+
elif self.opt_method == "smo":
|
|
109
|
+
self.alpha = torch.zeros(n, dtype=X.dtype)
|
|
110
|
+
self.b = 0
|
|
111
|
+
epochs_no_change = 2
|
|
112
|
+
|
|
113
|
+
iter_count = 0
|
|
114
|
+
num_no_change_iter = 0
|
|
115
|
+
while iter_count < epochs:
|
|
116
|
+
num_changed_alphas = 0
|
|
117
|
+
for i in range(n):
|
|
118
|
+
E_i = (self.alpha * self.y) @ K[i, :] + self.b - self.y[i]
|
|
119
|
+
if (self.y[i] * E_i < -self.tol and self.alpha[i] < self.C) or (self.y[i] * E_i > self.tol and self.alpha[i] > 0):
|
|
120
|
+
# Select j randomly
|
|
121
|
+
j = self._select_j(i, n)
|
|
122
|
+
E_j = (self.alpha * self.y) @ K[j, :] + self.b - self.y[j]
|
|
123
|
+
|
|
124
|
+
# Compute bounds for α_j
|
|
125
|
+
if self.y[i] == self.y[j]:
|
|
126
|
+
L = torch.max(torch.zeros(1), self.alpha[j] + self.alpha[i] - self.C)
|
|
127
|
+
H = torch.min(torch.tensor(self.C), self.alpha[j] + self.alpha[i])
|
|
128
|
+
else:
|
|
129
|
+
L = torch.max(torch.zeros(1), self.alpha[j] - self.alpha[i])
|
|
130
|
+
H = torch.min(torch.tensor(self.C), self.C + self.alpha[j] - self.alpha[i])
|
|
131
|
+
if L == H:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Compute η (second derivative of the objective function w.r.t. α_j)
|
|
135
|
+
eta = 2 * K[i, j] - K[i, i] - K[j, j]
|
|
136
|
+
if eta >= 0:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Update α_j
|
|
140
|
+
alpha_j_old = self.alpha[j].clone()
|
|
141
|
+
self.alpha[j] = self.alpha[j] - (self.y[j] * (E_i - E_j)) / eta
|
|
142
|
+
self.alpha[j] = torch.clamp(self.alpha[j], L, H)
|
|
143
|
+
|
|
144
|
+
if abs(self.alpha[j] - alpha_j_old) < self.tol:
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# Update α_i
|
|
148
|
+
alpha_i_old = self.alpha[i].clone()
|
|
149
|
+
self.alpha[i] = self.alpha[i] + self.y[i] * self.y[j] * (alpha_j_old - self.alpha[j])
|
|
150
|
+
|
|
151
|
+
# Update bias term
|
|
152
|
+
b1 = self.b - E_i - self.y[i] * (self.alpha[i] - alpha_i_old) * K[i, i] - self.y[j] * (self.alpha[j] - alpha_j_old) * K[i, j]
|
|
153
|
+
b2 = self.b - E_j - self.y[i] * (self.alpha[i] - alpha_i_old) * K[i, j] - self.y[j] * (self.alpha[j] - alpha_j_old) * K[j, j]
|
|
154
|
+
|
|
155
|
+
if 0 < self.alpha[i] < self.C:
|
|
156
|
+
self.b = b1
|
|
157
|
+
elif 0 < self.alpha[j] < self.C:
|
|
158
|
+
self.b = b2
|
|
159
|
+
else:
|
|
160
|
+
self.b = (b1 + b2) / 2
|
|
161
|
+
|
|
162
|
+
num_changed_alphas += 1
|
|
163
|
+
|
|
164
|
+
if num_changed_alphas == 0:
|
|
165
|
+
num_no_change_iter += 1
|
|
166
|
+
else:
|
|
167
|
+
num_no_change_iter = 0
|
|
168
|
+
if num_no_change_iter >= epochs_no_change:
|
|
169
|
+
break
|
|
170
|
+
iter_count += 1
|
|
171
|
+
|
|
172
|
+
self.is_sv = (self.alpha - self.tol > 0) & (self.alpha <= self.C)
|
|
173
|
+
self.margin_sv = torch.argmax(((0 < self.alpha - self.tol) & (self.alpha < self.C - self.tol)).to(torch.int32))
|
|
174
|
+
|
|
175
|
+
if self.opt_method != "smo":
|
|
176
|
+
x_s, y_s = self.X[self.margin_sv, torch.newaxis], self.y[self.margin_sv]
|
|
177
|
+
alpha_sv, y_sv, X_sv = self.alpha[self.is_sv], self.y[self.is_sv], self.X[self.is_sv]
|
|
178
|
+
self.b = y_s - torch.sum((alpha_sv * y_sv).unsqueeze(-1) * self._kernel_matrix(X_sv, x_s), dim=0)
|
|
179
|
+
|
|
180
|
+
def _select_j(self, i, n):
|
|
181
|
+
j = i
|
|
182
|
+
while j == i:
|
|
183
|
+
j = torch.randint(0, n, (1,)).item()
|
|
184
|
+
return j
|
|
185
|
+
|
|
186
|
+
def _multi_fit(self, X, y, epochs, method):
|
|
187
|
+
self.method = method
|
|
188
|
+
classes = torch.unique(y)
|
|
189
|
+
|
|
190
|
+
if method == "ovr":
|
|
191
|
+
self.n_classes = len(torch.unique(y))
|
|
192
|
+
self.classifiers = []
|
|
193
|
+
for label in classes:
|
|
194
|
+
Xs, ys = X, torch.where(y == label, 1, 0)
|
|
195
|
+
classifier = SVC(kernel=self.kernel, C=self.C, opt_method=self.opt_method)
|
|
196
|
+
classifier.fit(Xs, ys, epochs)
|
|
197
|
+
self.classifiers.append(classifier)
|
|
198
|
+
|
|
199
|
+
elif method == "ovo":
|
|
200
|
+
self.n_classes = len(torch.unique(y))
|
|
201
|
+
self.classifiers = []
|
|
202
|
+
for i in range(self.n_classes):
|
|
203
|
+
for j in range(i+1, self.n_classes):
|
|
204
|
+
class_i, class_j = classes[i], classes[j]
|
|
205
|
+
indices = (y == class_i) | (y == class_j)
|
|
206
|
+
X_subset, y_subset = X[indices], y[indices]
|
|
207
|
+
y_subset = torch.where(y_subset == class_i, 1, 0)
|
|
208
|
+
classifier = SVC(kernel=self.kernel, C=self.C, opt_method=self.opt_method)
|
|
209
|
+
classifier.fit(X_subset, y_subset, epochs)
|
|
210
|
+
self.classifiers.append((classifier, class_i, class_j))
|
|
211
|
+
|
|
212
|
+
def _decision_function(self, K):
|
|
213
|
+
return (self.alpha * self.y) @ K + self.b
|
|
214
|
+
|
|
215
|
+
def predict(self, X):
|
|
216
|
+
"""
|
|
217
|
+
Applies the fitted SVC model to the input data, predicting the correct classes.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data to be classified.
|
|
221
|
+
Returns:
|
|
222
|
+
labels (torch.Tensor of shape (n_samples,)): The predicted labels corresponding to each sample.
|
|
223
|
+
Raises:
|
|
224
|
+
NotFittedError: If the SVC model has not been fitted before predicting.
|
|
225
|
+
TypeError: If the input matrix is not a PyTorch tensor.
|
|
226
|
+
ValueError: If the input matrix is not the correct shape.
|
|
227
|
+
"""
|
|
228
|
+
if not hasattr(self, "multiclass"):
|
|
229
|
+
raise NotFittedError("SVC.fit() must be called before predicting.")
|
|
230
|
+
if not isinstance(X, torch.Tensor):
|
|
231
|
+
raise TypeError("The input matrix must be a PyTorch tensor.")
|
|
232
|
+
if X.ndim != 2 or X.shape[1] != self.n_features:
|
|
233
|
+
raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
|
|
234
|
+
|
|
235
|
+
if self.multiclass:
|
|
236
|
+
return self._multi_predict(X)
|
|
237
|
+
|
|
238
|
+
score = self._decision_function(self._kernel_matrix(self.X, X))
|
|
239
|
+
if self._from_multi:
|
|
240
|
+
return score
|
|
241
|
+
return ((torch.sign(score) + 1) / 2).to(torch.int32)
|
|
242
|
+
|
|
243
|
+
def _multi_predict(self, X):
|
|
244
|
+
if self.method == "ovr":
|
|
245
|
+
predictions = torch.zeros((X.shape[0], self.n_classes))
|
|
246
|
+
for i, classifier in enumerate(self.classifiers):
|
|
247
|
+
classifier._from_multi = True
|
|
248
|
+
predictions[:, i] = classifier.predict(X)
|
|
249
|
+
return torch.argmax(predictions, dim=1)
|
|
250
|
+
|
|
251
|
+
elif self.method == "ovo":
|
|
252
|
+
votes = torch.zeros((X.shape[0], self.n_classes))
|
|
253
|
+
for classifier, class_i, class_j in self.classifiers:
|
|
254
|
+
predictions = classifier.predict(X)
|
|
255
|
+
votes[:, class_i] += (predictions == 1) # If prediction is 1, vote for class_i
|
|
256
|
+
votes[:, class_j] += (predictions == 0) # If prediction is 0, vote for class_j
|
|
257
|
+
return torch.argmax(votes, dim=1)
|
|
258
|
+
|
|
259
|
+
def predict_proba(self, X):
|
|
260
|
+
"""
|
|
261
|
+
Predicts class probabilities for the given input.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
X (torch.Tensor of shape (n_samples, n_features)): The input data.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
torch.Tensor of shape (n_samples, n_classes) or (n_samples,): The predicted probabilities.
|
|
268
|
+
"""
|
|
269
|
+
if not hasattr(self, "multiclass"):
|
|
270
|
+
raise NotFittedError("SVC.fit() must be called before predicting.")
|
|
271
|
+
|
|
272
|
+
if not self.multiclass:
|
|
273
|
+
scores = self._decision_function(self._kernel_matrix(self.X, X))
|
|
274
|
+
return 1 / (1 + torch.exp(-scores))
|
|
275
|
+
|
|
276
|
+
votes = torch.zeros((X.shape[0], self.n_classes))
|
|
277
|
+
if self.method == "ovr":
|
|
278
|
+
for i, classifier in enumerate(self.classifiers):
|
|
279
|
+
scores = classifier._decision_function(classifier._kernel_matrix(classifier.X, X))
|
|
280
|
+
proba = 1 / (1 + torch.exp(-scores))
|
|
281
|
+
votes[:, i] = proba
|
|
282
|
+
elif self.method == "ovo":
|
|
283
|
+
pairwise_counts = torch.zeros((self.n_classes,), dtype=torch.float32)
|
|
284
|
+
for classifier, class_i, class_j in self.classifiers:
|
|
285
|
+
scores = classifier._decision_function(classifier._kernel_matrix(classifier.X, X))
|
|
286
|
+
proba = 1 / (1 + torch.exp(-scores))
|
|
287
|
+
votes[:, class_i] += proba
|
|
288
|
+
votes[:, class_j] += 1 - proba
|
|
289
|
+
pairwise_counts[class_i] += 1
|
|
290
|
+
pairwise_counts[class_j] += 1
|
|
291
|
+
votes /= pairwise_counts
|
|
292
|
+
|
|
293
|
+
return votes / votes.sum(dim=1, keepdim=True)
|