scratchkit 0.2.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 (68) hide show
  1. mlscratch/__init__.py +56 -0
  2. mlscratch/__main__.py +118 -0
  3. mlscratch/bayesian/__init__.py +53 -0
  4. mlscratch/bayesian/bayesian_linear_regression.py +171 -0
  5. mlscratch/bayesian/bayesian_network.py +248 -0
  6. mlscratch/bayesian/bayesian_nn.py +315 -0
  7. mlscratch/bayesian/gaussian_process.py +207 -0
  8. mlscratch/bayesian/hmm.py +277 -0
  9. mlscratch/bayesian/init.py +52 -0
  10. mlscratch/bayesian/kalman_filter.py +182 -0
  11. mlscratch/bayesian/naive_bayes.py +209 -0
  12. mlscratch/metrics/__init__.py +59 -0
  13. mlscratch/metrics/classification.py +365 -0
  14. mlscratch/metrics/regression.py +79 -0
  15. mlscratch/neural/__init__.py +121 -0
  16. mlscratch/neural/attention.py +420 -0
  17. mlscratch/neural/autoencoder.py +543 -0
  18. mlscratch/neural/boltzmann.py +231 -0
  19. mlscratch/neural/cnn.py +593 -0
  20. mlscratch/neural/cvnn.py +322 -0
  21. mlscratch/neural/gan.py +364 -0
  22. mlscratch/neural/hopfield.py +193 -0
  23. mlscratch/neural/perceptron.py +398 -0
  24. mlscratch/neural/rbf_network.py +230 -0
  25. mlscratch/neural/recurrent.py +569 -0
  26. mlscratch/preprocessing/__init__.py +38 -0
  27. mlscratch/preprocessing/encoders.py +140 -0
  28. mlscratch/preprocessing/model_selection.py +119 -0
  29. mlscratch/preprocessing/polynomial.py +105 -0
  30. mlscratch/preprocessing/scalers.py +220 -0
  31. mlscratch/py.typed +0 -0
  32. mlscratch/reinforcement/__init__.py +59 -0
  33. mlscratch/reinforcement/ddpg.py +363 -0
  34. mlscratch/reinforcement/dqn.py +319 -0
  35. mlscratch/reinforcement/ppo.py +452 -0
  36. mlscratch/reinforcement/q_learning.py +352 -0
  37. mlscratch/reinforcement/sac.py +382 -0
  38. mlscratch/reinforcement/utils.py +594 -0
  39. mlscratch/supervised/__init__.py +76 -0
  40. mlscratch/supervised/_validation.py +50 -0
  41. mlscratch/supervised/adaboost.py +255 -0
  42. mlscratch/supervised/decision_tree.py +495 -0
  43. mlscratch/supervised/gradient_boosting.py +354 -0
  44. mlscratch/supervised/knn.py +234 -0
  45. mlscratch/supervised/lasso_regression.py +125 -0
  46. mlscratch/supervised/linear_models.py +459 -0
  47. mlscratch/supervised/linear_regression.py +197 -0
  48. mlscratch/supervised/logistic_regression.py +119 -0
  49. mlscratch/supervised/naive_bayes.py +113 -0
  50. mlscratch/supervised/random_forest.py +321 -0
  51. mlscratch/supervised/ridge_regression.py +93 -0
  52. mlscratch/supervised/svm.py +356 -0
  53. mlscratch/unsupervised/__init__.py +39 -0
  54. mlscratch/unsupervised/apriori.py +178 -0
  55. mlscratch/unsupervised/dbscan.py +141 -0
  56. mlscratch/unsupervised/gmm.py +204 -0
  57. mlscratch/unsupervised/hierarchical_clustering.py +137 -0
  58. mlscratch/unsupervised/ica.py +167 -0
  59. mlscratch/unsupervised/kmeans.py +135 -0
  60. mlscratch/unsupervised/kmedoids.py +133 -0
  61. mlscratch/unsupervised/pca.py +103 -0
  62. mlscratch/unsupervised/tsne.py +200 -0
  63. scratchkit-0.2.0.dist-info/METADATA +241 -0
  64. scratchkit-0.2.0.dist-info/RECORD +68 -0
  65. scratchkit-0.2.0.dist-info/WHEEL +5 -0
  66. scratchkit-0.2.0.dist-info/entry_points.txt +2 -0
  67. scratchkit-0.2.0.dist-info/licenses/LICENSE +201 -0
  68. scratchkit-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,230 @@
