PyDLLib 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. DLL/Data/Metrics.py +626 -0
  2. DLL/Data/Preprocessing.py +384 -0
  3. DLL/Data/_DataReader.py +55 -0
  4. DLL/Data/__init__.py +1 -0
  5. DLL/DeepLearning/Callbacks/_BackUp.py +71 -0
  6. DLL/DeepLearning/Callbacks/_BaseCallback.py +43 -0
  7. DLL/DeepLearning/Callbacks/_EarlyStopping.py +102 -0
  8. DLL/DeepLearning/Callbacks/_ReduceLROnPlateau.py +90 -0
  9. DLL/DeepLearning/Callbacks/__init__.py +4 -0
  10. DLL/DeepLearning/Initialisers/_Initialiser.py +24 -0
  11. DLL/DeepLearning/Initialisers/_Kaiming_He.py +55 -0
  12. DLL/DeepLearning/Initialisers/_Xavier_Glorot.py +39 -0
  13. DLL/DeepLearning/Initialisers/__init__.py +2 -0
  14. DLL/DeepLearning/Layers/Activations/_Activation.py +23 -0
  15. DLL/DeepLearning/Layers/Activations/_ReLU.py +51 -0
  16. DLL/DeepLearning/Layers/Activations/_Sigmoid.py +51 -0
  17. DLL/DeepLearning/Layers/Activations/_SoftMax.py +83 -0
  18. DLL/DeepLearning/Layers/Activations/_Tanh.py +47 -0
  19. DLL/DeepLearning/Layers/Activations/__init__.py +4 -0
  20. DLL/DeepLearning/Layers/Regularisation/_BaseRegularisation.py +12 -0
  21. DLL/DeepLearning/Layers/Regularisation/_BatchNormalisation.py +106 -0
  22. DLL/DeepLearning/Layers/Regularisation/_Dropout.py +79 -0
  23. DLL/DeepLearning/Layers/Regularisation/_GroupNormalisation.py +112 -0
  24. DLL/DeepLearning/Layers/Regularisation/_InstanceNormalisation.py +50 -0
  25. DLL/DeepLearning/Layers/Regularisation/_LayerNormalisation.py +37 -0
  26. DLL/DeepLearning/Layers/Regularisation/__init__.py +5 -0
  27. DLL/DeepLearning/Layers/_Add.py +109 -0
  28. DLL/DeepLearning/Layers/_Attention.py +161 -0
  29. DLL/DeepLearning/Layers/_BaseLayer.py +87 -0
  30. DLL/DeepLearning/Layers/_Bidirectional.py +113 -0
  31. DLL/DeepLearning/Layers/_Conv2D.py +133 -0
  32. DLL/DeepLearning/Layers/_Dense.py +109 -0
  33. DLL/DeepLearning/Layers/_DenseKAN.py +184 -0
  34. DLL/DeepLearning/Layers/_Flatten.py +55 -0
  35. DLL/DeepLearning/Layers/_Identity.py +50 -0
  36. DLL/DeepLearning/Layers/_Input.py +11 -0
  37. DLL/DeepLearning/Layers/_LSTM.py +261 -0
  38. DLL/DeepLearning/Layers/_LayerList.py +98 -0
  39. DLL/DeepLearning/Layers/_MaxPooling2D.py +101 -0
  40. DLL/DeepLearning/Layers/_RNN.py +185 -0
  41. DLL/DeepLearning/Layers/_Reshape.py +52 -0
  42. DLL/DeepLearning/Layers/__init__.py +14 -0
  43. DLL/DeepLearning/Losses/_BCE.py +96 -0
  44. DLL/DeepLearning/Losses/_BaseLoss.py +18 -0
  45. DLL/DeepLearning/Losses/_CCE.py +87 -0
  46. DLL/DeepLearning/Losses/_Exponential.py +101 -0
  47. DLL/DeepLearning/Losses/_Huber.py +104 -0
  48. DLL/DeepLearning/Losses/_MAE.py +85 -0
  49. DLL/DeepLearning/Losses/_MSE.py +86 -0
  50. DLL/DeepLearning/Losses/__init__.py +6 -0
  51. DLL/DeepLearning/Model.py +265 -0
  52. DLL/DeepLearning/Optimisers/_ADADELTA.py +53 -0
  53. DLL/DeepLearning/Optimisers/_ADAGRAD.py +51 -0
  54. DLL/DeepLearning/Optimisers/_ADAM.py +65 -0
  55. DLL/DeepLearning/Optimisers/_BaseOptimiser.py +20 -0
  56. DLL/DeepLearning/Optimisers/_LBFGS.py +120 -0
  57. DLL/DeepLearning/Optimisers/_RMSPROP.py +62 -0
  58. DLL/DeepLearning/Optimisers/_SGD.py +44 -0
  59. DLL/DeepLearning/Optimisers/__init__.py +6 -0
  60. DLL/DeepLearning/__init__.py +0 -0
  61. DLL/Exceptions/_Exceptions.py +19 -0
  62. DLL/Exceptions/__init__.py +2 -0
  63. DLL/MachineLearning/SupervisedLearning/Calibration/_CalibratedClassifier.py +88 -0
  64. DLL/MachineLearning/SupervisedLearning/Calibration/_IsotonicRegression.py +114 -0
  65. DLL/MachineLearning/SupervisedLearning/Calibration/__init__.py +2 -0
  66. DLL/MachineLearning/SupervisedLearning/GaussianProcesses/_GaussianProcessRegressor.py +190 -0
  67. DLL/MachineLearning/SupervisedLearning/GaussianProcesses/__init__.py +1 -0
  68. DLL/MachineLearning/SupervisedLearning/Kernels.py +851 -0
  69. DLL/MachineLearning/SupervisedLearning/LinearModels/TimeSeries.py +147 -0
  70. DLL/MachineLearning/SupervisedLearning/LinearModels/_Elasticnet.py +161 -0
  71. DLL/MachineLearning/SupervisedLearning/LinearModels/_LassoRegression.py +157 -0
  72. DLL/MachineLearning/SupervisedLearning/LinearModels/_LinearRegression.py +81 -0
  73. DLL/MachineLearning/SupervisedLearning/LinearModels/_LogisticRegression.py +182 -0
  74. DLL/MachineLearning/SupervisedLearning/LinearModels/_RANSAC.py +97 -0
  75. DLL/MachineLearning/SupervisedLearning/LinearModels/_RidgeRegression.py +78 -0
  76. DLL/MachineLearning/SupervisedLearning/LinearModels/__init__.py +6 -0
  77. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_BernoulliNaiveBayes.py +115 -0
  78. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_GaussianNaiveBayes.py +111 -0
  79. DLL/MachineLearning/SupervisedLearning/NaiveBayes/_MultinomialNaiveBayes.py +115 -0
  80. DLL/MachineLearning/SupervisedLearning/NaiveBayes/__init__.py +3 -0
  81. DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsClassifier.py +115 -0
  82. DLL/MachineLearning/SupervisedLearning/Neighbors/_KNearestNeighborsRegressor.py +90 -0
  83. DLL/MachineLearning/SupervisedLearning/Neighbors/__init__.py +2 -0
  84. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVC.py +293 -0
  85. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/_SVR.py +100 -0
  86. DLL/MachineLearning/SupervisedLearning/SupportVectorMachines/__init__.py +2 -0
  87. DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostClassifier.py +179 -0
  88. DLL/MachineLearning/SupervisedLearning/Trees/_AdaBoostRegressor.py +141 -0
  89. DLL/MachineLearning/SupervisedLearning/Trees/_DecisionTree.py +219 -0
  90. DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingClassifier.py +207 -0
  91. DLL/MachineLearning/SupervisedLearning/Trees/_GradientBoostingRegressor.py +122 -0
  92. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMClassifier.py +288 -0
  93. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMRegressor.py +168 -0
  94. DLL/MachineLearning/SupervisedLearning/Trees/_LGBMTree.py +199 -0
  95. DLL/MachineLearning/SupervisedLearning/Trees/_RandomForest.py +96 -0
  96. DLL/MachineLearning/SupervisedLearning/Trees/_RegressionForest.py +75 -0
  97. DLL/MachineLearning/SupervisedLearning/Trees/_RegressionTree.py +148 -0
  98. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostClassifier.py +239 -0
  99. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostRegressor.py +131 -0
  100. DLL/MachineLearning/SupervisedLearning/Trees/_XGBoostTree.py +105 -0
  101. DLL/MachineLearning/SupervisedLearning/Trees/__init__.py +12 -0
  102. DLL/MachineLearning/SupervisedLearning/__init__.py +0 -0
  103. DLL/MachineLearning/UnsupervisedLearning/Clustering/_DBScan.py +87 -0
  104. DLL/MachineLearning/UnsupervisedLearning/Clustering/_GaussianMixtureModels.py +121 -0
  105. DLL/MachineLearning/UnsupervisedLearning/Clustering/_KMeans.py +120 -0
  106. DLL/MachineLearning/UnsupervisedLearning/Clustering/_SpectralClustering.py +105 -0
  107. DLL/MachineLearning/UnsupervisedLearning/Clustering/__init__.py +4 -0
  108. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_LDA.py +118 -0
  109. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_PCA.py +101 -0
  110. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_QDA.py +69 -0
  111. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_RobustPCA.py +143 -0
  112. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_TSNE.py +151 -0
  113. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/_UMAP.py +176 -0
  114. DLL/MachineLearning/UnsupervisedLearning/DimensionalityReduction/__init__.py +6 -0
  115. DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/_IsolationForest.py +175 -0
  116. DLL/MachineLearning/UnsupervisedLearning/OutlierDetection/__init__.py +1 -0
  117. DLL/MachineLearning/UnsupervisedLearning/__init__.py +0 -0
  118. DLL/MachineLearning/__init__.py +0 -0
  119. DLL/__init__.py +0 -0
  120. pydllib-1.0.dist-info/METADATA +104 -0
  121. pydllib-1.0.dist-info/RECORD +124 -0
  122. pydllib-1.0.dist-info/WHEEL +5 -0
  123. pydllib-1.0.dist-info/licenses/LICENSE +21 -0
  124. pydllib-1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,87 @@
