vectorphile 0.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 (53) hide show
  1. vectorphile/__init__.py +154 -0
  2. vectorphile/amp/__init__.py +52 -0
  3. vectorphile/autograd/__init__.py +13 -0
  4. vectorphile/autograd/engine.py +208 -0
  5. vectorphile/core/__init__.py +21 -0
  6. vectorphile/core/tensor.py +696 -0
  7. vectorphile/data/__init__.py +23 -0
  8. vectorphile/data/frame.py +537 -0
  9. vectorphile/data/groupby.py +98 -0
  10. vectorphile/data/index.py +86 -0
  11. vectorphile/data/series.py +228 -0
  12. vectorphile/fft/__init__.py +34 -0
  13. vectorphile/linalg/__init__.py +63 -0
  14. vectorphile/ml/__init__.py +44 -0
  15. vectorphile/ml/boost/__init__.py +133 -0
  16. vectorphile/ml/cluster.py +218 -0
  17. vectorphile/ml/decomposition.py +161 -0
  18. vectorphile/ml/ensemble.py +209 -0
  19. vectorphile/ml/linear.py +250 -0
  20. vectorphile/ml/metrics.py +148 -0
  21. vectorphile/ml/model_selection.py +171 -0
  22. vectorphile/ml/naive_bayes.py +113 -0
  23. vectorphile/ml/neighbors.py +95 -0
  24. vectorphile/ml/preprocessing.py +202 -0
  25. vectorphile/ml/svm.py +165 -0
  26. vectorphile/ml/tree.py +155 -0
  27. vectorphile/nn/__init__.py +20 -0
  28. vectorphile/nn/activations.py +97 -0
  29. vectorphile/nn/layers.py +231 -0
  30. vectorphile/nn/losses.py +172 -0
  31. vectorphile/nn/module.py +97 -0
  32. vectorphile/nn/sequential.py +47 -0
  33. vectorphile/optim/__init__.py +12 -0
  34. vectorphile/optim/adagrad.py +31 -0
  35. vectorphile/optim/adam.py +71 -0
  36. vectorphile/optim/lr_scheduler.py +110 -0
  37. vectorphile/optim/optimizer.py +30 -0
  38. vectorphile/optim/rmsprop.py +34 -0
  39. vectorphile/optim/sgd.py +38 -0
  40. vectorphile/optimize/__init__.py +97 -0
  41. vectorphile/random/__init__.py +64 -0
  42. vectorphile/signal/__init__.py +67 -0
  43. vectorphile/sparse/__init__.py +129 -0
  44. vectorphile/stats/__init__.py +90 -0
  45. vectorphile/utils/__init__.py +3 -0
  46. vectorphile/utils/data/__init__.py +4 -0
  47. vectorphile/utils/data/dataloader.py +47 -0
  48. vectorphile/utils/data/dataset.py +29 -0
  49. vectorphile-0.1.0.dist-info/METADATA +208 -0
  50. vectorphile-0.1.0.dist-info/RECORD +53 -0
  51. vectorphile-0.1.0.dist-info/WHEEL +5 -0
  52. vectorphile-0.1.0.dist-info/licenses/LICENSE +201 -0
  53. vectorphile-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,250 @@
