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.
- mlscratch/__init__.py +56 -0
- mlscratch/__main__.py +118 -0
- mlscratch/bayesian/__init__.py +53 -0
- mlscratch/bayesian/bayesian_linear_regression.py +171 -0
- mlscratch/bayesian/bayesian_network.py +248 -0
- mlscratch/bayesian/bayesian_nn.py +315 -0
- mlscratch/bayesian/gaussian_process.py +207 -0
- mlscratch/bayesian/hmm.py +277 -0
- mlscratch/bayesian/init.py +52 -0
- mlscratch/bayesian/kalman_filter.py +182 -0
- mlscratch/bayesian/naive_bayes.py +209 -0
- mlscratch/metrics/__init__.py +59 -0
- mlscratch/metrics/classification.py +365 -0
- mlscratch/metrics/regression.py +79 -0
- mlscratch/neural/__init__.py +121 -0
- mlscratch/neural/attention.py +420 -0
- mlscratch/neural/autoencoder.py +543 -0
- mlscratch/neural/boltzmann.py +231 -0
- mlscratch/neural/cnn.py +593 -0
- mlscratch/neural/cvnn.py +322 -0
- mlscratch/neural/gan.py +364 -0
- mlscratch/neural/hopfield.py +193 -0
- mlscratch/neural/perceptron.py +398 -0
- mlscratch/neural/rbf_network.py +230 -0
- mlscratch/neural/recurrent.py +569 -0
- mlscratch/preprocessing/__init__.py +38 -0
- mlscratch/preprocessing/encoders.py +140 -0
- mlscratch/preprocessing/model_selection.py +119 -0
- mlscratch/preprocessing/polynomial.py +105 -0
- mlscratch/preprocessing/scalers.py +220 -0
- mlscratch/py.typed +0 -0
- mlscratch/reinforcement/__init__.py +59 -0
- mlscratch/reinforcement/ddpg.py +363 -0
- mlscratch/reinforcement/dqn.py +319 -0
- mlscratch/reinforcement/ppo.py +452 -0
- mlscratch/reinforcement/q_learning.py +352 -0
- mlscratch/reinforcement/sac.py +382 -0
- mlscratch/reinforcement/utils.py +594 -0
- mlscratch/supervised/__init__.py +76 -0
- mlscratch/supervised/_validation.py +50 -0
- mlscratch/supervised/adaboost.py +255 -0
- mlscratch/supervised/decision_tree.py +495 -0
- mlscratch/supervised/gradient_boosting.py +354 -0
- mlscratch/supervised/knn.py +234 -0
- mlscratch/supervised/lasso_regression.py +125 -0
- mlscratch/supervised/linear_models.py +459 -0
- mlscratch/supervised/linear_regression.py +197 -0
- mlscratch/supervised/logistic_regression.py +119 -0
- mlscratch/supervised/naive_bayes.py +113 -0
- mlscratch/supervised/random_forest.py +321 -0
- mlscratch/supervised/ridge_regression.py +93 -0
- mlscratch/supervised/svm.py +356 -0
- mlscratch/unsupervised/__init__.py +39 -0
- mlscratch/unsupervised/apriori.py +178 -0
- mlscratch/unsupervised/dbscan.py +141 -0
- mlscratch/unsupervised/gmm.py +204 -0
- mlscratch/unsupervised/hierarchical_clustering.py +137 -0
- mlscratch/unsupervised/ica.py +167 -0
- mlscratch/unsupervised/kmeans.py +135 -0
- mlscratch/unsupervised/kmedoids.py +133 -0
- mlscratch/unsupervised/pca.py +103 -0
- mlscratch/unsupervised/tsne.py +200 -0
- scratchkit-0.2.0.dist-info/METADATA +241 -0
- scratchkit-0.2.0.dist-info/RECORD +68 -0
- scratchkit-0.2.0.dist-info/WHEEL +5 -0
- scratchkit-0.2.0.dist-info/entry_points.txt +2 -0
- scratchkit-0.2.0.dist-info/licenses/LICENSE +201 -0
- 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)
|