import numpy as np class SimpleNN: def __init__(self, architecture): self.architecture = architecture self.weights = [] self.biases = [] # Initialize weights and biases np.random.seed(99) for i in range(len(architecture) - 1): self.weights.append(np.random.uniform( low=-1, high=1, size=(architecture[i], architecture[i+1]) )) self.biases.append(np.zeros((1, architecture[i+1]))) @staticmethod def relu(x): return np.maximum(0, x) @staticmethod def relu_as_weights(x): return (x > 0).astype(float) def forward(self, X): perceptron_inputs = [X] perceptron_outputs = [] for W, b in zip(self.weights, self.biases): Z = np.dot(perceptron_inputs[-1], W) + b perceptron_outputs.append(Z) if W is self.weights[-1]: # Last layer (output) A = Z # Linear output for regression else: A = self.relu(Z) perceptron_inputs.append(A) return perceptron_inputs, perceptron_outputs def backward(self, perceptron_inputs, perceptron_outputs, target): weight_changes = [] bias_changes = [] m = len(target) dA = perceptron_inputs[-1] - target.reshape(-1, 1) # Output layer gradient for i in reversed(range(len(self.weights))): dZ = dA if i == len(self.weights) - 1 else dA * self.relu_as_weights(perceptron_outputs[i]) dW = np.dot(perceptron_inputs[i].T, dZ) / m db = np.sum(dZ, axis=0, keepdims=True) / m weight_changes.append(dW) bias_changes.append(db) if i > 0: dA = np.dot(dZ, self.weights[i].T) return list(reversed(weight_changes)), list(reversed(bias_changes)) def predict(self, X): perceptron_inputs, _ = self.forward(X) return perceptron_inputs[-1].flatten() #defining a model architecture = [2, 64, 64, 64, 1] # Two inputs, two hidden layers, one output model = SimpleNN(architecture) #defining a sample input and target output input = np.array([[0.1,0.2]]) desired_output = np.array([0.5]) #doing forward and backward pass to calculate changes perceptron_inputs, perceptron_outputs = model.forward(input) weight_changes, bias_changes = model.backward(perceptron_inputs, perceptron_outputs, desired_output) #smaller numbers for printing np.set_printoptions(precision=2) for i, (layer_weights, layer_biases, layer_weight_changes, layer_bias_changes) in enumerate(zip(model.weights, model.biases, weight_changes, bias_changes)): print(f'layer {i}') print(f'weight matrix: {layer_weights.shape}') print(f'weight matrix changes: {layer_weight_changes.shape}') print(f'bias matrix: {layer_biases.shape}') print(f'bias matrix changes: {layer_bias_changes.shape}') print('') print('The weight and weight change matrix of the second layer:') print('weight matrix:') print(model.weights[1]) print('change matrix:') print(weight_changes[1])