1
+ """
2
+ Radial Basis Function (RBF) Network
3
+ =====================================
4
+ A two-layer network where the hidden layer applies radial basis
5
+ functions (typically Gaussians) centred on prototype points, and the
6
+ output layer is a linear combination of hidden activations.
7
+
8
+ Architecture
9
+ -------------
10
+ φ_j(x) = exp(-‖x - c_j‖² / (2σ_j²)) hidden layer (RBF units)
11
+ ŷ(x) = Σ_j w_j φ_j(x) + b output layer (linear)
12
+
13
+ Training (two-stage, classic RBF approach)
14
+ ---------------------------------------------
15
+ 1. **Centre selection**: choose RBF centres c_j via k-means clustering
16
+ on the training inputs (or random subsampling).
17
+ 2. **Width selection**: σ_j set to the mean distance to the nearest
18
+ neighbouring centre, scaled by a width factor.
19
+ 3. **Output weights**: solve the linear least-squares problem
20
+ Φ w = y
21
+ in closed form via the pseudo-inverse, where Φ_ij = φ_j(x_i).
22
+
23
+ This closed-form approach avoids backpropagation for the output layer
24
+ entirely — only the (optional) k-means step involves iteration.
25
+
26
+ Supports both regression and classification (one-hot targets + argmax).
27
+
28
+ Reference
29
+ ----------
30
+ Broomhead, D. S., & Lowe, D. (1988). Radial basis functions, multi-variable
31
+ functional interpolation and adaptive networks. RSRE Memorandum.
32
+
33
+ Only numpy is used.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import numpy as np
39
+
40
+
41
+ class RBFNetwork:
42
+ """
43
+ Radial Basis Function Network.
44
+
45
+ Parameters
46
+ ----------
47
+ n_centers : int
48
+ Number of RBF units (hidden neurons).
49
+ width_scaling : float
50
+ Multiplier applied to the mean nearest-centre distance to obtain
51
+ each unit's σ. Larger → smoother, wider basis functions.
52
+ task : str
53
+ ``'regression'`` or ``'classification'``.
54
+ n_classes : int
55
+ Number of classes (classification only).
56
+ ridge : float
57
+ L2 regularisation added to the normal equations
58
+ (Φᵗ Φ + ridge·I) w = Φᵗ y — improves numerical stability.
59
+ random_state : int or None
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ n_centers: int = 10,
65
+ width_scaling: float = 1.0,
66
+ task: str = "regression",
67
+ n_classes: int = 2,
68
+ ridge: float = 1e-6,
69
+ random_state: int | None = None,
70
+ ) -> None:
71
+ if task not in {"regression", "classification"}:
72
+ raise ValueError("task must be 'regression' or 'classification'.")
73
+ self.n_centers = n_centers
74
+ self.width_scaling = width_scaling
75
+ self.task = task
76
+ self.n_classes = n_classes
77
+ self.ridge = ridge
78
+ self._rng = np.random.default_rng(random_state)
79
+
80
+ self.centers_: np.ndarray | None = None
81
+ self.sigmas_: np.ndarray | None = None
82
+ self.weights_: np.ndarray | None = None
83
+
84
+ # ------------------------------------------------------------------
85
+ # Centre / width selection
86
+ # ------------------------------------------------------------------
87
+
88
+ def _select_centers(self, X: np.ndarray) -> np.ndarray:
89
+ """
90
+ Simple k-means clustering to select RBF centres.
91
+
92
+ Falls back to random sampling of distinct points if
93
+ n_centers >= n_samples.
94
+ """
95
+ n_samples = len(X)
96
+ k = min(self.n_centers, n_samples)
97
+
98
+ # Initialise centres from random data points
99
+ idx = self._rng.choice(n_samples, k, replace=False)
100
+ centers = X[idx].astype(float).copy()
101
+
102
+ for _ in range(50): # k-means iterations
103
+ # Assign
104
+ dists = np.linalg.norm(X[:, np.newaxis, :] - centers[np.newaxis, :, :], axis=2)
105
+ labels = np.argmin(dists, axis=1)
106
+
107
+ new_centers = centers.copy()
108
+ for j in range(k):
109
+ members = X[labels == j]
110
+ if len(members) > 0:
111
+ new_centers[j] = members.mean(axis=0)
112
+
113
+ if np.allclose(new_centers, centers):
114
+ break
115
+ centers = new_centers
116
+
117
+ return centers
118
+
119
+ def _compute_widths(self, centers: np.ndarray) -> np.ndarray:
120
+ """σ_j = width_scaling × (mean distance to nearest other centre)."""
121
+ k = len(centers)
122
+ if k == 1:
123
+ return np.array([1.0])
124
+
125
+ dists = np.linalg.norm(
126
+ centers[:, np.newaxis, :] - centers[np.newaxis, :, :], axis=2
127
+ )
128
+ np.fill_diagonal(dists, np.inf)
129
+ nearest = dists.min(axis=1)
130
+ sigmas = self.width_scaling * nearest
131
+ sigmas[sigmas == 0] = 1.0 # guard against duplicate centres
132
+ return sigmas
133
+
134
+ # ------------------------------------------------------------------
135
+ # Feature mapping
136
+ # ------------------------------------------------------------------
137
+
138
+ def _rbf_features(self, X: np.ndarray) -> np.ndarray:
139
+ """
140
+ Compute Φ_ij = exp(-‖x_i - c_j‖² / (2σ_j²)).
141
+
142
+ Returns
143
+ -------
144
+ ndarray of shape (n_samples, n_centers)
145
+ """
146
+ dists_sq = np.sum(
147
+ (X[:, np.newaxis, :] - self.centers_[np.newaxis, :, :]) ** 2, axis=2
148
+ )
149
+ return np.exp(-dists_sq / (2.0 * self.sigmas_ ** 2))
150
+
151
+ # ------------------------------------------------------------------
152
+ # Public API
153
+ # ------------------------------------------------------------------
154
+
155
+ def fit(self, X: np.ndarray, y: np.ndarray) -> "RBFNetwork":
156
+ """
157
+ Fit the RBF network.
158
+
159
+ Parameters
160
+ ----------
161
+ X : ndarray of shape (n_samples, n_features)
162
+ y : ndarray of shape (n_samples,)
163
+ Continuous targets (regression) or integer class labels
164
+ (classification).
165
+
166
+ Returns
167
+ -------
168
+ self
169
+ """
170
+ self.centers_ = self._select_centers(X)
171
+ self.sigmas_ = self._compute_widths(self.centers_)
172
+
173
+ Phi = self._rbf_features(X) # (n, n_centers)
174
+ Phi_bias = np.column_stack([Phi, np.ones(len(Phi))]) # add bias column
175
+
176
+ if self.task == "classification":
177
+ n = len(y)
178
+ Y = np.zeros((n, self.n_classes))
179
+ Y[np.arange(n), y.astype(int)] = 1.0
180
+ else:
181
+ Y = y.reshape(-1, 1).astype(float)
182
+
183
+ # Ridge-regularised normal equations: (ΦᵗΦ + λI) w = Φᵗ Y
184
+ A = Phi_bias.T @ Phi_bias + self.ridge * np.eye(Phi_bias.shape[1])
185
+ B = Phi_bias.T @ Y
186
+ self.weights_ = np.linalg.solve(A, B) # (n_centers+1, n_outputs)
187
+
188
+ return self
189
+
190
+ def _predict_raw(self, X: np.ndarray) -> np.ndarray:
191
+ Phi = self._rbf_features(X)
192
+ Phi_bias = np.column_stack([Phi, np.ones(len(Phi))])
193
+ return Phi_bias @ self.weights_
194
+
195
+ def predict(self, X: np.ndarray) -> np.ndarray:
196
+ """
197
+ Predict targets (regression) or class labels (classification).
198
+
199
+ Returns
200
+ -------
201
+ ndarray of shape (n_samples,)
202
+ """
203
+ out = self._predict_raw(X)
204
+ if self.task == "classification":
205
+ return np.argmax(out, axis=1)
206
+ return out.ravel()
207
+
208
+ def predict_proba(self, X: np.ndarray) -> np.ndarray:
209
+ """
210
+ Return softmax-normalised class scores (classification only).
211
+
212
+ Returns
213
+ -------
214
+ ndarray of shape (n_samples, n_classes)
215
+ """
216
+ if self.task != "classification":
217
+ raise ValueError("predict_proba is only available for classification.")
218
+ raw = self._predict_raw(X)
219
+ e = np.exp(raw - raw.max(axis=1, keepdims=True))
220
+ return e / e.sum(axis=1, keepdims=True)
221
+
222
+ def transform(self, X: np.ndarray) -> np.ndarray:
223
+ """
224
+ Return the RBF feature representation Φ(X).
225
+
226
+ Returns
227
+ -------
228
+ ndarray of shape (n_samples, n_centers)
229
+ """
230
+ return self._rbf_features(X)