NeuralEngine 0.2.4__tar.gz → 0.2.7__tar.gz

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 (23) hide show
  1. {neuralengine-0.2.4 → neuralengine-0.2.7/NeuralEngine.egg-info}/PKG-INFO +23 -11
  2. {neuralengine-0.2.4 → neuralengine-0.2.7}/NeuralEngine.egg-info/SOURCES.txt +1 -0
  3. {neuralengine-0.2.4/NeuralEngine.egg-info → neuralengine-0.2.7}/PKG-INFO +23 -11
  4. {neuralengine-0.2.4 → neuralengine-0.2.7}/README.md +22 -10
  5. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/config.py +0 -1
  6. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/__init__.py +2 -1
  7. neuralengine-0.2.7/neuralengine/nn/dataload.py +60 -0
  8. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/loss.py +14 -8
  9. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/metrics.py +47 -23
  10. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/model.py +49 -73
  11. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/optim.py +0 -3
  12. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/tensor.py +2 -0
  13. {neuralengine-0.2.4 → neuralengine-0.2.7}/pyproject.toml +1 -1
  14. {neuralengine-0.2.4 → neuralengine-0.2.7}/setup.py +1 -1
  15. {neuralengine-0.2.4 → neuralengine-0.2.7}/LICENSE +0 -0
  16. {neuralengine-0.2.4 → neuralengine-0.2.7}/MANIFEST.in +0 -0
  17. {neuralengine-0.2.4 → neuralengine-0.2.7}/NeuralEngine.egg-info/dependency_links.txt +0 -0
  18. {neuralengine-0.2.4 → neuralengine-0.2.7}/NeuralEngine.egg-info/requires.txt +0 -0
  19. {neuralengine-0.2.4 → neuralengine-0.2.7}/NeuralEngine.egg-info/top_level.txt +0 -0
  20. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/__init__.py +0 -0
  21. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/nn/layers.py +0 -0
  22. {neuralengine-0.2.4 → neuralengine-0.2.7}/neuralengine/utils.py +0 -0
  23. {neuralengine-0.2.4 → neuralengine-0.2.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NeuralEngine
3
- Version: 0.2.4
3
+ Version: 0.2.7
4
4
  Summary: A framework/library for building and training neural networks.
5
5
  Home-page: https://github.com/Prajjwal2404/NeuralEngine
6
6
  Author: Prajjwal Pratap Shah
@@ -123,9 +123,12 @@ ne.set_device(ne.Device.CUDA)
123
123
  # Load your dataset (example: MNIST)
124
124
  x_train, y_train, x_test, y_test = load_mnist_data()
125
125
 
126
- y_train = ne.one_hot(y_train)
126
+ y_train = ne.one_hot(y_train) # Preprocess if needed
127
127
  y_test = ne.one_hot(y_test)
128
128
 
129
+ train_data = ne.DataLoader(x_train, y_train, batch_size=10000)
130
+ test_data = ne.DataLoader(x_test, y_test, batch_size=10000, shuffle=False)
131
+
129
132
  # Build your model
130
133
  model = ne.Model(
131
134
  input_size=(28, 28),
@@ -140,8 +143,8 @@ model(
140
143
  )
141
144
 
142
145
  # Train and evaluate
143
- model.train(x_train, y_train, epochs=30, batch_size=10000)
144
- result = model.eval(x_test, y_test)
146
+ model.train(train_data, epochs=30)
147
+ result = model.eval(test_data)
145
148
  ```
146
149
 
147
150
  ## Project Structure
@@ -153,6 +156,7 @@ neuralengine/
153
156
  utils.py
154
157
  nn/
155
158
  __init__.py
159
+ dataload.py
156
160
  layers.py
157
161
  loss.py
158
162
  metrics.py
@@ -220,7 +224,7 @@ NeuralEngine offers the following core capabilities:
220
224
  - `ne.Huber(delta=1.0)`: Huber loss, robust to outliers.
221
225
  - `ne.GaussianNLL(eps=1e-7)`: Gaussian Negative Log Likelihood loss for probabilistic regression.
222
226
  - `ne.KLDivergence(eps=1e-7)`: Kullback-Leibler Divergence loss for measuring distribution differences.
223
- - All loss functions inherit from a common base and support autograd.
227
+ - All loss functions inherit from a common base, support autograd and loss accumulation.
224
228
 
225
229
  ### Optimizers
226
230
  - `ne.Adam(lr=1e-3, betas=(0.9, 0.99), eps=1e-7, reg=0)`: Adam optimizer (switches to RMSProp if only one beta is provided).
@@ -228,21 +232,28 @@ NeuralEngine offers the following core capabilities:
228
232
  - All optimizers support L2 regularization and gradient reset.
229
233
 
230
234
  ### Metrics
231
- - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False, cm=False)`: Computes accuracy, precision, recall, F1 score, and confusion matrix for classification tasks.
235
+ - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False)`: Computes accuracy, precision, recall and F1 score for classification tasks.
232
236
  - `ne.RMSE()`: Root Mean Squared Error for regression.
233
237
  - `ne.R2()`: R2 Score for regression.
234
- - All metrics return results as dictionaries and support batch evaluation.
238
+ - `ne.Perplexity()`: Perplexity metric for generative models.
239
+ - All metrics store results as dictionaries, support batch evaluation and metric accumulation.
235
240
 
236
241
  ### Model API
237
242
  - `ne.Model(input_size, optimizer, loss, metrics)`: Create a model specifying input size, optimizer, loss function, and metrics.
238
243
  - Add layers by calling the model instance: `model(layer1, layer2, ...)` or using `model.build(layer1, layer2, ...)`.
239
- - `model.train(x, y, epochs=10, batch_size=64, seed=None, ckpt_interval=None)`: Train the model on data, with support for batching, shuffling, and metric/loss reporting and checkpointing per epoch.
240
- - `model.eval(x, y)`: Evaluate the model on data, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor. Also prints confusion matrix if enabled in metrics.
244
+ - `model.train(dataloader, epochs=10, ckpt_interval=None)`: Train the model on dataset, with support for metric/loss reporting and checkpointing per epoch.
245
+ - `model.eval(dataloader)`: Evaluate the model on dataset, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor.
241
246
  - Layers are set to training or evaluation mode automatically during `train` and `eval`.
242
247
  - `model.save(filename, weights_only=False)`: Save the model architecture or model parameters to a file.
243
248
  - `model.load_params(filepath)`: Load model parameters from a saved file.
244
249
  - `ne.Model.load_model(filepath)`: Load a model from a saved file.
245
250
 
251
+ ### DataLoader
252
+ - `ne.DataLoader(x, y, dtype=None, batch_size=32, shuffle=True, seed=None, bar=30)`: Create a data loader for batching and shuffling datasets during training and evaluation.
253
+ - Supports lists, tuples, numpy arrays, pandas dataframes and tensors as input data.
254
+ - Provides batching, shuffling, and progress bar display during iteration.
255
+ - Extensible for custom data loading strategies.
256
+
246
257
  ### Utilities
247
258
  - Tensor creation: `tensor(data, requires_grad=False)`, `zeros(shape)`, `ones(shape)`, `rand(shape)`, `randn(shape, xavier=False)`, `randint(low, high, shape)` and their `_like` variants for matching shapes.
248
259
  - Tensor operations: `sum`, `max`, `min`, `mean`, `var`, `log`, `sqrt`, `exp`, `abs`, `concat`, `stack`, `where`, `clip`, `array(data, dtype=...)` for elementwise, reduction, and conversion operations.
@@ -253,8 +264,9 @@ NeuralEngine is designed for easy extension and customization:
253
264
  - **Custom Layers**: Create new layers by inheriting from the `Layer` base class and implementing the `forward(self, x)` method. You can add parameters, initialization logic, and custom computations as needed. All built-in layers follow this pattern, making it simple to add your own.
254
265
  - **Custom Losses**: Define new loss functions by inheriting from the `Loss` base class and implementing the `compute(self, z, y)` method. This allows you to integrate any custom loss logic with autograd support.
255
266
  - **Custom Optimizers**: Implement new optimization algorithms by inheriting from the `Optimizer` base class and providing your own `step(self)` method. You can manage optimizer state and parameter updates as required.
256
- - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. Alternatively, you can pass a function of the form `func(x, y) -> dict[str, float | np.ndarray]` directly to the model's metrics argument for flexible evaluation.
257
- - All core components are modular and can be replaced or extended for research, experimentation, or production use.
267
+ - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. This allows you to track any performance measure with metric accumulation.
268
+ - **Custom DataLoaders**: Extend the `DataLoader` class to create specialized data loading strategies. Override the `__getitem__` method to define how batches are constructed.
269
+ - All core components are modular and can be replaced or extended for research or production use.
258
270
 
259
271
  ## Contribution Guide
260
272
 
@@ -13,6 +13,7 @@ neuralengine/config.py
13
13
  neuralengine/tensor.py
14
14
  neuralengine/utils.py
15
15
  neuralengine/nn/__init__.py
16
+ neuralengine/nn/dataload.py
16
17
  neuralengine/nn/layers.py
17
18
  neuralengine/nn/loss.py
18
19
  neuralengine/nn/metrics.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: NeuralEngine
3
- Version: 0.2.4
3
+ Version: 0.2.7
4
4
  Summary: A framework/library for building and training neural networks.
5
5
  Home-page: https://github.com/Prajjwal2404/NeuralEngine
6
6
  Author: Prajjwal Pratap Shah
@@ -123,9 +123,12 @@ ne.set_device(ne.Device.CUDA)
123
123
  # Load your dataset (example: MNIST)
124
124
  x_train, y_train, x_test, y_test = load_mnist_data()
125
125
 
126
- y_train = ne.one_hot(y_train)
126
+ y_train = ne.one_hot(y_train) # Preprocess if needed
127
127
  y_test = ne.one_hot(y_test)
128
128
 
129
+ train_data = ne.DataLoader(x_train, y_train, batch_size=10000)
130
+ test_data = ne.DataLoader(x_test, y_test, batch_size=10000, shuffle=False)
131
+
129
132
  # Build your model
130
133
  model = ne.Model(
131
134
  input_size=(28, 28),
@@ -140,8 +143,8 @@ model(
140
143
  )
141
144
 
142
145
  # Train and evaluate
143
- model.train(x_train, y_train, epochs=30, batch_size=10000)
144
- result = model.eval(x_test, y_test)
146
+ model.train(train_data, epochs=30)
147
+ result = model.eval(test_data)
145
148
  ```
146
149
 
147
150
  ## Project Structure
@@ -153,6 +156,7 @@ neuralengine/
153
156
  utils.py
154
157
  nn/
155
158
  __init__.py
159
+ dataload.py
156
160
  layers.py
157
161
  loss.py
158
162
  metrics.py
@@ -220,7 +224,7 @@ NeuralEngine offers the following core capabilities:
220
224
  - `ne.Huber(delta=1.0)`: Huber loss, robust to outliers.
221
225
  - `ne.GaussianNLL(eps=1e-7)`: Gaussian Negative Log Likelihood loss for probabilistic regression.
222
226
  - `ne.KLDivergence(eps=1e-7)`: Kullback-Leibler Divergence loss for measuring distribution differences.
223
- - All loss functions inherit from a common base and support autograd.
227
+ - All loss functions inherit from a common base, support autograd and loss accumulation.
224
228
 
225
229
  ### Optimizers
226
230
  - `ne.Adam(lr=1e-3, betas=(0.9, 0.99), eps=1e-7, reg=0)`: Adam optimizer (switches to RMSProp if only one beta is provided).
@@ -228,21 +232,28 @@ NeuralEngine offers the following core capabilities:
228
232
  - All optimizers support L2 regularization and gradient reset.
229
233
 
230
234
  ### Metrics
231
- - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False, cm=False)`: Computes accuracy, precision, recall, F1 score, and confusion matrix for classification tasks.
235
+ - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False)`: Computes accuracy, precision, recall and F1 score for classification tasks.
232
236
  - `ne.RMSE()`: Root Mean Squared Error for regression.