1
+ import torch
2
+ from scipy.spatial import KDTree
3
+
4
+ from ....Exceptions import NotFittedError
5
+
6
+
7
+ class DBScan:
8
+ """
9
+ Density-based spatial clustering of applications with noise (DBSCAN) algorithm.
10
+
11
+ Args:
12
+ eps (float | int, optional): The distance inside of which datapoints are considered to be neighbours. Must be a positive real number. Defaults to 0.5.
13
+ min_samples (int, optional): The minimum number of neighbours for a non-leaf node. Must be a positive integer. Defaults to 5.
14
+
15
+ Note:
16
+ The algorithm is very sensitive to changes in `eps`. One should fine-tune the value of eps for optimal results.
17
+ """
18
+ def __init__(self, eps=0.5, min_samples=5):
19
+ if not isinstance(eps, int | float) or eps <= 0:
20
+ raise ValueError("eps must be a positive real number.")
21
+ if not isinstance(min_samples, int) or min_samples < 1:
22
+ raise ValueError("min_samples must be a positive integer.")
23
+
24
+ self.eps = eps
25
+ self.min_samples = min_samples
26
+
27
+ def fit(self, X):
28
+ """
29
+ Fits the algorithm to the given data. Recursively finds clusters by connecting near-by samples into the same cluster.
30
+
31
+ Args:
32
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
33
+ """
34
+ if not isinstance(X, torch.Tensor):
35
+ raise TypeError("The input matrix must be a PyTorch tensor.")
36
+ if X.ndim != 2:
37
+ raise ValueError("The input matrix must be a 2 dimensional tensor.")
38
+
39
+ X = X.numpy()
40
+ tree = KDTree(X)
41
+ n = len(X)
42
+ labels = torch.zeros(n)
43
+ current_class = 0
44
+
45
+ # loop over samples to find roots for clusters.
46
+ for i in range(n):
47
+ if labels[i] != 0:
48
+ continue
49
+
50
+ neighbour_indicies = tree.query_ball_point(X[i], r=self.eps)
51
+
52
+ if len(neighbour_indicies) < self.min_samples:
53
+ labels[i] = -1
54
+ else:
55
+ current_class += 1
56
+ labels[i] = current_class
57
+ self._grow_cluster(X, tree, labels, neighbour_indicies, current_class)
58
+ labels[labels != -1] -= 1 # transform the classes to start from 0.
59
+ self._labels = labels
60
+
61
+ def _grow_cluster(self, X, tree, labels, neighbour_indicies, current_class):
62
+ j = 0
63
+ while j < len(neighbour_indicies):
64
+ neighbour_index = neighbour_indicies[j]
65
+
66
+ if labels[neighbour_index] == -1:
67
+ labels[neighbour_index] = current_class
68
+ elif labels[neighbour_index] == 0:
69
+ labels[neighbour_index] = current_class
70
+ new_neighbours = tree.query_ball_point(X[neighbour_index], r=self.eps)
71
+ if len(new_neighbours) >= self.min_samples:
72
+ neighbour_indicies.extend(new_neighbours)
73
+ j += 1
74
+
75
+ def predict(self):
76
+ """
77
+ Applies the fitted DBScan model to the input data. Splits the training data into clusters.
78
+
79
+ Returns:
80
+ labels (torch.Tensor of shape (n_samples,)): The cluster corresponding to each sample. Label -1 indicates, that the algorithm considers that spesific samples as noise.
81
+ Raises:
82
+ NotFittedError: If the DBScan model has not been fitted before predicting.
83
+ """
84
+ if not hasattr(self, "_labels"):
85
+ raise NotFittedError("DBScan.fit() must be called before predicting.")
86
+
87
+ return self._labels
@@ -0,0 +1,121 @@
1
+ import torch
2
+
3
+ from ....Exceptions import NotFittedError
4
+
5
+
6
+ class GaussianMixture:
7
+ """
8
+ Gaussian mixture model. Fits `k` Gaussian distributions onto the data using maximum likelihood estimation.
9
+
10
+ Args:
11
+ k (int, optional): The number of Gaussian distributions (clusters). Must be a positive integer. Defaults to 3.
12
+ max_iters (int, optional): The maximum number of iterations. Must be a positive integer. Defaults to 10.
13
+ """
14
+ def __init__(self, k=3, max_iters=10, tol=1e-5):
15
+ if not isinstance(k, int) or k <= 0:
16
+ raise ValueError("k must be a positive integer.")
17
+ if not isinstance(max_iters, int) or max_iters <= 0:
18
+ raise ValueError("max_iters must be a positive integer.")
19
+
20
+ self.k = k
21
+ self.max_iters = max_iters
22
+ self.tol = tol
23
+
24
+ def _pdf(self, X):
25
+ # X (n_samples, n_features)
26
+ X_minus_mu = X[:, None, :] - self.mus
27
+ exponent = torch.einsum('nkd,kde,nke->nk', X_minus_mu, torch.linalg.inv(self.sigmas), X_minus_mu)
28
+ const = (2 * torch.pi) ** (-0.5 * self.n_features) * torch.linalg.det(self.sigmas) ** (-0.5)
29
+ return const * torch.exp(-0.5 * exponent)
30
+
31
+ def _expectation_step(self, X):
32
+ # Compute the posterior given prior, mean and covariance.
33
+ likelihood = self._pdf(X)
34
+ posterior = likelihood * self.prior[None, :]
35
+ norm_const = torch.sum(posterior, axis=1)
36
+ normalised_posterior = posterior / norm_const[:, None]
37
+ return normalised_posterior
38
+
39
+ def _maximization_step(self, X, posterior):
40
+ posterior_sum = torch.sum(posterior, axis=0)
41
+ new_prior = posterior_sum / len(X)
42
+
43
+ posterior_mus = (X.T @ posterior / posterior_sum).T
44
+
45
+ X_centered = X[:, None, :] - posterior_mus
46
+ X_weighted = X_centered * posterior[:, :, None]
47
+ posterior_sigmas = torch.einsum('nkd,nke->kde', X_weighted, X_centered) / posterior_sum[:, None, None]
48
+ return posterior_mus, posterior_sigmas, new_prior
49
+
50
+ def _log_likelihood(self, X):
51
+ return torch.log(torch.sum(self._pdf(X) * self.prior))
52
+
53
+ def fit(self, X, verbose=False):
54
+ """
55
+ Fits the k gaussian distributions to the data using maximum likelihood estimation.
56
+
57
+ Args:
58
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be clustered.
59
+ verbose (bool, optional): Determines if the likelihood should be calculated and printed during training. Must be a boolean. Defaults to False.
60
+ """
61
+ if not isinstance(X, torch.Tensor) or X.ndim != 2:
62
+ raise TypeError("X must be a 2 dimensional torch tensor.")
63
+ if not isinstance(verbose, bool):
64
+ raise TypeError("verbose must be a boolean.")
65
+
66
+ self.n_features = X.shape[1]
67
+ # self.mus = torch.randn(self.k, n_features, dtype=X.dtype, device=X.device)
68
+ random_indices = torch.randperm(len(X))[:self.k] # Random permutation of indices, pick first k
69
+ self.mus = X[random_indices].clone()
70
+ self.sigmas = torch.stack([torch.eye(self.n_features, dtype=X.dtype, device=X.device) for _ in range(self.k)])
71
+ self.prior = torch.ones(self.k, dtype=X.dtype, device=X.device)/self.k
72
+ if verbose: print(f"Initial log-likelihood: {self._log_likelihood(X)}")
73
+
74
+ for i in range(self.max_iters):
75
+ posterior = self._expectation_step(X)
76
+ mus, sigmas, prior = self._maximization_step(X, posterior)
77
+ if torch.all(mus - self.mus < self.tol) and torch.all(sigmas - self.sigmas < self.tol):
78
+ break
79
+ self.taus, self.mus, self.sigmas = prior, mus, sigmas
80
+ if verbose: print(f"Epoch: {i + 1} - Log-likelihood: {self._log_likelihood(X)}")
81
+
82
+ def predict(self, X):
83
+ """
84
+ Predicts the clusters of the data according to the fitted distributions.
85
+
86
+ Args:
87
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be clustered.
88
+
89
+ Returns:
90
+ torch.Tensor of shape (n_samples,): A tensor of labels corresponding to classes.
91
+ """
92
+ if not isinstance(X, torch.Tensor) or X.ndim != 2:
93
+ raise TypeError("X must be a 2 dimensional torch tensor.")
94
+ if X.shape[1] != self.n_features:
95
+ raise ValueError("X must have the same number of features as the training data.")
96
+ if not hasattr(self, "mus"):
97
+ raise NotFittedError("GaussianMixture.fit() must be fitted before predicting.")
98
+
99
+
100
+ posterior = self._expectation_step(X)
101
+ return torch.argmax(posterior, dim=1)
102
+
103
+ def predict_proba(self, X):
104
+ """
105
+ Predicts the probabilities of the data being in the fitted distributions.
106
+
107
+ Args:
108
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be clustered.
109
+
110
+ Returns:
111
+ torch.Tensor of shape (n_samples, k): A tensor of probabilities of the data being in the fitted distributions.
112
+ """
113
+ if not isinstance(X, torch.Tensor) or X.ndim != 2:
114
+ raise TypeError("X must be a 2 dimensional torch tensor.")
115
+ if X.shape[1] != self.n_features:
116
+ raise ValueError("X must have the same number of features as the training data.")
117
+ if not hasattr(self, "mus"):
118
+ raise NotFittedError("GaussianMixture.fit() must be fitted before predicting.")
119
+
120
+ posterior = self._expectation_step(X)
121
+ return posterior
@@ -0,0 +1,120 @@
1
+ import torch
2
+
3
+ from ....Exceptions import NotFittedError
4
+
5
+
6
+ class KMeansClustering:
7
+ """
8
+ KMeansClustering implements the K-Means clustering algorithm, which partitions data points into `k` clusters.
9
+
10
+ Args:
11
+ k (int, optional): The number of clusters. Defaults to 3. Must be a positive integer.
12
+ max_iters (int, optional): The maximum number of iterations for training the model. Defaults to 100. Must be a positive integer.
13
+ init ({`kmeans++`, `random`}, optional): The method for initialising the centroids. Defaults to `kmeans++`.
14
+ n_init (int, optional): The number of differently initialized centroids. Defaults to 10. Must be a positive integer.
15
+ Attributes:
16
+ centroids (torch.Tensor): The final chosen centroids.
17
+ inertia (float): The total squared distance to the nearest centroid.
18
+ """
19
+ def __init__(self, k=3, max_iters=100, init="kmeans++", n_init=10, tol=1e-5):
20
+ if not isinstance(k, int) or k < 1:
21
+ raise ValueError("k must be a positive integer.")
22
+ if not isinstance(max_iters, int) or max_iters < 1:
23
+ raise ValueError("max_iters must be a positive integer.")
24
+ if not isinstance(n_init, int) or n_init < 1:
25
+ raise ValueError("n_init must be a positive integer.")
26
+ if init not in ["random", "kmeans++"]:
27
+ raise ValueError('init must be one of "random" or "kmeans++".')
28
+ self.k = k
29
+ self.max_iters = max_iters
30
+ self.init = init
31
+ self.n_init = n_init
32
+ self.tol = tol
33
+
34
+ def fit(self, X):
35
+ """
36
+ Fits the KMeansClustering model to the input data by finding the best centroids.
37
+
38
+ Args:
39
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature. The number of samples must be atleast `k`.
40
+ Returns:
41
+ None
42
+ Raises:
43
+ TypeError: If the input matrix is not a PyTorch tensor.
44
+ ValueError: If the input matrix is not the correct shape.
45
+ """
46
+ if not isinstance(X, torch.Tensor):
47
+ raise TypeError("The input matrix must be a PyTorch tensor.")
48
+ if X.ndim != 2 or X.shape[0] < self.k:
49
+ raise ValueError("The input matrix must be a 2 dimensional tensor with atleast k samples.")
50
+ centroids = self._initialize_centroids(X) # (n_init, k, m)
51
+ best_centroid_index = -1
52
+ lowest_inertia = float("inf")
53
+ for i in range(self.n_init):
54
+ for _ in range(self.max_iters):
55
+ indicies = self._cluster(centroids[i], X)
56
+ old_centroids = centroids[i].clone()
57
+ centroids[i] = self._get_centroids(indicies, X)
58
+ if torch.norm(centroids[i] - old_centroids) < self.tol:
59
+ break
60
+ # select the centroids with the lowest inertia (total squared error)
61
+ inertia = ((X - centroids[i][indicies]) ** 2).sum().item()
62
+ if inertia < lowest_inertia:
63
+ lowest_inertia = inertia
64
+ best_centroid_index = i
65
+ else:
66
+ best_centroid_index = best_centroid_index
67
+ self.inertia = lowest_inertia
68
+ self.centroids = centroids[best_centroid_index]
69
+
70
+ def predict(self, X):
71
+ """
72
+ Applies the fitted KMeansClustering model to the input data, partitioning it to `k` clusters.
73
+
74
+ Args:
75
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be partitioned.
76
+ Returns:
77
+ labels (torch.Tensor of shape (n_samples,)): The cluster corresponding to each sample.
78
+ Raises:
79
+ NotFittedError: If the KMeansClustering model has not been fitted before predicting.
80
+ TypeError: If the input matrix is not a PyTorch tensor.
81
+ ValueError: If the input matrix is not the correct shape.
82
+ """
83
+ if not hasattr(self, "centroids"):
84
+ raise NotFittedError("KMeansClustering.fit() must be called before predicting.")
85
+ if not isinstance(X, torch.Tensor):
86
+ raise TypeError("The input matrix must be a PyTorch tensor.")
87
+ if X.ndim != 2 or X.shape[1] != self.centroids.shape[1]:
88
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
89
+ return self._cluster(self.centroids, X)
90
+
91
+ def _initialize_centroids(self, X):
92
+ if self.init == "random":
93
+ initial_centroid_indicies = torch.randint(0, len(X), (self.n_init, self.k))
94
+ centroids = torch.stack([X[initial_centroid_indicies[i]] for i in range(self.n_init)])
95
+ elif self.init == "kmeans++":
96
+ # https://en.wikipedia.org/wiki/K-means%2B%2B
97
+ centroids = torch.zeros((self.n_init, self.k, X.shape[1]), dtype=X.dtype)
98
+ centroids[:, 0] = X[torch.randint(0, len(X), (self.n_init,))]
99
+ for i in range(self.n_init):
100
+ for k in range(1, self.k):
101
+ # Calculate distance from points to the clostest of the chosen centroids
102
+ chosen_centroids = centroids[i, :k]
103
+ distances = torch.cdist(chosen_centroids, X, p=2).min(dim=0).values ** 2
104
+ probabilities = distances / distances.sum()
105
+ # Choose remaining points based on their distances
106
+ new_centroid_index = torch.multinomial(probabilities, num_samples=1)
107
+ centroids[i, k] = X[new_centroid_index]
108
+ return centroids
109
+
110
+ def _cluster(self, centroids, X):
111
+ distances = ((centroids.unsqueeze(0) - X.unsqueeze(1)) ** 2).sum(dim=2).sqrt() # (n, k)
112
+ indicies = torch.argmin(distances, dim=1)
113
+ return indicies
114
+
115
+ def _get_centroids(self, indicies, X):
116
+ new_centroids = torch.zeros((self.k, X.shape[1]))
117
+ for k in range(self.k):
118
+ X_cls = X[indicies == k]
119
+ new_centroids[k] = X_cls.mean(dim=0)
120
+ return new_centroids
@@ -0,0 +1,105 @@
1
+ import torch
2
+
3
+ from ...SupervisedLearning.Kernels import RBF, _Base
4
+ from . import KMeansClustering
5
+ from ....Exceptions import NotFittedError
6
+
7
+
8
+ class SpectralClustering:
9
+ """
10
+ SpectralClustering implements the spectral clustering algorithm, which partitions data points into `k` clusters.
11
+
12
+ Args:
13
+ kernel (:ref:`kernel_section_label`, optional): The similarity function for fitting the model. Defaults to RBF(correlation_length=0.1).
14
+ k (int, optional): The number of clusters. Defaults to 3. Must be a positive integer.
15
+ max_iters (int, optional): The maximum number of iterations for training the model. Defaults to 100. Must be a positive integer.
16
+ normalise (bool, optional): Determines if the laplacian matrix is calculated using L = I - sqrt(inv(D)) A sqrt(inv(D)) or just L = D - A. Defaults to True.
17
+ use_kmeans (bool, optional): Determines if the clustring in embedded space is done using kmeans or discretisation. Defaults to True.
18
+ **kwargs: Other arguments are passed into the `KMeansClustering` algorithm.
19
+
20
+ Note:
21
+ The result depends heavily on the chosen kernel function. Especially the correlation_length parameter should be fine-tuned for optimal performance.
22
+ """
23
+ def __init__(self, kernel=RBF(correlation_length=0.1), k=3, max_iters=100, normalise=True, use_kmeans=True, **kwargs):
24
+ if not isinstance(kernel, _Base):
25
+ raise ValueError("kernel must be from DLL.MachineLearning.SupervisedLearning.Kernels")
26
+ if not isinstance(k, int) or k < 1:
27
+ raise ValueError("k must be a positive integer.")
28
+ if not isinstance(max_iters, int) or max_iters < 1:
29
+ raise ValueError("max_iters must be a positive integer.")
30
+ if not isinstance(normalise, bool):
31
+ raise TypeError("normalise must be a boolean.")
32
+ if not isinstance(use_kmeans, bool):
33
+ raise TypeError("use_kmeans must be a boolean.")
34
+
35
+ self.kernel = kernel
36
+ self.k = k
37
+ self.max_iters = max_iters
38
+ self.normalise = normalise
39
+ self.use_kmeans = use_kmeans
40
+ if self.use_kmeans: self.kmeans = KMeansClustering(k=self.k, max_iters=self.max_iters, **kwargs)
41
+
42
+ def _transform_data(self, X):
43
+ similarity_matrix = self.kernel(X, X)
44
+ if self.normalise:
45
+ D_sqrt_inv = torch.diag(1.0 / torch.sqrt(similarity_matrix.sum(axis=1)))
46
+ laplacian_matrix = torch.eye(X.shape[0]) - D_sqrt_inv @ similarity_matrix @ D_sqrt_inv
47
+ else:
48
+ degree_matrix = torch.diag(similarity_matrix.sum(axis=1))
49
+ laplacian_matrix = degree_matrix - similarity_matrix
50
+
51
+ _, eigenvectors = torch.linalg.eigh(laplacian_matrix)
52
+ selected_eigenvectors = eigenvectors[:, :self.k]
53
+ return selected_eigenvectors
54
+
55
+ def _discretize_spectral_embedding(self, selected_eigenvectors):
56
+ selected_eigenvectors = selected_eigenvectors / torch.linalg.norm(selected_eigenvectors, axis=1, keepdims=True)
57
+ n_samples = len(selected_eigenvectors)
58
+ H = torch.zeros((n_samples, self.k), dtype=selected_eigenvectors.dtype)
59
+ indices = torch.argmax(selected_eigenvectors, axis=1)
60
+ H[torch.arange(n_samples), indices] = 1
61
+
62
+ for _ in range(self.max_iters):
63
+ U, _, Vt = torch.linalg.svd(selected_eigenvectors.T @ H)
64
+ Q = U @ Vt
65
+ H_new = torch.argmax(selected_eigenvectors @ Q, axis=1)
66
+ H = torch.zeros((n_samples, self.k), dtype=selected_eigenvectors.dtype)
67
+ H[torch.arange(n_samples), H_new] = 1
68
+
69
+ labels = torch.argmax(H, axis=1)
70
+ return labels
71
+
72
+ def fit(self, X):
73
+ """
74
+ Fits the algorithm to the given data. Transforms the data into the embedding space using the kernel function and clusters the data in the space.
75
+
76
+ Args:
77
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature. The number of samples must be atleast `k`.
78
+ """
79
+ if not isinstance(X, torch.Tensor):
80
+ raise TypeError("The input matrix must be a PyTorch tensor.")
81
+ if X.ndim != 2 or X.shape[0] < self.k:
82
+ raise ValueError("The input matrix must be a 2 dimensional tensor with atleast k samples.")
83
+
84
+ self.selected_eigenvectors = self._transform_data(X)
85
+ if self.use_kmeans:
86
+ self.kmeans.fit(self.selected_eigenvectors)
87
+ else:
88
+ self._labels = self._discretize_spectral_embedding(self.selected_eigenvectors)
89
+
90
+ def predict(self):
91
+ """
92
+ Applies the fitted SpectralClustering model to the input data, partitioning it to `k` clusters.
93
+
94
+ Returns:
95
+ labels (torch.Tensor of shape (n_samples,)): The cluster corresponding to each sample.
96
+ Raises:
97
+ NotFittedError: If the SpectralClustering model has not been fitted before predicting.
98
+ """
99
+ if not hasattr(self, "selected_eigenvectors"):
100
+ raise NotFittedError("SpectralClustering.fit() must be called before predicting.")
101
+
102
+ if self.use_kmeans:
103
+ return self.kmeans.predict(self.selected_eigenvectors)
104
+ else:
105
+ return self._labels
@@ -0,0 +1,4 @@
1
+ from ._KMeans import KMeansClustering
2
+ from ._GaussianMixtureModels import GaussianMixture
3
+ from ._SpectralClustering import SpectralClustering
4
+ from ._DBScan import DBScan
@@ -0,0 +1,118 @@
1
+ import torch
2
+
3
+ from ....Exceptions import NotFittedError
4
+
5
+
6
+ class LDA:
7
+ """
8
+ Linear discriminant analysis (LDA) class for dimensionality reduction.
9
+
10
+ Args:
11
+ n_components (int): Number of principal components to keep. The number must be a positive integer.
12
+ Attributes:
13
+ components (torch.Tensor): Components extracted from the data.
14
+ n_features (int): The number of features in the input.
15
+ """
16
+ def __init__(self, n_components=2):
17
+ if not isinstance(n_components, int) or n_components < 1:
18
+ raise ValueError("n_components must be a positive integer.")
19
+
20
+ self.n_components = n_components
21
+
22
+ def fit(self, X, y):
23
+ """
24
+ Fits the LDA model to the input data by calculating the components.
25
+
26
+ Args:
27
+ X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature.
28
+ Raises:
29
+ TypeError: If the input matrix is not a PyTorch tensor.
30
+ ValueError: If the input matrix is not the correct shape.
31
+ """
32
+ if not isinstance(X, torch.Tensor):
33
+ raise TypeError("The input matrix must be a PyTorch tensor.")
34
+ if X.ndim != 2 or X.shape[0] == 1:
35
+ raise ValueError("The input matrix must be a 2 dimensional tensor with atleast 2 samples.")
36
+
37
+ self.classes = torch.unique(y)
38
+ self.n_features = X.shape[1]
39
+ X_mean = torch.mean(X, dim=0)
40
+
41
+ Sw = torch.zeros((self.n_features, self.n_features), dtype=X.dtype)
42
+ Sb = torch.zeros((self.n_features, self.n_features), dtype=X.dtype)
43
+
44
+ self.class_means = []
45
+
46
+ for current_class in self.classes:
47
+ X_c = X[y == current_class]
48
+ C_mean = torch.mean(X_c, dim=0)
49
+ self.class_means.append(C_mean)
50
+ Sw += torch.cov(X_c.T, correction=0) * len(X_c)
51
+ mean_diff = (C_mean - X_mean).reshape(-1, 1)
52
+ Sb += len(X_c) * (mean_diff @ mean_diff.T)
53
+
54
+ A = torch.linalg.lstsq(Sw, Sb).solution
55
+ eig_vals, eig_vecs = torch.linalg.eig(A)
56
+ indicies = torch.argsort(eig_vals.real, descending=True)
57
+ self.components = eig_vecs.real.T[indicies][:self.n_components].T
58
+
59
+ def transform(self, X):
60
+ """
61
+ Applies the fitted LDA model to the input data, transforming it into the reduced feature space.
62
+
63
+ Args:
64
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be transformed.
65
+ Returns:
66
+ X_new (torch.Tensor of shape (n_samples, n_components)): The data transformed into the component space.
67
+ Raises:
68
+ NotFittedError: If the LDA model has not been fitted before transforming.
69
+ TypeError: If the input matrix is not a PyTorch tensor.
70
+ ValueError: If the input matrix is not the correct shape.
71
+ """
72
+ if not hasattr(self, "components"):
73
+ raise NotFittedError("LDA.fit() must be called before transforming.")
74
+ if not isinstance(X, torch.Tensor):
75
+ raise TypeError("The input matrix must be a PyTorch tensor.")
76
+ if X.ndim != 2 or X.shape[1] != self.n_features:
77
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
78
+
79
+ return X @ self.components
80
+
81
+ def fit_transform(self, X, y):
82
+ """
83
+ First finds the components of X and then transforms X to fitted space.
84
+
85
+ Args:
86
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be transformed.
87
+ Returns:
88
+ X_new (torch.Tensor of shape (n_samples, n_components)): The data transformed into the component space.
89
+ """
90
+ self.fit(X, y)
91
+ return self.transform(X)
92
+
93
+ def predict(self, X):
94
+ """
95
+ Applies the fitted LDA model to the input data, predicting the correct classes.
96
+
97
+ Args:
98
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be transformed.
99
+ Returns:
100
+ y (torch.Tensor of shape (n_samples,)): The predicted labels.
101
+ Raises:
102
+ NotFittedError: If the LDA model has not been fitted before transforming.
103
+ """
104
+ if not hasattr(self, "components"):
105
+ raise NotFittedError("LDA.fit() must be called before predicting.")
106
+
107
+ X_projected = self.transform(X)
108
+ class_means_projected = torch.stack([self.transform(C_mean.unsqueeze(0)) for C_mean in self.class_means])
109
+
110
+ min_dists = torch.full((len(X),), torch.inf, dtype=X.dtype)
111
+ y = torch.zeros_like(min_dists, dtype=X.dtype)
112
+
113
+ for i, C_mean in enumerate(class_means_projected):
114
+ class_dists = torch.sum((X_projected - C_mean) ** 2, dim=1)
115
+ mask = class_dists < min_dists
116
+ y[mask] = self.classes[i]
117
+ min_dists[mask] = class_dists[mask]
118
+ return y
@@ -0,0 +1,101 @@
1
+ import torch
2
+
3
+ from ....Exceptions import NotFittedError
4
+
5
+
6
+ class PCA:
7
+ """
8
+ Principal Component Analysis (PCA) class for dimensionality reduction.
9
+
10
+ Args:
11
+ n_components (int): Number of principal components to keep. The number must be a positive integer.
12
+ Attributes:
13
+ components (torch.Tensor): Principal components extracted from the data.
14
+ explained_variance (torch.Tensor): Variance explained by the selected components.
15
+ """
16
+ def __init__(self, n_components=2, epsilon=1e-10):
17
+ if not isinstance(n_components, int) or n_components < 1:
18
+ raise ValueError("n_components must be a positive integer.")
19
+ self.n_components = n_components
20
+ self.epsilon = epsilon
21
+
22
+ def fit(self, X, normalize=True, from_covariance=False):
23
+ """
24
+ Fits the PCA model to the input data by calculating the principal components.
25
+
26
+ The input data is always centered and if `normalize=True`, also normalized so that the standard deviation is 1 along each axis.
27
+
28
+ Args:
29
+ X (torch.Tensor of shape (n_samples, n_features) or (n_features, n_features)): The input data, where each row is a sample and each column is a feature if from_covariance is set to False and the covariance matrix otherwise.
30
+ normalize (bool, optional): Whether to normalize the data before computing the PCA. Defaults to True. Is ignored if from_covariance is True.
31
+ from_covariance (bool, optional): Determines if X is considered as the data matrix or the covariance matrix. Must be a boolean. Defaults to False.
32
+ Returns:
33
+ None
34
+ Raises:
35
+ TypeError: If the input matrix is not a PyTorch tensor or if the `normalize` parameter is not boolean.
36
+ ValueError: If the input matrix is not the correct shape.
37
+ """
38
+ if not isinstance(X, torch.Tensor):
39
+ raise TypeError("The input matrix must be a PyTorch tensor.")
40
+ if not isinstance(normalize, bool):
41
+ raise TypeError("The normalize parameter must be a boolean.")
42
+ if X.ndim != 2 or (X.shape[0] == 1 and not from_covariance) or (from_covariance and (X.shape[0] != X.shape[1])):
43
+ if from_covariance:
44
+ raise ValueError("The input matrix must be 2 dimensional, with the shape (n_features, n_features).")
45
+ raise ValueError("The input matrix must be a 2 dimensional tensor with atleast 2 samples.")
46
+
47
+ self.from_covariance = from_covariance
48
+ if not from_covariance:
49
+ self.normalize = normalize
50
+ self.mean = X.mean(dim=0)
51
+ X = X - self.mean
52
+ if self.normalize:
53
+ self.standard_deviation = X.std(dim=0, unbiased=True)
54
+ X = X / (self.standard_deviation + self.epsilon)
55
+ covariance = torch.cov(X.T)
56
+ else:
57
+ covariance = X
58
+ eig_vals, eig_vecs = torch.linalg.eig(covariance)
59
+ indicies = torch.argsort(eig_vals.real, descending=True)
60
+ self.components = eig_vecs.real.T[indicies][:self.n_components]
61
+ self.explained_variance = eig_vals.real[indicies][:self.n_components]
62
+
63
+ def transform(self, X):
64
+ """
65
+ Applies the fitted PCA model to the input data, transforming it into the reduced feature space. If covariance matrix was used when fitting, the input is assumed to be normalized appropriately.
66
+
67
+ Args:
68
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be transformed.
69
+ Returns:
70
+ X_new (torch.Tensor of shape (n_samples, n_components)): The data transformed into the principal component space.
71
+ Raises:
72
+ NotFittedError: If the PCA model has not been fitted before transforming.
73
+ TypeError: If the input matrix is not a PyTorch tensor.
74
+ ValueError: If the input matrix is not the correct shape.
75
+ """
76
+ if not hasattr(self, "from_covariance"):
77
+ raise NotFittedError("PCA.fit() must be called before transforming.")
78
+ if not isinstance(X, torch.Tensor):
79
+ raise TypeError("The input matrix must be a PyTorch tensor.")
80
+ if X.ndim != 2 or X.shape[1] != self.components.shape[1]:
81
+ raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.")
82
+
83
+ if not self.from_covariance:
84
+ X = (X - self.mean)
85
+ if self.normalize:
86
+ X = X / (self.standard_deviation + self.epsilon)
87
+ return X @ self.components.T
88
+
89
+ def fit_transform(self, X, normalize=True):
90
+ """
91
+ First finds the principal components of X and then transforms X to fitted space.
92
+
93
+ Args:
94
+ X (torch.Tensor of shape (n_samples, n_features)): The input data to be transformed.
95
+ normalize (bool, optional): Whether to normalize the data before computing the PCA. Defaults to True.
96
+ Returns:
97
+ X_new (torch.Tensor of shape (n_samples, n_components)): The data transformed into the principal component space.
98
+ """
99
+
100
+ self.fit(X, normalize=normalize)
101
+ return self.transform(X)