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,97 @@
|
|
|
1
|
+
"""Activation function modules."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from vectorphile.nn.module import Module
|
|
4
|
+
from vectorphile.core.tensor import Tensor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ReLU(Module):
|
|
8
|
+
def __init__(self, inplace=False):
|
|
9
|
+
super().__init__()
|
|
10
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
11
|
+
return x.relu()
|
|
12
|
+
def __repr__(self): return 'ReLU()'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LeakyReLU(Module):
|
|
16
|
+
def __init__(self, negative_slope=0.01):
|
|
17
|
+
super().__init__()
|
|
18
|
+
self.negative_slope = negative_slope
|
|
19
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
20
|
+
return Tensor(np.where(x._data >= 0, x._data, self.negative_slope * x._data), device=x.device)
|
|
21
|
+
def __repr__(self): return f'LeakyReLU(negative_slope={self.negative_slope})'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ELU(Module):
|
|
25
|
+
def __init__(self, alpha=1.0):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.alpha = alpha
|
|
28
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
29
|
+
return Tensor(np.where(x._data >= 0, x._data, self.alpha * (np.exp(x._data) - 1)), device=x.device)
|
|
30
|
+
def __repr__(self): return f'ELU(alpha={self.alpha})'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GELU(Module):
|
|
34
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
35
|
+
d = x._data
|
|
36
|
+
return Tensor(0.5 * d * (1 + np.tanh(np.sqrt(2/np.pi) * (d + 0.044715 * d**3))), device=x.device)
|
|
37
|
+
def __repr__(self): return 'GELU()'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Sigmoid(Module):
|
|
41
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
42
|
+
return x.sigmoid()
|
|
43
|
+
def __repr__(self): return 'Sigmoid()'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Tanh(Module):
|
|
47
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
48
|
+
return x.tanh()
|
|
49
|
+
def __repr__(self): return 'Tanh()'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Softmax(Module):
|
|
53
|
+
def __init__(self, dim=-1):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.dim = dim
|
|
56
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
57
|
+
from vectorphile.core.tensor import softmax
|
|
58
|
+
return softmax(x, self.dim)
|
|
59
|
+
def __repr__(self): return f'Softmax(dim={self.dim})'
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LogSoftmax(Module):
|
|
63
|
+
def __init__(self, dim=-1):
|
|
64
|
+
super().__init__()
|
|
65
|
+
self.dim = dim
|
|
66
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
67
|
+
from vectorphile.core.tensor import log_softmax
|
|
68
|
+
return log_softmax(x, self.dim)
|
|
69
|
+
def __repr__(self): return f'LogSoftmax(dim={self.dim})'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SELU(Module):
|
|
73
|
+
_alpha = 1.6732632423543772
|
|
74
|
+
_scale = 1.0507009873554805
|
|
75
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
76
|
+
d = x._data
|
|
77
|
+
return Tensor(self._scale * np.where(d >= 0, d, self._alpha * (np.exp(d) - 1)), device=x.device)
|
|
78
|
+
def __repr__(self): return 'SELU()'
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class SiLU(Module):
|
|
82
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
83
|
+
return Tensor(x._data * (1 / (1 + np.exp(-x._data))), device=x.device)
|
|
84
|
+
def __repr__(self): return 'SiLU()'
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Mish(Module):
|
|
88
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
89
|
+
return Tensor(x._data * np.tanh(np.log1p(np.exp(x._data))), device=x.device)
|
|
90
|
+
def __repr__(self): return 'Mish()'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Hardswish(Module):
|
|
94
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
95
|
+
d = x._data
|
|
96
|
+
return Tensor(d * np.clip(d + 3, 0, 6) / 6, device=x.device)
|
|
97
|
+
def __repr__(self): return 'Hardswish()'
|
vectorphile/nn/layers.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Neural network layers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import numpy as np
|
|
4
|
+
import math
|
|
5
|
+
from vectorphile.nn.module import Module
|
|
6
|
+
from vectorphile.core.tensor import Tensor
|
|
7
|
+
from vectorphile.autograd.engine import Function
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _LinearBackward(Function):
|
|
11
|
+
"""Custom backward for y = x @ W.T (W is weight matrix, shape out×in)."""
|
|
12
|
+
def __init__(self, x, weight):
|
|
13
|
+
super().__init__(x, weight)
|
|
14
|
+
self.x = x
|
|
15
|
+
self.weight = weight
|
|
16
|
+
|
|
17
|
+
def backward(self, grad):
|
|
18
|
+
# grad shape: (N, out)
|
|
19
|
+
# dL/dx = grad @ W (N, in)
|
|
20
|
+
# dL/dW = grad.T @ x (out, in)
|
|
21
|
+
dx = Tensor(grad._data @ self.weight._data)
|
|
22
|
+
dw = Tensor(grad._data.T @ self.x._data)
|
|
23
|
+
return dx, dw
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Linear(Module):
|
|
27
|
+
def __init__(self, in_features: int, out_features: int, bias: bool = True):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.in_features = in_features
|
|
30
|
+
self.out_features = out_features
|
|
31
|
+
k = math.sqrt(1.0 / in_features)
|
|
32
|
+
self.weight = Tensor(
|
|
33
|
+
np.random.uniform(-k, k, (out_features, in_features)).astype(np.float32),
|
|
34
|
+
requires_grad=True
|
|
35
|
+
)
|
|
36
|
+
self.bias_param = None
|
|
37
|
+
if bias:
|
|
38
|
+
self.bias_param = Tensor(
|
|
39
|
+
np.zeros(out_features, dtype=np.float32), requires_grad=True
|
|
40
|
+
)
|
|
41
|
+
self._parameters['bias_param'] = self.bias_param
|
|
42
|
+
self._parameters['weight'] = self.weight
|
|
43
|
+
|
|
44
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
45
|
+
# Manually compute matmul to keep weight in autograd graph
|
|
46
|
+
# x: (N, in), weight: (out, in) → out: (N, out)
|
|
47
|
+
from vectorphile.autograd.engine import MatMulBackward
|
|
48
|
+
import numpy as np
|
|
49
|
+
result_data = x._data @ self.weight._data.T
|
|
50
|
+
req_grad = x.requires_grad or self.weight.requires_grad
|
|
51
|
+
# Create a transposed view for the backward pass
|
|
52
|
+
weight_t = Tensor(self.weight._data.T, device=self.weight.device, requires_grad=False)
|
|
53
|
+
weight_t._data = self.weight._data.T # view, not copy
|
|
54
|
+
result = Tensor(result_data, device=x.device, requires_grad=req_grad)
|
|
55
|
+
if req_grad:
|
|
56
|
+
# Custom backward: dL/dx = grad @ W, dL/dW = grad.T @ x
|
|
57
|
+
result.grad_fn = _LinearBackward(x, self.weight)
|
|
58
|
+
result._is_leaf = False
|
|
59
|
+
if self.bias_param is not None:
|
|
60
|
+
result = result + self.bias_param
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
64
|
+
return f'Linear(in_features={self.in_features}, out_features={self.out_features}, bias={self.bias_param is not None})'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Conv2d(Module):
|
|
68
|
+
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, bias=True):
|
|
69
|
+
super().__init__()
|
|
70
|
+
self.in_channels = in_channels
|
|
71
|
+
self.out_channels = out_channels
|
|
72
|
+
self.kernel_size = kernel_size if isinstance(kernel_size, tuple) else (kernel_size, kernel_size)
|
|
73
|
+
self.stride = stride if isinstance(stride, tuple) else (stride, stride)
|
|
74
|
+
self.padding = padding if isinstance(padding, tuple) else (padding, padding)
|
|
75
|
+
kh, kw = self.kernel_size
|
|
76
|
+
k = math.sqrt(1.0 / (in_channels * kh * kw))
|
|
77
|
+
self.weight = Tensor(
|
|
78
|
+
np.random.uniform(-k, k, (out_channels, in_channels, kh, kw)).astype(np.float32),
|
|
79
|
+
requires_grad=True
|
|
80
|
+
)
|
|
81
|
+
self._parameters['weight'] = self.weight
|
|
82
|
+
self.bias_param = None
|
|
83
|
+
if bias:
|
|
84
|
+
self.bias_param = Tensor(np.zeros(out_channels, dtype=np.float32), requires_grad=True)
|
|
85
|
+
self._parameters['bias_param'] = self.bias_param
|
|
86
|
+
|
|
87
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
88
|
+
N, C, H, W = x._data.shape
|
|
89
|
+
kh, kw = self.kernel_size
|
|
90
|
+
sh, sw = self.stride
|
|
91
|
+
ph, pw = self.padding
|
|
92
|
+
x_pad = np.pad(x._data, ((0,0),(0,0),(ph,ph),(pw,pw)), mode='constant')
|
|
93
|
+
Ho = (H + 2*ph - kh) // sh + 1
|
|
94
|
+
Wo = (W + 2*pw - kw) // sw + 1
|
|
95
|
+
out = np.zeros((N, self.out_channels, Ho, Wo), dtype=np.float32)
|
|
96
|
+
w = self.weight._data
|
|
97
|
+
for i in range(Ho):
|
|
98
|
+
for j in range(Wo):
|
|
99
|
+
patch = x_pad[:, :, i*sh:i*sh+kh, j*sw:j*sw+kw]
|
|
100
|
+
out[:, :, i, j] = np.einsum('nckl,ockl->no', patch, w)
|
|
101
|
+
if self.bias_param is not None:
|
|
102
|
+
out += self.bias_param._data[None, :, None, None]
|
|
103
|
+
return Tensor(out, device=x.device)
|
|
104
|
+
|
|
105
|
+
def __repr__(self):
|
|
106
|
+
return (f'Conv2d({self.in_channels}, {self.out_channels}, '
|
|
107
|
+
f'kernel_size={self.kernel_size}, stride={self.stride}, padding={self.padding})')
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BatchNorm1d(Module):
|
|
111
|
+
def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True):
|
|
112
|
+
super().__init__()
|
|
113
|
+
self.num_features = num_features
|
|
114
|
+
self.eps = eps
|
|
115
|
+
self.momentum = momentum
|
|
116
|
+
self.affine = affine
|
|
117
|
+
if affine:
|
|
118
|
+
self.weight = Tensor(np.ones(num_features, dtype=np.float32), requires_grad=True)
|
|
119
|
+
self.bias_param = Tensor(np.zeros(num_features, dtype=np.float32), requires_grad=True)
|
|
120
|
+
self._parameters['weight'] = self.weight
|
|
121
|
+
self._parameters['bias_param'] = self.bias_param
|
|
122
|
+
self.running_mean = np.zeros(num_features, dtype=np.float32)
|
|
123
|
+
self.running_var = np.ones(num_features, dtype=np.float32)
|
|
124
|
+
|
|
125
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
126
|
+
data = x._data
|
|
127
|
+
if self.training:
|
|
128
|
+
mean = data.mean(axis=0)
|
|
129
|
+
var = data.var(axis=0)
|
|
130
|
+
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
|
|
131
|
+
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
|
|
132
|
+
else:
|
|
133
|
+
mean, var = self.running_mean, self.running_var
|
|
134
|
+
x_norm = (data - mean) / np.sqrt(var + self.eps)
|
|
135
|
+
if self.affine:
|
|
136
|
+
x_norm = x_norm * self.weight._data + self.bias_param._data
|
|
137
|
+
return Tensor(x_norm.astype(np.float32), device=x.device)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class BatchNorm2d(Module):
|
|
141
|
+
def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True):
|
|
142
|
+
super().__init__()
|
|
143
|
+
self.num_features = num_features
|
|
144
|
+
self.eps = eps
|
|
145
|
+
self.momentum = momentum
|
|
146
|
+
self.affine = affine
|
|
147
|
+
if affine:
|
|
148
|
+
self.weight = Tensor(np.ones(num_features, dtype=np.float32), requires_grad=True)
|
|
149
|
+
self.bias_param = Tensor(np.zeros(num_features, dtype=np.float32), requires_grad=True)
|
|
150
|
+
self._parameters['weight'] = self.weight
|
|
151
|
+
self._parameters['bias_param'] = self.bias_param
|
|
152
|
+
self.running_mean = np.zeros(num_features, dtype=np.float32)
|
|
153
|
+
self.running_var = np.ones(num_features, dtype=np.float32)
|
|
154
|
+
|
|
155
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
156
|
+
data = x._data # (N, C, H, W)
|
|
157
|
+
if self.training:
|
|
158
|
+
mean = data.mean(axis=(0, 2, 3))
|
|
159
|
+
var = data.var(axis=(0, 2, 3))
|
|
160
|
+
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
|
|
161
|
+
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
|
|
162
|
+
else:
|
|
163
|
+
mean, var = self.running_mean, self.running_var
|
|
164
|
+
x_norm = (data - mean[None,:,None,None]) / np.sqrt(var[None,:,None,None] + self.eps)
|
|
165
|
+
if self.affine:
|
|
166
|
+
x_norm = x_norm * self.weight._data[None,:,None,None] + self.bias_param._data[None,:,None,None]
|
|
167
|
+
return Tensor(x_norm.astype(np.float32), device=x.device)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class LayerNorm(Module):
|
|
171
|
+
def __init__(self, normalized_shape, eps=1e-5, elementwise_affine=True):
|
|
172
|
+
super().__init__()
|
|
173
|
+
if isinstance(normalized_shape, int):
|
|
174
|
+
normalized_shape = (normalized_shape,)
|
|
175
|
+
self.normalized_shape = tuple(normalized_shape)
|
|
176
|
+
self.eps = eps
|
|
177
|
+
self.elementwise_affine = elementwise_affine
|
|
178
|
+
if elementwise_affine:
|
|
179
|
+
self.weight = Tensor(np.ones(normalized_shape, dtype=np.float32), requires_grad=True)
|
|
180
|
+
self.bias_param = Tensor(np.zeros(normalized_shape, dtype=np.float32), requires_grad=True)
|
|
181
|
+
self._parameters['weight'] = self.weight
|
|
182
|
+
self._parameters['bias_param'] = self.bias_param
|
|
183
|
+
|
|
184
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
185
|
+
ndim = len(self.normalized_shape)
|
|
186
|
+
axes = tuple(range(-ndim, 0))
|
|
187
|
+
mean = x._data.mean(axis=axes, keepdims=True)
|
|
188
|
+
var = x._data.var(axis=axes, keepdims=True)
|
|
189
|
+
x_norm = (x._data - mean) / np.sqrt(var + self.eps)
|
|
190
|
+
if self.elementwise_affine:
|
|
191
|
+
x_norm = x_norm * self.weight._data + self.bias_param._data
|
|
192
|
+
return Tensor(x_norm.astype(np.float32), device=x.device)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class Dropout(Module):
|
|
196
|
+
def __init__(self, p=0.5):
|
|
197
|
+
super().__init__()
|
|
198
|
+
self.p = p
|
|
199
|
+
|
|
200
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
201
|
+
if not self.training or self.p == 0:
|
|
202
|
+
return x
|
|
203
|
+
mask = (np.random.rand(*x._data.shape) > self.p).astype(np.float32)
|
|
204
|
+
return Tensor(x._data * mask / (1.0 - self.p), device=x.device, requires_grad=x.requires_grad)
|
|
205
|
+
|
|
206
|
+
def __repr__(self):
|
|
207
|
+
return f'Dropout(p={self.p})'
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class Embedding(Module):
|
|
211
|
+
def __init__(self, num_embeddings, embedding_dim, padding_idx=None):
|
|
212
|
+
super().__init__()
|
|
213
|
+
self.num_embeddings = num_embeddings
|
|
214
|
+
self.embedding_dim = embedding_dim
|
|
215
|
+
self.padding_idx = padding_idx
|
|
216
|
+
self.weight = Tensor(
|
|
217
|
+
np.random.randn(num_embeddings, embedding_dim).astype(np.float32) * 0.02,
|
|
218
|
+
requires_grad=True
|
|
219
|
+
)
|
|
220
|
+
self._parameters['weight'] = self.weight
|
|
221
|
+
|
|
222
|
+
def forward(self, indices: Tensor) -> Tensor:
|
|
223
|
+
idx = indices._data.astype(int)
|
|
224
|
+
out = self.weight._data[idx]
|
|
225
|
+
if self.padding_idx is not None:
|
|
226
|
+
mask = (idx == self.padding_idx)[..., None]
|
|
227
|
+
out = np.where(mask, 0.0, out)
|
|
228
|
+
return Tensor(out.astype(np.float32), device=indices.device)
|
|
229
|
+
|
|
230
|
+
def __repr__(self):
|
|
231
|
+
return f'Embedding({self.num_embeddings}, {self.embedding_dim})'
|
vectorphile/nn/losses.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Loss functions."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from vectorphile.nn.module import Module
|
|
4
|
+
from vectorphile.core.tensor import Tensor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MSELoss(Module):
|
|
8
|
+
def __init__(self, reduction='mean'):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.reduction = reduction
|
|
11
|
+
|
|
12
|
+
def forward(self, pred: Tensor, target: Tensor) -> Tensor:
|
|
13
|
+
diff = pred - target
|
|
14
|
+
sq = diff * diff
|
|
15
|
+
if self.reduction == 'mean':
|
|
16
|
+
return sq.mean()
|
|
17
|
+
elif self.reduction == 'sum':
|
|
18
|
+
return sq.sum()
|
|
19
|
+
return sq
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MAELoss(Module):
|
|
23
|
+
def __init__(self, reduction='mean'):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.reduction = reduction
|
|
26
|
+
|
|
27
|
+
def forward(self, pred: Tensor, target: Tensor) -> Tensor:
|
|
28
|
+
diff = np.abs(pred._data - target._data)
|
|
29
|
+
if self.reduction == 'mean':
|
|
30
|
+
return Tensor(diff.mean(), device=pred.device, requires_grad=True)
|
|
31
|
+
elif self.reduction == 'sum':
|
|
32
|
+
return Tensor(diff.sum(), device=pred.device, requires_grad=True)
|
|
33
|
+
return Tensor(diff, device=pred.device, requires_grad=True)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CrossEntropyLoss(Module):
|
|
37
|
+
def __init__(self, reduction='mean', ignore_index=-100, label_smoothing=0.0):
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.reduction = reduction
|
|
40
|
+
self.ignore_index = ignore_index
|
|
41
|
+
self.label_smoothing = label_smoothing
|
|
42
|
+
|
|
43
|
+
def forward(self, logits: Tensor, targets: Tensor) -> Tensor:
|
|
44
|
+
log_probs = logits._data - np.log(np.exp(logits._data).sum(axis=-1, keepdims=True) + 1e-12)
|
|
45
|
+
t = targets._data.astype(int)
|
|
46
|
+
n = log_probs.shape[0]
|
|
47
|
+
nll = -log_probs[np.arange(n), t]
|
|
48
|
+
if self.label_smoothing > 0:
|
|
49
|
+
smooth_loss = -log_probs.mean(axis=-1)
|
|
50
|
+
nll = (1 - self.label_smoothing) * nll + self.label_smoothing * smooth_loss
|
|
51
|
+
if self.reduction == 'mean':
|
|
52
|
+
return Tensor(nll.mean(), device=logits.device, requires_grad=True)
|
|
53
|
+
elif self.reduction == 'sum':
|
|
54
|
+
return Tensor(nll.sum(), device=logits.device, requires_grad=True)
|
|
55
|
+
return Tensor(nll, device=logits.device, requires_grad=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _BCEBackward:
|
|
59
|
+
"""Custom backward for BCE loss that keeps the computation in the autograd graph."""
|
|
60
|
+
def __init__(self, pred, target, reduction):
|
|
61
|
+
self.inputs = (pred,)
|
|
62
|
+
self.pred = pred
|
|
63
|
+
self.target = target
|
|
64
|
+
self.reduction = reduction
|
|
65
|
+
|
|
66
|
+
def backward(self, grad):
|
|
67
|
+
p = np.clip(self.pred._data, 1e-7, 1 - 1e-7)
|
|
68
|
+
t = self.target._data
|
|
69
|
+
n = p.size if self.reduction == 'mean' else 1
|
|
70
|
+
dp = (-(t / p) + (1 - t) / (1 - p)) / n
|
|
71
|
+
return (Tensor(grad._data * dp),)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class BCELoss(Module):
|
|
75
|
+
def __init__(self, reduction='mean'):
|
|
76
|
+
super().__init__()
|
|
77
|
+
self.reduction = reduction
|
|
78
|
+
|
|
79
|
+
def forward(self, pred: Tensor, target: Tensor) -> Tensor:
|
|
80
|
+
p = np.clip(pred._data, 1e-7, 1 - 1e-7)
|
|
81
|
+
t = target._data
|
|
82
|
+
loss_data = -(t * np.log(p) + (1 - t) * np.log(1 - p))
|
|
83
|
+
if self.reduction == 'mean':
|
|
84
|
+
val = loss_data.mean()
|
|
85
|
+
elif self.reduction == 'sum':
|
|
86
|
+
val = loss_data.sum()
|
|
87
|
+
else:
|
|
88
|
+
val = loss_data
|
|
89
|
+
result = Tensor(val, device=pred.device, requires_grad=pred.requires_grad)
|
|
90
|
+
if pred.requires_grad:
|
|
91
|
+
result.grad_fn = _BCEBackward(pred, target, self.reduction)
|
|
92
|
+
result._is_leaf = False
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class BCEWithLogitsLoss(Module):
|
|
97
|
+
def __init__(self, reduction='mean'):
|
|
98
|
+
super().__init__()
|
|
99
|
+
self.reduction = reduction
|
|
100
|
+
|
|
101
|
+
def forward(self, logits: Tensor, target: Tensor) -> Tensor:
|
|
102
|
+
d = logits._data
|
|
103
|
+
t = target._data
|
|
104
|
+
loss = np.maximum(d, 0) - d * t + np.log(1 + np.exp(-np.abs(d)))
|
|
105
|
+
if self.reduction == 'mean':
|
|
106
|
+
return Tensor(loss.mean(), device=logits.device, requires_grad=True)
|
|
107
|
+
elif self.reduction == 'sum':
|
|
108
|
+
return Tensor(loss.sum(), device=logits.device, requires_grad=True)
|
|
109
|
+
return Tensor(loss, device=logits.device, requires_grad=True)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class NLLLoss(Module):
|
|
113
|
+
def __init__(self, reduction='mean'):
|
|
114
|
+
super().__init__()
|
|
115
|
+
self.reduction = reduction
|
|
116
|
+
|
|
117
|
+
def forward(self, log_probs: Tensor, targets: Tensor) -> Tensor:
|
|
118
|
+
t = targets._data.astype(int)
|
|
119
|
+
n = log_probs._data.shape[0]
|
|
120
|
+
nll = -log_probs._data[np.arange(n), t]
|
|
121
|
+
if self.reduction == 'mean':
|
|
122
|
+
return Tensor(nll.mean(), device=log_probs.device, requires_grad=True)
|
|
123
|
+
elif self.reduction == 'sum':
|
|
124
|
+
return Tensor(nll.sum(), device=log_probs.device, requires_grad=True)
|
|
125
|
+
return Tensor(nll, device=log_probs.device, requires_grad=True)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class HuberLoss(Module):
|
|
129
|
+
def __init__(self, reduction='mean', delta=1.0):
|
|
130
|
+
super().__init__()
|
|
131
|
+
self.reduction = reduction
|
|
132
|
+
self.delta = delta
|
|
133
|
+
|
|
134
|
+
def forward(self, pred: Tensor, target: Tensor) -> Tensor:
|
|
135
|
+
diff = np.abs(pred._data - target._data)
|
|
136
|
+
loss = np.where(diff < self.delta, 0.5 * diff**2, self.delta * (diff - 0.5 * self.delta))
|
|
137
|
+
if self.reduction == 'mean':
|
|
138
|
+
return Tensor(loss.mean(), device=pred.device, requires_grad=True)
|
|
139
|
+
elif self.reduction == 'sum':
|
|
140
|
+
return Tensor(loss.sum(), device=pred.device, requires_grad=True)
|
|
141
|
+
return Tensor(loss, device=pred.device, requires_grad=True)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class KLDivLoss(Module):
|
|
145
|
+
def __init__(self, reduction='mean', log_target=False):
|
|
146
|
+
super().__init__()
|
|
147
|
+
self.reduction = reduction
|
|
148
|
+
self.log_target = log_target
|
|
149
|
+
|
|
150
|
+
def forward(self, input: Tensor, target: Tensor) -> Tensor:
|
|
151
|
+
if self.log_target:
|
|
152
|
+
loss = np.exp(target._data) * (target._data - input._data)
|
|
153
|
+
else:
|
|
154
|
+
loss = target._data * (np.log(target._data + 1e-12) - input._data)
|
|
155
|
+
if self.reduction == 'mean':
|
|
156
|
+
return Tensor(loss.mean(), device=input.device, requires_grad=True)
|
|
157
|
+
elif self.reduction == 'sum':
|
|
158
|
+
return Tensor(loss.sum(), device=input.device, requires_grad=True)
|
|
159
|
+
return Tensor(loss, device=input.device, requires_grad=True)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class CosineSimilarityLoss(Module):
|
|
163
|
+
def __init__(self, dim=1, eps=1e-8):
|
|
164
|
+
super().__init__()
|
|
165
|
+
self.dim = dim
|
|
166
|
+
self.eps = eps
|
|
167
|
+
|
|
168
|
+
def forward(self, x1: Tensor, x2: Tensor) -> Tensor:
|
|
169
|
+
n1 = np.linalg.norm(x1._data, axis=self.dim, keepdims=True)
|
|
170
|
+
n2 = np.linalg.norm(x2._data, axis=self.dim, keepdims=True)
|
|
171
|
+
cos_sim = (x1._data * x2._data).sum(axis=self.dim) / (n1.squeeze(self.dim) * n2.squeeze(self.dim) + self.eps)
|
|
172
|
+
return Tensor((1 - cos_sim).mean(), device=x1.device, requires_grad=True)
|
vectorphile/nn/module.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Base Module class — mirrors PyTorch's nn.Module API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Dict, Iterator, List, Optional, Tuple, Any
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Module:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._parameters: OrderedDict = OrderedDict()
|
|
11
|
+
self._modules: OrderedDict = OrderedDict()
|
|
12
|
+
self._buffers: OrderedDict = OrderedDict()
|
|
13
|
+
self.training: bool = True
|
|
14
|
+
|
|
15
|
+
def __setattr__(self, name: str, value):
|
|
16
|
+
from vectorphile.core.tensor import Tensor
|
|
17
|
+
if isinstance(value, Tensor) and value.requires_grad:
|
|
18
|
+
self._parameters[name] = value
|
|
19
|
+
elif isinstance(value, Module):
|
|
20
|
+
self._modules[name] = value
|
|
21
|
+
super().__setattr__(name, value)
|
|
22
|
+
|
|
23
|
+
def parameters(self) -> Iterator:
|
|
24
|
+
for p in self._parameters.values():
|
|
25
|
+
yield p
|
|
26
|
+
for module in self._modules.values():
|
|
27
|
+
yield from module.parameters()
|
|
28
|
+
|
|
29
|
+
def named_parameters(self, prefix='') -> Iterator[Tuple[str, Any]]:
|
|
30
|
+
for name, p in self._parameters.items():
|
|
31
|
+
yield (f'{prefix}.{name}' if prefix else name), p
|
|
32
|
+
for mname, module in self._modules.items():
|
|
33
|
+
full = f'{prefix}.{mname}' if prefix else mname
|
|
34
|
+
yield from module.named_parameters(prefix=full)
|
|
35
|
+
|
|
36
|
+
def modules(self) -> Iterator['Module']:
|
|
37
|
+
yield self
|
|
38
|
+
for m in self._modules.values():
|
|
39
|
+
yield from m.modules()
|
|
40
|
+
|
|
41
|
+
def named_modules(self, prefix='') -> Iterator[Tuple[str, 'Module']]:
|
|
42
|
+
yield prefix or '', self
|
|
43
|
+
for name, m in self._modules.items():
|
|
44
|
+
full = f'{prefix}.{name}' if prefix else name
|
|
45
|
+
yield from m.named_modules(prefix=full)
|
|
46
|
+
|
|
47
|
+
def state_dict(self) -> OrderedDict:
|
|
48
|
+
sd = OrderedDict()
|
|
49
|
+
for name, p in self.named_parameters():
|
|
50
|
+
sd[name] = p._data.copy()
|
|
51
|
+
return sd
|
|
52
|
+
|
|
53
|
+
def load_state_dict(self, state_dict: dict, strict=True):
|
|
54
|
+
from vectorphile.core.tensor import Tensor
|
|
55
|
+
own = dict(self.named_parameters())
|
|
56
|
+
for key, val in state_dict.items():
|
|
57
|
+
if key in own:
|
|
58
|
+
own[key]._data[:] = val
|
|
59
|
+
elif strict:
|
|
60
|
+
raise KeyError(f'Unexpected key "{key}" in state_dict')
|
|
61
|
+
|
|
62
|
+
def train(self, mode=True) -> 'Module':
|
|
63
|
+
self.training = mode
|
|
64
|
+
for m in self._modules.values():
|
|
65
|
+
m.train(mode)
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def eval(self) -> 'Module':
|
|
69
|
+
return self.train(False)
|
|
70
|
+
|
|
71
|
+
def zero_grad(self):
|
|
72
|
+
for p in self.parameters():
|
|
73
|
+
p.grad = None
|
|
74
|
+
|
|
75
|
+
def forward(self, *args, **kwargs):
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
def __call__(self, *args, **kwargs):
|
|
79
|
+
return self.forward(*args, **kwargs)
|
|
80
|
+
|
|
81
|
+
def __repr__(self):
|
|
82
|
+
lines = [self.__class__.__name__ + '(']
|
|
83
|
+
for name, m in self._modules.items():
|
|
84
|
+
mod_str = repr(m)
|
|
85
|
+
mod_str = ' ' + mod_str.replace('\n', '\n ')
|
|
86
|
+
lines.append(f' ({name}): {mod_str}')
|
|
87
|
+
lines.append(')')
|
|
88
|
+
return '\n'.join(lines)
|
|
89
|
+
|
|
90
|
+
def register_buffer(self, name: str, tensor):
|
|
91
|
+
self._buffers[name] = tensor
|
|
92
|
+
object.__setattr__(self, name, tensor)
|
|
93
|
+
|
|
94
|
+
def apply(self, fn) -> 'Module':
|
|
95
|
+
for m in self.modules():
|
|
96
|
+
fn(m)
|
|
97
|
+
return self
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Sequential container."""
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from vectorphile.nn.module import Module
|
|
4
|
+
from vectorphile.core.tensor import Tensor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Sequential(Module):
|
|
8
|
+
def __init__(self, *args):
|
|
9
|
+
super().__init__()
|
|
10
|
+
if len(args) == 1 and isinstance(args[0], OrderedDict):
|
|
11
|
+
for key, module in args[0].items():
|
|
12
|
+
self._modules[key] = module
|
|
13
|
+
setattr(self, key, module)
|
|
14
|
+
else:
|
|
15
|
+
for i, module in enumerate(args):
|
|
16
|
+
self._modules[str(i)] = module
|
|
17
|
+
object.__setattr__(self, str(i), module)
|
|
18
|
+
|
|
19
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
20
|
+
for module in self._modules.values():
|
|
21
|
+
x = module(x)
|
|
22
|
+
return x
|
|
23
|
+
|
|
24
|
+
def append(self, module: Module) -> 'Sequential':
|
|
25
|
+
idx = str(len(self._modules))
|
|
26
|
+
self._modules[idx] = module
|
|
27
|
+
object.__setattr__(self, idx, module)
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def __getitem__(self, idx):
|
|
31
|
+
if isinstance(idx, slice):
|
|
32
|
+
keys = list(self._modules.keys())[idx]
|
|
33
|
+
return Sequential(OrderedDict((k, self._modules[k]) for k in keys))
|
|
34
|
+
return list(self._modules.values())[idx]
|
|
35
|
+
|
|
36
|
+
def __len__(self):
|
|
37
|
+
return len(self._modules)
|
|
38
|
+
|
|
39
|
+
def __iter__(self):
|
|
40
|
+
return iter(self._modules.values())
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
lines = ['Sequential(']
|
|
44
|
+
for i, (name, m) in enumerate(self._modules.items()):
|
|
45
|
+
lines.append(f' ({name}): {repr(m)}')
|
|
46
|
+
lines.append(')')
|
|
47
|
+
return '\n'.join(lines)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from vectorphile.optim.sgd import SGD
|
|
2
|
+
from vectorphile.optim.adam import Adam, AdamW
|
|
3
|
+
from vectorphile.optim.rmsprop import RMSprop
|
|
4
|
+
from vectorphile.optim.adagrad import Adagrad
|
|
5
|
+
from vectorphile.optim.lr_scheduler import (
|
|
6
|
+
StepLR, ExponentialLR, CosineAnnealingLR, LinearLR, ReduceLROnPlateau,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'SGD', 'Adam', 'AdamW', 'RMSprop', 'Adagrad',
|
|
11
|
+
'StepLR', 'ExponentialLR', 'CosineAnnealingLR', 'LinearLR', 'ReduceLROnPlateau',
|
|
12
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Adagrad optimizer."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from vectorphile.optim.optimizer import Optimizer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Adagrad(Optimizer):
|
|
7
|
+
def __init__(self, params, lr=0.01, lr_decay=0.0, weight_decay=0.0, eps=1e-10):
|
|
8
|
+
defaults = dict(lr=lr, lr_decay=lr_decay, weight_decay=weight_decay, eps=eps)
|
|
9
|
+
super().__init__(params, defaults)
|
|
10
|
+
self.state = {}
|
|
11
|
+
|
|
12
|
+
def step(self):
|
|
13
|
+
for group in self.param_groups:
|
|
14
|
+
lr = group['lr']
|
|
15
|
+
lr_decay = group['lr_decay']
|
|
16
|
+
wd = group['weight_decay']
|
|
17
|
+
eps = group['eps']
|
|
18
|
+
for p in group['params']:
|
|
19
|
+
if p.grad is None:
|
|
20
|
+
continue
|
|
21
|
+
g = p.grad._data.copy()
|
|
22
|
+
if wd != 0:
|
|
23
|
+
g += wd * p._data
|
|
24
|
+
pid = id(p)
|
|
25
|
+
if pid not in self.state:
|
|
26
|
+
self.state[pid] = {'step': 0, 'sum': np.zeros_like(p._data)}
|
|
27
|
+
s = self.state[pid]
|
|
28
|
+
s['step'] += 1
|
|
29
|
+
clr = lr / (1 + (s['step'] - 1) * lr_decay)
|
|
30
|
+
s['sum'] += g**2
|
|
31
|
+
p._data -= clr * g / (np.sqrt(s['sum']) + eps)
|