233
237
  - `ne.R2()`: R2 Score for regression.
234
- - All metrics return results as dictionaries and support batch evaluation.
238
+ - `ne.Perplexity()`: Perplexity metric for generative models.
239
+ - All metrics store results as dictionaries, support batch evaluation and metric accumulation.
235
240
 
236
241
  ### Model API
237
242
  - `ne.Model(input_size, optimizer, loss, metrics)`: Create a model specifying input size, optimizer, loss function, and metrics.
238
243
  - Add layers by calling the model instance: `model(layer1, layer2, ...)` or using `model.build(layer1, layer2, ...)`.
239
- - `model.train(x, y, epochs=10, batch_size=64, seed=None, ckpt_interval=None)`: Train the model on data, with support for batching, shuffling, and metric/loss reporting and checkpointing per epoch.
240
- - `model.eval(x, y)`: Evaluate the model on data, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor. Also prints confusion matrix if enabled in metrics.
244
+ - `model.train(dataloader, epochs=10, ckpt_interval=None)`: Train the model on dataset, with support for metric/loss reporting and checkpointing per epoch.
245
+ - `model.eval(dataloader)`: Evaluate the model on dataset, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor.
241
246
  - Layers are set to training or evaluation mode automatically during `train` and `eval`.
242
247
  - `model.save(filename, weights_only=False)`: Save the model architecture or model parameters to a file.
