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,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)))
|