ntk-ml 1.0.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.
- neuraltoolkit/CLI.py +19 -0
- neuraltoolkit/__init__.py +30 -0
- neuraltoolkit/core/__init__.py +6 -0
- neuraltoolkit/core/device.py +5 -0
- neuraltoolkit/core/dtype.py +18 -0
- neuraltoolkit/core/no_grad.py +9 -0
- neuraltoolkit/core/parameter.py +12 -0
- neuraltoolkit/core/tensor.py +354 -0
- neuraltoolkit/core/tensor_ops.py +173 -0
- neuraltoolkit/data/__init__.py +3 -0
- neuraltoolkit/data/dataloader.py +91 -0
- neuraltoolkit/data/dataset.py +42 -0
- neuraltoolkit/data/subset.py +14 -0
- neuraltoolkit/datasets/__init__.py +3 -0
- neuraltoolkit/datasets/management/__init__.py +5 -0
- neuraltoolkit/datasets/management/cache.py +47 -0
- neuraltoolkit/datasets/management/data_resource.py +9 -0
- neuraltoolkit/datasets/management/downloader.py +37 -0
- neuraltoolkit/datasets/management/paths.py +6 -0
- neuraltoolkit/datasets/management/retrieve.py +13 -0
- neuraltoolkit/datasets/management/verify.py +12 -0
- neuraltoolkit/datasets/mnist/__init__.py +2 -0
- neuraltoolkit/datasets/mnist/mnist.py +65 -0
- neuraltoolkit/datasets/mnist/mnistloader.py +32 -0
- neuraltoolkit/graph.py +71 -0
- neuraltoolkit/initializers/__init__.py +19 -0
- neuraltoolkit/initializers/glorot_initializer.py +34 -0
- neuraltoolkit/initializers/he_initializer.py +46 -0
- neuraltoolkit/loss/BCE.py +22 -0
- neuraltoolkit/loss/CCE.py +51 -0
- neuraltoolkit/loss/MSE.py +25 -0
- neuraltoolkit/loss/__init__.py +3 -0
- neuraltoolkit/modules/Registry.py +23 -0
- neuraltoolkit/modules/__init__.py +5 -0
- neuraltoolkit/modules/activations/__init__.py +6 -0
- neuraltoolkit/modules/activations/activation.py +16 -0
- neuraltoolkit/modules/activations/leakyrelu.py +22 -0
- neuraltoolkit/modules/activations/relu.py +17 -0
- neuraltoolkit/modules/activations/sigmoid.py +17 -0
- neuraltoolkit/modules/activations/softmax.py +21 -0
- neuraltoolkit/modules/activations/tanh.py +20 -0
- neuraltoolkit/modules/layers/__init__.py +18 -0
- neuraltoolkit/modules/layers/adaptive_max_pool2d.py +62 -0
- neuraltoolkit/modules/layers/conv2d.py +88 -0
- neuraltoolkit/modules/layers/dense.py +59 -0
- neuraltoolkit/modules/layers/flatten.py +37 -0
- neuraltoolkit/modules/layers/max_pool2d.py +64 -0
- neuraltoolkit/modules/models/__init__.py +1 -0
- neuraltoolkit/modules/models/sequential.py +122 -0
- neuraltoolkit/modules/module.py +112 -0
- neuraltoolkit/ops/__init__.py +2 -0
- neuraltoolkit/ops/data.py +37 -0
- neuraltoolkit/ops/image_processing.py +266 -0
- neuraltoolkit/optimizers/__init__.py +5 -0
- neuraltoolkit/optimizers/adagrad.py +33 -0
- neuraltoolkit/optimizers/adam.py +50 -0
- neuraltoolkit/optimizers/optimizer.py +10 -0
- neuraltoolkit/optimizers/rmsprop.py +35 -0
- neuraltoolkit/optimizers/sgd.py +33 -0
- neuraltoolkit/training/__init__.py +3 -0
- neuraltoolkit/training/config.py +9 -0
- neuraltoolkit/training/history.py +45 -0
- neuraltoolkit/training/trainer.py +239 -0
- ntk_ml-1.0.0.dist-info/METADATA +208 -0
- ntk_ml-1.0.0.dist-info/RECORD +68 -0
- ntk_ml-1.0.0.dist-info/WHEEL +5 -0
- ntk_ml-1.0.0.dist-info/licenses/LICENSE +219 -0
- ntk_ml-1.0.0.dist-info/top_level.txt +1 -0
neuraltoolkit/CLI.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
def progress_bar(frac, bar_length=40, front_str="", end_str=""):
|
|
4
|
+
filled_length = int(bar_length * frac)
|
|
5
|
+
|
|
6
|
+
# block character unicode escape: \u2588
|
|
7
|
+
bar = "\u2588" * filled_length + "-" * (bar_length - filled_length)
|
|
8
|
+
sys.stdout.write(f"\r{front_str} |{bar}| {end_str}")
|
|
9
|
+
sys.stdout.flush()
|
|
10
|
+
|
|
11
|
+
def epoch_summary(config, metrics, epoch):
|
|
12
|
+
# accepts training config
|
|
13
|
+
sys.stdout.write((f"\rEpoch: {epoch} / {config.epochs} "
|
|
14
|
+
f"- Loss: {metrics.loss:.5f} "))
|
|
15
|
+
|
|
16
|
+
if metrics.val_loss != None:
|
|
17
|
+
sys.stdout.write(f"- Validation Loss: {metrics.val_loss:.5f}")
|
|
18
|
+
|
|
19
|
+
sys.stdout.write(" " * 100 + "\n")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (C) 2026 <Your Name or Organization>
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://gnu.org>.
|
|
15
|
+
|
|
16
|
+
__version__ = "1.0.0"
|
|
17
|
+
|
|
18
|
+
from .modules import *
|
|
19
|
+
|
|
20
|
+
from .data import *
|
|
21
|
+
from .core import *
|
|
22
|
+
from .modules.layers import *
|
|
23
|
+
from .loss import *
|
|
24
|
+
from .optimizers import *
|
|
25
|
+
from .initializers import *
|
|
26
|
+
from .training import Trainer
|
|
27
|
+
from . import datasets
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
print("Neural Tool Kit loaded!")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import Enum, auto
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class Dtype(Enum):
|
|
5
|
+
FLOAT32 = auto()
|
|
6
|
+
FLOAT64 = auto()
|
|
7
|
+
INT32 = auto()
|
|
8
|
+
INT64 = auto()
|
|
9
|
+
|
|
10
|
+
# Converts python and numpy types into dtypes -> DTYPE
|
|
11
|
+
PYTHON_TYPE_TO_DTYPE = {
|
|
12
|
+
int: Dtype.INT64,
|
|
13
|
+
float: Dtype.FLOAT64,
|
|
14
|
+
np.int32: Dtype.INT32,
|
|
15
|
+
np.int64: Dtype.INT64,
|
|
16
|
+
np.float32: Dtype.FLOAT32,
|
|
17
|
+
np.float64: Dtype.FLOAT64,
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .tensor import Tensor
|
|
2
|
+
from .device import Device
|
|
3
|
+
|
|
4
|
+
class Parameter(Tensor):
|
|
5
|
+
"""
|
|
6
|
+
Tensor for learnable values
|
|
7
|
+
|
|
8
|
+
Parameters always have gradients
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, data):
|
|
11
|
+
super().__init__(data, requires_grad=True)
|
|
12
|
+
self.name = "Parameter"
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .dtype import Dtype
|
|
3
|
+
from .device import Device
|
|
4
|
+
|
|
5
|
+
class Tensor:
|
|
6
|
+
"""
|
|
7
|
+
Standard multidemsional datastorage
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
data (numpy array or list): Tensor data (lists are converted to numpy arrays)
|
|
11
|
+
requires_grad (bool): Whether the tensor tracks gradients (defaults to False)
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
data: np.ndarray
|
|
15
|
+
shape: tuple[int, ...]
|
|
16
|
+
dtype: Dtype
|
|
17
|
+
|
|
18
|
+
grad_enabled=True
|
|
19
|
+
|
|
20
|
+
def __init__(self, data, requires_grad=False):
|
|
21
|
+
self._parents = set()
|
|
22
|
+
self._backward_fn = None
|
|
23
|
+
|
|
24
|
+
self.data = self._init_data(data)
|
|
25
|
+
|
|
26
|
+
self.shape = self.data.shape
|
|
27
|
+
|
|
28
|
+
if self.data.dtype != np.float32:
|
|
29
|
+
self.data = self.data.astype(np.float32)
|
|
30
|
+
self.dtype = self.data.dtype
|
|
31
|
+
|
|
32
|
+
self.requires_grad = requires_grad
|
|
33
|
+
|
|
34
|
+
self.grad = np.zeros(shape=self.shape, dtype=np.float32) if requires_grad else None
|
|
35
|
+
|
|
36
|
+
self.name = "Tensor"
|
|
37
|
+
|
|
38
|
+
def _init_data(self, d):
|
|
39
|
+
data = None
|
|
40
|
+
if isinstance(d, np.ndarray):
|
|
41
|
+
data = d
|
|
42
|
+
elif isinstance(d, list):
|
|
43
|
+
data = np.array(d)
|
|
44
|
+
else:
|
|
45
|
+
data = np.array([d])
|
|
46
|
+
|
|
47
|
+
if not np.issubdtype(data.dtype, np.number):
|
|
48
|
+
raise TypeError("Tensor data must be numeric")
|
|
49
|
+
|
|
50
|
+
return data
|
|
51
|
+
|
|
52
|
+
def clear_grad(self):
|
|
53
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
54
|
+
self.grad *= 0
|
|
55
|
+
|
|
56
|
+
def backward(self):
|
|
57
|
+
topo = []
|
|
58
|
+
visited = set()
|
|
59
|
+
|
|
60
|
+
self.grad = np.ones_like(self.data)
|
|
61
|
+
|
|
62
|
+
def build(node):
|
|
63
|
+
if node not in visited:
|
|
64
|
+
visited.add(node)
|
|
65
|
+
|
|
66
|
+
for parent in node._parents:
|
|
67
|
+
build(parent)
|
|
68
|
+
topo.append(node)
|
|
69
|
+
|
|
70
|
+
build(self)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
for node in reversed(topo):
|
|
74
|
+
if node._backward_fn:
|
|
75
|
+
node._backward_fn()
|
|
76
|
+
self._clear_links()
|
|
77
|
+
|
|
78
|
+
def _clear_links(self):
|
|
79
|
+
self._parents = set()
|
|
80
|
+
self._backward_fn = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def T(self):
|
|
84
|
+
out = Tensor(self.data.T, requires_grad=self.requires_grad)
|
|
85
|
+
|
|
86
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
87
|
+
def _transpose_backward():
|
|
88
|
+
self.grad += out.grad.T
|
|
89
|
+
|
|
90
|
+
out._parents = {self}
|
|
91
|
+
out._backward_fn = _transpose_backward
|
|
92
|
+
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _reduce_broadcast(grad, shape):
|
|
97
|
+
# Remove extra leading dims
|
|
98
|
+
while grad.ndim > len(shape):
|
|
99
|
+
grad = grad.sum(axis=0)
|
|
100
|
+
|
|
101
|
+
# Collapse broadcasted axes
|
|
102
|
+
for axis, size in enumerate(shape):
|
|
103
|
+
if size == 1:
|
|
104
|
+
grad = grad.sum(axis=axis, keepdims=True)
|
|
105
|
+
|
|
106
|
+
return grad
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
return f"Tensor:\n {self.data} \n"
|
|
110
|
+
|
|
111
|
+
def _Tensor_wrapper(self, other):
|
|
112
|
+
return other if isinstance(other, Tensor) else Tensor(other)
|
|
113
|
+
|
|
114
|
+
def __getitem__(self, idx):
|
|
115
|
+
sliced_data = self.data[idx]
|
|
116
|
+
|
|
117
|
+
out = Tensor(sliced_data, requires_grad=self.requires_grad)
|
|
118
|
+
|
|
119
|
+
def _slice_backward():
|
|
120
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
121
|
+
self.grad[idx] += out.grad
|
|
122
|
+
|
|
123
|
+
out._parents = {self}
|
|
124
|
+
out._backward_fn = _slice_backward
|
|
125
|
+
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
def __add__(self, other):
|
|
129
|
+
other = self._Tensor_wrapper(other)
|
|
130
|
+
out = Tensor(self.data + other.data, requires_grad=True)
|
|
131
|
+
|
|
132
|
+
out._parents = {self, other}
|
|
133
|
+
|
|
134
|
+
def _add_backward():
|
|
135
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
136
|
+
grad_self = out.grad.copy()
|
|
137
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
138
|
+
|
|
139
|
+
if other.requires_grad:
|
|
140
|
+
grad_other = out.grad.copy()
|
|
141
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
142
|
+
|
|
143
|
+
out._backward_fn = _add_backward
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
def __radd__(self, other):
|
|
147
|
+
other = self._Tensor_wrapper(other)
|
|
148
|
+
out = Tensor(other.data + self.data, requires_grad=True)
|
|
149
|
+
|
|
150
|
+
out._parents = {self, other}
|
|
151
|
+
|
|
152
|
+
def _add_backward():
|
|
153
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
154
|
+
grad_self = out.grad.copy()
|
|
155
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
156
|
+
|
|
157
|
+
if other.requires_grad:
|
|
158
|
+
grad_other = out.grad.copy()
|
|
159
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
160
|
+
|
|
161
|
+
out._backward_fn = _add_backward
|
|
162
|
+
return out
|
|
163
|
+
|
|
164
|
+
def __sub__(self, other):
|
|
165
|
+
other = self._Tensor_wrapper(other)
|
|
166
|
+
out = Tensor(self.data - other.data, requires_grad=True)
|
|
167
|
+
|
|
168
|
+
out._parents = {self, other}
|
|
169
|
+
|
|
170
|
+
def _sub_backward():
|
|
171
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
172
|
+
grad_self = out.grad.copy()
|
|
173
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
174
|
+
|
|
175
|
+
if other.requires_grad:
|
|
176
|
+
grad_other = out.grad.copy()
|
|
177
|
+
other.grad -= self._reduce_broadcast(grad_other, other.shape)
|
|
178
|
+
|
|
179
|
+
out._backward_fn = _sub_backward
|
|
180
|
+
return out
|
|
181
|
+
|
|
182
|
+
def __rsub__(self, other):
|
|
183
|
+
other = self._Tensor_wrapper(other)
|
|
184
|
+
out = Tensor(other.data - self.data, requires_grad=True)
|
|
185
|
+
|
|
186
|
+
out._parents = {self, other}
|
|
187
|
+
|
|
188
|
+
def _sub_backward():
|
|
189
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
190
|
+
grad_self = out.grad.copy()
|
|
191
|
+
self.grad -= self._reduce_broadcast(grad_self, self.shape)
|
|
192
|
+
|
|
193
|
+
if other.requires_grad:
|
|
194
|
+
grad_other = out.grad.copy()
|
|
195
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
196
|
+
|
|
197
|
+
out._backward_fn = _sub_backward
|
|
198
|
+
return out
|
|
199
|
+
|
|
200
|
+
def __mul__(self, other):
|
|
201
|
+
other = self._Tensor_wrapper(other)
|
|
202
|
+
out = Tensor(self.data * other.data, requires_grad=True)
|
|
203
|
+
|
|
204
|
+
out._parents = {self, other}
|
|
205
|
+
|
|
206
|
+
def _mul_backward():
|
|
207
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
208
|
+
grad_self = out.grad * other.data
|
|
209
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
210
|
+
|
|
211
|
+
if other.requires_grad:
|
|
212
|
+
grad_other = out.grad * self.data
|
|
213
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
214
|
+
|
|
215
|
+
out._backward_fn = _mul_backward
|
|
216
|
+
return out
|
|
217
|
+
|
|
218
|
+
def __rmul__(self, other):
|
|
219
|
+
other = self._Tensor_wrapper(other)
|
|
220
|
+
out = Tensor(other.data * self.data, requires_grad=True)
|
|
221
|
+
|
|
222
|
+
out._parents = {self, other}
|
|
223
|
+
|
|
224
|
+
def _mul_backward():
|
|
225
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
226
|
+
grad_self = out.grad * other.data
|
|
227
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
228
|
+
|
|
229
|
+
if other.requires_grad:
|
|
230
|
+
grad_other = out.grad * self.data
|
|
231
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
232
|
+
|
|
233
|
+
out._backward_fn = _mul_backward
|
|
234
|
+
return out
|
|
235
|
+
|
|
236
|
+
def __truediv__(self, other):
|
|
237
|
+
other = self._Tensor_wrapper(other)
|
|
238
|
+
out = Tensor(self.data / other.data, requires_grad=True)
|
|
239
|
+
|
|
240
|
+
out._parents = {self, other}
|
|
241
|
+
|
|
242
|
+
def _div_backward():
|
|
243
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
244
|
+
grad_self = out.grad / other.data
|
|
245
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
246
|
+
|
|
247
|
+
if other.requires_grad:
|
|
248
|
+
grad_other = -out.grad * self.data / (other.data ** 2)
|
|
249
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
250
|
+
|
|
251
|
+
out._backward_fn = _div_backward
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
def __rtruediv__(self, other):
|
|
255
|
+
other = self._Tensor_wrapper(other)
|
|
256
|
+
out = Tensor(other.data / self.data, requires_grad=True)
|
|
257
|
+
|
|
258
|
+
out._parents = {self, other}
|
|
259
|
+
|
|
260
|
+
def _div_backward():
|
|
261
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
262
|
+
grad_self = -out.grad * other.data / (self.data ** 2)
|
|
263
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
264
|
+
|
|
265
|
+
if other.requires_grad:
|
|
266
|
+
grad_other = out.grad / self.data
|
|
267
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
268
|
+
|
|
269
|
+
out._backward_fn = _div_backward
|
|
270
|
+
return out
|
|
271
|
+
|
|
272
|
+
def __pow__(self, other):
|
|
273
|
+
other = self._Tensor_wrapper(other)
|
|
274
|
+
out = Tensor(self.data ** other.data, requires_grad=True)
|
|
275
|
+
|
|
276
|
+
out._parents = {self, other}
|
|
277
|
+
|
|
278
|
+
def _pow_backward():
|
|
279
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
280
|
+
grad_self = other.data * (self.data ** (other.data - 1)) * out.grad
|
|
281
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
282
|
+
|
|
283
|
+
if other.requires_grad:
|
|
284
|
+
grad_other = np.log(self.data) * (self.data ** other.data) * out.grad
|
|
285
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
286
|
+
|
|
287
|
+
out._backward_fn = _pow_backward
|
|
288
|
+
return out
|
|
289
|
+
|
|
290
|
+
def __rpow__(self, other):
|
|
291
|
+
other = self._Tensor_wrapper(other)
|
|
292
|
+
out = Tensor(other.data ** self.data, requires_grad=True)
|
|
293
|
+
|
|
294
|
+
out._parents = {self, other}
|
|
295
|
+
|
|
296
|
+
def _pow_backward():
|
|
297
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
298
|
+
grad_self = np.log(other.data) * (other.data ** self.data) * out.grad
|
|
299
|
+
self.grad += self._reduce_broadcast(grad_self, self.shape)
|
|
300
|
+
|
|
301
|
+
if other.requires_grad:
|
|
302
|
+
grad_other = self.data * (other.data ** (self.data - 1)) * out.grad
|
|
303
|
+
other.grad += self._reduce_broadcast(grad_other, other.shape)
|
|
304
|
+
|
|
305
|
+
out._backward_fn = _pow_backward
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
def __matmul__(self, other):
|
|
309
|
+
other = self._Tensor_wrapper(other)
|
|
310
|
+
out = Tensor(self.data @ other.data, requires_grad=True)
|
|
311
|
+
|
|
312
|
+
out._parents = {self, other}
|
|
313
|
+
|
|
314
|
+
def _matmul_backward():
|
|
315
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
316
|
+
self.grad += out.grad @ other.data.T
|
|
317
|
+
|
|
318
|
+
if other.requires_grad:
|
|
319
|
+
other.grad += self.data.T @ out.grad
|
|
320
|
+
|
|
321
|
+
out._backward_fn = _matmul_backward
|
|
322
|
+
return out
|
|
323
|
+
|
|
324
|
+
def __rmatmul__(self, other):
|
|
325
|
+
other = self._Tensor_wrapper(other)
|
|
326
|
+
out = Tensor(other.data @ self.data, requires_grad=True)
|
|
327
|
+
|
|
328
|
+
out._parents = {self, other}
|
|
329
|
+
|
|
330
|
+
def _matmul_backward():
|
|
331
|
+
if self.requires_grad and Tensor.grad_enabled:
|
|
332
|
+
self.grad += other.data.T @ out.grad
|
|
333
|
+
|
|
334
|
+
if other.requires_grad:
|
|
335
|
+
other.grad += out.grad @ self.data.T
|
|
336
|
+
|
|
337
|
+
out._backward_fn = _matmul_backward
|
|
338
|
+
return out
|
|
339
|
+
|
|
340
|
+
def __floordiv__(self, other):
|
|
341
|
+
other = self._Tensor_wrapper(other)
|
|
342
|
+
out = Tensor(self.data // other.data)
|
|
343
|
+
|
|
344
|
+
def __rfloordiv__(self, other):
|
|
345
|
+
other = self._Tensor_wrapper(other)
|
|
346
|
+
out = Tensor(other.data // self.data)
|
|
347
|
+
|
|
348
|
+
def __mod__(self, other):
|
|
349
|
+
other = self._Tensor_wrapper(other)
|
|
350
|
+
out = Tensor(self.data % other.data)
|
|
351
|
+
|
|
352
|
+
def __rmod__(self, other):
|
|
353
|
+
other = self._Tensor_wrapper(other)
|
|
354
|
+
out = Tensor(other.data % self._unwrap)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from .tensor import Tensor
|
|
2
|
+
from ..ops.image_processing import *
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def conv2d(x, kernel, stride, pad, flat_index_map) -> Tensor:
|
|
7
|
+
"""
|
|
8
|
+
Performs a convolutional operation on a batch of images.
|
|
9
|
+
--------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
x (tensor (N, C_in, H, W)): Batch of input images.
|
|
13
|
+
kernel (parameter (C_out, C_in, K, K)): kernel/filter weights.
|
|
14
|
+
stride (int or tuple): The number pixels the kernel/filter moves at each step. Defaults to 1.
|
|
15
|
+
pad (int or tuple): The number of filler pixels (0s) to be inserted around the borders of the images.
|
|
16
|
+
flat_index_map: output from get_im2col_indices(). must match complete conv output shape
|
|
17
|
+
------------------------------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Tensor(N, C_out, out_height, out_width)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
stride_h, stride_w = split_2d_param(stride)
|
|
24
|
+
pad_h, pad_w = split_2d_param(pad)
|
|
25
|
+
|
|
26
|
+
C_out = kernel.shape[0]
|
|
27
|
+
K_h = kernel.shape[2] # kernel/filter height
|
|
28
|
+
K_w = kernel.shape[3] # kernel/filter width
|
|
29
|
+
|
|
30
|
+
N, C_in, H, W = x.shape
|
|
31
|
+
|
|
32
|
+
if pad_h > 0 or pad_w > 0:
|
|
33
|
+
x_padded = np.pad(x.data, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)), mode="constant")
|
|
34
|
+
else:
|
|
35
|
+
x_padded = x.data
|
|
36
|
+
out_height, out_width = output_image_size(H, W, (K_h, K_w), stride, pad)
|
|
37
|
+
|
|
38
|
+
kernel_flat = np.reshape(kernel.data, shape=(C_out, C_in * K_h * K_w))
|
|
39
|
+
|
|
40
|
+
img_patches = im2col_fast(x_padded, flat_index_map)
|
|
41
|
+
|
|
42
|
+
out_flat = img_patches @ kernel_flat.T # shape (N, H_out * W_out, C_out)
|
|
43
|
+
|
|
44
|
+
out = out_flat.reshape((N, out_height, out_width, C_out))
|
|
45
|
+
out = out.transpose(0, 3, 1, 2).copy()
|
|
46
|
+
out = Tensor(out, requires_grad=True)
|
|
47
|
+
|
|
48
|
+
def _conv2d_backward():
|
|
49
|
+
if x.requires_grad and Tensor.grad_enabled:
|
|
50
|
+
grad_x = out.grad.copy() # (N, C_out, H_out, W_out)
|
|
51
|
+
flat_grad_x = grad_x.reshape(N, C_out, out_height * out_width)
|
|
52
|
+
flat_grad_x = flat_grad_x.transpose(0, 2, 1).copy()
|
|
53
|
+
dx_col = flat_grad_x @ kernel_flat
|
|
54
|
+
|
|
55
|
+
dx = col2im_fast(dx_col, flat_index_map, x_padded.shape)
|
|
56
|
+
|
|
57
|
+
if pad_h > 0 or pad_w > 0:
|
|
58
|
+
x.grad += unpad(dx, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)))
|
|
59
|
+
else:
|
|
60
|
+
x.grad += dx
|
|
61
|
+
|
|
62
|
+
if kernel.requires_grad and Tensor.grad_enabled:
|
|
63
|
+
grad = out.grad.copy() # (N, C_out, H_out, W_out)
|
|
64
|
+
grad_reshaped = grad.reshape((N, C_out, out_height * out_width))
|
|
65
|
+
|
|
66
|
+
# (N, C_out, H_out*W_out) @ (N H_out*W_out, C_in*Kh*kw) = (N, C_out, C_int*Kh*Kw)
|
|
67
|
+
dw_batches = grad_reshaped @ img_patches
|
|
68
|
+
|
|
69
|
+
# summed acrossed batches and set to original kernel shape
|
|
70
|
+
dw = np.sum(dw_batches, axis=0).reshape(C_out, C_in, K_h, K_w)
|
|
71
|
+
|
|
72
|
+
kernel.grad += dw
|
|
73
|
+
|
|
74
|
+
out._parents = {x, kernel}
|
|
75
|
+
out._backward_fn = _conv2d_backward
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
def reshape(x, shape):
|
|
79
|
+
x_shape = x.shape
|
|
80
|
+
data = x.data.copy()
|
|
81
|
+
out = np.reshape(data, shape=shape)
|
|
82
|
+
out = Tensor(out, requires_grad=True)
|
|
83
|
+
|
|
84
|
+
out._parents = {x}
|
|
85
|
+
|
|
86
|
+
def _reshape_backward():
|
|
87
|
+
if x.requires_grad and Tensor.grad_enabled:
|
|
88
|
+
grad_x = out.grad.copy()
|
|
89
|
+
x.grad += np.reshape(grad_x, shape=x_shape)
|
|
90
|
+
|
|
91
|
+
out._backward_fn = _reshape_backward
|
|
92
|
+
return out
|
|
93
|
+
|
|
94
|
+
def max_pool2d(
|
|
95
|
+
x:Tensor,
|
|
96
|
+
kernel_size:int|tuple,
|
|
97
|
+
stride:int|tuple,
|
|
98
|
+
pad:int|tuple,
|
|
99
|
+
flat_index_map
|
|
100
|
+
):
|
|
101
|
+
"""
|
|
102
|
+
Performs a max pooling operation on a batch of images.
|
|
103
|
+
-----------------------------------------------------
|
|
104
|
+
|
|
105
|
+
Arguments:
|
|
106
|
+
x (tensor of shape: (N, C_in, H, W)): A batch of input images.
|
|
107
|
+
kernel_size (int or tuple): The size of kernel/filter.
|
|
108
|
+
stride (int or tuple): The number pixels the kernel/filter moves at each step. Defaults to 1.
|
|
109
|
+
pad (int or tuple): The number of filler pixels (0s) to be inserted around the borders of the images.
|
|
110
|
+
flat_index_map: output from get_im2col_indices(). must match complete max_pool output shape
|
|
111
|
+
Returns:
|
|
112
|
+
scaled down numpy array of max values
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
kh, kw = split_2d_param(kernel_size)
|
|
117
|
+
kernel_area = kh * kw
|
|
118
|
+
|
|
119
|
+
N, C, H, W = x.shape # N: samples, C: channels, H: height, W: width
|
|
120
|
+
pad_h, pad_w = split_2d_param(pad)
|
|
121
|
+
|
|
122
|
+
if pad_h > 0 or pad_w > 0:
|
|
123
|
+
padded_x = np.pad(x.data, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)), mode="constant")
|
|
124
|
+
else:
|
|
125
|
+
padded_x = x.data
|
|
126
|
+
H_out, W_out = output_image_size(H, W, kernel_size, stride, pad)
|
|
127
|
+
|
|
128
|
+
HW_out = H_out*W_out
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
patches_flat = im2col_fast(padded_x, flat_index_map) # Shape (N, H*W, C*K*K)
|
|
132
|
+
patches_flat = np.reshape(patches_flat, shape=(N, HW_out, C, kernel_area))
|
|
133
|
+
#patches_flat = patches_flat.transpose(0, 2, 1, 3) # Shape (N, C, HW_out, K*K)
|
|
134
|
+
|
|
135
|
+
max_val_indices = np.argmax(patches_flat, axis=-1, keepdims=True) # Shape (N, HW_out, C, 1)
|
|
136
|
+
max_pool = np.take_along_axis(patches_flat, max_val_indices, axis=-1) # Shape (N, HW_out, C, 1)
|
|
137
|
+
max_pool = np.squeeze(max_pool, axis=-1) # Shape (N, HW_out C,)
|
|
138
|
+
max_pool = max_pool.transpose(0, 2, 1).copy()
|
|
139
|
+
max_pool = max_pool.reshape(N, C, H_out, W_out)
|
|
140
|
+
|
|
141
|
+
out = Tensor(max_pool, requires_grad=True)
|
|
142
|
+
|
|
143
|
+
out._parents = {x}
|
|
144
|
+
|
|
145
|
+
def _max_pool2d_backward():
|
|
146
|
+
if x.requires_grad and Tensor.grad_enabled:
|
|
147
|
+
max_indices = max_val_indices.squeeze(-1) # (N, HW_out, C)
|
|
148
|
+
|
|
149
|
+
n_idx = np.arange(N)[:, None, None]
|
|
150
|
+
hw_idx = np.arange(HW_out)[None, :, None]
|
|
151
|
+
c_idx = np.arange(C)[None, None, :]
|
|
152
|
+
|
|
153
|
+
flat_index_map_reshaped = flat_index_map.reshape(N, HW_out, C, kernel_area)
|
|
154
|
+
|
|
155
|
+
# Shape (N, HW_out, C) - Advanced indexing
|
|
156
|
+
im_index_map = flat_index_map_reshaped[n_idx, hw_idx, c_idx, max_indices]
|
|
157
|
+
|
|
158
|
+
# Shape (N, C, HW_out)
|
|
159
|
+
im_index_map = im_index_map.transpose(0, 2, 1).copy()
|
|
160
|
+
|
|
161
|
+
grad_reshaped = out.grad.reshape((N, C, HW_out))
|
|
162
|
+
|
|
163
|
+
dx_flat = np.bincount(im_index_map.ravel(), grad_reshaped.ravel(), minlength=np.prod(padded_x.shape))
|
|
164
|
+
dx = dx_flat.reshape(N, C, H, W)
|
|
165
|
+
|
|
166
|
+
if pad_h > 0 or pad_w > 0:
|
|
167
|
+
x.grad += unpad(dx, ((0, 0), (0, 0), (pad_h, pad_h), (pad_w, pad_w)))
|
|
168
|
+
else:
|
|
169
|
+
x.grad += dx
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
out._backward_fn = _max_pool2d_backward
|
|
173
|
+
return out
|