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.
Files changed (68) hide show
  1. neuraltoolkit/CLI.py +19 -0
  2. neuraltoolkit/__init__.py +30 -0
  3. neuraltoolkit/core/__init__.py +6 -0
  4. neuraltoolkit/core/device.py +5 -0
  5. neuraltoolkit/core/dtype.py +18 -0
  6. neuraltoolkit/core/no_grad.py +9 -0
  7. neuraltoolkit/core/parameter.py +12 -0
  8. neuraltoolkit/core/tensor.py +354 -0
  9. neuraltoolkit/core/tensor_ops.py +173 -0
  10. neuraltoolkit/data/__init__.py +3 -0
  11. neuraltoolkit/data/dataloader.py +91 -0
  12. neuraltoolkit/data/dataset.py +42 -0
  13. neuraltoolkit/data/subset.py +14 -0
  14. neuraltoolkit/datasets/__init__.py +3 -0
  15. neuraltoolkit/datasets/management/__init__.py +5 -0
  16. neuraltoolkit/datasets/management/cache.py +47 -0
  17. neuraltoolkit/datasets/management/data_resource.py +9 -0
  18. neuraltoolkit/datasets/management/downloader.py +37 -0
  19. neuraltoolkit/datasets/management/paths.py +6 -0
  20. neuraltoolkit/datasets/management/retrieve.py +13 -0
  21. neuraltoolkit/datasets/management/verify.py +12 -0
  22. neuraltoolkit/datasets/mnist/__init__.py +2 -0
  23. neuraltoolkit/datasets/mnist/mnist.py +65 -0
  24. neuraltoolkit/datasets/mnist/mnistloader.py +32 -0
  25. neuraltoolkit/graph.py +71 -0
  26. neuraltoolkit/initializers/__init__.py +19 -0
  27. neuraltoolkit/initializers/glorot_initializer.py +34 -0
  28. neuraltoolkit/initializers/he_initializer.py +46 -0
  29. neuraltoolkit/loss/BCE.py +22 -0
  30. neuraltoolkit/loss/CCE.py +51 -0
  31. neuraltoolkit/loss/MSE.py +25 -0
  32. neuraltoolkit/loss/__init__.py +3 -0
  33. neuraltoolkit/modules/Registry.py +23 -0
  34. neuraltoolkit/modules/__init__.py +5 -0
  35. neuraltoolkit/modules/activations/__init__.py +6 -0
  36. neuraltoolkit/modules/activations/activation.py +16 -0
  37. neuraltoolkit/modules/activations/leakyrelu.py +22 -0
  38. neuraltoolkit/modules/activations/relu.py +17 -0
  39. neuraltoolkit/modules/activations/sigmoid.py +17 -0
  40. neuraltoolkit/modules/activations/softmax.py +21 -0
  41. neuraltoolkit/modules/activations/tanh.py +20 -0
  42. neuraltoolkit/modules/layers/__init__.py +18 -0
  43. neuraltoolkit/modules/layers/adaptive_max_pool2d.py +62 -0
  44. neuraltoolkit/modules/layers/conv2d.py +88 -0
  45. neuraltoolkit/modules/layers/dense.py +59 -0
  46. neuraltoolkit/modules/layers/flatten.py +37 -0
  47. neuraltoolkit/modules/layers/max_pool2d.py +64 -0
  48. neuraltoolkit/modules/models/__init__.py +1 -0
  49. neuraltoolkit/modules/models/sequential.py +122 -0
  50. neuraltoolkit/modules/module.py +112 -0
  51. neuraltoolkit/ops/__init__.py +2 -0
  52. neuraltoolkit/ops/data.py +37 -0
  53. neuraltoolkit/ops/image_processing.py +266 -0
  54. neuraltoolkit/optimizers/__init__.py +5 -0
  55. neuraltoolkit/optimizers/adagrad.py +33 -0
  56. neuraltoolkit/optimizers/adam.py +50 -0
  57. neuraltoolkit/optimizers/optimizer.py +10 -0
  58. neuraltoolkit/optimizers/rmsprop.py +35 -0
  59. neuraltoolkit/optimizers/sgd.py +33 -0
  60. neuraltoolkit/training/__init__.py +3 -0
  61. neuraltoolkit/training/config.py +9 -0
  62. neuraltoolkit/training/history.py +45 -0
  63. neuraltoolkit/training/trainer.py +239 -0
  64. ntk_ml-1.0.0.dist-info/METADATA +208 -0
  65. ntk_ml-1.0.0.dist-info/RECORD +68 -0
  66. ntk_ml-1.0.0.dist-info/WHEEL +5 -0
  67. ntk_ml-1.0.0.dist-info/licenses/LICENSE +219 -0
  68. 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,6 @@
1
+ from .tensor import Tensor
2
+ from .parameter import Parameter
3
+ from .dtype import Dtype ,PYTHON_TYPE_TO_DTYPE
4
+ from .device import Device
5
+ from .tensor_ops import conv2d
6
+ from .no_grad import no_grad
@@ -0,0 +1,5 @@
1
+ from enum import Enum, auto
2
+
3
+ class Device(Enum):
4
+ CPU = auto()
5
+ GPU = auto()
@@ -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,9 @@
1
+ from .tensor import Tensor
2
+
3
+ class no_grad:
4
+ def __enter__(self):
5
+ self.previous = Tensor.grad_enabled
6
+ Tensor.grad_enabled = False
7
+
8
+ def __exit__(self, exc_type, exc, tb):
9
+ Tensor.grad_enabled = self.previous
@@ -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
@@ -0,0 +1,3 @@
1
+ from .dataset import Dataset
2
+ from .dataloader import Dataloader
3
+ from .subset import Subset