243
248
  - `model.load_params(filepath)`: Load model parameters from a saved file.
244
249
  - `ne.Model.load_model(filepath)`: Load a model from a saved file.
245
250
 
251
+ ### DataLoader
252
+ - `ne.DataLoader(x, y, dtype=None, batch_size=32, shuffle=True, seed=None, bar=30)`: Create a data loader for batching and shuffling datasets during training and evaluation.
253
+ - Supports lists, tuples, numpy arrays, pandas dataframes and tensors as input data.
254
+ - Provides batching, shuffling, and progress bar display during iteration.
255
+ - Extensible for custom data loading strategies.
256
+
246
257
  ### Utilities
247
258
  - Tensor creation: `tensor(data, requires_grad=False)`, `zeros(shape)`, `ones(shape)`, `rand(shape)`, `randn(shape, xavier=False)`, `randint(low, high, shape)` and their `_like` variants for matching shapes.
248
259
  - Tensor operations: `sum`, `max`, `min`, `mean`, `var`, `log`, `sqrt`, `exp`, `abs`, `concat`, `stack`, `where`, `clip`, `array(data, dtype=...)` for elementwise, reduction, and conversion operations.
@@ -253,8 +264,9 @@ NeuralEngine is designed for easy extension and customization:
253
264
  - **Custom Layers**: Create new layers by inheriting from the `Layer` base class and implementing the `forward(self, x)` method. You can add parameters, initialization logic, and custom computations as needed. All built-in layers follow this pattern, making it simple to add your own.
254
265
  - **Custom Losses**: Define new loss functions by inheriting from the `Loss` base class and implementing the `compute(self, z, y)` method. This allows you to integrate any custom loss logic with autograd support.
255
266
  - **Custom Optimizers**: Implement new optimization algorithms by inheriting from the `Optimizer` base class and providing your own `step(self)` method. You can manage optimizer state and parameter updates as required.
256
- - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. Alternatively, you can pass a function of the form `func(x, y) -> dict[str, float | np.ndarray]` directly to the model's metrics argument for flexible evaluation.
257
- - All core components are modular and can be replaced or extended for research, experimentation, or production use.
267
+ - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. This allows you to track any performance measure with metric accumulation.
268
+ - **Custom DataLoaders**: Extend the `DataLoader` class to create specialized data loading strategies. Override the `__getitem__` method to define how batches are constructed.
269
+ - All core components are modular and can be replaced or extended for research or production use.
258
270
 
259
271
  ## Contribution Guide
260
272
 
@@ -75,9 +75,12 @@ ne.set_device(ne.Device.CUDA)
75
75
  # Load your dataset (example: MNIST)
76
76
  x_train, y_train, x_test, y_test = load_mnist_data()
77
77
 
78
- y_train = ne.one_hot(y_train)
78
+ y_train = ne.one_hot(y_train) # Preprocess if needed
79
79
  y_test = ne.one_hot(y_test)
80
80
 
81
+ train_data = ne.DataLoader(x_train, y_train, batch_size=10000)
82
+ test_data = ne.DataLoader(x_test, y_test, batch_size=10000, shuffle=False)
83
+
81
84
  # Build your model
