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.
- vectorphile/__init__.py +154 -0
- vectorphile/amp/__init__.py +52 -0
- vectorphile/autograd/__init__.py +13 -0
- vectorphile/autograd/engine.py +208 -0
- vectorphile/core/__init__.py +21 -0
- vectorphile/core/tensor.py +696 -0
- vectorphile/data/__init__.py +23 -0
- vectorphile/data/frame.py +537 -0
- vectorphile/data/groupby.py +98 -0
- vectorphile/data/index.py +86 -0
- vectorphile/data/series.py +228 -0
- vectorphile/fft/__init__.py +34 -0
- vectorphile/linalg/__init__.py +63 -0
- vectorphile/ml/__init__.py +44 -0
- vectorphile/ml/boost/__init__.py +133 -0
- vectorphile/ml/cluster.py +218 -0
- vectorphile/ml/decomposition.py +161 -0
- vectorphile/ml/ensemble.py +209 -0
- vectorphile/ml/linear.py +250 -0
- vectorphile/ml/metrics.py +148 -0
- vectorphile/ml/model_selection.py +171 -0
- vectorphile/ml/naive_bayes.py +113 -0
- vectorphile/ml/neighbors.py +95 -0
- vectorphile/ml/preprocessing.py +202 -0
- vectorphile/ml/svm.py +165 -0
- vectorphile/ml/tree.py +155 -0
- vectorphile/nn/__init__.py +20 -0
- vectorphile/nn/activations.py +97 -0
- vectorphile/nn/layers.py +231 -0
- vectorphile/nn/losses.py +172 -0
- vectorphile/nn/module.py +97 -0
- vectorphile/nn/sequential.py +47 -0
- vectorphile/optim/__init__.py +12 -0
- vectorphile/optim/adagrad.py +31 -0
- vectorphile/optim/adam.py +71 -0
- vectorphile/optim/lr_scheduler.py +110 -0
- vectorphile/optim/optimizer.py +30 -0
- vectorphile/optim/rmsprop.py +34 -0
- vectorphile/optim/sgd.py +38 -0
- vectorphile/optimize/__init__.py +97 -0
- vectorphile/random/__init__.py +64 -0
- vectorphile/signal/__init__.py +67 -0
- vectorphile/sparse/__init__.py +129 -0
- vectorphile/stats/__init__.py +90 -0
- vectorphile/utils/__init__.py +3 -0
- vectorphile/utils/data/__init__.py +4 -0
- vectorphile/utils/data/dataloader.py +47 -0
- vectorphile/utils/data/dataset.py +29 -0
- vectorphile-0.1.0.dist-info/METADATA +208 -0
- vectorphile-0.1.0.dist-info/RECORD +53 -0
- vectorphile-0.1.0.dist-info/WHEEL +5 -0
- vectorphile-0.1.0.dist-info/licenses/LICENSE +201 -0
- vectorphile-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Clustering algorithms."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class KMeans:
|
|
7
|
+
def __init__(self, n_clusters=8, max_iter=300, tol=1e-4, n_init=10, random_state=None):
|
|
8
|
+
self.n_clusters = n_clusters
|
|
9
|
+
self.max_iter = max_iter
|
|
10
|
+
self.tol = tol
|
|
11
|
+
self.n_init = n_init
|
|
12
|
+
self.random_state = random_state
|
|
13
|
+
self.cluster_centers_: Optional[np.ndarray] = None
|
|
14
|
+
self.labels_: Optional[np.ndarray] = None
|
|
15
|
+
self.inertia_: float = np.inf
|
|
16
|
+
|
|
17
|
+
def _run_once(self, X, seed):
|
|
18
|
+
rng = np.random.RandomState(seed)
|
|
19
|
+
idx = rng.choice(len(X), self.n_clusters, replace=False)
|
|
20
|
+
centers = X[idx].copy()
|
|
21
|
+
for _ in range(self.max_iter):
|
|
22
|
+
dists = np.linalg.norm(X[:, None] - centers[None], axis=2)
|
|
23
|
+
labels = dists.argmin(axis=1)
|
|
24
|
+
new_centers = np.array([X[labels == k].mean(axis=0) if (labels == k).any() else centers[k]
|
|
25
|
+
for k in range(self.n_clusters)])
|
|
26
|
+
if np.max(np.abs(new_centers - centers)) < self.tol:
|
|
27
|
+
break
|
|
28
|
+
centers = new_centers
|
|
29
|
+
inertia = sum(np.sum((X[labels == k] - centers[k])**2) for k in range(self.n_clusters))
|
|
30
|
+
return centers, labels, float(inertia)
|
|
31
|
+
|
|
32
|
+
def fit(self, X) -> 'KMeans':
|
|
33
|
+
X = np.array(X, dtype=float)
|
|
34
|
+
if self.random_state is not None:
|
|
35
|
+
np.random.seed(self.random_state)
|
|
36
|
+
best_inertia = np.inf
|
|
37
|
+
for i in range(self.n_init):
|
|
38
|
+
centers, labels, inertia = self._run_once(X, seed=i)
|
|
39
|
+
if inertia < best_inertia:
|
|
40
|
+
best_inertia = inertia
|
|
41
|
+
self.cluster_centers_ = centers
|
|
42
|
+
self.labels_ = labels
|
|
43
|
+
self.inertia_ = inertia
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def predict(self, X) -> np.ndarray:
|
|
47
|
+
X = np.array(X, dtype=float)
|
|
48
|
+
dists = np.linalg.norm(X[:, None] - self.cluster_centers_[None], axis=2)
|
|
49
|
+
return dists.argmin(axis=1)
|
|
50
|
+
|
|
51
|
+
def fit_predict(self, X) -> np.ndarray:
|
|
52
|
+
self.fit(X)
|
|
53
|
+
return self.labels_
|
|
54
|
+
|
|
55
|
+
def score(self, X) -> float:
|
|
56
|
+
return -self.inertia_
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DBSCAN:
|
|
60
|
+
def __init__(self, eps=0.5, min_samples=5, metric='euclidean'):
|
|
61
|
+
self.eps = eps
|
|
62
|
+
self.min_samples = min_samples
|
|
63
|
+
self.metric = metric
|
|
64
|
+
self.labels_: Optional[np.ndarray] = None
|
|
65
|
+
self.core_sample_indices_: Optional[np.ndarray] = None
|
|
66
|
+
|
|
67
|
+
def fit(self, X) -> 'DBSCAN':
|
|
68
|
+
X = np.array(X, dtype=float)
|
|
69
|
+
n = len(X)
|
|
70
|
+
dists = np.linalg.norm(X[:, None] - X[None], axis=2)
|
|
71
|
+
labels = np.full(n, -1, dtype=int)
|
|
72
|
+
visited = np.zeros(n, dtype=bool)
|
|
73
|
+
cluster_id = 0
|
|
74
|
+
neighbors = [np.where(dists[i] <= self.eps)[0] for i in range(n)]
|
|
75
|
+
core = np.array([i for i in range(n) if len(neighbors[i]) >= self.min_samples])
|
|
76
|
+
self.core_sample_indices_ = core
|
|
77
|
+
|
|
78
|
+
def expand(idx, cid):
|
|
79
|
+
queue = list(neighbors[idx])
|
|
80
|
+
while queue:
|
|
81
|
+
q = queue.pop()
|
|
82
|
+
if not visited[q]:
|
|
83
|
+
visited[q] = True
|
|
84
|
+
if len(neighbors[q]) >= self.min_samples:
|
|
85
|
+
for nb in neighbors[q]:
|
|
86
|
+
if not visited[nb]:
|
|
87
|
+
queue.append(nb)
|
|
88
|
+
if labels[q] == -1:
|
|
89
|
+
labels[q] = cid
|
|
90
|
+
|
|
91
|
+
for i in core:
|
|
92
|
+
if not visited[i]:
|
|
93
|
+
visited[i] = True
|
|
94
|
+
labels[i] = cluster_id
|
|
95
|
+
expand(i, cluster_id)
|
|
96
|
+
cluster_id += 1
|
|
97
|
+
self.labels_ = labels
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def fit_predict(self, X) -> np.ndarray:
|
|
101
|
+
self.fit(X)
|
|
102
|
+
return self.labels_
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AgglomerativeClustering:
|
|
106
|
+
def __init__(self, n_clusters=2, linkage='ward'):
|
|
107
|
+
self.n_clusters = n_clusters
|
|
108
|
+
self.linkage = linkage
|
|
109
|
+
self.labels_: Optional[np.ndarray] = None
|
|
110
|
+
|
|
111
|
+
def fit(self, X) -> 'AgglomerativeClustering':
|
|
112
|
+
X = np.array(X, dtype=float)
|
|
113
|
+
n = len(X)
|
|
114
|
+
clusters = [{i} for i in range(n)]
|
|
115
|
+
|
|
116
|
+
def dist(c1, c2):
|
|
117
|
+
pts1 = X[list(c1)]
|
|
118
|
+
pts2 = X[list(c2)]
|
|
119
|
+
if self.linkage == 'ward':
|
|
120
|
+
merged = np.vstack([pts1, pts2])
|
|
121
|
+
return len(merged) * np.sum((merged.mean(0) - np.vstack([
|
|
122
|
+
np.tile(pts1.mean(0), (len(pts1),1)),
|
|
123
|
+
np.tile(pts2.mean(0), (len(pts2),1))
|
|
124
|
+
]))**2)
|
|
125
|
+
d = np.linalg.norm(pts1[:, None] - pts2[None], axis=2)
|
|
126
|
+
if self.linkage == 'single':
|
|
127
|
+
return d.min()
|
|
128
|
+
if self.linkage == 'complete':
|
|
129
|
+
return d.max()
|
|
130
|
+
return d.mean()
|
|
131
|
+
|
|
132
|
+
while len(clusters) > self.n_clusters:
|
|
133
|
+
best_d, merge_i, merge_j = np.inf, 0, 1
|
|
134
|
+
for i in range(len(clusters)):
|
|
135
|
+
for j in range(i+1, len(clusters)):
|
|
136
|
+
d = dist(clusters[i], clusters[j])
|
|
137
|
+
if d < best_d:
|
|
138
|
+
best_d, merge_i, merge_j = d, i, j
|
|
139
|
+
clusters[merge_i] = clusters[merge_i] | clusters[merge_j]
|
|
140
|
+
clusters.pop(merge_j)
|
|
141
|
+
|
|
142
|
+
self.labels_ = np.zeros(n, dtype=int)
|
|
143
|
+
for label, cluster in enumerate(clusters):
|
|
144
|
+
for idx in cluster:
|
|
145
|
+
self.labels_[idx] = label
|
|
146
|
+
return self
|
|
147
|
+
|
|
148
|
+
def fit_predict(self, X) -> np.ndarray:
|
|
149
|
+
self.fit(X)
|
|
150
|
+
return self.labels_
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class GaussianMixture:
|
|
154
|
+
def __init__(self, n_components=1, max_iter=100, tol=1e-3, random_state=None, covariance_type='full'):
|
|
155
|
+
self.n_components = n_components
|
|
156
|
+
self.max_iter = max_iter
|
|
157
|
+
self.tol = tol
|
|
158
|
+
self.random_state = random_state
|
|
159
|
+
self.covariance_type = covariance_type
|
|
160
|
+
self.weights_: Optional[np.ndarray] = None
|
|
161
|
+
self.means_: Optional[np.ndarray] = None
|
|
162
|
+
self.covariances_: Optional[np.ndarray] = None
|
|
163
|
+
|
|
164
|
+
def _gaussian_pdf(self, X, mean, cov):
|
|
165
|
+
d = X.shape[1]
|
|
166
|
+
diff = X - mean
|
|
167
|
+
try:
|
|
168
|
+
cov_inv = np.linalg.inv(cov + np.eye(d) * 1e-6)
|
|
169
|
+
cov_det = np.linalg.det(cov + np.eye(d) * 1e-6)
|
|
170
|
+
except np.linalg.LinAlgError:
|
|
171
|
+
cov_inv = np.eye(d)
|
|
172
|
+
cov_det = 1.0
|
|
173
|
+
exponent = -0.5 * np.einsum('ni,ij,nj->n', diff, cov_inv, diff)
|
|
174
|
+
return np.exp(exponent) / (np.sqrt((2*np.pi)**d * np.abs(cov_det)) + 1e-300)
|
|
175
|
+
|
|
176
|
+
def fit(self, X) -> 'GaussianMixture':
|
|
177
|
+
if self.random_state is not None:
|
|
178
|
+
np.random.seed(self.random_state)
|
|
179
|
+
X = np.array(X, dtype=float)
|
|
180
|
+
n, d = X.shape
|
|
181
|
+
K = self.n_components
|
|
182
|
+
idx = np.random.choice(n, K, replace=False)
|
|
183
|
+
self.means_ = X[idx].copy()
|
|
184
|
+
self.covariances_ = np.array([np.eye(d) for _ in range(K)])
|
|
185
|
+
self.weights_ = np.ones(K) / K
|
|
186
|
+
for _ in range(self.max_iter):
|
|
187
|
+
# E-step
|
|
188
|
+
resp = np.array([self.weights_[k] * self._gaussian_pdf(X, self.means_[k], self.covariances_[k]) for k in range(K)]).T
|
|
189
|
+
resp_sum = resp.sum(axis=1, keepdims=True)
|
|
190
|
+
resp /= (resp_sum + 1e-300)
|
|
191
|
+
# M-step
|
|
192
|
+
Nk = resp.sum(axis=0)
|
|
193
|
+
new_means = (resp.T @ X) / (Nk[:, None] + 1e-300)
|
|
194
|
+
new_covs = []
|
|
195
|
+
for k in range(K):
|
|
196
|
+
diff = X - new_means[k]
|
|
197
|
+
new_covs.append((resp[:, k:k+1] * diff).T @ diff / (Nk[k] + 1e-300))
|
|
198
|
+
old_means = self.means_.copy()
|
|
199
|
+
self.means_ = new_means
|
|
200
|
+
self.covariances_ = np.array(new_covs)
|
|
201
|
+
self.weights_ = Nk / n
|
|
202
|
+
if np.max(np.abs(self.means_ - old_means)) < self.tol:
|
|
203
|
+
break
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
def predict(self, X) -> np.ndarray:
|
|
207
|
+
return self.predict_proba(X).argmax(axis=1)
|
|
208
|
+
|
|
209
|
+
def predict_proba(self, X) -> np.ndarray:
|
|
210
|
+
X = np.array(X, dtype=float)
|
|
211
|
+
K = self.n_components
|
|
212
|
+
resp = np.array([self.weights_[k] * self._gaussian_pdf(X, self.means_[k], self.covariances_[k]) for k in range(K)]).T
|
|
213
|
+
resp_sum = resp.sum(axis=1, keepdims=True)
|
|
214
|
+
return resp / (resp_sum + 1e-300)
|
|
215
|
+
|
|
216
|
+
def fit_predict(self, X) -> np.ndarray:
|
|
217
|
+
self.fit(X)
|
|
218
|
+
return self.predict(X)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Dimensionality reduction."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PCA:
|
|
7
|
+
def __init__(self, n_components=None, whiten=False, random_state=None):
|
|
8
|
+
self.n_components = n_components
|
|
9
|
+
self.whiten = whiten
|
|
10
|
+
self.random_state = random_state
|
|
11
|
+
self.components_: Optional[np.ndarray] = None
|
|
12
|
+
self.explained_variance_: Optional[np.ndarray] = None
|
|
13
|
+
self.explained_variance_ratio_: Optional[np.ndarray] = None
|
|
14
|
+
self.mean_: Optional[np.ndarray] = None
|
|
15
|
+
self.singular_values_: Optional[np.ndarray] = None
|
|
16
|
+
|
|
17
|
+
def fit(self, X) -> 'PCA':
|
|
18
|
+
X = np.array(X, dtype=float)
|
|
19
|
+
self.mean_ = X.mean(axis=0)
|
|
20
|
+
Xc = X - self.mean_
|
|
21
|
+
U, S, Vt = np.linalg.svd(Xc, full_matrices=False)
|
|
22
|
+
n = len(X)
|
|
23
|
+
k = self.n_components or Vt.shape[0]
|
|
24
|
+
self.components_ = Vt[:k]
|
|
25
|
+
self.singular_values_ = S[:k]
|
|
26
|
+
self.explained_variance_ = S[:k]**2 / (n - 1)
|
|
27
|
+
self.explained_variance_ratio_ = self.explained_variance_ / (S**2 / (n - 1)).sum()
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def transform(self, X) -> np.ndarray:
|
|
31
|
+
X = np.array(X, dtype=float)
|
|
32
|
+
Xc = X - self.mean_
|
|
33
|
+
result = Xc @ self.components_.T
|
|
34
|
+
if self.whiten:
|
|
35
|
+
result /= np.sqrt(self.explained_variance_) + 1e-12
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def fit_transform(self, X) -> np.ndarray:
|
|
39
|
+
self.fit(X)
|
|
40
|
+
return self.transform(X)
|
|
41
|
+
|
|
42
|
+
def inverse_transform(self, X) -> np.ndarray:
|
|
43
|
+
X = np.array(X, dtype=float)
|
|
44
|
+
if self.whiten:
|
|
45
|
+
X = X * np.sqrt(self.explained_variance_)
|
|
46
|
+
return X @ self.components_ + self.mean_
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TruncatedSVD:
|
|
50
|
+
def __init__(self, n_components=2, random_state=None):
|
|
51
|
+
self.n_components = n_components
|
|
52
|
+
self.random_state = random_state
|
|
53
|
+
self.components_: Optional[np.ndarray] = None
|
|
54
|
+
self.explained_variance_: Optional[np.ndarray] = None
|
|
55
|
+
self.explained_variance_ratio_: Optional[np.ndarray] = None
|
|
56
|
+
self.singular_values_: Optional[np.ndarray] = None
|
|
57
|
+
|
|
58
|
+
def fit(self, X) -> 'TruncatedSVD':
|
|
59
|
+
X = np.array(X, dtype=float)
|
|
60
|
+
U, S, Vt = np.linalg.svd(X, full_matrices=False)
|
|
61
|
+
k = self.n_components
|
|
62
|
+
self.components_ = Vt[:k]
|
|
63
|
+
self.singular_values_ = S[:k]
|
|
64
|
+
total = (S**2).sum()
|
|
65
|
+
self.explained_variance_ = S[:k]**2 / len(X)
|
|
66
|
+
self.explained_variance_ratio_ = S[:k]**2 / (total + 1e-12)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def transform(self, X) -> np.ndarray:
|
|
70
|
+
return np.array(X, dtype=float) @ self.components_.T
|
|
71
|
+
|
|
72
|
+
def fit_transform(self, X) -> np.ndarray:
|
|
73
|
+
self.fit(X)
|
|
74
|
+
return self.transform(X)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class NMF:
|
|
78
|
+
def __init__(self, n_components=2, max_iter=200, tol=1e-4, random_state=None):
|
|
79
|
+
self.n_components = n_components
|
|
80
|
+
self.max_iter = max_iter
|
|
81
|
+
self.tol = tol
|
|
82
|
+
self.random_state = random_state
|
|
83
|
+
self.components_: Optional[np.ndarray] = None
|
|
84
|
+
self.reconstruction_err_: float = np.inf
|
|
85
|
+
|
|
86
|
+
def fit_transform(self, X) -> np.ndarray:
|
|
87
|
+
if self.random_state is not None:
|
|
88
|
+
np.random.seed(self.random_state)
|
|
89
|
+
X = np.array(X, dtype=float)
|
|
90
|
+
X = np.maximum(X, 0)
|
|
91
|
+
n, m = X.shape
|
|
92
|
+
k = self.n_components
|
|
93
|
+
W = np.random.rand(n, k) + 0.1
|
|
94
|
+
H = np.random.rand(k, m) + 0.1
|
|
95
|
+
for _ in range(self.max_iter):
|
|
96
|
+
WH = W @ H + 1e-10
|
|
97
|
+
H *= (W.T @ X) / (W.T @ WH)
|
|
98
|
+
WH = W @ H + 1e-10
|
|
99
|
+
W *= (X @ H.T) / (WH @ H.T)
|
|
100
|
+
self.components_ = H
|
|
101
|
+
self.reconstruction_err_ = float(np.linalg.norm(X - W @ H))
|
|
102
|
+
return W
|
|
103
|
+
|
|
104
|
+
def fit(self, X) -> 'NMF':
|
|
105
|
+
self.fit_transform(X)
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def transform(self, X) -> np.ndarray:
|
|
109
|
+
X = np.maximum(np.array(X, dtype=float), 0)
|
|
110
|
+
H = self.components_
|
|
111
|
+
W, _ = np.linalg.lstsq(H.T, X.T, rcond=None)
|
|
112
|
+
return np.maximum(W.T, 0)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class FastICA:
|
|
116
|
+
def __init__(self, n_components=None, max_iter=200, tol=1e-4, fun='logcosh', random_state=None):
|
|
117
|
+
self.n_components = n_components
|
|
118
|
+
self.max_iter = max_iter
|
|
119
|
+
self.tol = tol
|
|
120
|
+
self.fun = fun
|
|
121
|
+
self.random_state = random_state
|
|
122
|
+
self.components_: Optional[np.ndarray] = None
|
|
123
|
+
self.mean_: Optional[np.ndarray] = None
|
|
124
|
+
|
|
125
|
+
def _g(self, x):
|
|
126
|
+
if self.fun == 'logcosh':
|
|
127
|
+
return np.tanh(x), (1 - np.tanh(x)**2).mean(axis=-1)
|
|
128
|
+
return x * np.exp(-x**2 / 2), (1 - x**2) * np.exp(-x**2 / 2).mean(axis=-1)
|
|
129
|
+
|
|
130
|
+
def fit_transform(self, X) -> np.ndarray:
|
|
131
|
+
if self.random_state is not None:
|
|
132
|
+
np.random.seed(self.random_state)
|
|
133
|
+
X = np.array(X, dtype=float)
|
|
134
|
+
n, p = X.shape
|
|
135
|
+
self.mean_ = X.mean(axis=0)
|
|
136
|
+
X = X - self.mean_
|
|
137
|
+
_, S, Vt = np.linalg.svd(X, full_matrices=False)
|
|
138
|
+
k = self.n_components or min(n, p)
|
|
139
|
+
X_white = (X @ Vt[:k].T) / (S[:k] / np.sqrt(n) + 1e-10)
|
|
140
|
+
W = np.random.randn(k, k)
|
|
141
|
+
for i in range(self.max_iter):
|
|
142
|
+
WX = W @ X_white.T
|
|
143
|
+
g, g_prime = self._g(WX)
|
|
144
|
+
W_new = (g @ X_white) / n - g_prime[:, None] * W
|
|
145
|
+
# Orthogonalization
|
|
146
|
+
u, _, vt = np.linalg.svd(W_new)
|
|
147
|
+
W_new = u @ vt
|
|
148
|
+
if np.max(np.abs(np.abs(np.diag(W_new @ W.T)) - 1)) < self.tol:
|
|
149
|
+
W = W_new
|
|
150
|
+
break
|
|
151
|
+
W = W_new
|
|
152
|
+
self.components_ = W @ np.diag(1.0 / (S[:k] / np.sqrt(n) + 1e-10)) @ Vt[:k]
|
|
153
|
+
return X_white @ W.T
|
|
154
|
+
|
|
155
|
+
def fit(self, X) -> 'FastICA':
|
|
156
|
+
self.fit_transform(X)
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def transform(self, X) -> np.ndarray:
|
|
160
|
+
X = np.array(X, dtype=float) - self.mean_
|
|
161
|
+
return X @ self.components_.T
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Ensemble methods: Random Forest, Gradient Boosting."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from vectorphile.ml.tree import DecisionTreeClassifier, DecisionTreeRegressor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RandomForestClassifier:
|
|
8
|
+
def __init__(self, n_estimators=100, max_depth=None, min_samples_split=2,
|
|
9
|
+
min_samples_leaf=1, max_features='sqrt', bootstrap=True, random_state=None):
|
|
10
|
+
self.n_estimators = n_estimators
|
|
11
|
+
self.max_depth = max_depth
|
|
12
|
+
self.min_samples_split = min_samples_split
|
|
13
|
+
self.min_samples_leaf = min_samples_leaf
|
|
14
|
+
self.max_features = max_features
|
|
15
|
+
self.bootstrap = bootstrap
|
|
16
|
+
self.random_state = random_state
|
|
17
|
+
self.estimators_ = []
|
|
18
|
+
self.classes_: Optional[np.ndarray] = None
|
|
19
|
+
|
|
20
|
+
def fit(self, X, y) -> 'RandomForestClassifier':
|
|
21
|
+
if self.random_state is not None:
|
|
22
|
+
np.random.seed(self.random_state)
|
|
23
|
+
X, y = np.array(X, dtype=float), np.array(y)
|
|
24
|
+
self.classes_ = np.unique(y)
|
|
25
|
+
n = len(X)
|
|
26
|
+
self.estimators_ = []
|
|
27
|
+
for _ in range(self.n_estimators):
|
|
28
|
+
if self.bootstrap:
|
|
29
|
+
idx = np.random.choice(n, n, replace=True)
|
|
30
|
+
Xb, yb = X[idx], y[idx]
|
|
31
|
+
else:
|
|
32
|
+
Xb, yb = X, y
|
|
33
|
+
tree = DecisionTreeClassifier(
|
|
34
|
+
max_depth=self.max_depth,
|
|
35
|
+
min_samples_split=self.min_samples_split,
|
|
36
|
+
min_samples_leaf=self.min_samples_leaf,
|
|
37
|
+
max_features=self.max_features,
|
|
38
|
+
)
|
|
39
|
+
tree.fit(Xb, yb)
|
|
40
|
+
self.estimators_.append(tree)
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def predict_proba(self, X) -> np.ndarray:
|
|
44
|
+
X = np.array(X, dtype=float)
|
|
45
|
+
all_proba = np.array([t.predict_proba(X) for t in self.estimators_])
|
|
46
|
+
return all_proba.mean(axis=0)
|
|
47
|
+
|
|
48
|
+
def predict(self, X) -> np.ndarray:
|
|
49
|
+
proba = self.predict_proba(X)
|
|
50
|
+
return self.classes_[np.argmax(proba, axis=1)]
|
|
51
|
+
|
|
52
|
+
def score(self, X, y) -> float:
|
|
53
|
+
return float(np.mean(self.predict(X) == np.array(y)))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RandomForestRegressor:
|
|
57
|
+
def __init__(self, n_estimators=100, max_depth=None, min_samples_split=2,
|
|
58
|
+
min_samples_leaf=1, max_features='sqrt', bootstrap=True, random_state=None):
|
|
59
|
+
self.n_estimators = n_estimators
|
|
60
|
+
self.max_depth = max_depth
|
|
61
|
+
self.min_samples_split = min_samples_split
|
|
62
|
+
self.min_samples_leaf = min_samples_leaf
|
|
63
|
+
self.max_features = max_features
|
|
64
|
+
self.bootstrap = bootstrap
|
|
65
|
+
self.random_state = random_state
|
|
66
|
+
self.estimators_ = []
|
|
67
|
+
|
|
68
|
+
def fit(self, X, y) -> 'RandomForestRegressor':
|
|
69
|
+
if self.random_state is not None:
|
|
70
|
+
np.random.seed(self.random_state)
|
|
71
|
+
X, y = np.array(X, dtype=float), np.array(y, dtype=float)
|
|
72
|
+
n = len(X)
|
|
73
|
+
self.estimators_ = []
|
|
74
|
+
for _ in range(self.n_estimators):
|
|
75
|
+
if self.bootstrap:
|
|
76
|
+
idx = np.random.choice(n, n, replace=True)
|
|
77
|
+
Xb, yb = X[idx], y[idx]
|
|
78
|
+
else:
|
|
79
|
+
Xb, yb = X, y
|
|
80
|
+
tree = DecisionTreeRegressor(
|
|
81
|
+
max_depth=self.max_depth,
|
|
82
|
+
min_samples_split=self.min_samples_split,
|
|
83
|
+
min_samples_leaf=self.min_samples_leaf,
|
|
84
|
+
max_features=self.max_features,
|
|
85
|
+
)
|
|
86
|
+
tree.fit(Xb, yb)
|
|
87
|
+
self.estimators_.append(tree)
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def predict(self, X) -> np.ndarray:
|
|
91
|
+
X = np.array(X, dtype=float)
|
|
92
|
+
preds = np.array([t.predict(X) for t in self.estimators_])
|
|
93
|
+
return preds.mean(axis=0)
|
|
94
|
+
|
|
95
|
+
def score(self, X, y) -> float:
|
|
96
|
+
y = np.array(y, dtype=float)
|
|
97
|
+
pred = self.predict(X)
|
|
98
|
+
ss_res = np.sum((y - pred)**2)
|
|
99
|
+
ss_tot = np.sum((y - y.mean())**2)
|
|
100
|
+
return float(1 - ss_res / (ss_tot + 1e-12))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class GradientBoostingRegressor:
|
|
104
|
+
def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3,
|
|
105
|
+
min_samples_split=2, subsample=1.0, loss='mse', random_state=None):
|
|
106
|
+
self.n_estimators = n_estimators
|
|
107
|
+
self.learning_rate = learning_rate
|
|
108
|
+
self.max_depth = max_depth
|
|
109
|
+
self.min_samples_split = min_samples_split
|
|
110
|
+
self.subsample = subsample
|
|
111
|
+
self.loss = loss
|
|
112
|
+
self.random_state = random_state
|
|
113
|
+
self.estimators_ = []
|
|
114
|
+
self.init_pred_: float = 0.0
|
|
115
|
+
|
|
116
|
+
def fit(self, X, y) -> 'GradientBoostingRegressor':
|
|
117
|
+
if self.random_state is not None:
|
|
118
|
+
np.random.seed(self.random_state)
|
|
119
|
+
X, y = np.array(X, dtype=float), np.array(y, dtype=float)
|
|
120
|
+
self.init_pred_ = float(y.mean())
|
|
121
|
+
F = np.full(len(y), self.init_pred_)
|
|
122
|
+
self.estimators_ = []
|
|
123
|
+
n = len(X)
|
|
124
|
+
for _ in range(self.n_estimators):
|
|
125
|
+
residuals = y - F
|
|
126
|
+
if self.subsample < 1.0:
|
|
127
|
+
idx = np.random.choice(n, int(n * self.subsample), replace=False)
|
|
128
|
+
Xs, rs = X[idx], residuals[idx]
|
|
129
|
+
else:
|
|
130
|
+
Xs, rs = X, residuals
|
|
131
|
+
tree = DecisionTreeRegressor(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
|
|
132
|
+
tree.fit(Xs, rs)
|
|
133
|
+
update = tree.predict(X)
|
|
134
|
+
F += self.learning_rate * update
|
|
135
|
+
self.estimators_.append(tree)
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def predict(self, X) -> np.ndarray:
|
|
139
|
+
X = np.array(X, dtype=float)
|
|
140
|
+
F = np.full(len(X), self.init_pred_)
|
|
141
|
+
for tree in self.estimators_:
|
|
142
|
+
F += self.learning_rate * tree.predict(X)
|
|
143
|
+
return F
|
|
144
|
+
|
|
145
|
+
def score(self, X, y) -> float:
|
|
146
|
+
y = np.array(y, dtype=float)
|
|
147
|
+
pred = self.predict(X)
|
|
148
|
+
ss_res = np.sum((y - pred)**2)
|
|
149
|
+
ss_tot = np.sum((y - y.mean())**2)
|
|
150
|
+
return float(1 - ss_res / (ss_tot + 1e-12))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class GradientBoostingClassifier:
|
|
154
|
+
def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3,
|
|
155
|
+
min_samples_split=2, subsample=1.0, random_state=None):
|
|
156
|
+
self.n_estimators = n_estimators
|
|
157
|
+
self.learning_rate = learning_rate
|
|
158
|
+
self.max_depth = max_depth
|
|
159
|
+
self.min_samples_split = min_samples_split
|
|
160
|
+
self.subsample = subsample
|
|
161
|
+
self.random_state = random_state
|
|
162
|
+
self.estimators_ = []
|
|
163
|
+
self.classes_: Optional[np.ndarray] = None
|
|
164
|
+
self.init_pred_: Optional[np.ndarray] = None
|
|
165
|
+
|
|
166
|
+
def _softmax(self, z):
|
|
167
|
+
z = z - z.max(axis=1, keepdims=True)
|
|
168
|
+
e = np.exp(z)
|
|
169
|
+
return e / e.sum(axis=1, keepdims=True)
|
|
170
|
+
|
|
171
|
+
def fit(self, X, y) -> 'GradientBoostingClassifier':
|
|
172
|
+
if self.random_state is not None:
|
|
173
|
+
np.random.seed(self.random_state)
|
|
174
|
+
X, y = np.array(X, dtype=float), np.array(y)
|
|
175
|
+
self.classes_ = np.unique(y)
|
|
176
|
+
K = len(self.classes_)
|
|
177
|
+
n = len(X)
|
|
178
|
+
y_ohe = np.zeros((n, K))
|
|
179
|
+
for i, c in enumerate(self.classes_):
|
|
180
|
+
y_ohe[:, i] = (y == c).astype(float)
|
|
181
|
+
F = np.zeros((n, K))
|
|
182
|
+
self.init_pred_ = np.log(y_ohe.mean(axis=0) + 1e-12)
|
|
183
|
+
F += self.init_pred_
|
|
184
|
+
self.estimators_ = []
|
|
185
|
+
for _ in range(self.n_estimators):
|
|
186
|
+
probs = self._softmax(F)
|
|
187
|
+
self.estimators_.append([])
|
|
188
|
+
for k in range(K):
|
|
189
|
+
residuals = y_ohe[:, k] - probs[:, k]
|
|
190
|
+
tree = DecisionTreeRegressor(max_depth=self.max_depth, min_samples_split=self.min_samples_split)
|
|
191
|
+
tree.fit(X, residuals)
|
|
192
|
+
F[:, k] += self.learning_rate * tree.predict(X)
|
|
193
|
+
self.estimators_[-1].append(tree)
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
def predict_proba(self, X) -> np.ndarray:
|
|
197
|
+
X = np.array(X, dtype=float)
|
|
198
|
+
F = np.tile(self.init_pred_, (len(X), 1)).copy()
|
|
199
|
+
for group in self.estimators_:
|
|
200
|
+
for k, tree in enumerate(group):
|
|
201
|
+
F[:, k] += self.learning_rate * tree.predict(X)
|
|
202
|
+
return self._softmax(F)
|
|
203
|
+
|
|
204
|
+
def predict(self, X) -> np.ndarray:
|
|
205
|
+
proba = self.predict_proba(X)
|
|
206
|
+
return self.classes_[np.argmax(proba, axis=1)]
|
|
207
|
+
|
|
208
|
+
def score(self, X, y) -> float:
|
|
209
|
+
return float(np.mean(self.predict(X) == np.array(y)))
|