refactor(network.py): annotate Neuron constructor (comments other functions)

This commit is contained in:
2026-01-18 18:26:06 +01:00
parent 17be224d25
commit 4cca66db99

View File

@@ -1,6 +1,7 @@
import math import math
import random import random
def sigmoid(x: float) -> float: def sigmoid(x: float) -> float:
return 1 / (1 + math.exp(-x)) return 1 / (1 + math.exp(-x))
@@ -9,123 +10,154 @@ def sigmoid_deriv(x: float) -> float:
y: float = sigmoid(x) y: float = sigmoid(x)
return y * (1 - y) return y * (1 - y)
# neuron class
class Neuron: class Neuron:
""" def __init__(self, input_size: int) -> None:
z : linear combination of inputs and weights plus bias (pre-activation)
y : output of the activation function (sigmoid(z))
w : list of weights, one for each input
"""
def __init__(self, isize):
# number of inputs to this neuron
self.isize = isize
# importance to each input
self.weight = [random.uniform(-1, 1) for _ in range(self.isize)]
# importance of the neuron
self.bias = random.uniform(-1, 1)
def forward(self, x, activate=True):
""" """
x : list of input values to the neuron Set up the neuron's parameters (weights, and bias)
:param input_size: Number of incomming inputs (must be > 0)
""" """
# computes the weighted sum of inputs and add the bias # Store input dimensions for structural consistency
self.z = sum(w * xi for w, xi in zip(self.weight, x)) + self.bias self._input_size: int = input_size
# normalize the output between 0 and 1 if activate
last_output = sigmoid(self.z) if activate else self.z
return last_output # Scale each input influence using random weights.
self._weight: list[float] = [
random.uniform(-1., 1.) for _ in range(input_size)
]
# adjust weight and bias of neuron # Initialize a shift to the activation threshold with a random bias
def backward(self, x, dcost_dy, learning_rate): self._bias: float = random.uniform(-1., 1.)
"""
x : list of input values to the neuron
dcost_dy : derivate of the cost function `(2 * (output - target))`
learning_rate : learning factor (adjust the speed of weight/bias change during training)
weight -= learning_rate * dC/dy * dy/dz * dz/dw def __repr__(self) -> str:
bias -= learning_rate * dC/dy * dy/dz * dz/db jmp: int = int(math.sqrt(self._input_size))
""" text: list[str] = []
# dy/dz: derivate of the sigmoid activation
dy_dz = sigmoid_deriv(self.z)
# dz/dw = x
dz_dw = x
assert len(dz_dw) >= self.isize, "too many value for input size" for i in range(0, self._input_size, jmp):
line: str = str.join("", str(self._weight[i: (i + jmp)]))
text.append(line)
# dz/db = 1 return f"weight:\n{str.join("\n", text)}\nbias: {self._bias}"
dz_db = 1
for i in range(self.isize): # # neuron class
# update each weight `weight -= learning_rate * dC/dy * dy/dz * x_i` # class Neuron:
self.weight[i] -= learning_rate * dcost_dy * dy_dz * dz_dw[i] # """
# z : linear combination of inputs and weights plus bias (pre-activation)
# y : output of the activation function (sigmoid(z))
# w : list of weights, one for each input
# """
# def __init__(self, input_size):
# # number of inputs to this neuron
# self._input_size = input_size
# # importance to each input
# self._weight = [random.uniform(-1, 1) for _ in range(self._input_size)]
# # importance of the neuron
# self._bias = random.uniform(-1, 1)
# update bias: bias -= learning_rate * dC/dy * dy/dz * dz/db # def forward(self, x, activate=True):
self.bias -= learning_rate * dcost_dy * dy_dz * dz_db # """
# x : list of input values to the neuron
# """
# # computes the weighted sum of inputs and add the bias
# self._z = sum(w * xi for w, xi in zip(self.weight, x)) + self.bias
# # normalize the output between 0 and 1 if activate
# last_output = sigmoid(self.z) if activate else self.z
# return gradient vector len(input) dimension # return last_output
return [dcost_dy * dy_dz * w for w in self.weight]
# # adjust weight and bias of neuron
# def backward(self, x, dcost_dy, learning_rate):
# """
# x : list of input values to the neuron
# dcost_dy : derivate of the cost function `(2 * (output - target))`
# learning_rate : learning factor (adjust the speed of weight/bias change during training)
class Layer: # weight -= learning_rate * dC/dy * dy/dz * dz/dw
def __init__(self, input_size, output_size): # bias -= learning_rate * dC/dy * dy/dz * dz/db
""" # """
input_size : size of each neuron input # # dy/dz: derivate of the sigmoid activation
output_size : size of neurons # dy_dz = sigmoid_deriv(self.z)
""" # # dz/dw = x
self.size = output_size # dz_dw = x
# list of neurons
self.neurons = [Neuron(input_size) for _ in range(output_size)]
def forward(self, inputs, activate=True): # assert len(dz_dw) >= self.isize, "too many value for input size"
self.inputs = inputs
# give the same inputs to each neuron in the layer
return [neuron.forward(inputs, activate) for neuron in self.neurons]
# adjust weight and bias of the layer (all neurons) # # dz/db = 1
def backward(self, dcost_dy_list, learning_rate=0.1): # dz_db = 1
# init layer gradient vector len(input) dimention
input_gradients = [0.0] * len(self.inputs)
for i, neuron in enumerate(self.neurons): # for i in range(self.isize):
dcost_dy = dcost_dy_list[i] # # update each weight `weight -= learning_rate * dC/dy * dy/dz * x_i`
grad_to_input = neuron.backward(self.inputs, dcost_dy, learning_rate) # self.weight[i] -= learning_rate * dcost_dy * dy_dz * dz_dw[i]
# accumulate the input gradients from all neurons # # update bias: bias -= learning_rate * dC/dy * dy/dz * dz/db
for j in range(len(grad_to_input)): # self.bias -= learning_rate * dcost_dy * dy_dz * dz_db
input_gradients[j] += grad_to_input[j]
# return layer gradient # # return gradient vector len(weight) dimension
return input_gradients # return [dcost_dy * dy_dz * w for w in self.weight]
class NeuralNetwork: # def __repr__(self):
def __init__(self, layer_size): # pass
self.layers = [Layer(layer_size[i], layer_size[i+1]) for i in range(len(layer_size) - 1)]
def forward(self, inputs): # class Layer:
output = inputs # def __init__(self, input_size, output_size):
for i, layer in enumerate(self.layers): # """
activate = (i != len(self.layers) - 1) # deactivate sigmoid latest neuron # input_size : size of each neuron input
output = layer.forward(output, activate=activate) # output_size : size of neurons
return output # """
# self.size = output_size
# # list of neurons
# self.neurons = [Neuron(input_size) for _ in range(output_size)]
def backward(self, inputs, targets, learning_rate=0.1): # def forward(self, inputs, activate=True):
""" # self.inputs = inputs
target must be a list with the same length that the final layer # # give the same inputs to each neuron in the layer
input # return [neuron.forward(inputs, activate) for neuron in self.neurons]
"""
output = self.forward(inputs)
# computes the initial gradient of the cost function for each neuron # # adjust weight and bias of the layer (all neurons)
# by using Mean Squared Error's derivate: dC/dy = 2 * (output - target) # def backward(self, dcost_dy_list, learning_rate=0.1):
dcost_dy_list = [2 * (o - t) for o, t in zip(output, targets)] # # init layer gradient vector len(input) dimention
# input_gradients = [0.0] * len(self.inputs)
grad = dcost_dy_list # for i, neuron in enumerate(self.neurons):
for layer in reversed(self.layers): # dcost_dy = dcost_dy_list[i]
# backpropagate the gradient of the layer to update weights and biases # grad_to_input = neuron.backward(self.inputs, dcost_dy, learning_rate)
grad = layer.backward(grad, learning_rate)
# return final gradient # # accumulate the input gradients from all neurons
return grad # for j in range(len(grad_to_input)):
# input_gradients[j] += grad_to_input[j]
if __name__ == "__main__": # # return layer gradient
print("you might want to run main.py instead of network.py") # return input_gradients
# class NeuralNetwork:
# def __init__(self, layer_size):
# self.layers = [Layer(layer_size[i], layer_size[i+1]) for i in range(len(layer_size) - 1)]
# def forward(self, inputs):
# output = inputs
# for i, layer in enumerate(self.layers):
# activate = (i != len(self.layers) - 1) # deactivate sigmoid latest neuron
# output = layer.forward(output, activate=activate)
# return output
# def backward(self, inputs, targets, learning_rate=0.1):
# """
# target must be a list with the same length that the final layer
# input
# """
# output = self.forward(inputs)
# # computes the initial gradient of the cost function for each neuron
# # by using Mean Squared Error's derivate: dC/dy = 2 * (output - target)
# dcost_dy_list = [2 * (o - t) for o, t in zip(output, targets)]
# grad = dcost_dy_list
# for layer in reversed(self.layers):
# # backpropagate the gradient of the layer to update weights and biases
# grad = layer.backward(grad, learning_rate)
# # return final gradient
# return grad
# if __name__ == "__main__":
# print("you might want to run main.py instead of network.py")