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,696 @@
|
|
|
1
|
+
"""Core Tensor class — the foundation of vectorphile."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Any, Optional, Tuple, Union, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_DTYPE_MAP = {
|
|
8
|
+
'float32': np.float32, 'float': np.float32,
|
|
9
|
+
'float64': np.float64, 'double': np.float64,
|
|
10
|
+
'float16': np.float16, 'half': np.float16,
|
|
11
|
+
'int32': np.int32, 'int': np.int32,
|
|
12
|
+
'int64': np.int64, 'long': np.int64,
|
|
13
|
+
'int8': np.int8, 'uint8': np.uint8,
|
|
14
|
+
'bool': np.bool_,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Tensor:
|
|
19
|
+
"""N-dimensional tensor backed by numpy, with autograd support."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, data, dtype=None, device='cpu', requires_grad=False):
|
|
22
|
+
if isinstance(data, Tensor):
|
|
23
|
+
data = data._data
|
|
24
|
+
if isinstance(data, np.ndarray):
|
|
25
|
+
self._data = data.astype(_DTYPE_MAP.get(dtype, data.dtype)) if dtype else data.copy()
|
|
26
|
+
else:
|
|
27
|
+
np_dtype = _DTYPE_MAP.get(dtype, np.float32) if dtype else None
|
|
28
|
+
self._data = np.array(data, dtype=np_dtype)
|
|
29
|
+
self.device = device
|
|
30
|
+
self.requires_grad = requires_grad
|
|
31
|
+
self.grad: Optional[Tensor] = None
|
|
32
|
+
self.grad_fn = None
|
|
33
|
+
self._is_leaf = True
|
|
34
|
+
|
|
35
|
+
# ── properties ──────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def shape(self) -> Tuple[int, ...]:
|
|
39
|
+
return self._data.shape
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def ndim(self) -> int:
|
|
43
|
+
return self._data.ndim
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def dtype(self) -> str:
|
|
47
|
+
return str(self._data.dtype)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def size(self) -> int:
|
|
51
|
+
return self._data.size
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def T(self) -> 'Tensor':
|
|
55
|
+
return self.transpose()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def data(self) -> np.ndarray:
|
|
59
|
+
return self._data
|
|
60
|
+
|
|
61
|
+
# ── creation helpers (class methods) ───────────────────────────────────
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def zeros(cls, *shape, dtype='float32', device='cpu', requires_grad=False) -> 'Tensor':
|
|
65
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
66
|
+
shape = shape[0]
|
|
67
|
+
return cls(np.zeros(shape, dtype=_DTYPE_MAP[dtype]), device=device, requires_grad=requires_grad)
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def ones(cls, *shape, dtype='float32', device='cpu', requires_grad=False) -> 'Tensor':
|
|
71
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
72
|
+
shape = shape[0]
|
|
73
|
+
return cls(np.ones(shape, dtype=_DTYPE_MAP[dtype]), device=device, requires_grad=requires_grad)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def eye(cls, n, m=None, dtype='float32', device='cpu') -> 'Tensor':
|
|
77
|
+
return cls(np.eye(n, m, dtype=_DTYPE_MAP[dtype]), device=device)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def arange(cls, start, stop=None, step=1, dtype='float32', device='cpu') -> 'Tensor':
|
|
81
|
+
if stop is None:
|
|
82
|
+
start, stop = 0, start
|
|
83
|
+
return cls(np.arange(start, stop, step, dtype=_DTYPE_MAP[dtype]), device=device)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def linspace(cls, start, stop, num, dtype='float32', device='cpu') -> 'Tensor':
|
|
87
|
+
return cls(np.linspace(start, stop, num, dtype=_DTYPE_MAP[dtype]), device=device)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def full(cls, shape, fill_value, dtype='float32', device='cpu') -> 'Tensor':
|
|
91
|
+
return cls(np.full(shape, fill_value, dtype=_DTYPE_MAP[dtype]), device=device)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def empty(cls, *shape, dtype='float32', device='cpu') -> 'Tensor':
|
|
95
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
96
|
+
shape = shape[0]
|
|
97
|
+
return cls(np.empty(shape, dtype=_DTYPE_MAP[dtype]), device=device)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def rand(cls, *shape, device='cpu', requires_grad=False) -> 'Tensor':
|
|
101
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
102
|
+
shape = shape[0]
|
|
103
|
+
return cls(np.random.rand(*shape).astype(np.float32), device=device, requires_grad=requires_grad)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def randn(cls, *shape, device='cpu', requires_grad=False) -> 'Tensor':
|
|
107
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
108
|
+
shape = shape[0]
|
|
109
|
+
return cls(np.random.randn(*shape).astype(np.float32), device=device, requires_grad=requires_grad)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def randint(cls, low, high, shape, dtype='int64', device='cpu') -> 'Tensor':
|
|
113
|
+
return cls(np.random.randint(low, high, shape).astype(_DTYPE_MAP[dtype]), device=device)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def from_numpy(cls, arr: np.ndarray) -> 'Tensor':
|
|
117
|
+
return cls(arr)
|
|
118
|
+
|
|
119
|
+
# ── type casting ────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
def float(self) -> 'Tensor':
|
|
122
|
+
return Tensor(self._data.astype(np.float32), device=self.device)
|
|
123
|
+
|
|
124
|
+
def double(self) -> 'Tensor':
|
|
125
|
+
return Tensor(self._data.astype(np.float64), device=self.device)
|
|
126
|
+
|
|
127
|
+
def half(self) -> 'Tensor':
|
|
128
|
+
return Tensor(self._data.astype(np.float16), device=self.device)
|
|
129
|
+
|
|
130
|
+
def int(self) -> 'Tensor':
|
|
131
|
+
return Tensor(self._data.astype(np.int32), device=self.device)
|
|
132
|
+
|
|
133
|
+
def long(self) -> 'Tensor':
|
|
134
|
+
return Tensor(self._data.astype(np.int64), device=self.device)
|
|
135
|
+
|
|
136
|
+
def bool(self) -> 'Tensor':
|
|
137
|
+
return Tensor(self._data.astype(np.bool_), device=self.device)
|
|
138
|
+
|
|
139
|
+
def to(self, device=None, dtype=None) -> 'Tensor':
|
|
140
|
+
data = self._data
|
|
141
|
+
if dtype is not None:
|
|
142
|
+
data = data.astype(_DTYPE_MAP.get(dtype, data.dtype))
|
|
143
|
+
return Tensor(data, device=device or self.device, requires_grad=self.requires_grad)
|
|
144
|
+
|
|
145
|
+
def numpy(self) -> np.ndarray:
|
|
146
|
+
return self._data.copy()
|
|
147
|
+
|
|
148
|
+
def item(self):
|
|
149
|
+
return self._data.item()
|
|
150
|
+
|
|
151
|
+
def tolist(self):
|
|
152
|
+
return self._data.tolist()
|
|
153
|
+
|
|
154
|
+
def detach(self) -> 'Tensor':
|
|
155
|
+
t = Tensor(self._data, device=self.device)
|
|
156
|
+
t.requires_grad = False
|
|
157
|
+
return t
|
|
158
|
+
|
|
159
|
+
def clone(self) -> 'Tensor':
|
|
160
|
+
return Tensor(self._data.copy(), device=self.device, requires_grad=self.requires_grad)
|
|
161
|
+
|
|
162
|
+
# ── shape ops ───────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
def reshape(self, *shape) -> 'Tensor':
|
|
165
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
166
|
+
shape = shape[0]
|
|
167
|
+
return Tensor(self._data.reshape(shape), device=self.device, requires_grad=self.requires_grad)
|
|
168
|
+
|
|
169
|
+
def view(self, *shape) -> 'Tensor':
|
|
170
|
+
return self.reshape(*shape)
|
|
171
|
+
|
|
172
|
+
def transpose(self, dim0=None, dim1=None) -> 'Tensor':
|
|
173
|
+
if dim0 is None and dim1 is None:
|
|
174
|
+
return Tensor(self._data.T, device=self.device, requires_grad=self.requires_grad)
|
|
175
|
+
axes = list(range(self.ndim))
|
|
176
|
+
axes[dim0], axes[dim1] = axes[dim1], axes[dim0]
|
|
177
|
+
return Tensor(np.transpose(self._data, axes), device=self.device, requires_grad=self.requires_grad)
|
|
178
|
+
|
|
179
|
+
def permute(self, *dims) -> 'Tensor':
|
|
180
|
+
if len(dims) == 1 and isinstance(dims[0], (tuple, list)):
|
|
181
|
+
dims = dims[0]
|
|
182
|
+
return Tensor(np.transpose(self._data, dims), device=self.device, requires_grad=self.requires_grad)
|
|
183
|
+
|
|
184
|
+
def squeeze(self, dim=None) -> 'Tensor':
|
|
185
|
+
if dim is None:
|
|
186
|
+
return Tensor(np.squeeze(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
187
|
+
return Tensor(np.squeeze(self._data, axis=dim), device=self.device, requires_grad=self.requires_grad)
|
|
188
|
+
|
|
189
|
+
def unsqueeze(self, dim) -> 'Tensor':
|
|
190
|
+
return Tensor(np.expand_dims(self._data, axis=dim), device=self.device, requires_grad=self.requires_grad)
|
|
191
|
+
|
|
192
|
+
def flatten(self, start_dim=0, end_dim=-1) -> 'Tensor':
|
|
193
|
+
shape = self._data.shape
|
|
194
|
+
if end_dim < 0:
|
|
195
|
+
end_dim = self.ndim + end_dim
|
|
196
|
+
new_shape = shape[:start_dim] + (-1,) + shape[end_dim+1:]
|
|
197
|
+
return Tensor(self._data.reshape(new_shape), device=self.device, requires_grad=self.requires_grad)
|
|
198
|
+
|
|
199
|
+
def expand(self, *shape) -> 'Tensor':
|
|
200
|
+
if len(shape) == 1 and isinstance(shape[0], (tuple, list)):
|
|
201
|
+
shape = shape[0]
|
|
202
|
+
return Tensor(np.broadcast_to(self._data, shape).copy(), device=self.device, requires_grad=self.requires_grad)
|
|
203
|
+
|
|
204
|
+
def contiguous(self) -> 'Tensor':
|
|
205
|
+
return Tensor(np.ascontiguousarray(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
206
|
+
|
|
207
|
+
def chunk(self, chunks, dim=0) -> List['Tensor']:
|
|
208
|
+
return [Tensor(a, device=self.device) for a in np.array_split(self._data, chunks, axis=dim)]
|
|
209
|
+
|
|
210
|
+
def split(self, split_size, dim=0) -> List['Tensor']:
|
|
211
|
+
indices = list(range(split_size, self.shape[dim], split_size))
|
|
212
|
+
return [Tensor(a, device=self.device) for a in np.split(self._data, indices, axis=dim)]
|
|
213
|
+
|
|
214
|
+
def repeat(self, *repeats) -> 'Tensor':
|
|
215
|
+
if len(repeats) == 1 and isinstance(repeats[0], (tuple, list)):
|
|
216
|
+
repeats = repeats[0]
|
|
217
|
+
return Tensor(np.tile(self._data, repeats), device=self.device)
|
|
218
|
+
|
|
219
|
+
# ── reduction ops ───────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
def sum(self, dim=None, keepdim=False) -> 'Tensor':
|
|
222
|
+
from vectorphile.autograd.engine import SumBackward
|
|
223
|
+
result = Tensor(self._data.sum(axis=dim, keepdims=keepdim), device=self.device,
|
|
224
|
+
requires_grad=self.requires_grad)
|
|
225
|
+
if self.requires_grad:
|
|
226
|
+
result.grad_fn = SumBackward(self, dim, keepdim)
|
|
227
|
+
result._is_leaf = False
|
|
228
|
+
return result
|
|
229
|
+
|
|
230
|
+
def mean(self, dim=None, keepdim=False) -> 'Tensor':
|
|
231
|
+
from vectorphile.autograd.engine import MeanBackward
|
|
232
|
+
result = Tensor(self._data.mean(axis=dim, keepdims=keepdim), device=self.device,
|
|
233
|
+
requires_grad=self.requires_grad)
|
|
234
|
+
if self.requires_grad:
|
|
235
|
+
result.grad_fn = MeanBackward(self, dim, keepdim)
|
|
236
|
+
result._is_leaf = False
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
def max(self, dim=None, keepdim=False):
|
|
240
|
+
if dim is None:
|
|
241
|
+
return Tensor(self._data.max(), device=self.device)
|
|
242
|
+
vals = self._data.max(axis=dim, keepdims=keepdim)
|
|
243
|
+
idxs = self._data.argmax(axis=dim)
|
|
244
|
+
return Tensor(vals, device=self.device), Tensor(idxs, device=self.device)
|
|
245
|
+
|
|
246
|
+
def min(self, dim=None, keepdim=False):
|
|
247
|
+
if dim is None:
|
|
248
|
+
return Tensor(self._data.min(), device=self.device)
|
|
249
|
+
vals = self._data.min(axis=dim, keepdims=keepdim)
|
|
250
|
+
idxs = self._data.argmin(axis=dim)
|
|
251
|
+
return Tensor(vals, device=self.device), Tensor(idxs, device=self.device)
|
|
252
|
+
|
|
253
|
+
def argmax(self, dim=None) -> 'Tensor':
|
|
254
|
+
return Tensor(self._data.argmax(axis=dim), device=self.device)
|
|
255
|
+
|
|
256
|
+
def argmin(self, dim=None) -> 'Tensor':
|
|
257
|
+
return Tensor(self._data.argmin(axis=dim), device=self.device)
|
|
258
|
+
|
|
259
|
+
def std(self, dim=None, keepdim=False, unbiased=True) -> 'Tensor':
|
|
260
|
+
ddof = 1 if unbiased else 0
|
|
261
|
+
return Tensor(self._data.std(axis=dim, keepdims=keepdim, ddof=ddof), device=self.device)
|
|
262
|
+
|
|
263
|
+
def var(self, dim=None, keepdim=False, unbiased=True) -> 'Tensor':
|
|
264
|
+
ddof = 1 if unbiased else 0
|
|
265
|
+
return Tensor(self._data.var(axis=dim, keepdims=keepdim, ddof=ddof), device=self.device)
|
|
266
|
+
|
|
267
|
+
def norm(self, p=2, dim=None, keepdim=False) -> 'Tensor':
|
|
268
|
+
return Tensor(np.linalg.norm(self._data, ord=p, axis=dim, keepdims=keepdim), device=self.device)
|
|
269
|
+
|
|
270
|
+
def prod(self, dim=None, keepdim=False) -> 'Tensor':
|
|
271
|
+
return Tensor(self._data.prod(axis=dim, keepdims=keepdim), device=self.device)
|
|
272
|
+
|
|
273
|
+
def cumsum(self, dim) -> 'Tensor':
|
|
274
|
+
return Tensor(self._data.cumsum(axis=dim), device=self.device)
|
|
275
|
+
|
|
276
|
+
def cumprod(self, dim) -> 'Tensor':
|
|
277
|
+
return Tensor(self._data.cumprod(axis=dim), device=self.device)
|
|
278
|
+
|
|
279
|
+
# ── element-wise math ───────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
def exp(self) -> 'Tensor':
|
|
282
|
+
return Tensor(np.exp(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
283
|
+
|
|
284
|
+
def log(self) -> 'Tensor':
|
|
285
|
+
return Tensor(np.log(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
286
|
+
|
|
287
|
+
def log2(self) -> 'Tensor':
|
|
288
|
+
return Tensor(np.log2(self._data), device=self.device)
|
|
289
|
+
|
|
290
|
+
def log10(self) -> 'Tensor':
|
|
291
|
+
return Tensor(np.log10(self._data), device=self.device)
|
|
292
|
+
|
|
293
|
+
def sqrt(self) -> 'Tensor':
|
|
294
|
+
return Tensor(np.sqrt(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
295
|
+
|
|
296
|
+
def abs(self) -> 'Tensor':
|
|
297
|
+
return Tensor(np.abs(self._data), device=self.device, requires_grad=self.requires_grad)
|
|
298
|
+
|
|
299
|
+
def sign(self) -> 'Tensor':
|
|
300
|
+
return Tensor(np.sign(self._data), device=self.device)
|
|
301
|
+
|
|
302
|
+
def sin(self) -> 'Tensor':
|
|
303
|
+
return Tensor(np.sin(self._data), device=self.device)
|
|
304
|
+
|
|
305
|
+
def cos(self) -> 'Tensor':
|
|
306
|
+
return Tensor(np.cos(self._data), device=self.device)
|
|
307
|
+
|
|
308
|
+
def tan(self) -> 'Tensor':
|
|
309
|
+
return Tensor(np.tan(self._data), device=self.device)
|
|
310
|
+
|
|
311
|
+
def tanh(self) -> 'Tensor':
|
|
312
|
+
from vectorphile.autograd.engine import TanhBackward
|
|
313
|
+
out_data = np.tanh(self._data)
|
|
314
|
+
result = Tensor(out_data, device=self.device, requires_grad=self.requires_grad)
|
|
315
|
+
if self.requires_grad:
|
|
316
|
+
result.grad_fn = TanhBackward(self, result)
|
|
317
|
+
result._is_leaf = False
|
|
318
|
+
return result
|
|
319
|
+
|
|
320
|
+
def sigmoid(self) -> 'Tensor':
|
|
321
|
+
from vectorphile.autograd.engine import SigmoidBackward
|
|
322
|
+
out_data = 1.0 / (1.0 + np.exp(-self._data))
|
|
323
|
+
result = Tensor(out_data, device=self.device, requires_grad=self.requires_grad)
|
|
324
|
+
if self.requires_grad:
|
|
325
|
+
result.grad_fn = SigmoidBackward(self, result)
|
|
326
|
+
result._is_leaf = False
|
|
327
|
+
return result
|
|
328
|
+
|
|
329
|
+
def relu(self) -> 'Tensor':
|
|
330
|
+
from vectorphile.autograd.engine import ReLUBackward
|
|
331
|
+
result = Tensor(np.maximum(0, self._data), device=self.device, requires_grad=self.requires_grad)
|
|
332
|
+
if self.requires_grad:
|
|
333
|
+
result.grad_fn = ReLUBackward(self)
|
|
334
|
+
result._is_leaf = False
|
|
335
|
+
return result
|
|
336
|
+
|
|
337
|
+
def clamp(self, min=None, max=None) -> 'Tensor':
|
|
338
|
+
return Tensor(np.clip(self._data, min, max), device=self.device)
|
|
339
|
+
|
|
340
|
+
def clip(self, min=None, max=None) -> 'Tensor':
|
|
341
|
+
return self.clamp(min, max)
|
|
342
|
+
|
|
343
|
+
def floor(self) -> 'Tensor':
|
|
344
|
+
return Tensor(np.floor(self._data), device=self.device)
|
|
345
|
+
|
|
346
|
+
def ceil(self) -> 'Tensor':
|
|
347
|
+
return Tensor(np.ceil(self._data), device=self.device)
|
|
348
|
+
|
|
349
|
+
def round(self, decimals=0) -> 'Tensor':
|
|
350
|
+
return Tensor(np.round(self._data, decimals), device=self.device)
|
|
351
|
+
|
|
352
|
+
def pow(self, exponent) -> 'Tensor':
|
|
353
|
+
exp = exponent._data if isinstance(exponent, Tensor) else exponent
|
|
354
|
+
return Tensor(np.power(self._data, exp), device=self.device, requires_grad=self.requires_grad)
|
|
355
|
+
|
|
356
|
+
# ── matrix ops ──────────────────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
def matmul(self, other: 'Tensor') -> 'Tensor':
|
|
359
|
+
return Tensor(np.matmul(self._data, other._data), device=self.device, requires_grad=self.requires_grad or other.requires_grad)
|
|
360
|
+
|
|
361
|
+
def mm(self, other: 'Tensor') -> 'Tensor':
|
|
362
|
+
return self.matmul(other)
|
|
363
|
+
|
|
364
|
+
def bmm(self, other: 'Tensor') -> 'Tensor':
|
|
365
|
+
return Tensor(np.matmul(self._data, other._data), device=self.device)
|
|
366
|
+
|
|
367
|
+
def dot(self, other: 'Tensor') -> 'Tensor':
|
|
368
|
+
return Tensor(np.dot(self._data, other._data), device=self.device)
|
|
369
|
+
|
|
370
|
+
# ── comparison ops ──────────────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
def eq(self, other) -> 'Tensor':
|
|
373
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
374
|
+
return Tensor(self._data == o, device=self.device)
|
|
375
|
+
|
|
376
|
+
def ne(self, other) -> 'Tensor':
|
|
377
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
378
|
+
return Tensor(self._data != o, device=self.device)
|
|
379
|
+
|
|
380
|
+
def lt(self, other) -> 'Tensor':
|
|
381
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
382
|
+
return Tensor(self._data < o, device=self.device)
|
|
383
|
+
|
|
384
|
+
def le(self, other) -> 'Tensor':
|
|
385
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
386
|
+
return Tensor(self._data <= o, device=self.device)
|
|
387
|
+
|
|
388
|
+
def gt(self, other) -> 'Tensor':
|
|
389
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
390
|
+
return Tensor(self._data > o, device=self.device)
|
|
391
|
+
|
|
392
|
+
def ge(self, other) -> 'Tensor':
|
|
393
|
+
o = other._data if isinstance(other, Tensor) else other
|
|
394
|
+
return Tensor(self._data >= o, device=self.device)
|
|
395
|
+
|
|
396
|
+
def all(self, dim=None) -> 'Tensor':
|
|
397
|
+
return Tensor(self._data.all(axis=dim), device=self.device)
|
|
398
|
+
|
|
399
|
+
def any(self, dim=None) -> 'Tensor':
|
|
400
|
+
return Tensor(self._data.any(axis=dim), device=self.device)
|
|
401
|
+
|
|
402
|
+
# ── operators ───────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
def _coerce(self, other):
|
|
405
|
+
return other._data if isinstance(other, Tensor) else other
|
|
406
|
+
|
|
407
|
+
def __add__(self, other) -> 'Tensor':
|
|
408
|
+
from vectorphile.autograd.engine import AddBackward
|
|
409
|
+
result = Tensor(self._data + self._coerce(other), device=self.device,
|
|
410
|
+
requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
|
|
411
|
+
if result.requires_grad:
|
|
412
|
+
result.grad_fn = AddBackward(self, other)
|
|
413
|
+
result._is_leaf = False
|
|
414
|
+
return result
|
|
415
|
+
|
|
416
|
+
def __radd__(self, other) -> 'Tensor':
|
|
417
|
+
return self.__add__(other)
|
|
418
|
+
|
|
419
|
+
def __sub__(self, other) -> 'Tensor':
|
|
420
|
+
from vectorphile.autograd.engine import SubBackward
|
|
421
|
+
result = Tensor(self._data - self._coerce(other), device=self.device,
|
|
422
|
+
requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
|
|
423
|
+
if result.requires_grad:
|
|
424
|
+
result.grad_fn = SubBackward(self, other)
|
|
425
|
+
result._is_leaf = False
|
|
426
|
+
return result
|
|
427
|
+
|
|
428
|
+
def __rsub__(self, other) -> 'Tensor':
|
|
429
|
+
return Tensor(self._coerce(other) - self._data, device=self.device)
|
|
430
|
+
|
|
431
|
+
def __mul__(self, other) -> 'Tensor':
|
|
432
|
+
from vectorphile.autograd.engine import MulBackward
|
|
433
|
+
result = Tensor(self._data * self._coerce(other), device=self.device,
|
|
434
|
+
requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
|
|
435
|
+
if result.requires_grad:
|
|
436
|
+
result.grad_fn = MulBackward(self, other)
|
|
437
|
+
result._is_leaf = False
|
|
438
|
+
return result
|
|
439
|
+
|
|
440
|
+
def __rmul__(self, other) -> 'Tensor':
|
|
441
|
+
return self.__mul__(other)
|
|
442
|
+
|
|
443
|
+
def __truediv__(self, other) -> 'Tensor':
|
|
444
|
+
from vectorphile.autograd.engine import DivBackward
|
|
445
|
+
result = Tensor(self._data / self._coerce(other), device=self.device,
|
|
446
|
+
requires_grad=self.requires_grad or (isinstance(other, Tensor) and other.requires_grad))
|
|
447
|
+
if result.requires_grad:
|
|
448
|
+
result.grad_fn = DivBackward(self, other)
|
|
449
|
+
result._is_leaf = False
|
|
450
|
+
return result
|
|
451
|
+
|
|
452
|
+
def __rtruediv__(self, other) -> 'Tensor':
|
|
453
|
+
return Tensor(self._coerce(other) / self._data, device=self.device)
|
|
454
|
+
|
|
455
|
+
def __pow__(self, exponent) -> 'Tensor':
|
|
456
|
+
exp = self._coerce(exponent)
|
|
457
|
+
return Tensor(np.power(self._data, exp), device=self.device, requires_grad=self.requires_grad)
|
|
458
|
+
|
|
459
|
+
def __neg__(self) -> 'Tensor':
|
|
460
|
+
return Tensor(-self._data, device=self.device, requires_grad=self.requires_grad)
|
|
461
|
+
|
|
462
|
+
def __abs__(self) -> 'Tensor':
|
|
463
|
+
return self.abs()
|
|
464
|
+
|
|
465
|
+
def __matmul__(self, other) -> 'Tensor':
|
|
466
|
+
from vectorphile.autograd.engine import MatMulBackward
|
|
467
|
+
result = Tensor(np.matmul(self._data, other._data), device=self.device,
|
|
468
|
+
requires_grad=self.requires_grad or other.requires_grad)
|
|
469
|
+
if result.requires_grad:
|
|
470
|
+
result.grad_fn = MatMulBackward(self, other)
|
|
471
|
+
result._is_leaf = False
|
|
472
|
+
return result
|
|
473
|
+
|
|
474
|
+
def __eq__(self, other) -> 'Tensor':
|
|
475
|
+
return self.eq(other)
|
|
476
|
+
|
|
477
|
+
def __ne__(self, other) -> 'Tensor':
|
|
478
|
+
return self.ne(other)
|
|
479
|
+
|
|
480
|
+
def __lt__(self, other) -> 'Tensor':
|
|
481
|
+
return self.lt(other)
|
|
482
|
+
|
|
483
|
+
def __le__(self, other) -> 'Tensor':
|
|
484
|
+
return self.le(other)
|
|
485
|
+
|
|
486
|
+
def __gt__(self, other) -> 'Tensor':
|
|
487
|
+
return self.gt(other)
|
|
488
|
+
|
|
489
|
+
def __ge__(self, other) -> 'Tensor':
|
|
490
|
+
return self.ge(other)
|
|
491
|
+
|
|
492
|
+
def __getitem__(self, idx):
|
|
493
|
+
if isinstance(idx, Tensor):
|
|
494
|
+
idx = idx._data
|
|
495
|
+
return Tensor(self._data[idx], device=self.device)
|
|
496
|
+
|
|
497
|
+
def __setitem__(self, idx, value):
|
|
498
|
+
if isinstance(idx, Tensor):
|
|
499
|
+
idx = idx._data
|
|
500
|
+
self._data[idx] = value._data if isinstance(value, Tensor) else value
|
|
501
|
+
|
|
502
|
+
def __len__(self) -> int:
|
|
503
|
+
return len(self._data)
|
|
504
|
+
|
|
505
|
+
def __iter__(self):
|
|
506
|
+
for i in range(len(self)):
|
|
507
|
+
yield self[i]
|
|
508
|
+
|
|
509
|
+
def __repr__(self) -> str:
|
|
510
|
+
return f"tensor({self._data}, dtype={self.dtype}, device='{self.device}')"
|
|
511
|
+
|
|
512
|
+
def __str__(self) -> str:
|
|
513
|
+
return self.__repr__()
|
|
514
|
+
|
|
515
|
+
def __hash__(self):
|
|
516
|
+
return id(self)
|
|
517
|
+
|
|
518
|
+
# ── autograd ────────────────────────────────────────────────────────────
|
|
519
|
+
|
|
520
|
+
def backward(self, gradient=None):
|
|
521
|
+
from vectorphile.autograd.engine import backward
|
|
522
|
+
if gradient is None:
|
|
523
|
+
gradient = Tensor(np.ones_like(self._data), device=self.device)
|
|
524
|
+
backward(self, gradient)
|
|
525
|
+
|
|
526
|
+
def zero_grad(self):
|
|
527
|
+
self.grad = None
|
|
528
|
+
|
|
529
|
+
# ── utility ─────────────────────────────────────────────────────────────
|
|
530
|
+
|
|
531
|
+
def cpu(self) -> 'Tensor':
|
|
532
|
+
return Tensor(self._data, device='cpu', requires_grad=self.requires_grad)
|
|
533
|
+
|
|
534
|
+
def cuda(self) -> 'Tensor':
|
|
535
|
+
# Falls back to CPU gracefully (Phase 2 will add real CUDA)
|
|
536
|
+
return Tensor(self._data, device='cuda', requires_grad=self.requires_grad)
|
|
537
|
+
|
|
538
|
+
def is_cuda(self) -> bool:
|
|
539
|
+
return self.device == 'cuda'
|
|
540
|
+
|
|
541
|
+
def is_leaf(self) -> bool:
|
|
542
|
+
return self._is_leaf
|
|
543
|
+
|
|
544
|
+
def fill_(self, value) -> 'Tensor':
|
|
545
|
+
self._data.fill(value)
|
|
546
|
+
return self
|
|
547
|
+
|
|
548
|
+
def copy_(self, src: 'Tensor') -> 'Tensor':
|
|
549
|
+
self._data[:] = src._data
|
|
550
|
+
return self
|
|
551
|
+
|
|
552
|
+
def masked_fill(self, mask: 'Tensor', value: float) -> 'Tensor':
|
|
553
|
+
result = self._data.copy()
|
|
554
|
+
result[mask._data] = value
|
|
555
|
+
return Tensor(result, device=self.device)
|
|
556
|
+
|
|
557
|
+
def where(self, condition: 'Tensor', other: 'Tensor') -> 'Tensor':
|
|
558
|
+
return Tensor(np.where(condition._data, self._data, other._data), device=self.device)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# ── functional tensor creation ───────────────────────────────────────────────
|
|
562
|
+
|
|
563
|
+
def tensor(data, dtype=None, device='cpu', requires_grad=False) -> Tensor:
|
|
564
|
+
return Tensor(data, dtype=dtype, device=device, requires_grad=requires_grad)
|
|
565
|
+
|
|
566
|
+
def zeros(*shape, dtype='float32', device='cpu', requires_grad=False) -> Tensor:
|
|
567
|
+
return Tensor.zeros(*shape, dtype=dtype, device=device, requires_grad=requires_grad)
|
|
568
|
+
|
|
569
|
+
def ones(*shape, dtype='float32', device='cpu', requires_grad=False) -> Tensor:
|
|
570
|
+
return Tensor.ones(*shape, dtype=dtype, device=device, requires_grad=requires_grad)
|
|
571
|
+
|
|
572
|
+
def eye(n, m=None, dtype='float32', device='cpu') -> Tensor:
|
|
573
|
+
return Tensor.eye(n, m, dtype=dtype, device=device)
|
|
574
|
+
|
|
575
|
+
def arange(start, stop=None, step=1, dtype='float32', device='cpu') -> Tensor:
|
|
576
|
+
return Tensor.arange(start, stop, step, dtype=dtype, device=device)
|
|
577
|
+
|
|
578
|
+
def linspace(start, stop, num, dtype='float32', device='cpu') -> Tensor:
|
|
579
|
+
return Tensor.linspace(start, stop, num, dtype=dtype, device=device)
|
|
580
|
+
|
|
581
|
+
def full(shape, fill_value, dtype='float32', device='cpu') -> Tensor:
|
|
582
|
+
return Tensor.full(shape, fill_value, dtype=dtype, device=device)
|
|
583
|
+
|
|
584
|
+
def empty(*shape, dtype='float32', device='cpu') -> Tensor:
|
|
585
|
+
return Tensor.empty(*shape, dtype=dtype, device=device)
|
|
586
|
+
|
|
587
|
+
def rand(*shape, device='cpu', requires_grad=False) -> Tensor:
|
|
588
|
+
return Tensor.rand(*shape, device=device, requires_grad=requires_grad)
|
|
589
|
+
|
|
590
|
+
def randn(*shape, device='cpu', requires_grad=False) -> Tensor:
|
|
591
|
+
return Tensor.randn(*shape, device=device, requires_grad=requires_grad)
|
|
592
|
+
|
|
593
|
+
def randint(low, high, shape, dtype='int64', device='cpu') -> Tensor:
|
|
594
|
+
return Tensor.randint(low, high, shape, dtype=dtype, device=device)
|
|
595
|
+
|
|
596
|
+
def from_numpy(arr: np.ndarray) -> Tensor:
|
|
597
|
+
return Tensor.from_numpy(arr)
|
|
598
|
+
|
|
599
|
+
def zeros_like(t: Tensor, requires_grad=False) -> Tensor:
|
|
600
|
+
return Tensor(np.zeros_like(t._data), device=t.device, requires_grad=requires_grad)
|
|
601
|
+
|
|
602
|
+
def ones_like(t: Tensor, requires_grad=False) -> Tensor:
|
|
603
|
+
return Tensor(np.ones_like(t._data), device=t.device, requires_grad=requires_grad)
|
|
604
|
+
|
|
605
|
+
def empty_like(t: Tensor) -> Tensor:
|
|
606
|
+
return Tensor(np.empty_like(t._data), device=t.device)
|
|
607
|
+
|
|
608
|
+
def cat(tensors, dim=0) -> Tensor:
|
|
609
|
+
arrays = [t._data for t in tensors]
|
|
610
|
+
return Tensor(np.concatenate(arrays, axis=dim), device=tensors[0].device)
|
|
611
|
+
|
|
612
|
+
def stack(tensors, dim=0) -> Tensor:
|
|
613
|
+
arrays = [t._data for t in tensors]
|
|
614
|
+
return Tensor(np.stack(arrays, axis=dim), device=tensors[0].device)
|
|
615
|
+
|
|
616
|
+
def split(tensor, split_size, dim=0):
|
|
617
|
+
return tensor.split(split_size, dim)
|
|
618
|
+
|
|
619
|
+
def chunk(tensor, chunks, dim=0):
|
|
620
|
+
return tensor.chunk(chunks, dim)
|
|
621
|
+
|
|
622
|
+
def squeeze(tensor, dim=None) -> Tensor:
|
|
623
|
+
return tensor.squeeze(dim)
|
|
624
|
+
|
|
625
|
+
def unsqueeze(tensor, dim) -> Tensor:
|
|
626
|
+
return tensor.unsqueeze(dim)
|
|
627
|
+
|
|
628
|
+
def flatten(tensor, start_dim=0, end_dim=-1) -> Tensor:
|
|
629
|
+
return tensor.flatten(start_dim, end_dim)
|
|
630
|
+
|
|
631
|
+
def transpose(tensor, dim0, dim1) -> Tensor:
|
|
632
|
+
return tensor.transpose(dim0, dim1)
|
|
633
|
+
|
|
634
|
+
def permute(tensor, *dims) -> Tensor:
|
|
635
|
+
return tensor.permute(*dims)
|
|
636
|
+
|
|
637
|
+
def matmul(a: Tensor, b: Tensor) -> Tensor:
|
|
638
|
+
return a.matmul(b)
|
|
639
|
+
|
|
640
|
+
def dot(a: Tensor, b: Tensor) -> Tensor:
|
|
641
|
+
return a.dot(b)
|
|
642
|
+
|
|
643
|
+
def where(condition: Tensor, x: Tensor, y: Tensor) -> Tensor:
|
|
644
|
+
return Tensor(np.where(condition._data, x._data, y._data), device=x.device)
|
|
645
|
+
|
|
646
|
+
def max(input, dim=None, keepdim=False):
|
|
647
|
+
return input.max(dim, keepdim)
|
|
648
|
+
|
|
649
|
+
def min(input, dim=None, keepdim=False):
|
|
650
|
+
return input.min(dim, keepdim)
|
|
651
|
+
|
|
652
|
+
def sum(input, dim=None, keepdim=False) -> Tensor:
|
|
653
|
+
return input.sum(dim, keepdim)
|
|
654
|
+
|
|
655
|
+
def mean(input, dim=None, keepdim=False) -> Tensor:
|
|
656
|
+
return input.mean(dim, keepdim)
|
|
657
|
+
|
|
658
|
+
def exp(input: Tensor) -> Tensor:
|
|
659
|
+
return input.exp()
|
|
660
|
+
|
|
661
|
+
def log(input: Tensor) -> Tensor:
|
|
662
|
+
return input.log()
|
|
663
|
+
|
|
664
|
+
def sqrt(input: Tensor) -> Tensor:
|
|
665
|
+
return input.sqrt()
|
|
666
|
+
|
|
667
|
+
def abs(input: Tensor) -> Tensor:
|
|
668
|
+
return input.abs()
|
|
669
|
+
|
|
670
|
+
def clamp(input: Tensor, min=None, max=None) -> Tensor:
|
|
671
|
+
return input.clamp(min, max)
|
|
672
|
+
|
|
673
|
+
def softmax(input: Tensor, dim=-1) -> Tensor:
|
|
674
|
+
data = input._data
|
|
675
|
+
shifted = data - data.max(axis=dim, keepdims=True)
|
|
676
|
+
e = np.exp(shifted)
|
|
677
|
+
return Tensor(e / e.sum(axis=dim, keepdims=True), device=input.device)
|
|
678
|
+
|
|
679
|
+
def log_softmax(input: Tensor, dim=-1) -> Tensor:
|
|
680
|
+
data = input._data
|
|
681
|
+
shifted = data - data.max(axis=dim, keepdims=True)
|
|
682
|
+
log_sum_exp = np.log(np.exp(shifted).sum(axis=dim, keepdims=True))
|
|
683
|
+
return Tensor(shifted - log_sum_exp, device=input.device)
|
|
684
|
+
|
|
685
|
+
def sigmoid(input: Tensor) -> Tensor:
|
|
686
|
+
return input.sigmoid()
|
|
687
|
+
|
|
688
|
+
def relu(input: Tensor) -> Tensor:
|
|
689
|
+
return input.relu()
|
|
690
|
+
|
|
691
|
+
def tanh(input: Tensor) -> Tensor:
|
|
692
|
+
return input.tanh()
|
|
693
|
+
|
|
694
|
+
def einsum(equation: str, *operands) -> Tensor:
|
|
695
|
+
arrays = [op._data for op in operands]
|
|
696
|
+
return Tensor(np.einsum(equation, *arrays), device=operands[0].device)
|