82
85
  model = ne.Model(
83
86
  input_size=(28, 28),
@@ -92,8 +95,8 @@ model(
92
95
  )
93
96
 
94
97
  # Train and evaluate
95
- model.train(x_train, y_train, epochs=30, batch_size=10000)
96
- result = model.eval(x_test, y_test)
98
+ model.train(train_data, epochs=30)
99
+ result = model.eval(test_data)
97
100
  ```
98
101
 
99
102
  ## Project Structure
@@ -105,6 +108,7 @@ neuralengine/
105
108
  utils.py
106
109
  nn/
107
110
  __init__.py
111
+ dataload.py
108
112
  layers.py
109
113
  loss.py
110
114
  metrics.py
@@ -172,7 +176,7 @@ NeuralEngine offers the following core capabilities:
172
176
  - `ne.Huber(delta=1.0)`: Huber loss, robust to outliers.
173
177
  - `ne.GaussianNLL(eps=1e-7)`: Gaussian Negative Log Likelihood loss for probabilistic regression.
174
178
  - `ne.KLDivergence(eps=1e-7)`: Kullback-Leibler Divergence loss for measuring distribution differences.
175
- - All loss functions inherit from a common base and support autograd.
179
+ - All loss functions inherit from a common base, support autograd and loss accumulation.
176
180
 
177
181
  ### Optimizers
178
182
  - `ne.Adam(lr=1e-3, betas=(0.9, 0.99), eps=1e-7, reg=0)`: Adam optimizer (switches to RMSProp if only one beta is provided).
@@ -180,21 +184,28 @@ NeuralEngine offers the following core capabilities:
180
184
  - All optimizers support L2 regularization and gradient reset.
181
185
 
182
186
  ### Metrics
183
- - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False, cm=False)`: Computes accuracy, precision, recall, F1 score, and confusion matrix for classification tasks.
187
+ - `ne.ClassificationMetrics(num_classes=None, acc=True, prec=False, rec=False, f1=False)`: Computes accuracy, precision, recall and F1 score for classification tasks.
184
188
  - `ne.RMSE()`: Root Mean Squared Error for regression.
185
189
  - `ne.R2()`: R2 Score for regression.
186
- - All metrics return results as dictionaries and support batch evaluation.
190
+ - `ne.Perplexity()`: Perplexity metric for generative models.
191
+ - All metrics store results as dictionaries, support batch evaluation and metric accumulation.
187
192
 
188
193
  ### Model API
189
194
  - `ne.Model(input_size, optimizer, loss, metrics)`: Create a model specifying input size, optimizer, loss function, and metrics.
190
195
  - Add layers by calling the model instance: `model(layer1, layer2, ...)` or using `model.build(layer1, layer2, ...)`.
191
- - `model.train(x, y, epochs=10, batch_size=64, seed=None, ckpt_interval=None)`: Train the model on data, with support for batching, shuffling, and metric/loss reporting and checkpointing per epoch.
192
- - `model.eval(x, y)`: Evaluate the model on data, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor. Also prints confusion matrix if enabled in metrics.
196
+ - `model.train(dataloader, epochs=10, ckpt_interval=None)`: Train the model on dataset, with support for metric/loss reporting and checkpointing per epoch.
197
+ - `model.eval(dataloader)`: Evaluate the model on dataset, disables gradient tracking using `with ne.NoGrad():`, prints loss and metrics, and returns output tensor.
193
198
  - Layers are set to training or evaluation mode automatically during `train` and `eval`.
194
199
  - `model.save(filename, weights_only=False)`: Save the model architecture or model parameters to a file.
195
200
  - `model.load_params(filepath)`: Load model parameters from a saved file.
196
201
  - `ne.Model.load_model(filepath)`: Load a model from a saved file.
197
202
 
203
+ ### DataLoader
204
+ - `ne.DataLoader(x, y, dtype=None, batch_size=32, shuffle=True, seed=None, bar=30)`: Create a data loader for batching and shuffling datasets during training and evaluation.
205
+ - Supports lists, tuples, numpy arrays, pandas dataframes and tensors as input data.
206
+ - Provides batching, shuffling, and progress bar display during iteration.
207
+ - Extensible for custom data loading strategies.
208
+
198
209
  ### Utilities
199
210
  - Tensor creation: `tensor(data, requires_grad=False)`, `zeros(shape)`, `ones(shape)`, `rand(shape)`, `randn(shape, xavier=False)`, `randint(low, high, shape)` and their `_like` variants for matching shapes.
200
211
  - Tensor operations: `sum`, `max`, `min`, `mean`, `var`, `log`, `sqrt`, `exp`, `abs`, `concat`, `stack`, `where`, `clip`, `array(data, dtype=...)` for elementwise, reduction, and conversion operations.
@@ -205,8 +216,9 @@ NeuralEngine is designed for easy extension and customization:
205
216
  - **Custom Layers**: Create new layers by inheriting from the `Layer` base class and implementing the `forward(self, x)` method. You can add parameters, initialization logic, and custom computations as needed. All built-in layers follow this pattern, making it simple to add your own.
206
217
  - **Custom Losses**: Define new loss functions by inheriting from the `Loss` base class and implementing the `compute(self, z, y)` method. This allows you to integrate any custom loss logic with autograd support.
207
218
  - **Custom Optimizers**: Implement new optimization algorithms by inheriting from the `Optimizer` base class and providing your own `step(self)` method. You can manage optimizer state and parameter updates as required.
208
- - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. Alternatively, you can pass a function of the form `func(x, y) -> dict[str, float | np.ndarray]` directly to the model's metrics argument for flexible evaluation.
209
- - All core components are modular and can be replaced or extended for research, experimentation, or production use.
219
+ - **Custom Metrics**: Add new metrics by inheriting from the `Metric` base class and implementing the `compute(self, z, y)` method. This allows you to track any performance measure with metric accumulation.
220
+ - **Custom DataLoaders**: Extend the `DataLoader` class to create specialized data loading strategies. Override the `__getitem__` method to define how batches are constructed.
221
+ - All core components are modular and can be replaced or extended for research or production use.
210
222
 
211
223
  ## Contribution Guide
212
224
 
@@ -24,7 +24,6 @@ def set_device(device: Device) -> None:
24
24
  Sets the current device for tensor operations.
25
25
  @param device: The device to set, either Device.CPU or Device.CUDA
26
26
  """
27
-
28
27
  global nu, _current_device
29
28
  if device == Device.CPU:
30
29
  nu = np
@@ -2,4 +2,5 @@ from .layers import *
2
2
  from .optim import *
3
3
  from .loss import *
4
4
  from .metrics import *
5
- from .model import *
5
+ from .model import *
6
+ from .dataload import *
@@ -0,0 +1,60 @@
1
+ import neuralengine.config as cf
2
+ from ..tensor import Tensor
3
+
4
+
5
+ class DataLoader:
6
+ """Data loader class for batching and shuffling data."""
7
+ def __init__(self, x, y, dtype=None, batch_size: int = 32, shuffle: bool = True, seed: int = None, bar: int = 30):
8
+ """
9
+ @param x: Input data (array-like).
10
+ @param y: Target data (array-like).
11
+ @param dtype: Data type for the dataset.
12
+ @param batch_size: Number of samples per batch.
13
+ @param shuffle: Whether to shuffle the data at the start of each epoch.
14
+ @param seed: Seed for random number generator to ensure reproducibility.
15
+ @param bar: Length of the progress bar.
16
+ """
17
+ self.x = Tensor(x, dtype=dtype)
18
+ self.y = Tensor(y, dtype=dtype)
19
+ self.batch_size = batch_size
20
+ self.shuffle = shuffle
21
+ self.current_batch = 0
22
+ self.num_samples = self.x.shape[0]
23
+ self.num_batches = (self.num_samples + batch_size - 1) // batch_size
24
+ self.indices = cf.nu.arange(self.num_samples)
25
+ self.rng = cf.nu.random.RandomState(seed)
26
+ self.bar = bar
27
+
28
+ def __len__(self) -> int:
29
+ """Returns the number of batches."""
30
+ return self.num_batches
31
+
32
+ def __getitem__(self, index: int | slice) -> tuple[Tensor, Tensor]:
33
+ """Gets a batch of data by index. Override for custom behavior."""
34
+ return self.x[index], self.y[index]
35
+
36
+ def __iter__(self) -> 'DataLoader':
37
+ """Returns an iterator over the batches."""
38
+ self.current_batch = 0
39
+ if self.shuffle:
40
+ self.rng.shuffle(self.indices)
41
+ return self
42
+
43
+ def __next__(self) -> tuple[Tensor, Tensor]:
44
+ """Returns the next batch of data."""
45
+ if self.current_batch < self.num_batches:
46
+ start_idx = self.current_batch * self.batch_size
47
+ end_idx = min(start_idx + self.batch_size, self.num_samples)
48
+ batch_indices = self.indices[start_idx:end_idx]
49
+ batch_data = self[batch_indices]
50
+ self.current_batch += 1
51
+ return batch_data
52
+ else:
53
+ raise StopIteration
54
+
55
+ def __repr__(self) -> str:
56
+ """String representation showing progress bar."""
57
+ percent = (self.current_batch / self.num_batches) * 100
58
+ filled = int(self.bar * self.current_batch // self.num_batches)
59
+ progress = '█' * filled + '-' * (self.bar - filled)
60
+ return f"\r|{progress}| {percent:.0f}%"
@@ -5,7 +5,8 @@ from ..utils import *
5
5
  class Loss:
6
6
  """Base class for all loss functions."""
7
7
  def __init__(self):
8
- self.loss_val = None
8
+ self.loss_val = 0
9
+ self.count = 0
9
10
 
10
11
  def __call__(self, z, y, *args, **kwargs):
11
12
  """ Calls the loss compute method with the provided predictions and targets.
@@ -16,17 +17,23 @@ class Loss:
16
17
  y = y if isinstance(y, Tensor) else Tensor(y)
17
18
  loss = self.compute(z, y, *args, **kwargs)
18
19
 
19
- self.loss_val = loss.data.mean() if isinstance(loss, Tensor) else loss
20
+ self.loss_val += loss.data.mean() if isinstance(loss, Tensor) else loss
21
+ self.count += 1
20
22
  return loss
21
23
 
22
24
  def __repr__(self) -> str:
23
25
  """ Returns a string representation of the loss with its value if computed. """
24
- if self.loss_val is not None:
25
- return f"{self.__class__.__name__}: {self.loss_val:.4f}"
26
+ if self.count > 0:
27
+ return f"{self.__class__.__name__}: {(self.loss_val / self.count):.4f}"
26
28
  else:
27
29
  return "No loss computed yet."
28
30
 
29
- def compute(self, z, y, *args, **kwargs):
31
+ def reset(self) -> None:
32
+ """ Resets the accumulated loss value and count. """
33
+ self.loss_val = 0
34
+ self.count = 0
35
+
36
+ def compute(self, z, y, *args, **kwargs) -> Tensor:
30
37
  """ Computes the loss given predictions and targets. To be implemented by subclasses. """
31
38
  raise NotImplementedError("compute() must be implemented in subclasses")
32
39
 
@@ -64,9 +71,8 @@ class Huber(Loss):
64
71
 
65
72
  def compute(self, z, y):
66
73
  # Huber: if |z - y| <= δ: 1/2 (z - y)² else: δ(|z - y| - 1/2 δ)
67
- diff = z - y
68
- abs_diff = abs(diff)
69
- loss = where(abs_diff <= self.delta, 0.5 * diff ** 2, self.delta * (abs_diff - 0.5 * self.delta))
74
+ diff = abs(z - y)
75
+ loss = where(diff <= self.delta, 0.5 * diff ** 2, self.delta * (diff - 0.5 * self.delta))
70
76
  return mean(loss, axis=-1, keepdims=False)
71
77
 
72
78
 
@@ -5,7 +5,8 @@ from ..tensor import array
5
5
  class Metric:
6
6
  """Base class for all metrics."""
7
7
  def __init__(self):
8
- self.metric_val = None
8
+ self.metric_val = {}
9
+ self.count = 0
9
10
 
10
11
  def __call__(self, z, y, *args, **kwargs):
11
12
  """ Calls the metric compute method with the provided predictions and targets.
@@ -14,28 +15,40 @@ class Metric:
14
15
  """
15
16
  z = z if isinstance(z, cf.nu.ndarray) else array(z)
16
17
  y = y if isinstance(y, cf.nu.ndarray) else array(y)
17
- metric_val = self.compute(z, y, *args, **kwargs)
18
+ metric = self.compute(z, y, *args, **kwargs)
18
19
 
19
- if not isinstance(metric_val, dict):
20
+ if not isinstance(metric, dict):
20
21
  raise ValueError("Metric compute method must return a dictionary.")
21
22
 
22
- self.metric_val = metric_val
23
- return self.metric_val
23
+ self.metric_val = {key: self.metric_val.get(key, 0) + value for key, value in metric.items()}
24
+ self.count += 1
25
+ return self
26
+
27
+ def __getitem__(self, key: str):
28
+ """ Allows access to individual metric values by key. """
29
+ return self.metric_val.get(key, None)
24
30
 
25
31
  def __repr__(self) -> str:
26
32
  """ Returns a string representation of the metric with its value if computed. """
27
- if self.metric_val is not None:
28
-
33
+ if self.count > 0:
29
34
  metric_str = ""
30
35
  for key, value in self.metric_val.items():
31
- if value is None: continue
32
36
  if isinstance(value, (cf.nu.ndarray)) and value.ndim == 1:
33
37
  value = value.mean(keepdims=False)
34
- metric_str += f"{key}: {value:.4f}, "
35
- return metric_str[:-2]
38
+ metric_str += f"{key}: {(value / self.count):.4f}, "
39
+ return metric_str[:-2] # Remove trailing comma and space
36
40
 
37
41
  else:
38
42
  return "No metric computed yet."
43
+
44
+ def reset(self) -> None:
45
+ """ Resets the accumulated metric values and count. """
46
+ self.metric_val = {}
47
+ self.count = 0
48
+
49
+ def compute(self, z, y, *args, **kwargs) -> dict[str, float | cf.np.ndarray]:
50
+ """ Computes the metric given predictions and targets. To be implemented by subclasses. """
51
+ raise NotImplementedError("compute() must be implemented in subclasses")
39
52
 
40
53
 
41
54
  class RMSE(Metric):
@@ -68,14 +81,13 @@ class R2(Metric):
68
81
 
69
82
  class ClassificationMetrics(Metric):
70
83
  """Classification metrics: Accuracy, Precision, Recall, F1 Score, Confusion Matrix."""
71
- def __init__(self, num_classes: int = None, acc: bool = True, prec: bool = False, rec: bool = False, f1: bool = False, cm: bool = False):
84
+ def __init__(self, num_classes: int = None, acc: bool = True, prec: bool = False, rec: bool = False, f1: bool = False):
72
85
  """
73
86
  @param num_classes: Number of classes for classification tasks.
74
87
  @param acc: Whether to compute accuracy.
75
88
  @param prec: Whether to compute precision.
76
89
  @param rec: Whether to compute recall.
77
90
  @param f1: Whether to compute F1 score.
78
- @param cm: Whether to compute confusion matrix. Only for evaluation mode.
79
91
  """
80
92
  super().__init__()
81
93
  self.num_classes = num_classes
@@ -83,7 +95,6 @@ class ClassificationMetrics(Metric):
83
95
  self.prec = prec
84
96
  self.rec = rec
85
97
  self.f1 = f1
86
- self.cm = cm
87
98
 
88
99
  def compute(self, z, y):
89
100
  if self.num_classes is None:
@@ -100,28 +111,41 @@ class ClassificationMetrics(Metric):
100
111
  FP = cf.nu.sum(cm, axis=0) - TP
101
112
  FN = cf.nu.sum(cm, axis=1) - TP
102
113
 
103
- acc, prec, rec, f1 = None, None, None, None
114
+ acc, prec, rec, f1, metrics = None, None, None, None, {}
104
115
  if self.acc:
105
116
  # Accuracy = trace(cm) / sum(cm)
106
117
  acc = cf.nu.trace(cm) / cf.nu.sum(cm) if cf.nu.sum(cm) > 0 else 0
118
+ metrics["Accuracy"] = acc
107
119
  if self.prec:
108
120
  # Precision = TP / (TP + FP)
109
121
  prec = cf.nu.zeros_like(TP, dtype=cf.nu.float32)
110
122
  cf.nu.divide(TP, TP + FP, out=prec, where=(TP + FP) != 0)
123
+ metrics["Precision"] = prec
111
124
  if self.rec:
112
125
  # Recall = TP / (TP + FN)
113
126
  rec = cf.nu.zeros_like(TP, dtype=cf.nu.float32)
114
127
  cf.nu.divide(TP, TP + FN, out=rec, where=(TP + FN) != 0)
128
+ metrics["Recall"] = rec
115
129
  if self.f1:
116
130
  # F1 = 2 * Precision * Recall / (Precision + Recall)
117
131
  f1 = cf.nu.zeros_like(TP, dtype=cf.nu.float32)
118
132
  cf.nu.divide(2 * prec * rec, prec + rec, out=f1, where=(prec + rec) != 0)
119
- if self.cm is not False:
120
- self.cm = cm
121
-
122
- return {
123
- "Accuracy": acc,
124
- "Precision": prec,
125
- "Recall": rec,
126
- "F1 Score": f1
127
- }
133
+ metrics["F1 Score"] = f1
134
+ return metrics
135
+
136
+
137
+ class Perplexity(Metric):
138
+ """Perplexity metric for generative models."""
139
+ def __init__(self, eps: float = 1e-7):
140
+ """
141
+ @param eps: Small value for numerical stability
142
+ """
143
+ super().__init__()
144
+ self.eps = eps
145
+
146
+ def compute(self, z, y):
147
+ # Perplexity = exp(-1/N Σ log(p(y|x)))
148
+ z = cf.nu.clip(z, self.eps, 1 - self.eps)
149
+ log_likelihood = -cf.nu.sum(y * cf.nu.log(z), axis=-1)
150
+ perplexity = cf.nu.exp(cf.nu.mean(log_likelihood))
151
+ return {"Perplexity": perplexity}
@@ -2,10 +2,12 @@ import os
2
2
  import pickle as pkl
3
3
  import neuralengine.config as cf
4
4
  from itertools import chain
5
- from ..tensor import Tensor, NoGrad, array
5
+ from ..tensor import Tensor, NoGrad
6
+ from ..utils import concat
6
7
  from .layers import Layer, Mode, Flatten, LSTM
7
8
  from .optim import Optimizer
8
9
  from .loss import Loss
10
+ from .dataload import DataLoader
9
11
 
10
12
 
11
13
  class Model:
@@ -13,15 +15,13 @@ class Model:
13
15
  Allows for defining the model architecture, optimizer, loss function, and metrics.
14
16
  The model can be trained and evaluated.
15
17
  """
16
-
17
18
  def __init__(self, input_size: tuple | int, optimizer: Optimizer = None, loss: Loss = None, metrics=()):
18
19
  """
19
20
  @param input_size: Tuple or int, shape of input data samples (int if 1D).
20
21
  @param optimizer: Optimizer instance.
21
22
  @param loss: Loss instance.
22
- @param metrics: List/tuple of Metric or Loss instances or func(x, y) → dict[str, float | np.ndarray].
23
+ @param metrics: List/tuple of Metric or Loss instances.
23
24
  """
24
-
25
25
  self.input_size = input_size
26
26
 
27
27
  if not isinstance(optimizer, Optimizer):
@@ -48,7 +48,6 @@ class Model:
48
48
  Builds the model by adding layers.
49
49
  @param layers: Variable number of Layer instances to add to the model.
50
50
  """
51
-
52
51
  self.parameters, prevLayer = {}, None
53
52
  for i, layer in enumerate(layers):
54
53
 
@@ -73,12 +72,12 @@ class Model:
73
72
  layer.in_size = self.input_size
74
73
  self.input_size = layer.out_size if hasattr(layer, 'out_size') else self.input_size
75
74
  if isinstance(layer, Flatten):
76
- self.input_size = int(cf.nu.prod(array(self.input_size)))
75
+ self.input_size = int(cf.np.prod(self.input_size))
77
76
 
78
77
  self.parameters[f"layer_{i}"] = list(layer.parameters()) # Collect parameters from the layer
79
78
 
80
79
  self.layers = layers
81
- self.optimizer.parameters = list(chain.from_iterable(self.parameters.values()))
80
+ self.optimizer.parameters = list(chain(*self.parameters.values()))
82
81
 
83
82
 
84
83
  @classmethod
@@ -141,110 +140,87 @@ class Model:
141
140
  else: pkl.dump(self, file)
142
141
 
143
142
 
144
- def train(self, x, y, epochs: int = 10, batch_size: int = 64, seed: int = None, ckpt_interval: int = None):
143
+ def train(self, dataloader: DataLoader, epochs: int = 10, ckpt_interval: int = None) -> None:
145
144
  """
146
145
  Trains the model on data.
147
- @param x: Input data, shape (N, features)
148
- @param y: Target data, shape (N, target_features)
149
- @param epochs: Number of epochs
150
- @param batch_size: Batch size (None for full batch)
151
- @param seed: Seed for random shuffling
146
+ @param dataloader: DataLoader instance providing training data.
147
+ @param epochs: Number of epochs to train
152
148
  @param ckpt_interval: Interval (in epochs) to save checkpoints
153
149
  """
150
+ if not isinstance(dataloader, DataLoader):
151
+ raise ValueError("dataloader must be an instance of DataLoader class")
154
152
 
155
153
  for layer in self.layers:
156
154
  layer.mode = Mode.TRAIN
157
155
 
158
- x, y = array(x), array(y)
159
-
160
- if batch_size is None:
161
- batch_size = x.shape[0]
162
- if batch_size <= 0 or batch_size > x.shape[0]:
163
- raise ValueError("batch_size must be a positive integer ≤ number of samples")
164
-
165
156
  for i in range(epochs):
166
157
 
167
- loss_val, metric_vals = 0, {}
168
- cf.nu.random.seed(seed)
169
- shuffle_indices = cf.nu.random.permutation(x.shape[0])
170
- x, y = x[shuffle_indices], y[shuffle_indices] # Shuffle data
171
-
172
- for j in range(0, x.shape[0], batch_size):
173
- x_batch = x[j:j + batch_size]
174
- y_batch = y[j:j + batch_size]
158
+ for batch in dataloader:
159
+ x, y = batch
175
160
 
176
161
  # Forward pass
177
162
  for layer in self.layers:
178
- x_batch = layer(x_batch)
163
+ x = layer(x)
179
164
  # For stacked LSTM, pass outputs accordingly
180
- if isinstance(layer, LSTM): x_batch = x_batch[layer.use_output[0]]
165
+ if isinstance(layer, LSTM): x = x[layer.use_output[0]]
181
166
 
182
- # Compute loss
183
- loss = self.loss(x_batch, y_batch)
184
- loss_val += self.loss.loss_val
185
-
167
+ loss = self.loss(x, y) # Compute loss
186
168
  loss.backward() # Backward pass
187
169
 
188
170
  # Compute metrics
189
171
  for metric in self.metrics:
190
- metric_val = metric(x_batch, y_batch)
191
- if isinstance(metric, Loss):
192
- key = metric.__class__.__name__
193
- metric_vals[key] = metric_vals.get(key, 0.0) + metric.loss_val
194
- continue
195
- for key, value in metric_val.items():
196
- if value is None: continue
197
- metric_vals[key] = metric_vals.get(key, 0.0) + value
172
+ metric(x, y)
198
173
 
199
174
  # Update parameters
200
175
  self.optimizer.step()
201
176
  self.optimizer.reset_grad() # Reset gradients
202
177
 
203
- loss_val /= (x.shape[0] / batch_size) # Average loss over batches
204
- output_strs = [f"Epoch {i + 1}/{epochs}", f"Loss: {loss_val:.4f}"]
178
+ print(dataloader, f'Epoch {i + 1}/{epochs}', sep=', ', end='', flush=True) # Show progress bar
205
179
 
206
- for key, value in metric_vals.items():
207
- value /= (x.shape[0] / batch_size) # Average metric over batches
208
- if isinstance(value, (cf.nu.ndarray)) and value.ndim == 1:
209
- value = value.mean(keepdims=False)
210
- output_strs.append(f"{key}: {value:.4f}")
180
+ output_strs = [f": (Loss) {self.loss}", *self.metrics]
211
181
 
212
182
  # Save checkpoint
213
183
  if ckpt_interval and (i + 1) % ckpt_interval == 0:
214
184
  self.save(f"checkpoints/model_epoch_{i + 1}.pkl", weights_only=True)
215
185
  output_strs.append("Checkpoint saved")
186
+ print(*output_strs, sep=', ', flush=True)
216
187
 
217
- print(*output_strs, sep=", ")
188
+ # Reset loss and metrics for next epoch
189
+ self.loss.reset()
190
+ for metric in self.metrics: metric.reset()
218
191
 
219
192
 
220
- def eval(self, x, y) -> Tensor:
193
+ def eval(self, dataloader: DataLoader) -> Tensor:
221
194
  """
222
195
  Evaluates the model on data.
223
- @param x: Input data, shape (N, features)
224
- @param y: Target data, shape (N, target_features)
196
+ @param dataloader: DataLoader instance providing evaluation data.
225
197
  @return: Output tensor after evaluation
226
198
  """
199
+ if not isinstance(dataloader, DataLoader):
200
+ raise ValueError("dataloader must be an instance of DataLoader class")
227
201
 
228
202
  for layer in self.layers:
229
203
  layer.mode = Mode.EVAL
230
204
 
231
- # Forward pass
232
- with NoGrad():
233
- z = x
234
- for layer in self.layers:
235
- z = layer(z)
236
- # For stacked LSTM, pass outputs accordingly
237
- if isinstance(layer, LSTM): z = z[layer.use_output[0]]
238
-
239
- self.loss(z, y) # Compute loss
240
-
241
- # Compute metrics
242
- cm = False
243
- for metric in self.metrics:
244
- metric(z, y)
245
- cm = metric.cm if hasattr(metric, 'cm') else cm
246
-
247
- print(f"Evaluation: (Loss) {self.loss}", *self.metrics, sep=", ")
248
- if cm is not False:
249
- print(f"Confusion Matrix:\n{cm}")
250
- return z
205
+ z = []
206
+ for batch in dataloader:
207
+ x, y = batch
208
+
209
+ # Forward pass
210
+ with NoGrad():
211
+ for layer in self.layers:
212
+ x = layer(x)
213
+ # For stacked LSTM, pass outputs accordingly
214
+ if isinstance(layer, LSTM): x = x[layer.use_output[0]]
215
+
216
+ z.append(x) # Accumulate outputs
217
+ self.loss(x, y) # Compute loss
218
+
219
+ # Compute metrics
220
+ for metric in self.metrics:
221
+ metric(x, y)
222
+
223
+ print(dataloader, 'Evaluation', sep=', ', end='', flush=True) # Show progress bar
224
+
225
+ print(f": (Loss) {self.loss}", *self.metrics, sep=', ', flush=True)
226
+ return concat(*z, axis=0)
@@ -43,7 +43,6 @@ class SGD(Optimizer):
43
43
  @param momentum: Momentum factor
44
44
  @param nesterov: Use Nesterov momentum
45
45
  """
46
-
47
46
  super().__init__()
48
47
  self.lr = lr
49
48
  self.reg = reg
@@ -83,7 +82,6 @@ class Adam(Optimizer):
83
82
  @param eps: Numerical stability
84
83
  @param reg: L2 regularization
85
84
  """
86
-
87
85
  super().__init__()
88
86
  self.lr = lr
89
87
  betas = betas if isinstance(betas, (list, tuple)) else (betas,)
@@ -105,7 +103,6 @@ class Adam(Optimizer):
105
103
  Updates the parameters using Adam optimization algorithm.
106
104
  RMSProp algorithm is used if only one beta is provided.
107
105
  """
108
-
109
106
  self.t += 1
110
107
  for param in self._params:
111
108
  # grad = ∂L/∂w + λw (L2 regularization)
@@ -758,6 +758,8 @@ def array(data, dtype=cf.nu.float32):
758
758
  @param dtype: Desired data type of the output array.
759
759
  """
760
760
  if isinstance(data, Tensor):
761
+ if dtype is None:
762
+ return data.data.copy()
761
763
  return data.data.astype(dtype)
762
764
  return cf.nu.asarray(data, dtype=dtype)
763
765
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "NeuralEngine"
7
- version = "0.2.4"
7
+ version = "0.2.7"
8
8
  description = "A framework/library for building and training neural networks."
9
9
  authors = [
10
10
  { name = "Prajjwal Pratap Shah", email = "prajjwalpratapshah@outlook.com" }
@@ -11,7 +11,7 @@ def read_requirements():
11
11
 
12
12
  setup(
13
13
  name="NeuralEngine",
14
- version="0.2.4",
14
+ version="0.2.7",
15
15
  author="Prajjwal Pratap Shah",
16
16
  author_email="prajjwalpratapshah@outlook.com",
17
17
  maintainer="Prajjwal Pratap Shah",
File without changes
File without changes
File without changes