1
+ """Linear models."""
2
+ import numpy as np
3
+ from typing import Optional
4
+
5
+
6
+ class _BaseLinear:
7
+ def __init__(self):
8
+ self.coef_: Optional[np.ndarray] = None
9
+ self.intercept_: float = 0.0
10
+
11
+ def predict(self, X) -> np.ndarray:
12
+ X = np.array(X)
13
+ return X @ self.coef_ + self.intercept_
14
+
15
+ def score(self, X, y) -> float:
16
+ y = np.array(y)
17
+ pred = self.predict(X)
18
+ ss_res = np.sum((y - pred) ** 2)
19
+ ss_tot = np.sum((y - y.mean()) ** 2)
20
+ return 1 - ss_res / (ss_tot + 1e-12)
21
+
22
+
23
+ class LinearRegression(_BaseLinear):
24
+ def __init__(self, fit_intercept=True):
25
+ super().__init__()
26
+ self.fit_intercept = fit_intercept
27
+
28
+ def fit(self, X, y) -> 'LinearRegression':
29
+ X, y = np.array(X, dtype=float), np.array(y, dtype=float)
30
+ if self.fit_intercept:
31
+ X = np.c_[np.ones(len(X)), X]
32
+ sol, _, _, _ = np.linalg.lstsq(X, y, rcond=None)
33
+ if self.fit_intercept:
34
+ self.intercept_, self.coef_ = float(sol[0]), sol[1:]
35
+ else:
36
+ self.coef_ = sol
37
+ return self
38
+
39
+
40
+ class Ridge(_BaseLinear):
41
+ def __init__(self, alpha=1.0, fit_intercept=True):
42
+ super().__init__()
43
+ self.alpha = alpha
44
+ self.fit_intercept = fit_intercept
45
+
46
+ def fit(self, X, y) -> 'Ridge':
47
+ X, y = np.array(X, dtype=float), np.array(y, dtype=float)
48
+ if self.fit_intercept:
49
+ X_mean, y_mean = X.mean(0), y.mean()
50
+ Xc = X - X_mean
51
+ sol = np.linalg.solve(Xc.T @ Xc + self.alpha * np.eye(Xc.shape[1]), Xc.T @ (y - y_mean))
52
+ self.coef_ = sol
53
+ self.intercept_ = float(y_mean - X_mean @ sol)
54
+ else:
55
+ sol = np.linalg.solve(X.T @ X + self.alpha * np.eye(X.shape[1]), X.T @ y)
56
+ self.coef_ = sol
57
+ return self
58
+
59
+
60
+ class Lasso(_BaseLinear):
61
+ def __init__(self, alpha=1.0, max_iter=1000, tol=1e-4, fit_intercept=True):
62
+ super().__init__()
63
+ self.alpha = alpha
64
+ self.max_iter = max_iter
65
+ self.tol = tol
66
+ self.fit_intercept = fit_intercept
67
+
68
+ def fit(self, X, y) -> 'Lasso':
69
+ X, y = np.array(X, dtype=float), np.array(y, dtype=float)
70
+ if self.fit_intercept:
71
+ X_mean, y_mean = X.mean(0), y.mean()
72
+ X, y = X - X_mean, y - y_mean
73
+ n, p = X.shape
74
+ coef = np.zeros(p)
75
+ for _ in range(self.max_iter):
76
+ coef_old = coef.copy()
77
+ for j in range(p):
78
+ r = y - X @ coef + X[:, j] * coef[j]
79
+ rho = X[:, j] @ r
80
+ z = X[:, j] @ X[:, j]
81
+ if rho < -self.alpha * n / 2:
82
+ coef[j] = (rho + self.alpha * n / 2) / z
83
+ elif rho > self.alpha * n / 2:
84
+ coef[j] = (rho - self.alpha * n / 2) / z
85
+ else:
86
+ coef[j] = 0.0
87
+ if np.max(np.abs(coef - coef_old)) < self.tol:
88
+ break
89
+ self.coef_ = coef
90
+ if self.fit_intercept:
91
+ self.intercept_ = float(y_mean - X_mean @ coef)
92
+ return self
93
+
94
+
95
+ class ElasticNet(_BaseLinear):
96
+ def __init__(self, alpha=1.0, l1_ratio=0.5, max_iter=1000, tol=1e-4, fit_intercept=True):
97
+ super().__init__()
98
+ self.alpha = alpha
99
+ self.l1_ratio = l1_ratio
100
+ self.max_iter = max_iter
101
+ self.tol = tol
102
+ self.fit_intercept = fit_intercept
103
+
104
+ def fit(self, X, y) -> 'ElasticNet':
105
+ X, y = np.array(X, dtype=float), np.array(y, dtype=float)
106
+ if self.fit_intercept:
107
+ X_mean, y_mean = X.mean(0), y.mean()
108
+ X, y = X - X_mean, y - y_mean
109
+ n, p = X.shape
110
+ l1 = self.alpha * self.l1_ratio
111
+ l2 = self.alpha * (1 - self.l1_ratio)
112
+ coef = np.zeros(p)
113
+ for _ in range(self.max_iter):
114
+ coef_old = coef.copy()
115
+ for j in range(p):
116
+ r = y - X @ coef + X[:, j] * coef[j]
117
+ rho = X[:, j] @ r
118
+ z = X[:, j] @ X[:, j] + l2 * n
119
+ thresh = l1 * n / 2
120
+ if rho < -thresh:
121
+ coef[j] = (rho + thresh) / z
122
+ elif rho > thresh:
123
+ coef[j] = (rho - thresh) / z
124
+ else:
125
+ coef[j] = 0.0
126
+ if np.max(np.abs(coef - coef_old)) < self.tol:
127
+ break
128
+ self.coef_ = coef
129
+ if self.fit_intercept:
130
+ self.intercept_ = float(y_mean - X_mean @ coef)
131
+ return self
132
+
133
+
134
+ class LogisticRegression:
135
+ def __init__(self, C=1.0, max_iter=200, tol=1e-4, fit_intercept=True, multi_class='ovr'):
136
+ self.C = C
137
+ self.max_iter = max_iter
138
+ self.tol = tol
139
+ self.fit_intercept = fit_intercept
140
+ self.multi_class = multi_class
141
+ self.coef_: Optional[np.ndarray] = None
142
+ self.intercept_: Optional[np.ndarray] = None
143
+ self.classes_: Optional[np.ndarray] = None
144
+
145
+ def _sigmoid(self, z):
146
+ return 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500)))
147
+
148
+ def _fit_binary(self, X, y, lr=0.1):
149
+ n, p = X.shape
150
+ w = np.zeros(p + (1 if self.fit_intercept else 0))
151
+ for _ in range(self.max_iter):
152
+ if self.fit_intercept:
153
+ z = X @ w[1:] + w[0]
154
+ else:
155
+ z = X @ w
156
+ prob = self._sigmoid(z)
157
+ error = prob - y
158
+ if self.fit_intercept:
159
+ dw = np.concatenate([[error.mean()], X.T @ error / n + w[1:] / (self.C * n)])
160
+ else:
161
+ dw = X.T @ error / n + w / (self.C * n)
162
+ w -= lr * dw
163
+ if np.max(np.abs(dw)) < self.tol:
164
+ break
165
+ return w
166
+
167
+ def fit(self, X, y) -> 'LogisticRegression':
168
+ X, y = np.array(X, dtype=float), np.array(y)
169
+ self.classes_ = np.unique(y)
170
+ if len(self.classes_) == 2:
171
+ y_bin = (y == self.classes_[1]).astype(float)
172
+ w = self._fit_binary(X, y_bin)
173
+ if self.fit_intercept:
174
+ self.coef_ = w[1:][np.newaxis]
175
+ self.intercept_ = np.array([w[0]])
176
+ else:
177
+ self.coef_ = w[np.newaxis]
178
+ self.intercept_ = np.array([0.0])
179
+ else:
180
+ self.coef_ = []
181
+ self.intercept_ = []
182
+ for cls in self.classes_:
183
+ y_bin = (y == cls).astype(float)
184
+ w = self._fit_binary(X, y_bin)
185
+ if self.fit_intercept:
186
+ self.coef_.append(w[1:])
187
+ self.intercept_.append(w[0])
188
+ else:
189
+ self.coef_.append(w)
190
+ self.intercept_.append(0.0)
191
+ self.coef_ = np.array(self.coef_)
192
+ self.intercept_ = np.array(self.intercept_)
193
+ return self
194
+
195
+ def predict_proba(self, X) -> np.ndarray:
196
+ X = np.array(X, dtype=float)
197
+ logits = X @ self.coef_.T + self.intercept_
198
+ probs = self._sigmoid(logits)
199
+ if probs.ndim == 1 or probs.shape[1] == 1:
200
+ p = probs.flatten()
201
+ return np.c_[1 - p, p]
202
+ probs = probs / probs.sum(axis=1, keepdims=True)
203
+ return probs
204
+
205
+ def predict(self, X) -> np.ndarray:
206
+ proba = self.predict_proba(X)
207
+ return self.classes_[np.argmax(proba, axis=1)]
208
+
209
+ def score(self, X, y) -> float:
210
+ return float(np.mean(self.predict(X) == np.array(y)))
211
+
212
+
213
+ class Perceptron:
214
+ def __init__(self, max_iter=1000, tol=1e-3, eta=1.0, fit_intercept=True):
215
+ self.max_iter = max_iter
216
+ self.tol = tol
217
+ self.eta = eta
218
+ self.fit_intercept = fit_intercept
219
+ self.coef_: Optional[np.ndarray] = None
220
+ self.intercept_: float = 0.0
221
+
222
+ def fit(self, X, y) -> 'Perceptron':
223
+ X, y = np.array(X, dtype=float), np.array(y)
224
+ classes = np.unique(y)
225
+ y_bin = np.where(y == classes[0], -1, 1)
226
+ n, p = X.shape
227
+ w = np.zeros(p)
228
+ b = 0.0
229
+ for _ in range(self.max_iter):
230
+ errors = 0
231
+ for xi, yi in zip(X, y_bin):
232
+ pred = np.sign(np.dot(w, xi) + b)
233
+ if pred != yi:
234
+ w += self.eta * yi * xi
235
+ b += self.eta * yi
236
+ errors += 1
237
+ if errors == 0:
238
+ break
239
+ self.coef_ = w
240
+ self.intercept_ = b
241
+ self.classes_ = classes
242
+ return self
243
+
244
+ def predict(self, X) -> np.ndarray:
245
+ X = np.array(X, dtype=float)
246
+ raw = np.sign(X @ self.coef_ + self.intercept_)
247
+ return np.where(raw <= 0, self.classes_[0], self.classes_[1])
248
+
249
+ def score(self, X, y) -> float:
250
+ return float(np.mean(self.predict(X) == np.array(y)))
@@ -0,0 +1,148 @@
1
+ """Evaluation metrics."""
2
+ import numpy as np
3
+ from typing import Optional
4
+
5
+
6
+ def accuracy_score(y_true, y_pred, normalize=True) -> float:
7
+ y_true, y_pred = np.array(y_true), np.array(y_pred)
8
+ correct = (y_true == y_pred).sum()
9
+ return float(correct / len(y_true)) if normalize else int(correct)
10
+
11
+
12
+ def confusion_matrix(y_true, y_pred, labels=None) -> np.ndarray:
13
+ y_true, y_pred = np.array(y_true), np.array(y_pred)
14
+ classes = labels if labels is not None else np.unique(np.concatenate([y_true, y_pred]))
15
+ K = len(classes)
16
+ cm = np.zeros((K, K), dtype=int)
17
+ class_map = {c: i for i, c in enumerate(classes)}
18
+ for t, p in zip(y_true, y_pred):
19
+ cm[class_map[t], class_map[p]] += 1
20
+ return cm
21
+
22
+
23
+ def precision_score(y_true, y_pred, average='binary', labels=None, zero_division=0) -> float:
24
+ y_true, y_pred = np.array(y_true), np.array(y_pred)
25
+ classes = labels or np.unique(np.concatenate([y_true, y_pred]))
26
+ precisions = []
27
+ for c in classes:
28
+ tp = ((y_pred == c) & (y_true == c)).sum()
29
+ fp = ((y_pred == c) & (y_true != c)).sum()
30
+ denom = tp + fp
31
+ precisions.append(tp / denom if denom > 0 else zero_division)
32
+ if average == 'micro':
33
+ tps = sum((y_pred == c) & (y_true == c) for c in classes).sum()
34
+ fps = sum((y_pred == c) & (y_true != c) for c in classes).sum()
35
+ return float(tps / (tps + fps)) if (tps + fps) > 0 else zero_division
36
+ if average == 'macro':
37
+ return float(np.mean(precisions))
38
+ if average == 'weighted':
39
+ weights = [(y_true == c).sum() for c in classes]
40
+ return float(np.average(precisions, weights=weights))
41
+ return float(precisions[0]) if len(precisions) == 1 else float(precisions[-1])
42
+
43
+
44
+ def recall_score(y_true, y_pred, average='binary', labels=None, zero_division=0) -> float:
45
+ y_true, y_pred = np.array(y_true), np.array(y_pred)
46
+ classes = labels or np.unique(np.concatenate([y_true, y_pred]))
47
+ recalls = []
48
+ for c in classes:
49
+ tp = ((y_pred == c) & (y_true == c)).sum()
50
+ fn = ((y_pred != c) & (y_true == c)).sum()
51
+ denom = tp + fn
52
+ recalls.append(tp / denom if denom > 0 else zero_division)
53
+ if average == 'macro':
54
+ return float(np.mean(recalls))
55
+ if average == 'weighted':
56
+ weights = [(y_true == c).sum() for c in classes]
57
+ return float(np.average(recalls, weights=weights))
58
+ return float(recalls[-1])
59
+
60
+
61
+ def f1_score(y_true, y_pred, average='binary', labels=None, zero_division=0) -> float:
62
+ p = precision_score(y_true, y_pred, average=average, labels=labels, zero_division=zero_division)
63
+ r = recall_score(y_true, y_pred, average=average, labels=labels, zero_division=zero_division)
64
+ return float(2 * p * r / (p + r)) if (p + r) > 0 else zero_division
65
+
66
+
67
+ def roc_auc_score(y_true, y_score, average='macro') -> float:
68
+ y_true, y_score = np.array(y_true), np.array(y_score)
69
+ if y_score.ndim == 1:
70
+ return _roc_auc_binary(y_true, y_score)
71
+ classes = np.unique(y_true)
72
+ aucs = [_roc_auc_binary((y_true == c).astype(int), y_score[:, i]) for i, c in enumerate(classes)]
73
+ return float(np.mean(aucs))
74
+
75
+
76
+ def _roc_auc_binary(y_true, y_score) -> float:
77
+ pos = y_true.sum()
78
+ neg = len(y_true) - pos
79
+ if pos == 0 or neg == 0:
80
+ return 0.5
81
+ order = np.argsort(y_score)[::-1]
82
+ y_sorted = y_true[order]
83
+ tpr = np.cumsum(y_sorted) / pos
84
+ fpr = np.cumsum(1 - y_sorted) / neg
85
+ tpr = np.concatenate([[0], tpr])
86
+ fpr = np.concatenate([[0], fpr])
87
+ return float(np.trapz(tpr, fpr))
88
+
89
+
90
+ def mse(y_true, y_pred) -> float:
91
+ y_true, y_pred = np.array(y_true, dtype=float), np.array(y_pred, dtype=float)
92
+ return float(np.mean((y_true - y_pred)**2))
93
+
94
+
95
+ def rmse(y_true, y_pred) -> float:
96
+ return float(np.sqrt(mse(y_true, y_pred)))
97
+
98
+
99
+ def mae(y_true, y_pred) -> float:
100
+ y_true, y_pred = np.array(y_true, dtype=float), np.array(y_pred, dtype=float)
101
+ return float(np.mean(np.abs(y_true - y_pred)))
102
+
103
+
104
+ def r2_score(y_true, y_pred) -> float:
105
+ y_true, y_pred = np.array(y_true, dtype=float), np.array(y_pred, dtype=float)
106
+ ss_res = np.sum((y_true - y_pred)**2)
107
+ ss_tot = np.sum((y_true - y_true.mean())**2)
108
+ return float(1 - ss_res / (ss_tot + 1e-12))
109
+
110
+
111
+ def mean_absolute_percentage_error(y_true, y_pred) -> float:
112
+ y_true, y_pred = np.array(y_true, dtype=float), np.array(y_pred, dtype=float)
113
+ mask = y_true != 0
114
+ return float(np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])))
115
+
116
+
117
+ def silhouette_score(X, labels) -> float:
118
+ X, labels = np.array(X, dtype=float), np.array(labels)
119
+ n = len(X)
120
+ unique_labels = np.unique(labels)
121
+ scores = []
122
+ for i in range(n):
123
+ same = X[labels == labels[i]]
124
+ a = np.mean(np.linalg.norm(same - X[i], axis=1)) if len(same) > 1 else 0.0
125
+ b_vals = []
126
+ for c in unique_labels:
127
+ if c == labels[i]:
128
+ continue
129
+ other = X[labels == c]
130
+ if len(other) > 0:
131
+ b_vals.append(np.mean(np.linalg.norm(other - X[i], axis=1)))
132
+ b = min(b_vals) if b_vals else 0.0
133
+ s = (b - a) / max(a, b) if max(a, b) > 0 else 0.0
134
+ scores.append(s)
135
+ return float(np.mean(scores))
136
+
137
+
138
+ def log_loss(y_true, y_pred, eps=1e-15) -> float:
139
+ y_true = np.array(y_true)
140
+ y_pred = np.clip(np.array(y_pred, dtype=float), eps, 1 - eps)
141
+ if y_pred.ndim == 1:
142
+ return float(-np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)))
143
+ n = len(y_true)
144
+ classes = np.unique(y_true)
145
+ y_ohe = np.zeros((n, len(classes)))
146
+ for i, c in enumerate(classes):
147
+ y_ohe[:, i] = (y_true == c)
148
+ return float(-np.mean(np.sum(y_ohe * np.log(y_pred), axis=1)))
@@ -0,0 +1,171 @@
1
+ """Model selection and validation utilities."""
2
+ import numpy as np
3
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
4
+
5
+
6
+ def train_test_split(*arrays, test_size=0.2, random_state=None, shuffle=True, stratify=None):
7
+ if random_state is not None:
8
+ np.random.seed(random_state)
9
+ n = len(arrays[0])
10
+ n_test = int(n * test_size)
11
+ if shuffle:
12
+ idx = np.random.permutation(n)
13
+ else:
14
+ idx = np.arange(n)
15
+ test_idx = idx[:n_test]
16
+ train_idx = idx[n_test:]
17
+ result = []
18
+ for arr in arrays:
19
+ arr = np.array(arr)
20
+ result.append(arr[train_idx])
21
+ result.append(arr[test_idx])
22
+ return result
23
+
24
+
25
+ class KFold:
26
+ def __init__(self, n_splits=5, shuffle=False, random_state=None):
27
+ self.n_splits = n_splits
28
+ self.shuffle = shuffle
29
+ self.random_state = random_state
30
+
31
+ def split(self, X, y=None):
32
+ X = np.array(X)
33
+ n = len(X)
34
+ idx = np.arange(n)
35
+ if self.shuffle:
36
+ if self.random_state is not None:
37
+ np.random.seed(self.random_state)
38
+ np.random.shuffle(idx)
39
+ fold_sizes = np.full(self.n_splits, n // self.n_splits, dtype=int)
40
+ fold_sizes[:n % self.n_splits] += 1
41
+ current = 0
42
+ for fold_size in fold_sizes:
43
+ start, stop = current, current + fold_size
44
+ test_idx = idx[start:stop]
45
+ train_idx = np.concatenate([idx[:start], idx[stop:]])
46
+ yield train_idx, test_idx
47
+ current = stop
48
+
49
+ def get_n_splits(self):
50
+ return self.n_splits
51
+
52
+
53
+ class StratifiedKFold:
54
+ def __init__(self, n_splits=5, shuffle=False, random_state=None):
55
+ self.n_splits = n_splits
56
+ self.shuffle = shuffle
57
+ self.random_state = random_state
58
+
59
+ def split(self, X, y):
60
+ X, y = np.array(X), np.array(y)
61
+ classes, y_indices = np.unique(y, return_inverse=True)
62
+ rng = np.random.RandomState(self.random_state) if self.random_state else np.random.RandomState()
63
+ folds = [[] for _ in range(self.n_splits)]
64
+ for cls_idx in range(len(classes)):
65
+ class_indices = np.where(y_indices == cls_idx)[0]
66
+ if self.shuffle:
67
+ rng.shuffle(class_indices)
68
+ for i, idx in enumerate(class_indices):
69
+ folds[i % self.n_splits].append(idx)
70
+ for fold_i in range(self.n_splits):
71
+ test_idx = np.array(folds[fold_i])
72
+ train_idx = np.concatenate([np.array(folds[j]) for j in range(self.n_splits) if j != fold_i])
73
+ yield train_idx, test_idx
74
+
75
+
76
+ def cross_val_score(estimator, X, y, cv=5, scoring='accuracy') -> np.ndarray:
77
+ X, y = np.array(X), np.array(y)
78
+ kf = KFold(n_splits=cv, shuffle=True, random_state=42)
79
+ scores = []
80
+ for train_idx, test_idx in kf.split(X):
81
+ X_train, X_test = X[train_idx], X[test_idx]
82
+ y_train, y_test = y[train_idx], y[test_idx]
83
+ estimator.fit(X_train, y_train)
84
+ if scoring == 'accuracy' or scoring == 'r2':
85
+ scores.append(estimator.score(X_test, y_test))
86
+ else:
87
+ scores.append(estimator.score(X_test, y_test))
88
+ return np.array(scores)
89
+
90
+
91
+ class GridSearchCV:
92
+ def __init__(self, estimator, param_grid: Dict, cv=5, scoring='accuracy', refit=True, n_jobs=1):
93
+ self.estimator = estimator
94
+ self.param_grid = param_grid
95
+ self.cv = cv
96
+ self.scoring = scoring
97
+ self.refit = refit
98
+ self.n_jobs = n_jobs
99
+ self.best_params_: Optional[Dict] = None
100
+ self.best_score_: float = -np.inf
101
+ self.best_estimator_ = None
102
+ self.cv_results_: Dict = {}
103
+
104
+ def _param_combinations(self):
105
+ from itertools import product
106
+ keys = list(self.param_grid.keys())
107
+ values = list(self.param_grid.values())
108
+ for combo in product(*values):
109
+ yield dict(zip(keys, combo))
110
+
111
+ def fit(self, X, y) -> 'GridSearchCV':
112
+ X, y = np.array(X), np.array(y)
113
+ results = []
114
+ for params in self._param_combinations():
115
+ est = type(self.estimator)(**{**self.estimator.__dict__, **params})
116
+ scores = cross_val_score(est, X, y, cv=self.cv, scoring=self.scoring)
117
+ mean_score = scores.mean()
118
+ results.append({'params': params, 'mean_test_score': mean_score, 'std_test_score': scores.std()})
119
+ if mean_score > self.best_score_:
120
+ self.best_score_ = mean_score
121
+ self.best_params_ = params
122
+ self.cv_results_ = {k: [r[k] for r in results] for k in results[0]}
123
+ if self.refit:
124
+ self.best_estimator_ = type(self.estimator)(**{**self.estimator.__dict__, **self.best_params_})
125
+ self.best_estimator_.fit(X, y)
126
+ return self
127
+
128
+ def predict(self, X) -> np.ndarray:
129
+ return self.best_estimator_.predict(X)
130
+
131
+ def score(self, X, y) -> float:
132
+ return self.best_estimator_.score(X, y)
133
+
134
+
135
+ class RandomizedSearchCV:
136
+ def __init__(self, estimator, param_distributions: Dict, n_iter=10, cv=5,
137
+ scoring='accuracy', refit=True, random_state=None):
138
+ self.estimator = estimator
139
+ self.param_distributions = param_distributions
140
+ self.n_iter = n_iter
141
+ self.cv = cv
142
+ self.scoring = scoring
143
+ self.refit = refit
144
+ self.random_state = random_state
145
+ self.best_params_: Optional[Dict] = None
146
+ self.best_score_: float = -np.inf
147
+ self.best_estimator_ = None
148
+
149
+ def fit(self, X, y) -> 'RandomizedSearchCV':
150
+ if self.random_state is not None:
151
+ np.random.seed(self.random_state)
152
+ X, y = np.array(X), np.array(y)
153
+ for _ in range(self.n_iter):
154
+ params = {k: np.random.choice(v) if isinstance(v, (list, np.ndarray))
155
+ else float(np.random.uniform(*v)) for k, v in self.param_distributions.items()}
156
+ try:
157
+ est = type(self.estimator)(**{**self.estimator.__dict__, **params})
158
+ scores = cross_val_score(est, X, y, cv=self.cv, scoring=self.scoring)
159
+ mean_score = scores.mean()
160
+ if mean_score > self.best_score_:
161
+ self.best_score_ = mean_score
162
+ self.best_params_ = params
163
+ except Exception:
164
+ continue
165
+ if self.refit and self.best_params_ is not None:
166
+ self.best_estimator_ = type(self.estimator)(**{**self.estimator.__dict__, **self.best_params_})
167
+ self.best_estimator_.fit(X, y)
168
+ return self
169
+
170
+ def predict(self, X) -> np.ndarray:
171
+ return self.best_estimator_.predict(X)
@@ -0,0 +1,113 @@
1
+ """Naive Bayes classifiers."""
2
+ import numpy as np
3
+ from typing import Optional
4
+
5
+
6
+ class GaussianNB:
7
+ def __init__(self, var_smoothing=1e-9):
8
+ self.var_smoothing = var_smoothing
9
+ self.classes_: Optional[np.ndarray] = None
10
+ self.theta_: Optional[np.ndarray] = None
11
+ self.var_: Optional[np.ndarray] = None
12
+ self.class_prior_: Optional[np.ndarray] = None
13
+
14
+ def fit(self, X, y) -> 'GaussianNB':
15
+ X, y = np.array(X, dtype=float), np.array(y)
16
+ self.classes_ = np.unique(y)
17
+ self.theta_ = np.array([X[y == c].mean(axis=0) for c in self.classes_])
18
+ self.var_ = np.array([X[y == c].var(axis=0) for c in self.classes_])
19
+ max_var = self.var_.max()
20
+ self.var_ += self.var_smoothing * max_var
21
+ counts = np.array([(y == c).sum() for c in self.classes_])
22
+ self.class_prior_ = counts / len(y)
23
+ return self
24
+
25
+ def _log_likelihood(self, X):
26
+ log_probs = []
27
+ for k in range(len(self.classes_)):
28
+ log_prior = np.log(self.class_prior_[k])
29
+ diff = X - self.theta_[k]
30
+ log_lk = -0.5 * np.sum(np.log(2 * np.pi * self.var_[k]) + diff**2 / self.var_[k], axis=1)
31
+ log_probs.append(log_prior + log_lk)
32
+ return np.array(log_probs).T
33
+
34
+ def predict(self, X) -> np.ndarray:
35
+ return self.classes_[self._log_likelihood(np.array(X, dtype=float)).argmax(axis=1)]
36
+
37
+ def predict_proba(self, X) -> np.ndarray:
38
+ log_probs = self._log_likelihood(np.array(X, dtype=float))
39
+ log_probs -= log_probs.max(axis=1, keepdims=True)
40
+ probs = np.exp(log_probs)
41
+ return probs / probs.sum(axis=1, keepdims=True)
42
+
43
+ def score(self, X, y) -> float:
44
+ return float(np.mean(self.predict(X) == np.array(y)))
45
+
46
+
47
+ class MultinomialNB:
48
+ def __init__(self, alpha=1.0):
49
+ self.alpha = alpha
50
+ self.classes_: Optional[np.ndarray] = None
51
+ self.feature_log_prob_: Optional[np.ndarray] = None
52
+ self.class_log_prior_: Optional[np.ndarray] = None
53
+
54
+ def fit(self, X, y) -> 'MultinomialNB':
55
+ X, y = np.array(X, dtype=float), np.array(y)
56
+ self.classes_ = np.unique(y)
57
+ K, p = len(self.classes_), X.shape[1]
58
+ smoothed = np.array([X[y == c].sum(axis=0) + self.alpha for c in self.classes_])
59
+ self.feature_log_prob_ = np.log(smoothed / smoothed.sum(axis=1, keepdims=True))
60
+ counts = np.array([(y == c).sum() for c in self.classes_])
61
+ self.class_log_prior_ = np.log(counts / len(y))
62
+ return self
63
+
64
+ def predict(self, X) -> np.ndarray:
65
+ X = np.array(X, dtype=float)
66
+ log_probs = X @ self.feature_log_prob_.T + self.class_log_prior_
67
+ return self.classes_[log_probs.argmax(axis=1)]
68
+
69
+ def predict_proba(self, X) -> np.ndarray:
70
+ X = np.array(X, dtype=float)
71
+ log_probs = X @ self.feature_log_prob_.T + self.class_log_prior_
72
+ log_probs -= log_probs.max(axis=1, keepdims=True)
73
+ probs = np.exp(log_probs)
74
+ return probs / probs.sum(axis=1, keepdims=True)
75
+
76
+ def score(self, X, y) -> float:
77
+ return float(np.mean(self.predict(X) == np.array(y)))
78
+
79
+
80
+ class BernoulliNB:
81
+ def __init__(self, alpha=1.0, binarize=0.0):
82
+ self.alpha = alpha
83
+ self.binarize = binarize
84
+ self.classes_: Optional[np.ndarray] = None
85
+ self.feature_log_prob_: Optional[np.ndarray] = None
86
+ self.class_log_prior_: Optional[np.ndarray] = None
87
+
88
+ def fit(self, X, y) -> 'BernoulliNB':
89
+ X = np.array(X, dtype=float)
90
+ if self.binarize is not None:
91
+ X = (X > self.binarize).astype(float)
92
+ y = np.array(y)
93
+ self.classes_ = np.unique(y)
94
+ K = len(self.classes_)
95
+ smoothed_pos = np.array([X[y == c].sum(axis=0) + self.alpha for c in self.classes_])
96
+ smoothed_tot = np.array([(y == c).sum() + 2 * self.alpha for c in self.classes_])
97
+ prob = smoothed_pos / smoothed_tot[:, None]
98
+ self.feature_log_prob_ = np.log(prob)
99
+ self._feature_log_prob_neg = np.log(1 - prob)
100
+ counts = np.array([(y == c).sum() for c in self.classes_])
101
+ self.class_log_prior_ = np.log(counts / len(y))
102
+ return self
103
+
104
+ def predict(self, X) -> np.ndarray:
105
+ X = np.array(X, dtype=float)
106
+ if self.binarize is not None:
107
+ X = (X > self.binarize).astype(float)
108
+ log_probs = (X @ self.feature_log_prob_.T +
109
+ (1 - X) @ self._feature_log_prob_neg.T + self.class_log_prior_)
110
+ return self.classes_[log_probs.argmax(axis=1)]
111
+
112
+ def score(self, X, y) -> float:
113
+ return float(np.mean(self.predict(X) == np.array(y)))