neonet 0.1.0__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.
- neonet-0.1.0/LICENSE +21 -0
- neonet-0.1.0/PKG-INFO +32 -0
- neonet-0.1.0/README.md +0 -0
- neonet-0.1.0/pyproject.toml +66 -0
- neonet-0.1.0/setup.cfg +4 -0
- neonet-0.1.0/src/neonet/__init__.py +0 -0
- neonet-0.1.0/src/neonet/nn.py +356 -0
- neonet-0.1.0/src/neonet.egg-info/PKG-INFO +32 -0
- neonet-0.1.0/src/neonet.egg-info/SOURCES.txt +10 -0
- neonet-0.1.0/src/neonet.egg-info/dependency_links.txt +1 -0
- neonet-0.1.0/src/neonet.egg-info/requires.txt +13 -0
- neonet-0.1.0/src/neonet.egg-info/top_level.txt +1 -0
neonet-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ocedev112
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
neonet-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neonet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Neonet is a deep learning tool for building simple to medium sized neural networks, it supports multiple configurations and arguments for training
|
|
5
|
+
Author: Olewuenyi Emmanuel
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ocedev112
|
|
8
|
+
Project-URL: Repository, https://github.com/ocedev112/Neural_Network_from_scratch_numpy
|
|
9
|
+
Keywords: neural network,deep learning,numpy,machine learning
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=2.4.6
|
|
21
|
+
Requires-Dist: pydantic>=2.13.4
|
|
22
|
+
Requires-Dist: joblib>=1.5.3
|
|
23
|
+
Requires-Dist: typing_extensions>=4.15.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
29
|
+
Requires-Dist: scikit-learn>=1.9.0; extra == "dev"
|
|
30
|
+
Requires-Dist: scipy>=1.17.1; extra == "dev"
|
|
31
|
+
Requires-Dist: narwhals>=2.22.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
neonet-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=82.0", "wheel>=0.47.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "neonet"
|
|
7
|
+
description = "Neonet is a deep learning tool for building simple to medium sized neural networks, it supports multiple configurations and arguments for training"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Olewuenyi Emmanuel" }
|
|
15
|
+
]
|
|
16
|
+
keywords = ["neural network", "deep learning", "numpy", "machine learning"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"numpy>=2.4.6",
|
|
28
|
+
"pydantic>=2.13.4",
|
|
29
|
+
"joblib>=1.5.3",
|
|
30
|
+
"typing_extensions>=4.15.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=7.4",
|
|
36
|
+
"pytest-cov>=4.1",
|
|
37
|
+
"ruff>=0.4",
|
|
38
|
+
"mypy>=1.8",
|
|
39
|
+
"scikit-learn>=1.9.0",
|
|
40
|
+
"scipy>=1.17.1",
|
|
41
|
+
"narwhals>=2.22.1",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/ocedev112"
|
|
46
|
+
Repository = "https://github.com/ocedev112/Neural_Network_from_scratch_numpy"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
addopts = "--cov=src --cov-report=term-missing"
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 88
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
select = ["E", "F", "I", "UP"]
|
|
61
|
+
ignore = ["E501"]
|
|
62
|
+
|
|
63
|
+
[tool.mypy]
|
|
64
|
+
python_version = "3.11"
|
|
65
|
+
strict = true
|
|
66
|
+
ignore_missing_imports = true
|
neonet-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import joblib
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from pydantic import BaseModel, model_validator
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Optimizer(str, Enum):
|
|
9
|
+
SGD = "SGD"
|
|
10
|
+
GRADIENT_DESCENT = "GD"
|
|
11
|
+
ADAMS_LOSS = "adams_loss"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Regularization(str, Enum):
|
|
15
|
+
L1 = "L1"
|
|
16
|
+
L2 = "L2"
|
|
17
|
+
class Loss(str, Enum):
|
|
18
|
+
MSE = "MSE"
|
|
19
|
+
MAE = "MAE"
|
|
20
|
+
BCE = "BCE"
|
|
21
|
+
HUBER = "Huber"
|
|
22
|
+
|
|
23
|
+
class TrainArg(BaseModel):
|
|
24
|
+
batch_size: Optional[int] = None
|
|
25
|
+
learning_rate: float
|
|
26
|
+
optimizer: Optional[Optimizer] = None
|
|
27
|
+
regularization: Optional[Regularization] = None
|
|
28
|
+
alpha: Optional[float] = None
|
|
29
|
+
lasso: Optional[float] = None
|
|
30
|
+
b1_coefficient: float = 0.99
|
|
31
|
+
b2_coefficient: float = 0.999
|
|
32
|
+
epsilon: float = 1e-8
|
|
33
|
+
loss: Optional[Loss] = None
|
|
34
|
+
epochs: Optional[int] = None
|
|
35
|
+
use_decay: Optional[bool] = False
|
|
36
|
+
logging_steps: Optional[int] = 1
|
|
37
|
+
|
|
38
|
+
@model_validator(mode="after")
|
|
39
|
+
def validate_dependency(self):
|
|
40
|
+
if self.batch_size is not None and self.batch_size < 1:
|
|
41
|
+
raise ValueError("Batch size must be equal to ot greater than 1 when set")
|
|
42
|
+
if self.regularization is not None and (self.alpha is None and self.lasso is None):
|
|
43
|
+
raise ValueError("You need to set alpha or lasso coefficient when using regularization")
|
|
44
|
+
if self.regularization == "L1" and self.lasso == None:
|
|
45
|
+
raise ValueError("You need to use lasso coefficient with L1, alpha is for L2")
|
|
46
|
+
if self.regularization == "L2" and self.alpha == None:
|
|
47
|
+
raise ValueError("You need to use alpha coefficient with L2, lasso is for L1")
|
|
48
|
+
if self.optimizer == "adams_loss" and (self.epsilon <= 0):
|
|
49
|
+
raise ValueError("You need to use epsilon value for adams loss above 0")
|
|
50
|
+
if self.epochs is not None and (self.epochs < 1 or self.logging_steps < 1):
|
|
51
|
+
raise ValueError("Epochs and logging steps must be greater than 1")
|
|
52
|
+
if self.epochs is not None and self.logging_steps > self.epochs:
|
|
53
|
+
raise ValueError("Logging steps must be smaller than or equal to the epochs value")
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class NeuralNetwork():
|
|
59
|
+
|
|
60
|
+
def optimize_weights_activation(self, activation_sign, input_size, output_size):
|
|
61
|
+
He = ["ReLU", "LeakyReLU", "ELU", "Swish"]
|
|
62
|
+
Xavier = ["Tanh", "Sigmoid", "Softmax"]
|
|
63
|
+
if activation_sign in He:
|
|
64
|
+
return np.sqrt(2/(input_size))
|
|
65
|
+
if activation_sign in Xavier:
|
|
66
|
+
return np.sqrt(2/(input_size + output_size))
|
|
67
|
+
if activation_sign == "None":
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
def __init__(self, input_size,layers, optimize_weights_activation=True):
|
|
71
|
+
self.layers = layers
|
|
72
|
+
self.weight_matrix = []
|
|
73
|
+
self.bias_matrix = []
|
|
74
|
+
for idx,layer in enumerate(layers):
|
|
75
|
+
if idx==0:
|
|
76
|
+
weights_balancer = self.optimize_weights_activation(layer[1], input_size, layer[0]) if optimize_weights_activation else 1
|
|
77
|
+
self.weight_matrix.append(np.random.randn(input_size, layer[0]) * weights_balancer)
|
|
78
|
+
self.bias_matrix.append(np.zeros(layer[0]))
|
|
79
|
+
else:
|
|
80
|
+
weights_balancer = self.optimize_weights_activation(layer[1], layers[idx-1][0], layer[0]) if optimize_weights_activation else 1
|
|
81
|
+
self.weight_matrix.append(np.random.randn(layers[idx-1][0] ,layer[0]) * weights_balancer)
|
|
82
|
+
self.bias_matrix.append(np.zeros(layer[0]))
|
|
83
|
+
|
|
84
|
+
def activation(self, sign, z):
|
|
85
|
+
if sign == "ReLU":
|
|
86
|
+
return np.atleast_1d(np.clip(z, a_min=0, a_max=None))
|
|
87
|
+
if sign == "LeakyReLU":
|
|
88
|
+
return np.atleast_1d(np.where(z > 0, z, 0.01 * z))
|
|
89
|
+
if sign == "ELU":
|
|
90
|
+
z = np.clip(z, -500, 500)
|
|
91
|
+
return np.atleast_1d(np.where(z > 0, z, 0.01 * (np.exp(z) - 1)))
|
|
92
|
+
if sign == "Sigmoid":
|
|
93
|
+
z = np.clip(z, -500, 500)
|
|
94
|
+
return np.atleast_1d(1 / (1 + np.exp(-z)))
|
|
95
|
+
if sign == "Softmax":
|
|
96
|
+
e = np.exp(z - np.max(z))
|
|
97
|
+
return np.atleast_1d(e / e.sum())
|
|
98
|
+
if sign == "Tanh":
|
|
99
|
+
return np.atleast_1d(np.tanh(z))
|
|
100
|
+
if sign == "Swish":
|
|
101
|
+
return np.atleast_1d(z * (1 / (1 + np.exp(-z))))
|
|
102
|
+
if sign == "None":
|
|
103
|
+
return np.atleast_1d(z)
|
|
104
|
+
|
|
105
|
+
def __forward(self,input, weight_matrix, bias_matrix,idx,isTraining, training_layers):
|
|
106
|
+
layer_dict = {}
|
|
107
|
+
weight = weight_matrix[idx-1]
|
|
108
|
+
bias = bias_matrix[idx-1]
|
|
109
|
+
layer = np.matmul(input, weight)
|
|
110
|
+
layer = (layer + bias)
|
|
111
|
+
activation_sign = self.layers[idx-1][1]
|
|
112
|
+
activated_layer = self.activation(activation_sign, layer)
|
|
113
|
+
if isTraining:
|
|
114
|
+
layer_dict["input"] = input
|
|
115
|
+
layer_dict["weight"] = weight
|
|
116
|
+
layer_dict["bias"] = bias
|
|
117
|
+
layer_dict["z_layer"] = layer
|
|
118
|
+
layer_dict["activated_layer"] = activated_layer
|
|
119
|
+
layer_dict["activation_sign"] = activation_sign
|
|
120
|
+
layer_dict["v_layer"] = []
|
|
121
|
+
training_layers.append(layer_dict)
|
|
122
|
+
|
|
123
|
+
if idx == len(weight_matrix):
|
|
124
|
+
if isTraining:
|
|
125
|
+
return activated_layer, training_layers
|
|
126
|
+
return activated_layer
|
|
127
|
+
idx+=1
|
|
128
|
+
return self.__forward(activated_layer, self.weight_matrix, self.bias_matrix, idx, isTraining, training_layers)
|
|
129
|
+
|
|
130
|
+
def __forward_pass(self,inputs, train_mode=False):
|
|
131
|
+
outputs = []
|
|
132
|
+
for input in inputs:
|
|
133
|
+
training_layers = []
|
|
134
|
+
output = self.__forward(input, self.weight_matrix, self.bias_matrix, 1, train_mode, training_layers)
|
|
135
|
+
if output is not None:
|
|
136
|
+
outputs.append(output)
|
|
137
|
+
return outputs
|
|
138
|
+
|
|
139
|
+
def derivate_activation(self,sign, a):
|
|
140
|
+
if sign == "ReLU":
|
|
141
|
+
return np.where(a > 0, 1, 0)
|
|
142
|
+
if sign == "LeakyReLU":
|
|
143
|
+
return np.where(a> 0, 1, 0.01)
|
|
144
|
+
if sign == "ELU":
|
|
145
|
+
return np.where(a > 0, 1.0, a + 0.01)
|
|
146
|
+
if sign == "Sigmoid":
|
|
147
|
+
return a*(1-a)
|
|
148
|
+
|
|
149
|
+
if sign == "Tanh":
|
|
150
|
+
return 1 - a ** 2
|
|
151
|
+
if sign == "Swish":
|
|
152
|
+
s = 1 / (1 + np.exp(-a))
|
|
153
|
+
return s + a * s * (1 - s)
|
|
154
|
+
if sign == "None":
|
|
155
|
+
return 1
|
|
156
|
+
def loss_derivative(self, predicted_output, real_output, loss):
|
|
157
|
+
if loss == "MSE":
|
|
158
|
+
return 2 * (predicted_output - real_output)
|
|
159
|
+
if loss == "MAE":
|
|
160
|
+
return np.sign(predicted_output - real_output)
|
|
161
|
+
if loss == "BCE":
|
|
162
|
+
predicted_output = np.clip(predicted_output, 1e-7, 1 - 1e-7)
|
|
163
|
+
real_output = np.array(real_output)
|
|
164
|
+
diff = -(real_output / predicted_output) + (1 - real_output) / (1 - predicted_output)
|
|
165
|
+
return np.atleast_1d(np.clip(diff, -10, 10)).squeeze()
|
|
166
|
+
if loss == "Huber":
|
|
167
|
+
delta = 1.0
|
|
168
|
+
diff = predicted_output - real_output
|
|
169
|
+
return np.where(np.abs(diff) < delta, diff, delta * np.sign(diff))
|
|
170
|
+
|
|
171
|
+
def __backprop(self, layer_dict, real_output, loss):
|
|
172
|
+
lay_idx = len(layer_dict) - 1
|
|
173
|
+
cost_weight_matrix = []
|
|
174
|
+
cost_bias_matrix = []
|
|
175
|
+
for lay_idx,( matrix, layer) in enumerate(zip(self.weight_matrix[::-1], layer_dict[::-1])):
|
|
176
|
+
real_lay_idx = len(layer_dict) - 1 - lay_idx
|
|
177
|
+
if lay_idx == 0:
|
|
178
|
+
if layer["activation_sign"] == "Softmax":
|
|
179
|
+
v_matrix = layer["activated_layer"] - np.array(real_output)
|
|
180
|
+
else:
|
|
181
|
+
in_v = self.loss_derivative(layer["activated_layer"], real_output, loss)
|
|
182
|
+
dadz = self.derivate_activation(layer["activation_sign"], layer["activated_layer"])
|
|
183
|
+
v_matrix = dadz * in_v
|
|
184
|
+
else:
|
|
185
|
+
prev_weights = layer_dict[real_lay_idx+1]["weight"]
|
|
186
|
+
v_layer = layer_dict[real_lay_idx+1]["v_layer"]
|
|
187
|
+
in_v = np.dot(prev_weights, v_layer)
|
|
188
|
+
dadz = self.derivate_activation(layer["activation_sign"], layer["activated_layer"])
|
|
189
|
+
v_matrix = dadz * in_v
|
|
190
|
+
cost_weight = np.outer(layer["input"], v_matrix)
|
|
191
|
+
cost_bias = v_matrix
|
|
192
|
+
|
|
193
|
+
layer["v_layer"] = v_matrix
|
|
194
|
+
cost_weight_matrix.append(cost_weight)
|
|
195
|
+
cost_bias_matrix.append(cost_bias)
|
|
196
|
+
|
|
197
|
+
return cost_weight_matrix, cost_bias_matrix
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def regularization(self,regularizer, matrix, coef):
|
|
201
|
+
if regularizer == None:
|
|
202
|
+
return 0
|
|
203
|
+
if regularizer == "L1":
|
|
204
|
+
matrix = np.sign(matrix)
|
|
205
|
+
return matrix * coef
|
|
206
|
+
elif regularizer == "L2":
|
|
207
|
+
return matrix * coef
|
|
208
|
+
|
|
209
|
+
def compute_loss(self, cost_weight, cost_bias, learning_rate, regularizer, coef):
|
|
210
|
+
for i, (cost_w, cost_b) in enumerate(zip(cost_weight, cost_bias)):
|
|
211
|
+
reg_w = self.regularization(regularizer, self.weight_matrix[-(i+1)], coef)
|
|
212
|
+
reg_b = self.regularization(regularizer, self.bias_matrix[-(i+1)], coef)
|
|
213
|
+
self.weight_matrix[-(i+1)] -= (cost_w + reg_w) * learning_rate
|
|
214
|
+
|
|
215
|
+
self.bias_matrix[-(i+1)] -= (cost_b + reg_b) * learning_rate
|
|
216
|
+
|
|
217
|
+
def compute_loss_adams(self, momentum_w, energy_w, momentum_b, energy_b, learning_rate, regularizer, coef, ep, t, b1,b2):
|
|
218
|
+
for i, (m_w,v_w, m_b, v_b ) in enumerate(zip(momentum_w, energy_w, momentum_b, energy_b)):
|
|
219
|
+
reg_w = self.regularization(regularizer, self.weight_matrix[-(i+1)], coef)
|
|
220
|
+
reg_b = self.regularization(regularizer, self.bias_matrix[-(i+1)], coef)
|
|
221
|
+
m_w_hat = m_w / (1 - b1 ** t)
|
|
222
|
+
v_w_hat = v_w / (1 - b2 ** t)
|
|
223
|
+
m_b_hat = m_b / (1 - b1 ** t)
|
|
224
|
+
v_b_hat = v_b / (1 - b2 ** t)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
self.weight_matrix[-(i+1)] -= (m_w_hat / (np.sqrt(v_w_hat) + ep) + reg_w) * learning_rate
|
|
228
|
+
self.bias_matrix[-(i+1)] -= (m_b_hat / (np.sqrt(v_b_hat) + ep) + reg_b) * learning_rate
|
|
229
|
+
|
|
230
|
+
def batch_data(self, outputs, batch_size):
|
|
231
|
+
if batch_size is None:
|
|
232
|
+
batch_size = len(outputs)
|
|
233
|
+
for i in range(0, len(outputs), batch_size):
|
|
234
|
+
yield outputs[i:i+batch_size]
|
|
235
|
+
|
|
236
|
+
def train(self, inputs, real_output, training_args=None, eval_dataset=None, check_loss=False):
|
|
237
|
+
if len(inputs) != len(real_output):
|
|
238
|
+
raise ValueError("input and output dataset must match")
|
|
239
|
+
regularizer = None
|
|
240
|
+
coef = 0.0
|
|
241
|
+
batch_size = None
|
|
242
|
+
loss = "MSE"
|
|
243
|
+
epochs = 1
|
|
244
|
+
if training_args == None:
|
|
245
|
+
learning_rate = 0.04
|
|
246
|
+
else:
|
|
247
|
+
learning_rate = training_args.learning_rate
|
|
248
|
+
regularizer = training_args.regularization
|
|
249
|
+
if regularizer is not None and regularizer == "L1":
|
|
250
|
+
coef = training_args.lasso
|
|
251
|
+
else:
|
|
252
|
+
coef = training_args.alpha
|
|
253
|
+
if training_args.batch_size is not None:
|
|
254
|
+
batch_size = training_args.batch_size
|
|
255
|
+
if training_args.loss is not None:
|
|
256
|
+
loss = training_args.loss
|
|
257
|
+
if training_args.epochs is not None:
|
|
258
|
+
epochs = training_args.epochs
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
m_w,v_w = None,None
|
|
266
|
+
m_b, v_b = None, None
|
|
267
|
+
g_m_w, g_v_w = None, None
|
|
268
|
+
g_m_b, g_v_b = None, None
|
|
269
|
+
t = 0
|
|
270
|
+
base_lr = learning_rate
|
|
271
|
+
for epoch in range(epochs):
|
|
272
|
+
outputs = self.__forward_pass(inputs, True)
|
|
273
|
+
cost_weight_list = []
|
|
274
|
+
cost_bias_list = []
|
|
275
|
+
if training_args is not None and training_args.use_decay:
|
|
276
|
+
decay = 1 / (1 + 0.001 * epoch)
|
|
277
|
+
learning_rate = base_lr * decay
|
|
278
|
+
global_idx = 0
|
|
279
|
+
for outputs_batch in self.batch_data(outputs, batch_size):
|
|
280
|
+
for idx, (output, layer_dict) in enumerate(outputs_batch):
|
|
281
|
+
label = real_output[global_idx]
|
|
282
|
+
global_idx += 1
|
|
283
|
+
if len(output) != len(label):
|
|
284
|
+
raise ValueError("Y value must be the same length as predicted value")
|
|
285
|
+
cost_weight_matrix, cost_bias_matrix = self.__backprop(layer_dict, label, loss)
|
|
286
|
+
if training_args == None or training_args.optimizer == "GD":
|
|
287
|
+
cost_weight_list.append(cost_weight_matrix)
|
|
288
|
+
cost_bias_list.append(cost_bias_matrix)
|
|
289
|
+
if training_args is not None and training_args.optimizer == "SGD":
|
|
290
|
+
self.compute_loss(cost_weight_matrix, cost_bias_matrix, learning_rate, regularizer, coef)
|
|
291
|
+
|
|
292
|
+
if training_args is not None and training_args.optimizer == "adams_loss":
|
|
293
|
+
b1 = training_args.b1_coefficient
|
|
294
|
+
b2 = training_args.b2_coefficient
|
|
295
|
+
ep = training_args.epsilon
|
|
296
|
+
g_m_w = cost_weight_matrix
|
|
297
|
+
g_v_w = [cw**2 for cw in cost_weight_matrix]
|
|
298
|
+
g_m_b = cost_bias_matrix
|
|
299
|
+
g_v_b = [cb**2 for cb in cost_bias_matrix]
|
|
300
|
+
|
|
301
|
+
if m_w is None:
|
|
302
|
+
m_w = [np.zeros_like(w) for w in cost_weight_matrix]
|
|
303
|
+
v_w = [np.zeros_like(w) for w in cost_weight_matrix]
|
|
304
|
+
m_b = [np.zeros_like(b) for b in cost_bias_matrix]
|
|
305
|
+
v_b = [np.zeros_like(b) for b in cost_bias_matrix]
|
|
306
|
+
|
|
307
|
+
mv_w = [(b1*mw + (1-b1)*gm, b2*vw + (1-b2)*gv) for mw, gm, vw, gv in zip(m_w, g_m_w, v_w, g_v_w)]
|
|
308
|
+
mv_b = [(b1*mb + (1-b1)*gm, b2*vb + (1-b2)*gv) for mb, gm, vb, gv in zip(m_b, g_m_b, v_b, g_v_b)]
|
|
309
|
+
|
|
310
|
+
m_w, v_w = zip(*mv_w)
|
|
311
|
+
m_b, v_b = zip(*mv_b)
|
|
312
|
+
t+=1
|
|
313
|
+
self.compute_loss_adams(m_w,v_w,m_b,v_b, learning_rate, regularizer, coef,ep,t, b1, b2)
|
|
314
|
+
|
|
315
|
+
if training_args == None or training_args.optimizer == "GD":
|
|
316
|
+
cost_weight_mean = [np.stack(arrays).mean(axis=0) for arrays in zip(*cost_weight_list)]
|
|
317
|
+
cost_bias_mean = [np.stack(arrays).mean(axis=0) for arrays in zip(*cost_bias_list)]
|
|
318
|
+
self.compute_loss(cost_weight_mean, cost_bias_mean, learning_rate, regularizer, coef)
|
|
319
|
+
|
|
320
|
+
if check_loss:
|
|
321
|
+
if training_args is not None and training_args.logging_steps is not None:
|
|
322
|
+
if epoch % training_args.logging_steps == 0:
|
|
323
|
+
train_sample = len(inputs) // 4
|
|
324
|
+
train_ouputs = self.predict(inputs[:train_sample])
|
|
325
|
+
train_loss = sum([np.sum((np.array(o) - np.array(y))**2) for o, y in zip(train_ouputs, real_output[:train_sample])])
|
|
326
|
+
print(f"Epoch {epoch} Loss: {round(train_loss, 4)}")
|
|
327
|
+
if eval_dataset is not None:
|
|
328
|
+
test_sample = len(inputs) // 4
|
|
329
|
+
test_outputs = self.predict(eval_dataset[0][:test_sample])
|
|
330
|
+
test_loss = sum([np.sum((np.array(o) - np.array(y))**2) for o, y in zip(test_outputs, eval_dataset[1][:test_sample])])
|
|
331
|
+
print(f"Epoch {epoch} Val Loss: {round(test_loss, 4)}")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def predict(self, inputs):
|
|
335
|
+
outputs = self.__forward_pass(inputs, False)
|
|
336
|
+
return outputs
|
|
337
|
+
|
|
338
|
+
def save(self, path):
|
|
339
|
+
joblib.dump({
|
|
340
|
+
"weight_matrix" : self.weight_matrix,
|
|
341
|
+
"bias_matrix" : self.bias_matrix,
|
|
342
|
+
"layers" :self.layers
|
|
343
|
+
},path)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@classmethod
|
|
347
|
+
def load(cls, path):
|
|
348
|
+
obj = cls.__new__(cls)
|
|
349
|
+
loaded = joblib.load(path)
|
|
350
|
+
obj.weight_matrix = loaded["weight_matrix"]
|
|
351
|
+
obj.bias_matrix = loaded["bias_matrix"]
|
|
352
|
+
obj.layers = loaded["layers"]
|
|
353
|
+
return obj
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neonet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Neonet is a deep learning tool for building simple to medium sized neural networks, it supports multiple configurations and arguments for training
|
|
5
|
+
Author: Olewuenyi Emmanuel
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ocedev112
|
|
8
|
+
Project-URL: Repository, https://github.com/ocedev112/Neural_Network_from_scratch_numpy
|
|
9
|
+
Keywords: neural network,deep learning,numpy,machine learning
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=2.4.6
|
|
21
|
+
Requires-Dist: pydantic>=2.13.4
|
|
22
|
+
Requires-Dist: joblib>=1.5.3
|
|
23
|
+
Requires-Dist: typing_extensions>=4.15.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
29
|
+
Requires-Dist: scikit-learn>=1.9.0; extra == "dev"
|
|
30
|
+
Requires-Dist: scipy>=1.17.1; extra == "dev"
|
|
31
|
+
Requires-Dist: narwhals>=2.22.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
neonet
|