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,86 @@
1
+ """Index class for DataFrame/Series labels."""
2
+ from __future__ import annotations
3
+ import numpy as np
4
+ from typing import Any, List, Optional
5
+
6
+
7
+ class Index:
8
+ def __init__(self, data=None, name=None):
9
+ if data is None:
10
+ self._data = np.array([])
11
+ elif isinstance(data, Index):
12
+ self._data = data._data.copy()
13
+ else:
14
+ self._data = np.array(data)
15
+ self.name = name
16
+
17
+ @property
18
+ def values(self) -> np.ndarray:
19
+ return self._data
20
+
21
+ @property
22
+ def dtype(self):
23
+ return self._data.dtype
24
+
25
+ def __len__(self):
26
+ return len(self._data)
27
+
28
+ def __contains__(self, item):
29
+ return item in self._data
30
+
31
+ def __getitem__(self, key):
32
+ return self._data[key]
33
+
34
+ def __iter__(self):
35
+ return iter(self._data)
36
+
37
+ def tolist(self) -> list:
38
+ return self._data.tolist()
39
+
40
+ def get_loc(self, key) -> int:
41
+ matches = np.where(self._data == key)[0]
42
+ if len(matches) == 0:
43
+ raise KeyError(key)
44
+ return int(matches[0])
45
+
46
+ def isin(self, values) -> np.ndarray:
47
+ return np.isin(self._data, values)
48
+
49
+ def unique(self) -> 'Index':
50
+ return Index(np.unique(self._data), name=self.name)
51
+
52
+ def sort_values(self, ascending=True) -> 'Index':
53
+ order = np.argsort(self._data)
54
+ if not ascending:
55
+ order = order[::-1]
56
+ return Index(self._data[order], name=self.name)
57
+
58
+ def append(self, other: 'Index') -> 'Index':
59
+ return Index(np.concatenate([self._data, other._data]), name=self.name)
60
+
61
+ def difference(self, other: 'Index') -> 'Index':
62
+ return Index(np.setdiff1d(self._data, other._data), name=self.name)
63
+
64
+ def intersection(self, other: 'Index') -> 'Index':
65
+ return Index(np.intersect1d(self._data, other._data), name=self.name)
66
+
67
+ def union(self, other: 'Index') -> 'Index':
68
+ return Index(np.union1d(self._data, other._data), name=self.name)
69
+
70
+ def __repr__(self):
71
+ return f"Index({self._data.tolist()}, name={self.name!r})"
72
+
73
+ def __str__(self):
74
+ return self.__repr__()
75
+
76
+
77
+ class RangeIndex(Index):
78
+ def __init__(self, start=0, stop=None, step=1, name=None):
79
+ if stop is None:
80
+ stop = start
81
+ start = 0
82
+ super().__init__(np.arange(start, stop, step), name=name)
83
+ self.start, self.stop, self.step = start, stop, step
84
+
85
+ def __repr__(self):
86
+ return f"RangeIndex(start={self.start}, stop={self.stop}, step={self.step})"
@@ -0,0 +1,228 @@
1
+ """Series — 1D labeled array, pandas-compatible API."""
2
+ from __future__ import annotations
3
+ import numpy as np
4
+ from typing import Any, Callable, Iterable, Optional, Union
5
+
6
+
7
+ class Series:
8
+ def __init__(self, data=None, index=None, name=None, dtype=None):
9
+ if isinstance(data, Series):
10
+ self._data = data._data.copy()
11
+ self._index = data._index.copy() if index is None else list(index)
12
+ self.name = data.name if name is None else name
13
+ elif isinstance(data, dict):
14
+ self._index = list(data.keys())
15
+ self._data = np.array(list(data.values()), dtype=dtype)
16
+ self.name = name
17
+ elif data is None:
18
+ self._data = np.array([], dtype=dtype or np.float64)
19
+ self._index = []
20
+ self.name = name
21
+ else:
22
+ self._data = np.array(data, dtype=dtype)
23
+ self._index = list(index) if index is not None else list(range(len(self._data)))
24
+ self.name = name
25
+
26
+ @property
27
+ def values(self) -> np.ndarray:
28
+ return self._data
29
+
30
+ @property
31
+ def index(self):
32
+ return self._index
33
+
34
+ @index.setter
35
+ def index(self, val):
36
+ self._index = list(val)
37
+
38
+ @property
39
+ def dtype(self):
40
+ return self._data.dtype
41
+
42
+ @property
43
+ def shape(self):
44
+ return self._data.shape
45
+
46
+ def __len__(self):
47
+ return len(self._data)
48
+
49
+ def __getitem__(self, key):
50
+ if isinstance(key, str) or (not isinstance(key, (int, slice, np.ndarray, list))):
51
+ if key in self._index:
52
+ return self._data[self._index.index(key)]
53
+ raise KeyError(key)
54
+ if isinstance(key, slice):
55
+ return Series(self._data[key], index=self._index[key], name=self.name)
56
+ if isinstance(key, (list, np.ndarray)):
57
+ if isinstance(key, list) and len(key) > 0 and isinstance(key[0], bool):
58
+ key = np.array(key)
59
+ if isinstance(key, np.ndarray) and key.dtype == bool:
60
+ idx = [self._index[i] for i in np.where(key)[0]]
61
+ return Series(self._data[key], index=idx, name=self.name)
62
+ idxs = [self._index.index(k) for k in key]
63
+ return Series(self._data[idxs], index=key, name=self.name)
64
+ return self._data[key]
65
+
66
+ def __setitem__(self, key, value):
67
+ if isinstance(key, str) and key in self._index:
68
+ self._data[self._index.index(key)] = value
69
+ else:
70
+ self._data[key] = value
71
+
72
+ def _op(self, other, fn):
73
+ if isinstance(other, Series):
74
+ return Series(fn(self._data, other._data), index=self._index, name=self.name)
75
+ return Series(fn(self._data, other), index=self._index, name=self.name)
76
+
77
+ def __add__(self, other): return self._op(other, np.add)
78
+ def __radd__(self, other): return self._op(other, lambda a, b: np.add(b, a))
79
+ def __sub__(self, other): return self._op(other, np.subtract)
80
+ def __rsub__(self, other): return self._op(other, lambda a, b: np.subtract(b, a))
81
+ def __mul__(self, other): return self._op(other, np.multiply)
82
+ def __rmul__(self, other): return self._op(other, lambda a, b: np.multiply(b, a))
83
+ def __truediv__(self, other): return self._op(other, np.true_divide)
84
+ def __floordiv__(self, other): return self._op(other, np.floor_divide)
85
+ def __pow__(self, other): return self._op(other, np.power)
86
+ def __neg__(self): return Series(-self._data, index=self._index, name=self.name)
87
+ def __eq__(self, other): return self._op(other, np.equal)
88
+ def __ne__(self, other): return self._op(other, np.not_equal)
89
+ def __lt__(self, other): return self._op(other, np.less)
90
+ def __le__(self, other): return self._op(other, np.less_equal)
91
+ def __gt__(self, other): return self._op(other, np.greater)
92
+ def __ge__(self, other): return self._op(other, np.greater_equal)
93
+
94
+ def apply(self, func: Callable) -> 'Series':
95
+ return Series([func(v) for v in self._data], index=self._index, name=self.name)
96
+
97
+ def map(self, func_or_dict) -> 'Series':
98
+ if callable(func_or_dict):
99
+ return self.apply(func_or_dict)
100
+ return Series([func_or_dict.get(v, np.nan) for v in self._data], index=self._index, name=self.name)
101
+
102
+ def isnull(self) -> 'Series':
103
+ try:
104
+ return Series(np.isnan(self._data.astype(float)), index=self._index)
105
+ except (ValueError, TypeError):
106
+ return Series([v is None for v in self._data], index=self._index)
107
+
108
+ def notnull(self) -> 'Series':
109
+ null = self.isnull()
110
+ return Series(~null._data, index=self._index)
111
+
112
+ def dropna(self) -> 'Series':
113
+ mask = self.notnull()._data
114
+ new_idx = [self._index[i] for i in range(len(mask)) if mask[i]]
115
+ return Series(self._data[mask], index=new_idx, name=self.name)
116
+
117
+ def fillna(self, value) -> 'Series':
118
+ result = self._data.copy().astype(object)
119
+ try:
120
+ null_mask = np.isnan(self._data.astype(float))
121
+ result = self._data.copy().astype(float)
122
+ result[null_mask] = value
123
+ except (ValueError, TypeError):
124
+ result = np.array([value if v is None else v for v in self._data])
125
+ return Series(result, index=self._index, name=self.name)
126
+
127
+ def sort_values(self, ascending=True) -> 'Series':
128
+ order = np.argsort(self._data)
129
+ if not ascending:
130
+ order = order[::-1]
131
+ new_idx = [self._index[i] for i in order]
132
+ return Series(self._data[order], index=new_idx, name=self.name)
133
+
134
+ def sort_index(self, ascending=True) -> 'Series':
135
+ order = np.argsort(self._index)
136
+ if not ascending:
137
+ order = order[::-1]
138
+ new_idx = [self._index[i] for i in order]
139
+ return Series(self._data[order], index=new_idx, name=self.name)
140
+
141
+ def unique(self) -> np.ndarray:
142
+ return np.unique(self._data)
143
+
144
+ def nunique(self) -> int:
145
+ return len(np.unique(self._data))
146
+
147
+ def value_counts(self, normalize=False) -> 'Series':
148
+ vals, counts = np.unique(self._data, return_counts=True)
149
+ order = np.argsort(-counts)
150
+ vals, counts = vals[order], counts[order]
151
+ if normalize:
152
+ counts = counts / counts.sum()
153
+ return Series(counts, index=list(vals), name=self.name)
154
+
155
+ def mean(self): return float(np.nanmean(self._data.astype(float)))
156
+ def median(self): return float(np.nanmedian(self._data.astype(float)))
157
+ def std(self, ddof=1): return float(np.nanstd(self._data.astype(float), ddof=ddof))
158
+ def var(self, ddof=1): return float(np.nanvar(self._data.astype(float), ddof=ddof))
159
+ def min(self): return self._data.min()
160
+ def max(self): return self._data.max()
161
+ def sum(self): return self._data.sum()
162
+ def prod(self): return self._data.prod()
163
+ def count(self): return int(self.notnull()._data.sum())
164
+ def abs(self): return Series(np.abs(self._data), index=self._index, name=self.name)
165
+ def cumsum(self): return Series(np.cumsum(self._data), index=self._index, name=self.name)
166
+ def cumprod(self): return Series(np.cumprod(self._data), index=self._index, name=self.name)
167
+
168
+ def head(self, n=5) -> 'Series':
169
+ return Series(self._data[:n], index=self._index[:n], name=self.name)
170
+
171
+ def tail(self, n=5) -> 'Series':
172
+ return Series(self._data[-n:], index=self._index[-n:], name=self.name)
173
+
174
+ def reset_index(self, drop=False) -> 'Series':
175
+ if drop:
176
+ return Series(self._data, index=list(range(len(self._data))), name=self.name)
177
+ # Return as DataFrame when drop=False
178
+ from vectorphile.data.frame import DataFrame
179
+ return DataFrame({'index': self._index, self.name or 0: self._data})
180
+
181
+ def to_list(self):
182
+ return self._data.tolist()
183
+
184
+ def to_numpy(self) -> np.ndarray:
185
+ return self._data.copy()
186
+
187
+ def __iter__(self):
188
+ return iter(self._data)
189
+
190
+ def __repr__(self):
191
+ lines = []
192
+ for i, v in zip(self._index, self._data):
193
+ lines.append(f'{i} {v}')
194
+ if self.name:
195
+ lines.append(f'Name: {self.name}, dtype: {self.dtype}')
196
+ return '\n'.join(lines)
197
+
198
+ def __str__(self):
199
+ return self.__repr__()
200
+
201
+ def astype(self, dtype) -> 'Series':
202
+ return Series(self._data.astype(dtype), index=self._index, name=self.name)
203
+
204
+ def clip(self, lower=None, upper=None) -> 'Series':
205
+ return Series(np.clip(self._data, lower, upper), index=self._index, name=self.name)
206
+
207
+ def round(self, decimals=0) -> 'Series':
208
+ return Series(np.round(self._data.astype(float), decimals), index=self._index, name=self.name)
209
+
210
+ def quantile(self, q=0.5):
211
+ return float(np.nanpercentile(self._data.astype(float), q * 100))
212
+
213
+ def corr(self, other: 'Series') -> float:
214
+ return float(np.corrcoef(self._data.astype(float), other._data.astype(float))[0, 1])
215
+
216
+ def describe(self) -> 'Series':
217
+ d = self._data.astype(float)
218
+ stats = {
219
+ 'count': float(len(d)),
220
+ 'mean': float(np.nanmean(d)),
221
+ 'std': float(np.nanstd(d, ddof=1)),
222
+ 'min': float(np.nanmin(d)),
223
+ '25%': float(np.nanpercentile(d, 25)),
224
+ '50%': float(np.nanpercentile(d, 50)),
225
+ '75%': float(np.nanpercentile(d, 75)),
226
+ 'max': float(np.nanmax(d)),
227
+ }
228
+ return Series(list(stats.values()), index=list(stats.keys()), name=self.name)
@@ -0,0 +1,34 @@
1
+ """FFT operations."""
2
+ import numpy as np
3
+ from vectorphile.core.tensor import Tensor
4
+
5
+
6
+ def fft(input: Tensor, n=None, dim=-1, norm=None) -> Tensor:
7
+ return Tensor(np.fft.fft(input._data, n=n, axis=dim, norm=norm))
8
+
9
+ def ifft(input: Tensor, n=None, dim=-1, norm=None) -> Tensor:
10
+ return Tensor(np.fft.ifft(input._data, n=n, axis=dim, norm=norm))
11
+
12
+ def fft2(input: Tensor, s=None, dim=(-2, -1), norm=None) -> Tensor:
13
+ return Tensor(np.fft.fft2(input._data, s=s, axes=dim, norm=norm))
14
+
15
+ def ifft2(input: Tensor, s=None, dim=(-2, -1), norm=None) -> Tensor:
16
+ return Tensor(np.fft.ifft2(input._data, s=s, axes=dim, norm=norm))
17
+
18
+ def rfft(input: Tensor, n=None, dim=-1, norm=None) -> Tensor:
19
+ return Tensor(np.fft.rfft(input._data, n=n, axis=dim, norm=norm))
20
+
21
+ def irfft(input: Tensor, n=None, dim=-1, norm=None) -> Tensor:
22
+ return Tensor(np.fft.irfft(input._data, n=n, axis=dim, norm=norm).astype(np.float32))
23
+
24
+ def fftshift(input: Tensor, dim=None) -> Tensor:
25
+ return Tensor(np.fft.fftshift(input._data, axes=dim))
26
+
27
+ def ifftshift(input: Tensor, dim=None) -> Tensor:
28
+ return Tensor(np.fft.ifftshift(input._data, axes=dim))
29
+
30
+ def fftfreq(n: int, d: float = 1.0) -> Tensor:
31
+ return Tensor(np.fft.fftfreq(n, d).astype(np.float32))
32
+
33
+ def rfftfreq(n: int, d: float = 1.0) -> Tensor:
34
+ return Tensor(np.fft.rfftfreq(n, d).astype(np.float32))
@@ -0,0 +1,63 @@
1
+ """Linear algebra operations."""
2
+ import numpy as np
3
+ from vectorphile.core.tensor import Tensor
4
+
5
+
6
+ def det(a: Tensor) -> Tensor:
7
+ return Tensor(np.linalg.det(a._data))
8
+
9
+ def inv(a: Tensor) -> Tensor:
10
+ return Tensor(np.linalg.inv(a._data))
11
+
12
+ def svd(a: Tensor, full_matrices=True):
13
+ U, S, Vh = np.linalg.svd(a._data, full_matrices=full_matrices)
14
+ return Tensor(U), Tensor(S), Tensor(Vh)
15
+
16
+ def eig(a: Tensor):
17
+ vals, vecs = np.linalg.eig(a._data)
18
+ return Tensor(vals.real.astype(np.float32)), Tensor(vecs.real.astype(np.float32))
19
+
20
+ def eigh(a: Tensor):
21
+ vals, vecs = np.linalg.eigh(a._data)
22
+ return Tensor(vals.astype(np.float32)), Tensor(vecs.astype(np.float32))
23
+
24
+ def lstsq(a: Tensor, b: Tensor):
25
+ result = np.linalg.lstsq(a._data, b._data, rcond=None)
26
+ return Tensor(result[0])
27
+
28
+ def solve(a: Tensor, b: Tensor) -> Tensor:
29
+ return Tensor(np.linalg.solve(a._data, b._data))
30
+
31
+ def norm(a: Tensor, ord=None, dim=None, keepdim=False) -> Tensor:
32
+ return Tensor(np.linalg.norm(a._data, ord=ord, axis=dim, keepdims=keepdim))
33
+
34
+ def matrix_rank(a: Tensor, tol=None) -> Tensor:
35
+ return Tensor(np.linalg.matrix_rank(a._data, tol=tol))
36
+
37
+ def qr(a: Tensor, mode='reduced'):
38
+ Q, R = np.linalg.qr(a._data, mode=mode)
39
+ return Tensor(Q), Tensor(R)
40
+
41
+ def cholesky(a: Tensor) -> Tensor:
42
+ return Tensor(np.linalg.cholesky(a._data))
43
+
44
+ def pinv(a: Tensor) -> Tensor:
45
+ return Tensor(np.linalg.pinv(a._data))
46
+
47
+ def trace(a: Tensor) -> Tensor:
48
+ return Tensor(np.trace(a._data))
49
+
50
+ def matrix_power(a: Tensor, n: int) -> Tensor:
51
+ return Tensor(np.linalg.matrix_power(a._data, n))
52
+
53
+ def cross(a: Tensor, b: Tensor, dim=-1) -> Tensor:
54
+ return Tensor(np.cross(a._data, b._data, axis=dim))
55
+
56
+ def outer(a: Tensor, b: Tensor) -> Tensor:
57
+ return Tensor(np.outer(a._data, b._data))
58
+
59
+ def kron(a: Tensor, b: Tensor) -> Tensor:
60
+ return Tensor(np.kron(a._data, b._data))
61
+
62
+ def vander(x: Tensor, N=None, increasing=False) -> Tensor:
63
+ return Tensor(np.vander(x._data, N, increasing))
@@ -0,0 +1,44 @@
1
+ from vectorphile.ml import linear, tree, ensemble, cluster, decomposition, neighbors, naive_bayes, preprocessing, metrics, model_selection, svm
2
+ from vectorphile.ml.linear import LinearRegression, Ridge, Lasso, ElasticNet, LogisticRegression, Perceptron
3
+ from vectorphile.ml.tree import DecisionTreeClassifier, DecisionTreeRegressor
4
+ from vectorphile.ml.ensemble import (
5
+ RandomForestClassifier, RandomForestRegressor,
6
+ GradientBoostingClassifier, GradientBoostingRegressor,
7
+ )
8
+ from vectorphile.ml.cluster import KMeans, DBSCAN, AgglomerativeClustering, GaussianMixture
9
+ from vectorphile.ml.decomposition import PCA, TruncatedSVD, NMF, FastICA
10
+ from vectorphile.ml.neighbors import KNeighborsClassifier, KNeighborsRegressor
11
+ from vectorphile.ml.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB
12
+ from vectorphile.ml.svm import SVC, SVR
13
+ from vectorphile.ml.preprocessing import (
14
+ StandardScaler, MinMaxScaler, RobustScaler,
15
+ LabelEncoder, OneHotEncoder, PolynomialFeatures,
16
+ )
17
+ from vectorphile.ml.metrics import (
18
+ accuracy_score, f1_score, precision_score, recall_score, roc_auc_score,
19
+ confusion_matrix, mse, rmse, mae, r2_score, silhouette_score, log_loss,
20
+ )
21
+ from vectorphile.ml.model_selection import (
22
+ train_test_split, KFold, StratifiedKFold, cross_val_score,
23
+ GridSearchCV, RandomizedSearchCV,
24
+ )
25
+ from vectorphile.ml.boost import XGBClassifier, XGBRegressor
26
+
27
+ __all__ = [
28
+ 'LinearRegression', 'Ridge', 'Lasso', 'ElasticNet', 'LogisticRegression', 'Perceptron',
29
+ 'DecisionTreeClassifier', 'DecisionTreeRegressor',
30
+ 'RandomForestClassifier', 'RandomForestRegressor',
31
+ 'GradientBoostingClassifier', 'GradientBoostingRegressor',
32
+ 'XGBClassifier', 'XGBRegressor',
33
+ 'KMeans', 'DBSCAN', 'AgglomerativeClustering', 'GaussianMixture',
34
+ 'PCA', 'TruncatedSVD', 'NMF', 'FastICA',
35
+ 'KNeighborsClassifier', 'KNeighborsRegressor',
36
+ 'GaussianNB', 'MultinomialNB', 'BernoulliNB',
37
+ 'SVC', 'SVR',
38
+ 'StandardScaler', 'MinMaxScaler', 'RobustScaler',
39
+ 'LabelEncoder', 'OneHotEncoder', 'PolynomialFeatures',
40
+ 'accuracy_score', 'f1_score', 'precision_score', 'recall_score', 'roc_auc_score',
41
+ 'confusion_matrix', 'mse', 'rmse', 'mae', 'r2_score', 'silhouette_score', 'log_loss',
42
+ 'train_test_split', 'KFold', 'StratifiedKFold', 'cross_val_score',
43
+ 'GridSearchCV', 'RandomizedSearchCV',
44
+ ]
@@ -0,0 +1,133 @@
1
+ """XGBoost-style gradient boosting (pure NumPy)."""
2
+ import numpy as np
3
+ from typing import Optional
4
+ from vectorphile.ml.tree import DecisionTreeRegressor
5
+
6
+
7
+ class XGBRegressor:
8
+ """Extreme Gradient Boosting Regressor with second-order statistics."""
9
+ def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=6,
10
+ min_child_weight=1, gamma=0.0, reg_alpha=0.0, reg_lambda=1.0,
11
+ subsample=1.0, colsample_bytree=1.0, random_state=None):
12
+ self.n_estimators = n_estimators
13
+ self.learning_rate = learning_rate
14
+ self.max_depth = max_depth
15
+ self.min_child_weight = min_child_weight
16
+ self.gamma = gamma
17
+ self.reg_alpha = reg_alpha
18
+ self.reg_lambda = reg_lambda
19
+ self.subsample = subsample
20
+ self.colsample_bytree = colsample_bytree
21
+ self.random_state = random_state
22
+ self.estimators_ = []
23
+ self.base_score_: float = 0.0
24
+
25
+ def fit(self, X, y, eval_set=None, verbose=False) -> 'XGBRegressor':
26
+ if self.random_state is not None:
27
+ np.random.seed(self.random_state)
28
+ X, y = np.array(X, dtype=float), np.array(y, dtype=float)
29
+ self.base_score_ = y.mean()
30
+ F = np.full(len(y), self.base_score_)
31
+ n, p = X.shape
32
+ for i in range(self.n_estimators):
33
+ # MSE gradient = -2*(y-F), hessian = 2 (constant for MSE)
34
+ g = -(y - F) # negative gradient
35
+ h = np.ones_like(g)
36
+ # Subsample rows
37
+ if self.subsample < 1.0:
38
+ row_idx = np.random.choice(n, int(n * self.subsample), replace=False)
39
+ Xs, gs, hs = X[row_idx], g[row_idx], h[row_idx]
40
+ else:
41
+ Xs, gs, hs = X, g, h
42
+ # Subsample cols
43
+ if self.colsample_bytree < 1.0:
44
+ col_idx = np.random.choice(p, int(p * self.colsample_bytree), replace=False)
45
+ Xs = Xs[:, col_idx]
46
+ # Weight: -g/h for leaf values
47
+ w = -gs / (hs + self.reg_lambda)
48
+ tree = DecisionTreeRegressor(max_depth=self.max_depth)
49
+ tree.fit(Xs, w)
50
+ update = tree.predict(X[:, col_idx] if self.colsample_bytree < 1.0 else X)
51
+ F += self.learning_rate * update
52
+ self.estimators_.append((tree, col_idx if self.colsample_bytree < 1.0 else None))
53
+ if verbose and (i + 1) % 10 == 0:
54
+ loss = np.mean((y - F)**2)
55
+ print(f'[{i+1}] train-rmse={np.sqrt(loss):.4f}')
56
+ return self
57
+
58
+ def predict(self, X) -> np.ndarray:
59
+ X = np.array(X, dtype=float)
60
+ F = np.full(len(X), self.base_score_)
61
+ for tree, col_idx in self.estimators_:
62
+ Xs = X[:, col_idx] if col_idx is not None else X
63
+ F += self.learning_rate * tree.predict(Xs)
64
+ return F
65
+
66
+ def score(self, X, y) -> float:
67
+ y = np.array(y, dtype=float)
68
+ pred = self.predict(X)
69
+ ss_res = np.sum((y - pred)**2)
70
+ ss_tot = np.sum((y - y.mean())**2)
71
+ return float(1 - ss_res / (ss_tot + 1e-12))
72
+
73
+
74
+ class XGBClassifier:
75
+ """Extreme Gradient Boosting Classifier."""
76
+ def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=6,
77
+ reg_lambda=1.0, subsample=1.0, random_state=None):
78
+ self.n_estimators = n_estimators
79
+ self.learning_rate = learning_rate
80
+ self.max_depth = max_depth
81
+ self.reg_lambda = reg_lambda
82
+ self.subsample = subsample
83
+ self.random_state = random_state
84
+ self.classes_: Optional[np.ndarray] = None
85
+ self.estimators_ = []
86
+ self.base_score_: Optional[np.ndarray] = None
87
+
88
+ def _sigmoid(self, x):
89
+ return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))
90
+
91
+ def fit(self, X, y) -> 'XGBClassifier':
92
+ if self.random_state is not None:
93
+ np.random.seed(self.random_state)
94
+ X, y = np.array(X, dtype=float), np.array(y)
95
+ self.classes_ = np.unique(y)
96
+ K = len(self.classes_)
97
+ n = len(X)
98
+ y_ohe = np.zeros((n, K))
99
+ for i, c in enumerate(self.classes_):
100
+ y_ohe[:, i] = (y == c).astype(float)
101
+ self.base_score_ = np.log(y_ohe.mean(axis=0) + 1e-12)
102
+ F = np.tile(self.base_score_, (n, 1)).copy()
103
+ self.estimators_ = []
104
+ for _ in range(self.n_estimators):
105
+ probs = self._sigmoid(F)
106
+ probs /= probs.sum(axis=1, keepdims=True)
107
+ group = []
108
+ for k in range(K):
109
+ g = -(y_ohe[:, k] - probs[:, k])
110
+ h = probs[:, k] * (1 - probs[:, k]) + 1e-6
111
+ w = -g / (h + self.reg_lambda)
112
+ tree = DecisionTreeRegressor(max_depth=self.max_depth)
113
+ tree.fit(X, w)
114
+ F[:, k] += self.learning_rate * tree.predict(X)
115
+ group.append(tree)
116
+ self.estimators_.append(group)
117
+ return self
118
+
119
+ def predict_proba(self, X) -> np.ndarray:
120
+ X = np.array(X, dtype=float)
121
+ F = np.tile(self.base_score_, (len(X), 1)).copy()
122
+ for group in self.estimators_:
123
+ for k, tree in enumerate(group):
124
+ F[:, k] += self.learning_rate * tree.predict(X)
125
+ probs = self._sigmoid(F)
126
+ probs /= probs.sum(axis=1, keepdims=True)
127
+ return probs
128
+
129
+ def predict(self, X) -> np.ndarray:
130
+ return self.classes_[self.predict_proba(X).argmax(axis=1)]
131
+
132
+ def score(self, X, y) -> float:
133
+ return float(np.mean(self.predict(X) == np.array(y)))