NN-E 0.1.4__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.
nn_e-0.1.4/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: NN_E
3
+ Version: 0.1.4
4
+ Summary: A modular neural network framework
5
+ Project-URL: Homepage, https://github.com/diri-daniel/NN_E.git
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: numpy
8
+ Requires-Dist: pandas
9
+ Requires-Dist: datetime
10
+ Requires-Dist: matplotlib
nn_e-0.1.4/README.md ADDED
File without changes
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "NN_E"
3
+ version = "0.1.4"
4
+ description = "A modular neural network framework"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "numpy",
8
+ "pandas",
9
+ "datetime",
10
+ "matplotlib"
11
+ ]
12
+
13
+ [project.urls]
14
+ Homepage = "https://github.com/diri-daniel/NN_E.git"
15
+
16
+ [tool.setuptools.package-data]
17
+ Experiment = ["Helper/*.dll", "Helper/*.so"]
nn_e-0.1.4/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,199 @@
1
+ import numpy as np
2
+ import ctypes
3
+
4
+ # Note:
5
+ # 1. testing is not implemented yet. only training is implemented. - Done
6
+ # 2. implement accuracy and other metrics. - Done
7
+ # 3. implement opencl and cuda backend for layers. - rewrite .. they keep failing at either extreme values or large datasets.
8
+ # 4. add save and reuse functionality.
9
+ # 5. maybe add a simple preprocessor extend functionality. 1/2
10
+ # 6. maybe code a simple parameter randomizer for testing and experimentation purposes.
11
+ # 7. timers for experimentation purposes. maybe make a simple class for this that can be used as a context manager.
12
+ # 8. bricked New_pc branch. wont miss it.
13
+
14
+
15
+ # Layers class represents a layer in the neural network. It contains the weights, biases, activation function, and other parameters for the layer.
16
+ class Layers:
17
+ # layer initializer. size is mandatory. activation, alpha, and backend are optional.
18
+ # input layers dont really need anything else.
19
+ # output layer is just a hidden layer with a specific activation function and weight distribution. must be set to accomodate network.compile() parameters.
20
+ def __init__(self, size:int, activation:str="relu", alpha:float=0.1, backend:str="numpy"):
21
+ # the activation functions and matmul functions are stored in dictionaries for easy access based on the provided keys.
22
+ act_functs = {
23
+ "relu" : [self.Relu, self.ReluSlope],
24
+ "lrelu" : [self.LRelu, self.LReluSlope],
25
+ "sigmoid" : [self.sigmoid, "CCE"],
26
+ "SFMX" : [self.SoftMax, "BCE"]
27
+ }
28
+ matmul_functs = {
29
+ "numpy": self.numpyMatmul,
30
+ "openCl": self.openCl
31
+ }
32
+
33
+ # size is the number of neurons in the layer. activation is the activation function for the layer.
34
+ # alpha is the slope for leaky relu.
35
+ # backend is the backend for matrix multiplication.
36
+ self.neuronLength = size
37
+ self.alpha = alpha
38
+ self.borrow = None
39
+ try:
40
+ if activation == "LRelu":
41
+ print(f"Warning: alpha already set. Set layers(alpha) at initialization")
42
+
43
+ act = act_functs[activation]
44
+ self.activation = act[0]
45
+ self.activation_slope = act[1]
46
+ except KeyError:
47
+ print(f"KeyError: {activation} is not a valid activation Key.\nValid keys are {act_functs.keys()}.\n")
48
+ raise(KeyError)
49
+
50
+ except Exception as e:
51
+ print(f"{e}:HUH !!!")
52
+ return
53
+ try:
54
+ if backend != "numpy":
55
+ print(f"{backend} has been set on the Network object ")
56
+
57
+ self.matmul = matmul_functs[backend]
58
+ self.backend = backend
59
+
60
+ except KeyError:
61
+ print(f"KeyError: {backend} is not a valid backend key.\nValid keys are {matmul_functs.keys()}.\n")
62
+ raise(KeyError)
63
+
64
+ except Exception as e:
65
+ print(f"{e}:HUH !!!")
66
+ return
67
+
68
+ # genWeightsBiases generates the weights and biases for the layer based on the provided weight distribution key.
69
+ def genWeightsBiases(self, previousNeuronLength:int, weightDist:str="default") -> None:
70
+ # the shape of the weights is determined by the number of neurons in the previous layer and the number of neurons in the current layer.
71
+ shape = (previousNeuronLength, self.neuronLength)
72
+
73
+ # the weights are generated based on the provided weight distribution key. the biases are initialized to zero. change this later. maybe add a bias distribution as well.
74
+ distributions = {
75
+ "default": lambda : np.random.uniform(-1/shape[1], 1/shape[1], shape),
76
+ "Xavier_Uniform": lambda : np.random.uniform(-np.sqrt(6 / sum(shape)), np.sqrt(6 / sum(shape)), shape),
77
+ "Xavier_Normal": lambda : np.random.normal(0, np.sqrt(2 / sum(shape)), shape),
78
+ "He_Uniform": lambda : np.random.uniform(-np.sqrt(6 / shape[0]), np.sqrt(6 / shape[0]), shape),
79
+ "He_Normal": lambda : np.random.normal(0, np.sqrt(2 / shape[0]), shape),
80
+ "Lecun_Normal": lambda : np.random.normal(0, np.sqrt(1 / shape[0]), shape),
81
+ "Uniform_Small": lambda : np.random.uniform(-0.1, 0.1, shape),
82
+ "Zeros": lambda : np.zeros(shape=shape)
83
+ }
84
+
85
+ try:
86
+ self.weights = distributions[weightDist]()
87
+
88
+ except KeyError:
89
+ print(f"KeyError: {weightDist} is not a valid weight distribution Key.\nValid keys are {distributions.keys()}.\n")
90
+ raise(KeyError)
91
+
92
+ except Exception as e:
93
+ print(f"{e}:HUH !!!")
94
+ return
95
+
96
+ self.biases = np.zeros(self.neuronLength)
97
+
98
+ # forward takes the input from the previous layer, performs the matrix multiplication with the weights, adds the biases, and applies the activation function to get the output of the layer.
99
+ def forward(self, inp:np.ndarray) -> np.ndarray:
100
+ self.inp = inp
101
+ self.values = self.matmul(inp, self.weights, "forward")
102
+ self.activatedValues = self.activation(self.values)
103
+ return self.activatedValues
104
+
105
+ # backward takes the gradient of the loss with respect to the output of the layer and calculates the gradients for the weights, biases, and input of the layer.
106
+ # It then updates the weights and biases based on the learning rate and returns the gradient of the loss with respect to the input of the layer for use in the backward pass of the previous layer.
107
+ # takes dL_dz which is the gradient of the loss with respect to the output of the layer. it is calculated in the loss function and stored in the network object for use in the backward pass.
108
+ # dl_dz is dependent on how output layer is set up.
109
+ # if the output layer activation slope is a string, it is assumed to be a loss function and dl_dz is calculated in the loss function.
110
+ # if its a function, it is assumed to be an activation slope and dl_dz is calculated by multiplying the gradient of the loss with respect to the output of the layer with the activation slope.
111
+ def backward(self, dL_dz:np.ndarray) -> np.ndarray:
112
+ if callable(self.activation_slope): #self.activation_slope is not a string
113
+ dL_daz = dL_dz * self.activation_slope(self.values)
114
+
115
+ else: # is a string
116
+ dL_daz = dL_dz
117
+
118
+ self.dl_daz = dL_daz
119
+
120
+ # the gradients for the weights, biases, and input of the layer are calculated based on the gradient of the loss with respect to the activated output of the layer and the input to the layer.
121
+ dL_dw = self.matmul(self.inp.T, dL_daz, "backward") / len(self.inp) # the weights are updated based on the average gradient over the batch.
122
+ dL_db = np.mean(dL_daz, axis=0)
123
+ dL_din = self.matmul(dL_daz, self.weights.T, "backward") # the gradient of the loss with respect to the input of the layer is calculated for use in the backward pass of the previous layer.
124
+ self.dl_din = dL_din
125
+ # the weights and biases are updated based on the learning rate and the gradients.
126
+ self.weights -= self.lr * dL_dw
127
+ self.biases -= self.lr * dL_db
128
+ return dL_din
129
+
130
+ # the activation functions and their slopes are defined as separate methods for modularity and ease of use in the forward and backward passes.
131
+ def Relu(self, neuron):
132
+ return np.maximum(neuron,0)
133
+
134
+ def ReluSlope(self, neuron):
135
+ return (neuron>=0).astype(float)
136
+
137
+ def LRelu(self, neuron):
138
+ return np.where(neuron>=0, neuron, neuron*self.alpha)
139
+
140
+ def LReluSlope(self, neuron):
141
+ return np.where(neuron>=0, 1, self.alpha)
142
+
143
+ def sigmoid(self ,neuron):
144
+ return 1 / (1 + np.exp(-neuron))
145
+
146
+ def SoftMax(self, neuron):
147
+ mx = np.max(neuron, axis=1, keepdims=True)
148
+ e = np.exp(neuron-mx)
149
+ return e/np.sum(e, axis=1, keepdims=True)
150
+
151
+ # the matrix multiplication functions are defined as separate methods for modularity and ease of use in the forward and backward passes.
152
+ def numpyMatmul(self, a, b, dir):
153
+ if dir == "forward":
154
+ return a @ b + self.biases
155
+ elif dir == "backward":
156
+ return a @ b
157
+
158
+ def openCl(self, a, b, dir):
159
+ if dir == "forward":
160
+ biases = self.biases
161
+ elif dir == "backward":
162
+ biases = np.zeros(self.biases.shape)
163
+
164
+ output = np.zeros(a.shape[0]*b.shape[1])
165
+
166
+ funct_a = a.flatten().astype(np.float32)
167
+ funct_b = b.flatten().astype(np.float32)
168
+ funct_biases = biases.astype(np.float32)
169
+ funct_output = output.astype(np.float32)
170
+
171
+ self.borrow(funct_a.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
172
+ funct_b.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
173
+ funct_biases.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
174
+ funct_output.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
175
+ a.shape[0],
176
+ b.shape[1],
177
+ a.shape[1])
178
+
179
+ return funct_output.reshape(a.shape[0], b.shape[1]).astype(np.float64)
180
+
181
+ def cuda(self, a, b):
182
+ self.borrow()
183
+
184
+ def show(self):
185
+ attrs = {
186
+ "weights": self.weights,
187
+ "biases": self.biases,
188
+ "inp": self.inp,
189
+ "values": self.values,
190
+ "activated": self.activatedValues,
191
+ }
192
+ for name, val in attrs.items():
193
+ if val is None:
194
+ print(f"{name}: None")
195
+ else:
196
+ import numpy as np
197
+ arr = np.array(val)
198
+ print(f"{name}: shape={arr.shape} min={arr.min():.4f} max={arr.max():.4f} mean={arr.mean():.4f} zeros={np.sum(arr==0)}")
199
+ print()
@@ -0,0 +1,367 @@
1
+ from .Network_types import NetworkType
2
+ import numpy as np
3
+ import datetime
4
+ import os
5
+
6
+ # Note:
7
+ # 1. testing is not implemented yet. only training is implemented. - Done
8
+ # 2. implement accuracy and other metrics. - Done
9
+ # 3. implement opencl and cuda backend for layers. - rewrite .. they keep failing at either extreme values or large datasets.
10
+ # 4. add save and reuse functionality. - done
11
+ # 5. maybe add a simple preprocessor extend functionality. 1/2
12
+ # 6. maybe code a simple parameter randomizer for testing and experimentation purposes.
13
+ # 7. timers for experimentation purposes. maybe make a simple class for this that can be used as a context manager.
14
+ # 8. bricked New_pc branch. wont miss it.
15
+ # 9. add import and export functionality for db uses. - done
16
+
17
+
18
+ class Network:
19
+ def __init__(self, layers:list, name:str, type:NetworkType=NetworkType.Simple_Neural_Network, helper="./Helper/")->None:
20
+ # layers should be a list of Layers objects.
21
+ # The first layer should be the input layer and the last layer should be the output layer.
22
+ # The input layer is not used for calculations but is used to set the input shape for the first hidden layer.
23
+ # The output layer is used to set the output shape for the last hidden layer.
24
+ # The hidden & output layers are used for calculations and can have any activation function and weight distribution.
25
+ self.layers = layers
26
+ self.helper = helper
27
+
28
+ try:
29
+ self.name = name
30
+ self.type = type
31
+
32
+ except Exception as e:
33
+ raise(e)
34
+
35
+ def initOpenCl(self):
36
+ import ctypes
37
+ self.lib = ctypes.CDLL(self.helper+"main.dll")
38
+ self.lib.init_opencl()
39
+ self.lib.init_snn()
40
+
41
+ self.lib.snn_forward.argtypes = [
42
+ ctypes.POINTER(ctypes.c_float),
43
+ ctypes.POINTER(ctypes.c_float),
44
+ ctypes.POINTER(ctypes.c_float),
45
+ ctypes.POINTER(ctypes.c_float),
46
+ ctypes.c_int,
47
+ ctypes.c_int,
48
+ ctypes.c_int
49
+ ]
50
+
51
+ # Compile should be called after initializing the network and before training.
52
+ # It sets the loss function, metric functions, and generates weights and biases for all layers except the input layer.
53
+ # It also sets the learning rate for all layers.
54
+ def Compile(self, LearningRate:float=0.01, Loss:str="CCE", metrics:list=["Accuracy"], weightDist:str="default", force:bool=False) -> None:
55
+ # checks for the last layer activation slope and sets the loss function accordingly.
56
+ # If the last layer activation slope is a string, it assumes its a loss function and sets it as the loss function.
57
+ # If its not a string, it assumes its a function and sets the loss function to the one provided in the Loss parameter.
58
+ # If force is True, it will set the loss function to the one provided in the Loss parameter regardless of the last layer activation slope.
59
+ lastLayerSimplify = self.layers[-1].activation_slope
60
+ if not callable(lastLayerSimplify) and not force:
61
+ Loss = lastLayerSimplify
62
+ print(f"Auto-set loss to {Loss}. Set force=True to override.")
63
+
64
+ elif force:
65
+ print(f"Warning: Loss function {Loss} is forced. Its not an error but please confirm {Loss} is appropriate for last layer slope")
66
+
67
+ else:
68
+ print(f"Warning: last layer is a function. Its not an error but please confirm {Loss} is appropriate")
69
+
70
+ if "F1" in metrics and ("Precision" in metrics or "Recall" in metrics):
71
+ print("Warning: F1 is set. Precision and Recall will be ignored in metric calculations to avoid redundancy. setting force=True does not override this.")
72
+
73
+ # set the loss function and metric functions based on the provided keys.
74
+ loss_functs = {
75
+ "CCE" : self.catCrossEnt,
76
+ "BCE" : self.binCrossEnt
77
+ }
78
+ metric_functs = {
79
+ "Accuracy" : self.accuracy,
80
+ "Precision" : self.precision,
81
+ "Recall" : self.recall,
82
+ "F1" : self.f1
83
+ }
84
+
85
+ try:
86
+ self.loss = loss_functs[Loss]
87
+
88
+ except KeyError:
89
+ print(f"KeyError: {Loss} is not a valid loss Key.\nValid keys are {loss_functs.keys()}.\n")
90
+ return
91
+
92
+ except Exception as e:
93
+ print(f"{e}:HUH !!!")
94
+ return
95
+
96
+ try:
97
+ self.metrics = {}
98
+
99
+
100
+ for x in metrics:
101
+ self.metrics[x] = metric_functs[x]
102
+
103
+ except KeyError:
104
+ print(f"KeyError: {x} is not a valid metric Key.\nValid keys are {metric_functs.keys()}.\n")
105
+ return
106
+
107
+ except Exception as e:
108
+ print(f"{e}:HUH !!!")
109
+ return
110
+
111
+ # generate weights and biases for all layers except the input layer. set learning rate for all layers.
112
+ init_ocl = False
113
+ init_cuda = False
114
+ for i in range(1, len(self.layers)):
115
+
116
+ self.layers[i].genWeightsBiases(self.layers[i-1].neuronLength, weightDist=weightDist)
117
+ self.layers[i].lr = LearningRate
118
+
119
+ if self.layers[i].backend == "openCl":
120
+ if not init_ocl:
121
+ self.initOpenCl()
122
+ init_ocl = True
123
+ self.layers[i].borrow = self.lib.snn_forward
124
+
125
+
126
+ # Forward should be called before Backward. It takes the input data and passes it through the network to get the output.
127
+ def Forward(self, train:np.ndarray) -> None:
128
+ # the input data is passed through the network layer by layer.
129
+ # The output of each layer is stored in the layer object for use in the backward pass.
130
+ # The final output is stored in the network object for use in the loss function and metric functions.
131
+
132
+ inp = train # contains the training data. it is passed through the network layer by layer.
133
+
134
+ # simple forward pass. the output of each layer is stored in the layer object for use in the backward pass.
135
+ for layer in self.layers[1:]:
136
+ inp = layer.forward(inp)
137
+
138
+ self.output = inp # stores the final output of the network for use in the loss function and metric functions.
139
+
140
+ # Backward should be called after Forward. It takes the output of the network and the target values and calculates the gradients for each layer and updates the weights and biases accordingly.
141
+ def Backward(self):
142
+ dL_dn = self.layers[-1].backward(self.dL_do) # the gradient of the loss with respect to the output of the network. it is calculated in the loss function and stored in the network object for use in the backward pass.
143
+
144
+ # simple backward pass. the gradient of the loss with respect to the output of the network is passed through the network layer by layer in reverse order.
145
+ for layer in reversed(self.layers[1:-1]):
146
+ dL_dn = layer.backward(dL_dn)
147
+
148
+ # Fit should be called after Compile.
149
+ # It takes the training data and the number of epochs and trains the network by calling Forward and Backward for each epoch.
150
+ # It also stores the loss for each epoch in the network object for use in plotting the loss curve.
151
+ def Fit(self, train, epochs=100, n=10, batch=1000):
152
+ #targets = self.encode(train[1]) fix this later. only encode if requested and appropriate parameters are set. for now, just assume the targets are already encoded.
153
+ self.pLoss = [] # stores loss for each epoch.
154
+ targets = train[1] # change this later. only encode if requested and appropriate parameters are set. for now, just assume the targets are already encoded.
155
+ self.sMV = {}
156
+ batch = batch
157
+ for metric in self.metrics:
158
+ self.sMV[metric] = [] # stores values for each epoch.
159
+ # simple training loop.
160
+ for epoch in range(epochs):
161
+ for i in range(0, len(train[0]), batch):
162
+ self.Forward(train[0][i:i+batch])
163
+ self.dL_do = self.loss(targets[i:i+batch]) # precariously stores the gradient of the loss with respect to the output of the network for use in the backward pass.
164
+ self.Backward()
165
+ self.pLoss.append(float(self.Loss))
166
+ # if epoch % (epochs // n) == 0: print(f"Epoch {epoch}/{epochs} - Batch {i//batch + 1}/{len(train[0])//batch} - Loss: {self.Loss:.4f}")
167
+
168
+ self.calcMetrics(targets[i:i+batch], self.sMV)
169
+
170
+ # some kinda progress loader
171
+
172
+ # for i in range(1,10):
173
+ # if epoch % (epochs // n) == i:
174
+ # print("_",end='\r')
175
+
176
+ if epoch % (epochs // n) == 0:
177
+ print(f"Epoch {epoch}/{epochs} - Loss: {self.Loss}")
178
+
179
+ # the loss functions return the gradient of the loss with respect to the output of the network and also store the loss in the network object for use in plotting the loss curve.
180
+ def catCrossEnt(self, target):
181
+ x = self.output - target
182
+ self.Loss = -np.mean(np.sum(target * np.log(np.clip(self.output, 1e-7, 1)), axis=1))
183
+ return x
184
+
185
+ def binCrossEnt(self, target):
186
+ x = self.output - target
187
+ self.Loss = -np.mean(target * np.log(np.clip(self.output, 1e-7, 1)) + (1 - target) * np.log(np.clip(1 - self.output, 1e-7, 1)))
188
+ return x
189
+
190
+ def Test(self, test):
191
+ targets = test[1]
192
+ self.Forward(test[0])
193
+ self.loss(targets)
194
+ self.testMetrics = {}
195
+ for metric in self.metrics:
196
+ self.testMetrics[metric] = []
197
+ self.calcMetrics(targets, self.testMetrics)
198
+ print(f"Test Loss: {self.Loss}")
199
+ for metric, values in self.testMetrics.items():
200
+ print(f"Test {metric}: {np.mean(values)}")
201
+
202
+ def accuracy(self, target):
203
+ predicted = np.argmax(self.output, axis=1)
204
+ actual = np.argmax(target, axis=1)
205
+ return np.mean(predicted == actual)
206
+
207
+ def precision(self, target):
208
+ predicted = np.argmax(self.output, axis=1)
209
+ actual = np.argmax(target, axis=1)
210
+ precisions = []
211
+ for c in range(target.shape[1]):
212
+ tp = np.sum((predicted == c) & (actual == c))
213
+ fp = np.sum((predicted == c) & (actual != c))
214
+ precisions.append(tp / (tp + fp + 1e-7))
215
+ return np.mean(precisions)
216
+
217
+ def recall(self, target):
218
+ predicted = np.argmax(self.output, axis=1)
219
+ actual = np.argmax(target, axis=1)
220
+ recalls = []
221
+ for c in range(target.shape[1]):
222
+ tp = np.sum((predicted == c) & (actual == c))
223
+ fn = np.sum((predicted != c) & (actual == c))
224
+ recalls.append(tp / (tp + fn + 1e-7))
225
+ return np.mean(recalls)
226
+
227
+ def f1(self, target):
228
+ precision = self.precision(target)
229
+ recall = self.recall(target)
230
+ return (2 * (precision * recall) / (precision + recall + 1e-7), precision, recall)
231
+
232
+ def calcMetrics(self, target, store):
233
+ for metric in self.metrics:
234
+ if metric == "F1":
235
+ f1, precision, recall = self.metrics[metric](target)
236
+ store[metric].append(float(f1))
237
+ if "Precision" in self.metrics:
238
+ store["Precision"].append(float(precision))
239
+ if "Recall" in self.metrics:
240
+ store["Recall"].append(float(recall))
241
+
242
+ elif (metric == "Precision" or metric == "Recall") and "F1" in self.metrics:
243
+ continue
244
+
245
+ else:
246
+ store[metric].append(float(self.metrics[metric](target)))
247
+
248
+ def plotMetrics(self):
249
+ import matplotlib.pyplot as plt
250
+ x = np.arange(start=0, step=1, stop= len(self.pLoss))
251
+ y = self.pLoss
252
+ plt.plot(x, y, label="Loss")
253
+ for metric in self.metrics:
254
+ plt.plot(x, self.sMV[metric], label=metric)
255
+ plt.legend()
256
+ plt.show()
257
+
258
+ def getDetails(self):
259
+ pass
260
+
261
+ def _writeReadme(self, path, content):
262
+ os.makedirs(os.path.dirname(path), exist_ok=True)
263
+ with open(path, "w") as f:
264
+ f.writelines(content)
265
+
266
+ def _writeNames(self, base):
267
+ nameMD = base + "/" + self.type + "/Names.md"
268
+ os.makedirs(os.path.dirname(nameMD), exist_ok=True)
269
+
270
+ if not os.path.exists(nameMD):
271
+ with open(nameMD, "w") as n:
272
+ n.writelines([f"{self.type} Names.\n", "\n"])
273
+
274
+ with open(nameMD, "r+") as n:
275
+ namesmd = n.readlines()
276
+
277
+ if len(namesmd) <= 2 or f"- {self.name}\n" not in namesmd[2:]:
278
+ with open(nameMD, "a") as n:
279
+ n.write(f"- {self.name}\n")
280
+
281
+ def save(self):
282
+ day = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
283
+ month = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
284
+ now = datetime.datetime.now()
285
+ cache = "Cache/" + self.type + "/" + self.name + "/" + str(now.year) +"/"+ month[now.month - 1] +"/"+ day[now.weekday()] + " " + str(now.day) +"/"+ str(now.hour) +"-"+ str(now.minute) +"-"+ str(now.second)
286
+ curr = "Outputs/" + self.type + "/" + self.name
287
+
288
+ i = 1
289
+ for layers in self.layers[1:]:
290
+ if i < len(self.layers) - 1:
291
+ tag = f"h{i}"
292
+ else:
293
+ tag = "out"
294
+
295
+ temp1_w = cache + "/" + tag + "/" + "weights.npy"
296
+ temp1_b = cache + "/" + tag + "/" + "biases.npy"
297
+ temp2_w = curr + "/" + tag + "/" + "weights.npy"
298
+ temp2_b = curr + "/" + tag + "/" + "biases.npy"
299
+
300
+ os.makedirs(os.path.dirname(temp1_w), exist_ok=True)
301
+ os.makedirs(os.path.dirname(temp1_b), exist_ok=True)
302
+ os.makedirs(os.path.dirname(temp2_w), exist_ok=True)
303
+ os.makedirs(os.path.dirname(temp2_b), exist_ok=True)
304
+
305
+ with open(temp1_w, "wb") as a:
306
+ np.save(a, layers.weights)
307
+ with open(temp2_w, "wb") as b:
308
+ np.save(b, layers.weights)
309
+ with open(temp1_b, "wb") as c:
310
+ np.save(c, layers.biases)
311
+ with open(temp2_b, "wb") as d:
312
+ np.save(d, layers.biases)
313
+
314
+ i+=1
315
+
316
+ self._writeNames("Outputs")
317
+ self._writeNames("Cache")
318
+
319
+ # details = self.getDetails()
320
+ # self._writeReadme(curr + "/README.md", details)
321
+ # self._writeReadme(cache + "/README.md", details)
322
+
323
+ def load(self, name:str=None, type:NetworkType=None):
324
+ if not name: name = self.name
325
+ if not type: type = self.type
326
+
327
+ curr = "Outputs/" + type + "/" + name
328
+ if not os.path.exists(curr):
329
+ raise FileNotFoundError(f"Model not found: {curr}")
330
+
331
+ i = 1
332
+ for layers in self.layers[1:]:
333
+ tag = f"h{i}" if i < len(self.layers) - 1 else "out"
334
+
335
+ w_path = curr + "/" + tag + "/weights.npy"
336
+ b_path = curr + "/" + tag + "/biases.npy"
337
+
338
+ with open(w_path, "rb") as f:
339
+ layers.weights = np.load(f)
340
+ with open(b_path, "rb") as f:
341
+ layers.biases = np.load(f)
342
+ i += 1
343
+
344
+ def export(self):
345
+ model_data = {}
346
+ i = 1
347
+ for layers in self.layers[1:]:
348
+ tag = f"h{i}" if i < len(self.layers) - 1 else "out"
349
+ model_data[tag] = {
350
+ "weights": layers.weights.tobytes(),
351
+ "biases": layers.biases.tobytes(),
352
+ "shape_w": layers.weights.shape,
353
+ "shape_b": layers.biases.shape
354
+ }
355
+ i += 1
356
+ return (self.type, self.name, model_data)
357
+
358
+ def import_model(self, model_data):
359
+ i = 1
360
+ for layers in self.layers[1:]:
361
+ tag = f"h{i}" if i < len(self.layers) - 1 else "out"
362
+ data = model_data[tag]
363
+
364
+ layers.weights = np.frombuffer(data["weights"]).reshape(data["shape_w"]).copy()
365
+ layers.biases = np.frombuffer(data["biases"]).reshape(data["shape_b"]).copy()
366
+ i += 1
367
+
@@ -0,0 +1,10 @@
1
+ from enum import StrEnum
2
+
3
+ class NetworkType(StrEnum):
4
+ Simple_Neural_Network = "SNN"
5
+ K_Nearest_Neighbours = "KNN"
6
+ Decision_Tree = "DT"
7
+
8
+ @property
9
+ def display_name(self):
10
+ return self.name.replace("_", " ")
@@ -0,0 +1,46 @@
1
+ import numpy as np
2
+
3
+ # Note:
4
+ # 1. testing is not implemented yet. only training is implemented. - Done
5
+ # 2. implement accuracy and other metrics. - Done
6
+ # 3. implement opencl and cuda backend for layers. - rewrite .. they keep failing at either extreme values or large datasets.
7
+ # 4. add save and reuse functionality.
8
+ # 5. maybe add a simple preprocessor extend functionality. 1/2
9
+ # 6. maybe code a simple parameter randomizer for testing and experimentation purposes.
10
+ # 7. timers for experimentation purposes. maybe make a simple class for this that can be used as a context manager.
11
+ # 8. bricked New_pc branch. wont miss it.
12
+
13
+
14
+ class Preprocessor():
15
+ def __init__(self, train:tuple, test:tuple)->None:
16
+ self.train_in = train[0]
17
+ self.train_out = train[1]
18
+ self.test_in = test[0]
19
+ self.test_out = test[1]
20
+ pass
21
+
22
+ def labels(self, into:str="onehot"):
23
+ if into == "onehot":
24
+ trainLabels = np.unique(self.train_out)
25
+ testLabels = np.unique(self.test_out)
26
+ if not np.array_equal(trainLabels, testLabels):
27
+ raise ValueError("Warning: Training and test labels are not equal.")
28
+
29
+ else:
30
+ print("Training and test labels are equal. Proceeding with one-hot encoding.")
31
+ self.train_out = np.array([[1 if label == x else 0 for x in trainLabels] for label in self.train_out])
32
+ self.test_out = np.array([[1 if label == x else 0 for x in testLabels] for label in self.test_out])
33
+ print("One-hot encoding complete.")
34
+
35
+ def features(self, into:str="normalize"):
36
+ if into == "normalize":
37
+ train_max = np.max(self.train_in)
38
+ test_max = np.max(self.test_in)
39
+
40
+
41
+ self.train_in = self.train_in / train_max
42
+ self.test_in = self.test_in / test_max
43
+ print("Normalization complete.")
44
+
45
+ def data(self):
46
+ return (self.train_in, self.train_out), (self.test_in, self.test_out)
@@ -0,0 +1,4 @@
1
+ from .Network import Network
2
+ from .Layers import Layers
3
+ from .Preprocessor import Preprocessor
4
+ from .Network_types import NetworkType
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: NN_E
3
+ Version: 0.1.4
4
+ Summary: A modular neural network framework
5
+ Project-URL: Homepage, https://github.com/diri-daniel/NN_E.git
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: numpy
8
+ Requires-Dist: pandas
9
+ Requires-Dist: datetime
10
+ Requires-Dist: matplotlib
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/NN_E/Layers.py
4
+ src/NN_E/Network.py
5
+ src/NN_E/Network_types.py
6
+ src/NN_E/Preprocessor.py
7
+ src/NN_E/__init__.py
8
+ src/NN_E.egg-info/PKG-INFO
9
+ src/NN_E.egg-info/SOURCES.txt
10
+ src/NN_E.egg-info/dependency_links.txt
11
+ src/NN_E.egg-info/requires.txt
12
+ src/NN_E.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ numpy
2
+ pandas
3
+ datetime
4
+ matplotlib
@@ -0,0 +1 @@
1
+ NN_E