pynet-ai 1.3.1__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.
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: pynet-ai
3
+ Version: 1.3.1
4
+ Summary: A Python neural network library for simple networks and educational use.
5
+ Author-email: Andru Cupala <andru@cupala.com>
6
+ License-Expression: LicenseRef-PolyForm-Noncommercial-1.0.0
7
+ Keywords: neural-network,machine-learning,deep-learning,education,python
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # PyNet
12
+
13
+ A simple educational neural network library built from scratch in Python.
14
+
15
+ PyNet focuses on understanding how neural networks work internally, including layers, activations, loss functions, backpropagation, optimizers, weight initialization, and model serialization.
16
+
17
+ ## Features
18
+
19
+ ### Layers
20
+
21
+ - Dense (fully connected) layers
22
+
23
+ ### Activations
24
+
25
+ - ReLU
26
+ - LeakyReLU
27
+ - Sigmoid
28
+ - Tanh
29
+ - Softmax
30
+ - ELU
31
+ - Swish
32
+ - GELU
33
+
34
+ ### Loss Functions
35
+
36
+ - MSELoss
37
+ - CrossEntropyLoss
38
+ - MAELoss
39
+ - HuberLoss
40
+ - BinaryCrossEntropyLoss
41
+
42
+ ### Optimizers
43
+
44
+ - SGD
45
+ - Momentum
46
+ - Adam
47
+
48
+ ### Weight Initialization
49
+
50
+ - He
51
+ - Xavier
52
+ - RandomNormal
53
+ - Zeros
54
+
55
+ ### Utilities
56
+
57
+ - Metrics
58
+ - Model saving/loading
59
+
60
+ ## Examples
61
+
62
+ PyNet includes examples demonstrating:
63
+
64
+ - Multiplication Prediction
65
+ - Regression
66
+ - XOR Classification
67
+
68
+ ## Installation
69
+
70
+ Copy PyNet files into project directory. PyNet will be on PyPi soon.
@@ -0,0 +1,60 @@
1
+ # PyNet
2
+
3
+ A simple educational neural network library built from scratch in Python.
4
+
5
+ PyNet focuses on understanding how neural networks work internally, including layers, activations, loss functions, backpropagation, optimizers, weight initialization, and model serialization.
6
+
7
+ ## Features
8
+
9
+ ### Layers
10
+
11
+ - Dense (fully connected) layers
12
+
13
+ ### Activations
14
+
15
+ - ReLU
16
+ - LeakyReLU
17
+ - Sigmoid
18
+ - Tanh
19
+ - Softmax
20
+ - ELU
21
+ - Swish
22
+ - GELU
23
+
24
+ ### Loss Functions
25
+
26
+ - MSELoss
27
+ - CrossEntropyLoss
28
+ - MAELoss
29
+ - HuberLoss
30
+ - BinaryCrossEntropyLoss
31
+
32
+ ### Optimizers
33
+
34
+ - SGD
35
+ - Momentum
36
+ - Adam
37
+
38
+ ### Weight Initialization
39
+
40
+ - He
41
+ - Xavier
42
+ - RandomNormal
43
+ - Zeros
44
+
45
+ ### Utilities
46
+
47
+ - Metrics
48
+ - Model saving/loading
49
+
50
+ ## Examples
51
+
52
+ PyNet includes examples demonstrating:
53
+
54
+ - Multiplication Prediction
55
+ - Regression
56
+ - XOR Classification
57
+
58
+ ## Installation
59
+
60
+ Copy PyNet files into project directory. PyNet will be on PyPi soon.
@@ -0,0 +1,10 @@
1
+ from .network import Network
2
+ from .layer import Layer, Dense
3
+ from .initialization import Initializer, RandomNormal, Zeros, Xavier, He
4
+ from .activation import Activation, ReLU, LeakyReLU, Sigmoid, Softmax, Tanh, ELU, Swish, GELU
5
+ from .loss import Loss, MSELoss, CrossEntropyLoss, MAELoss, HuberLoss, BinaryCrossEntropyLoss
6
+ from .optimizer import Optimizer, SGD, Momentum, Adam
7
+ from .metrics import accuracy, mean_squared_error, mean_absolute_error
8
+ from .serialization import save, load
9
+
10
+ __version__ = "1.3.1"
@@ -0,0 +1,134 @@
1
+ import math
2
+
3
+
4
+ class Activation:
5
+
6
+ def forward(self, x):
7
+ raise NotImplementedError
8
+
9
+ def backward(self, gradient):
10
+ raise NotImplementedError
11
+
12
+
13
+ class ReLU(Activation):
14
+
15
+ def __init__(self):
16
+ self.input = None
17
+
18
+ def forward(self, x):
19
+ self.input = x
20
+ return [max(0, value) for value in x]
21
+
22
+ def backward(self, gradient):
23
+ return [g if value > 0 else 0 for g, value in zip(gradient, self.input)]
24
+
25
+
26
+ class LeakyReLU(Activation):
27
+
28
+ def __init__(self, alpha=0.01):
29
+ self.alpha = alpha
30
+ self.input = None
31
+
32
+ def forward(self, x):
33
+ self.input = x
34
+ return [value if value > 0 else self.alpha * value for value in x]
35
+
36
+ def backward(self, gradient):
37
+ return [g if value > 0 else g * self.alpha for g, value in zip(gradient, self.input)]
38
+
39
+
40
+ class Sigmoid(Activation):
41
+
42
+ def __init__(self):
43
+ self.output = None
44
+
45
+ def forward(self, x):
46
+ self.output = []
47
+ for value in x:
48
+ value = max(min(value, 50), -50)
49
+ self.output.append(1 / (1 + math.exp(-value)))
50
+ return self.output
51
+
52
+ def backward(self, gradient):
53
+ return [g * value * (1 - value) for g, value in zip(gradient, self.output)]
54
+
55
+
56
+ class Softmax(Activation):
57
+
58
+ def __init__(self):
59
+ self.output = None
60
+
61
+ def forward(self, x):
62
+ maximum = max(x)
63
+ exp_values = [math.exp(value - maximum) for value in x]
64
+ total = sum(exp_values)
65
+ self.output = [value / total for value in exp_values]
66
+ return self.output
67
+
68
+ def backward(self, gradient):
69
+ return gradient
70
+
71
+
72
+ class Tanh(Activation):
73
+
74
+ def __init__(self):
75
+ self.output = None
76
+
77
+ def forward(self, x):
78
+ self.output = [math.tanh(value) for value in x]
79
+ return self.output
80
+
81
+ def backward(self, gradient):
82
+ return [g * (1 - value**2) for g, value in zip(gradient, self.output)]
83
+
84
+
85
+ class ELU(Activation):
86
+
87
+ def __init__(self, alpha=1.0):
88
+ self.alpha = alpha
89
+ self.input = None
90
+
91
+ def forward(self, x):
92
+ self.input = x
93
+ return [value if value > 0 else self.alpha * (math.exp(value) - 1) for value in x]
94
+
95
+ def backward(self, gradient):
96
+ return [g if value > 0 else g * self.alpha * math.exp(value) for g, value in zip(gradient, self.input)]
97
+
98
+
99
+ class Swish(Activation):
100
+
101
+ def __init__(self):
102
+ self.input = None
103
+ self.output = None
104
+
105
+ def forward(self, x):
106
+ self.input = x
107
+ self.output = [value / (1 + math.exp(-value)) for value in x]
108
+ return self.output
109
+
110
+ def backward(self, gradient):
111
+ result = []
112
+ for g, value in zip(gradient, self.input):
113
+ sigmoid = 1 / (1 + math.exp(-value))
114
+ derivative = sigmoid + value * sigmoid * (1 - sigmoid)
115
+ result.append(g * derivative)
116
+ return result
117
+
118
+
119
+ class GELU(Activation):
120
+
121
+ def __init__(self):
122
+ self.input = None
123
+
124
+ def forward(self, x):
125
+ self.input = x
126
+ return [0.5 * value * (1 + math.tanh(math.sqrt(2 / math.pi) * (value + 0.044715 * value**3))) for value in x]
127
+
128
+ def backward(self, gradient):
129
+ result = []
130
+ for g, value in zip(gradient, self.input):
131
+ tanh_value = math.tanh(math.sqrt(2 / math.pi) * (value + 0.044715 * value**3))
132
+ derivative = 0.5 * (1 + tanh_value) + 0.5 * value * (1 - tanh_value**2) * math.sqrt(2 / math.pi) * (1 + 3 * 0.044715 * value**2)
133
+ result.append(g * derivative)
134
+ return result
@@ -0,0 +1,38 @@
1
+ import math
2
+ import random
3
+
4
+
5
+ class Initializer:
6
+
7
+ def initialize(self, inputs, outputs):
8
+ raise NotImplementedError
9
+
10
+
11
+ class RandomNormal(Initializer):
12
+
13
+ def __init__(self, mean=0.0, std=1.0):
14
+ self.mean = mean
15
+ self.std = std
16
+
17
+ def initialize(self, inputs, outputs):
18
+ return [[random.gauss(self.mean, self.std) for _ in range(inputs)] for _ in range(outputs)]
19
+
20
+
21
+ class Zeros(Initializer):
22
+
23
+ def initialize(self, inputs, outputs):
24
+ return [[0.0 for _ in range(inputs)] for _ in range(outputs)]
25
+
26
+
27
+ class Xavier(Initializer):
28
+
29
+ def initialize(self, inputs, outputs):
30
+ scale = math.sqrt(1 / inputs)
31
+ return [[random.gauss(0, scale) for _ in range(inputs)] for _ in range(outputs)]
32
+
33
+
34
+ class He(Initializer):
35
+
36
+ def initialize(self, inputs, outputs):
37
+ scale = math.sqrt(2 / inputs)
38
+ return [[random.gauss(0, scale) for _ in range(inputs)] for _ in range(outputs)]
@@ -0,0 +1,73 @@
1
+ import random
2
+ from .initialization import He
3
+
4
+
5
+ class Layer:
6
+
7
+ def forward(self, x):
8
+ raise NotImplementedError
9
+
10
+ def backward(self, gradient):
11
+ raise NotImplementedError
12
+
13
+ def zero_grad(self):
14
+ pass
15
+
16
+ def get_config(self):
17
+ return {}
18
+
19
+ def get_parameters(self):
20
+ return {}
21
+
22
+ def set_parameters(self, parameters):
23
+ pass
24
+
25
+
26
+ class Dense(Layer):
27
+
28
+ def __init__(self, inputs, outputs, initializer=None):
29
+ self.inputs = inputs
30
+ self.outputs = outputs
31
+ if initializer is None:
32
+ initializer = He()
33
+ self.initializer = initializer
34
+ self.weights = initializer.initialize(inputs, outputs)
35
+ self.biases = [random.uniform(-0.01, 0.01) for _ in range(outputs)]
36
+ self.input = None
37
+ self.weight_gradients = [[0.0 for _ in range(inputs)] for _ in range(outputs)]
38
+ self.bias_gradients = [0.0 for _ in range(outputs)]
39
+
40
+ def forward(self, x):
41
+ self.input = x
42
+ output = []
43
+ for neuron_weights, bias in zip(self.weights, self.biases):
44
+ total = bias
45
+ for weight, value in zip(neuron_weights, x):
46
+ total += weight * value
47
+ output.append(total)
48
+ return output
49
+
50
+ def backward(self, gradient):
51
+ input_gradient = [0.0 for _ in range(self.inputs)]
52
+ for neuron in range(self.outputs):
53
+ self.bias_gradients[neuron] += gradient[neuron]
54
+ for previous in range(self.inputs):
55
+ self.weight_gradients[neuron][previous] += gradient[neuron] * self.input[previous]
56
+ input_gradient[previous] += self.weights[neuron][previous] * gradient[neuron]
57
+ return input_gradient
58
+
59
+ def zero_grad(self):
60
+ for neuron in range(self.outputs):
61
+ self.bias_gradients[neuron] = 0.0
62
+ for previous in range(self.inputs):
63
+ self.weight_gradients[neuron][previous] = 0.0
64
+
65
+ def get_config(self):
66
+ return {"inputs": self.inputs, "outputs": self.outputs, "initializer": self.initializer.__class__.__name__}
67
+
68
+ def get_parameters(self):
69
+ return {"weights": self.weights, "biases": self.biases}
70
+
71
+ def set_parameters(self, parameters):
72
+ self.weights = parameters["weights"]
73
+ self.biases = parameters["biases"]
@@ -0,0 +1,97 @@
1
+ import math
2
+
3
+
4
+ class Loss:
5
+
6
+ def forward(self, prediction, target):
7
+ raise NotImplementedError
8
+
9
+ def backward(self, prediction, target):
10
+ raise NotImplementedError
11
+
12
+
13
+ class MSELoss(Loss):
14
+
15
+ def forward(self, prediction, target):
16
+ total = 0.0
17
+ for p, t in zip(prediction, target):
18
+ total += (p - t) ** 2
19
+ return total / len(target)
20
+
21
+ def backward(self, prediction, target):
22
+ return [2 * (p - t) / len(target) for p, t in zip(prediction, target)]
23
+
24
+
25
+ class CrossEntropyLoss(Loss):
26
+
27
+ def forward(self, prediction, target):
28
+ total = 0.0
29
+ for p, t in zip(prediction, target):
30
+ total -= t * math.log(max(p, 1e-08))
31
+ return total
32
+
33
+ def backward(self, prediction, target):
34
+ return [p - t for p, t in zip(prediction, target)]
35
+
36
+
37
+ class MAELoss(Loss):
38
+
39
+ def forward(self, prediction, target):
40
+ total = 0.0
41
+ for p, t in zip(prediction, target):
42
+ total += abs(p - t)
43
+ return total / len(target)
44
+
45
+ def backward(self, prediction, target):
46
+ gradients = []
47
+ for p, t in zip(prediction, target):
48
+ if p > t:
49
+ gradients.append(1 / len(target))
50
+ elif p < t:
51
+ gradients.append(-1 / len(target))
52
+ else:
53
+ gradients.append(0)
54
+ return gradients
55
+
56
+
57
+ class HuberLoss(Loss):
58
+
59
+ def __init__(self, delta=1.0):
60
+ self.delta = delta
61
+
62
+ def forward(self, prediction, target):
63
+ total = 0.0
64
+ for p, t in zip(prediction, target):
65
+ error = abs(p - t)
66
+ if error <= self.delta:
67
+ total += 0.5 * error**2
68
+ else:
69
+ total += self.delta * (error - 0.5 * self.delta)
70
+ return total / len(target)
71
+
72
+ def backward(self, prediction, target):
73
+ gradients = []
74
+ for p, t in zip(prediction, target):
75
+ error = p - t
76
+ if abs(error) <= self.delta:
77
+ gradients.append(error / len(target))
78
+ else:
79
+ gradients.append(self.delta * (1 if error > 0 else -1) / len(target))
80
+ return gradients
81
+
82
+
83
+ class BinaryCrossEntropyLoss(Loss):
84
+
85
+ def forward(self, prediction, target):
86
+ total = 0.0
87
+ for p, t in zip(prediction, target):
88
+ p = max(min(p, 1 - 1e-08), 1e-08)
89
+ total += -t * math.log(p) - (1 - t) * math.log(1 - p)
90
+ return total / len(target)
91
+
92
+ def backward(self, prediction, target):
93
+ gradients = []
94
+ for p, t in zip(prediction, target):
95
+ p = max(min(p, 1 - 1e-08), 1e-08)
96
+ gradients.append((p - t) / (p * (1 - p) * len(target)))
97
+ return gradients
@@ -0,0 +1,30 @@
1
+ def accuracy(predictions, targets):
2
+ if len(targets) == 0:
3
+ raise ValueError("targets cannot be empty")
4
+ correct = 0
5
+ for prediction, target in zip(predictions, targets):
6
+ if isinstance(prediction, list):
7
+ prediction = prediction.index(max(prediction))
8
+ if isinstance(target, list):
9
+ target = target.index(max(target))
10
+ if prediction == target:
11
+ correct += 1
12
+ return correct / len(targets)
13
+
14
+
15
+ def mean_squared_error(predictions, targets):
16
+ if len(targets) == 0:
17
+ raise ValueError("targets cannot be empty")
18
+ total = 0.0
19
+ for prediction, target in zip(predictions, targets):
20
+ total += (prediction - target) ** 2
21
+ return total / len(targets)
22
+
23
+
24
+ def mean_absolute_error(predictions, targets):
25
+ if len(targets) == 0:
26
+ raise ValueError("targets cannot be empty")
27
+ total = 0.0
28
+ for prediction, target in zip(predictions, targets):
29
+ total += abs(prediction - target)
30
+ return total / len(targets)
@@ -0,0 +1,66 @@
1
+ class Network:
2
+
3
+ def __init__(self, *modules):
4
+ self.modules = list(modules)
5
+ self.training = True
6
+
7
+ def forward(self, x):
8
+ for module in self.modules:
9
+ x = module.forward(x)
10
+ return x
11
+
12
+ def backward(self, gradient):
13
+ for module in reversed(self.modules):
14
+ gradient = module.backward(gradient)
15
+ return gradient
16
+
17
+ def train(self, inputs, targets, loss, optimizer, epochs=1):
18
+ for _ in range(epochs):
19
+ for x, y in zip(inputs, targets):
20
+ prediction = self.forward(x)
21
+ gradient = loss.backward(prediction, y)
22
+ self.backward(gradient)
23
+ optimizer.step(self.modules)
24
+
25
+ def train_batch(self, inputs, targets, loss, optimizer):
26
+ self.zero_grad()
27
+ batch_size = len(inputs)
28
+ for x, y in zip(inputs, targets):
29
+ prediction = self.forward(x)
30
+ gradient = loss.backward(prediction, y)
31
+ self.backward(gradient)
32
+ for module in self.modules:
33
+ if hasattr(module, "weight_gradients"):
34
+ for neuron in range(len(module.weight_gradients)):
35
+ module.bias_gradients[neuron] /= batch_size
36
+ for previous in range(len(module.weight_gradients[neuron])):
37
+ module.weight_gradients[neuron][previous] /= batch_size
38
+ optimizer.step(self.modules)
39
+
40
+ def zero_grad(self):
41
+ for module in self.modules:
42
+ if hasattr(module, "zero_grad"):
43
+ module.zero_grad()
44
+
45
+ def predict(self, x):
46
+ return self.forward(x)
47
+
48
+ def train_mode(self):
49
+ self.training = True
50
+
51
+ def eval_mode(self):
52
+ self.training = False
53
+
54
+ def get_config(self):
55
+ return {"modules": [{"type": module.__class__.__name__, "config": module.get_config(), "parameters": module.get_parameters()} for module in self.modules]}
56
+
57
+ def save(self, path):
58
+ from .serialization import save
59
+
60
+ save(self, path)
61
+
62
+ @staticmethod
63
+ def load(path):
64
+ from .serialization import load
65
+
66
+ return load(path)
@@ -0,0 +1,87 @@
1
+ import math
2
+
3
+
4
+ class Optimizer:
5
+
6
+ def step(self, modules):
7
+ raise NotImplementedError
8
+
9
+
10
+ class SGD(Optimizer):
11
+
12
+ def __init__(self, learning_rate=0.01):
13
+ self.learning_rate = learning_rate
14
+
15
+ def step(self, modules):
16
+ for module in modules:
17
+ if hasattr(module, "weights"):
18
+ for neuron in range(len(module.weights)):
19
+ for previous in range(len(module.weights[neuron])):
20
+ module.weights[neuron][previous] -= self.learning_rate * module.weight_gradients[neuron][previous]
21
+ if hasattr(module, "biases"):
22
+ for neuron in range(len(module.biases)):
23
+ module.biases[neuron] -= self.learning_rate * module.bias_gradients[neuron]
24
+ if hasattr(module, "zero_grad"):
25
+ module.zero_grad()
26
+
27
+
28
+ class Momentum(Optimizer):
29
+
30
+ def __init__(self, learning_rate=0.01, momentum=0.9):
31
+ self.learning_rate = learning_rate
32
+ self.momentum = momentum
33
+ self.velocities = {}
34
+
35
+ def step(self, modules):
36
+ for module in modules:
37
+ if not hasattr(module, "weights"):
38
+ continue
39
+ module_id = id(module)
40
+ if module_id not in self.velocities:
41
+ self.velocities[module_id] = {"weights": [[0.0 for _ in row] for row in module.weights], "biases": [0.0 for _ in module.biases]}
42
+ velocity = self.velocities[module_id]
43
+ for neuron in range(len(module.weights)):
44
+ for previous in range(len(module.weights[neuron])):
45
+ velocity["weights"][neuron][previous] = self.momentum * velocity["weights"][neuron][previous] + module.weight_gradients[neuron][previous]
46
+ module.weights[neuron][previous] -= self.learning_rate * velocity["weights"][neuron][previous]
47
+ for neuron in range(len(module.biases)):
48
+ velocity["biases"][neuron] = self.momentum * velocity["biases"][neuron] + module.bias_gradients[neuron]
49
+ module.biases[neuron] -= self.learning_rate * velocity["biases"][neuron]
50
+ module.zero_grad()
51
+
52
+
53
+ class Adam(Optimizer):
54
+
55
+ def __init__(self, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08):
56
+ self.learning_rate = learning_rate
57
+ self.beta1 = beta1
58
+ self.beta2 = beta2
59
+ self.epsilon = epsilon
60
+ self.steps = 0
61
+ self.moments = {}
62
+
63
+ def step(self, modules):
64
+ self.steps += 1
65
+ for module in modules:
66
+ if not hasattr(module, "weights"):
67
+ continue
68
+ module_id = id(module)
69
+ if module_id not in self.moments:
70
+ self.moments[module_id] = {"m_weights": [[0.0 for _ in row] for row in module.weights], "v_weights": [[0.0 for _ in row] for row in module.weights], "m_biases": [0.0 for _ in module.biases], "v_biases": [0.0 for _ in module.biases]}
71
+ state = self.moments[module_id]
72
+ for neuron in range(len(module.weights)):
73
+ for previous in range(len(module.weights[neuron])):
74
+ gradient = module.weight_gradients[neuron][previous]
75
+ state["m_weights"][neuron][previous] = self.beta1 * state["m_weights"][neuron][previous] + (1 - self.beta1) * gradient
76
+ state["v_weights"][neuron][previous] = self.beta2 * state["v_weights"][neuron][previous] + (1 - self.beta2) * gradient**2
77
+ m_hat = state["m_weights"][neuron][previous] / (1 - self.beta1**self.steps)
78
+ v_hat = state["v_weights"][neuron][previous] / (1 - self.beta2**self.steps)
79
+ module.weights[neuron][previous] -= self.learning_rate * m_hat / (math.sqrt(v_hat) + self.epsilon)
80
+ for neuron in range(len(module.biases)):
81
+ gradient = module.bias_gradients[neuron]
82
+ state["m_biases"][neuron] = self.beta1 * state["m_biases"][neuron] + (1 - self.beta1) * gradient
83
+ state["v_biases"][neuron] = self.beta2 * state["v_biases"][neuron] + (1 - self.beta2) * gradient**2
84
+ m_hat = state["m_biases"][neuron] / (1 - self.beta1**self.steps)
85
+ v_hat = state["v_biases"][neuron] / (1 - self.beta2**self.steps)
86
+ module.biases[neuron] -= self.learning_rate * m_hat / (math.sqrt(v_hat) + self.epsilon)
87
+ module.zero_grad()
@@ -0,0 +1,20 @@
1
+ import json
2
+
3
+
4
+ def save(network, path):
5
+ data = {"modules": []}
6
+ for module in network.modules:
7
+ item = {"type": module.__class__.__name__, "config": {}, "parameters": {}}
8
+ if hasattr(module, "get_config"):
9
+ item["config"] = module.get_config()
10
+ if hasattr(module, "get_parameters"):
11
+ item["parameters"] = module.get_parameters()
12
+ data["modules"].append(item)
13
+ with open(path, "w") as file:
14
+ json.dump(data, file, indent=4)
15
+
16
+
17
+ def load(path):
18
+ with open(path) as file:
19
+ data = json.load(file)
20
+ return data
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: pynet-ai
3
+ Version: 1.3.1
4
+ Summary: A Python neural network library for simple networks and educational use.
5
+ Author-email: Andru Cupala <andru@cupala.com>
6
+ License-Expression: LicenseRef-PolyForm-Noncommercial-1.0.0
7
+ Keywords: neural-network,machine-learning,deep-learning,education,python
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # PyNet
12
+
13
+ A simple educational neural network library built from scratch in Python.
14
+
15
+ PyNet focuses on understanding how neural networks work internally, including layers, activations, loss functions, backpropagation, optimizers, weight initialization, and model serialization.
16
+
17
+ ## Features
18
+
19
+ ### Layers
20
+
21
+ - Dense (fully connected) layers
22
+
23
+ ### Activations
24
+
25
+ - ReLU
26
+ - LeakyReLU
27
+ - Sigmoid
28
+ - Tanh
29
+ - Softmax
30
+ - ELU
31
+ - Swish
32
+ - GELU
33
+
34
+ ### Loss Functions
35
+
36
+ - MSELoss
37
+ - CrossEntropyLoss
38
+ - MAELoss
39
+ - HuberLoss
40
+ - BinaryCrossEntropyLoss
41
+
42
+ ### Optimizers
43
+
44
+ - SGD
45
+ - Momentum
46
+ - Adam
47
+
48
+ ### Weight Initialization
49
+
50
+ - He
51
+ - Xavier
52
+ - RandomNormal
53
+ - Zeros
54
+
55
+ ### Utilities
56
+
57
+ - Metrics
58
+ - Model saving/loading
59
+
60
+ ## Examples
61
+
62
+ PyNet includes examples demonstrating:
63
+
64
+ - Multiplication Prediction
65
+ - Regression
66
+ - XOR Classification
67
+
68
+ ## Installation
69
+
70
+ Copy PyNet files into project directory. PyNet will be on PyPi soon.
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ pynet/__init__.py
4
+ pynet/activation.py
5
+ pynet/initialization.py
6
+ pynet/layer.py
7
+ pynet/loss.py
8
+ pynet/metrics.py
9
+ pynet/network.py
10
+ pynet/optimizer.py
11
+ pynet/serialization.py
12
+ pynet_ai.egg-info/PKG-INFO
13
+ pynet_ai.egg-info/SOURCES.txt
14
+ pynet_ai.egg-info/dependency_links.txt
15
+ pynet_ai.egg-info/top_level.txt
16
+ tests/test.py
@@ -0,0 +1 @@
1
+ pynet
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Required
7
+ name = "pynet-ai" # Must be unique on PyPI
8
+ version = "1.3.1"
9
+ description = "A Python neural network library for simple networks and educational use."
10
+ readme = "README.md"
11
+ requires-python = ">=3.10"
12
+
13
+ # Licensing
14
+ license = "LicenseRef-PolyForm-Noncommercial-1.0.0"
15
+ license-files = ["LICENSE.md"]
16
+
17
+ # Author information
18
+ authors = [
19
+ { name = "Andru Cupala", email = "andru@cupala.com" }
20
+ ]
21
+
22
+ # Keywords help with discoverability
23
+ keywords = [
24
+ "neural-network",
25
+ "machine-learning",
26
+ "deep-learning",
27
+ "education",
28
+ "python",
29
+ ]
30
+
31
+ # Runtime dependencies
32
+ dependencies = []
33
+
34
+ [tool.setuptools]
35
+ include-package-data = true
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["pynet*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,80 @@
1
+ from pynet import Network, Dense, ReLU, LeakyReLU, Sigmoid, Tanh, ELU, Swish, GELU, Softmax, MSELoss, CrossEntropyLoss, MAELoss, HuberLoss, BinaryCrossEntropyLoss, SGD, Momentum, Adam, accuracy, mean_squared_error, mean_absolute_error
2
+ from pynet.serialization import save, load
3
+
4
+
5
+ def test_activations():
6
+ activations = [ReLU(), LeakyReLU(), Sigmoid(), Tanh(), ELU(), Swish(), GELU(), Softmax()]
7
+ values = [-2.0, -1.0, 0.0, 1.0, 2.0]
8
+ for activation in activations:
9
+ output = activation.forward(values)
10
+ gradient = activation.backward([1.0 for _ in output])
11
+ assert len(output) == len(values)
12
+ assert len(gradient) == len(values)
13
+ print(activation.__class__.__name__, "OK")
14
+
15
+
16
+ def test_losses():
17
+ prediction = [0.2, 0.8]
18
+ target = [0, 1]
19
+ losses = [MSELoss(), CrossEntropyLoss(), MAELoss(), HuberLoss(), BinaryCrossEntropyLoss()]
20
+ for loss in losses:
21
+ value = loss.forward(prediction, target)
22
+ gradient = loss.backward(prediction, target)
23
+ assert isinstance(value, float)
24
+ assert len(gradient) == len(target)
25
+ print(loss.__class__.__name__, "OK")
26
+
27
+
28
+ def create_network():
29
+ return Network(Dense(2, 8), ReLU(), Dense(8, 4), Tanh(), Dense(4, 1), Sigmoid())
30
+
31
+
32
+ def test_training():
33
+ inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
34
+ targets = [[0], [1], [1], [0]]
35
+ optimizers = [SGD(0.1), Momentum(0.1), Adam(0.01)]
36
+ for optimizer in optimizers:
37
+ network = create_network()
38
+ network.train(inputs, targets, BinaryCrossEntropyLoss(), optimizer, epochs=100)
39
+ output = network.predict([0, 1])
40
+ assert len(output) == 1
41
+ print(optimizer.__class__.__name__, "OK")
42
+
43
+
44
+ def test_metrics():
45
+ predictions = [[1, 0], [0, 1], [1, 0]]
46
+ targets = [[1, 0], [0, 1], [0, 1]]
47
+ acc = accuracy(predictions, targets)
48
+ assert 0 <= acc <= 1
49
+ mse = mean_squared_error([1, 2, 3], [1, 2, 4])
50
+ mae = mean_absolute_error([1, 2, 3], [1, 2, 4])
51
+ print("Accuracy:", acc)
52
+ print("MSE:", mse)
53
+ print("MAE:", mae)
54
+ print("Metrics OK")
55
+
56
+
57
+ def test_saving():
58
+ network = create_network()
59
+ save(network, "test_model.json")
60
+ loaded = load("test_model.json")
61
+ assert loaded is not None
62
+ print("Saving/loading OK")
63
+
64
+
65
+ def main():
66
+ print("\n--- Activations ---")
67
+ test_activations()
68
+ print("\n--- Losses ---")
69
+ test_losses()
70
+ print("\n--- Training ---")
71
+ test_training()
72
+ print("\n--- Metrics ---")
73
+ test_metrics()
74
+ print("\n--- Saving ---")
75
+ test_saving()
76
+ print("\nAll V1.1 tests passed")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()