import unittest import numpy as np from neural_net.activation_layers.sigmoid_layer import SigmoidLayer # noinspection PyMethodMayBeStatic class SigmoidLayerTests(unittest.TestCase): def test_sigmoid_layer_1x1(self): ############## # Arrange # ############## inputs = np.array([[1.0]]) weights = np.array([[0.5]]) biases = np.array([0.0]) learning_rate = 0.001 # Pre-activation value (z) # This is the intermediate value calculated as the weighted sum of inputs plus the bias. z = np.dot(inputs, weights) + biases # Ouput # The result of applying the activation function to the pre-activation value z # Sigmoid activation formula: 1 / (1 + e^-z) expected_output = 1 / (1 + np.exp(-z)) # Loss gradient dL/dout # Represents how much the loss changes when the output changes. dL_dout = np.array([[1.0]]) # Activation derivative dout/dz # This tells you how much the output of the activation function changes with respect to the pre-activation value z. # Sigmoid derivative formula: σ(z) * (1 - σ(z)) dout_dz = expected_output * (1.0 - expected_output) # Gradient of the loss with respect to weights (dL/dweights) # This represents how much the loss changes when the weights change. # Formula: dL/dweights = inputs × dL/dout × σ′(z) expected_dl_dweights = inputs * dL_dout * dout_dz # Gradient of the loss with respect to the bias (dL/dbias) expected_dL_dbias = np.sum(dL_dout * dout_dz) # Gradient of the loss with respect to inputs (dL/dinputs) # This is the gradient of the loss with respect to the input of the neuron or layer, often needed if you want to backpropagate further. # Formula: dL / dinputs = dL/dout × σ′(z) × weights expected_dl_dinputs = dL_dout * dout_dz * weights # Calculate expected new weights and biases expected_weights = weights - learning_rate * expected_dl_dweights expected_biases = biases - learning_rate * expected_dL_dbias # Initialize SigmoidLayer layer = SigmoidLayer(weights.shape[0], weights.shape[1], weights=weights, biases=biases) ############## # Act # ############## # Forward pass output = layer.forward(inputs) # Backward pass dl_dinputs = layer.backward(dL_dout, learning_rate) ############## # Assert # ############## # Forward output correctness self.assertTrue(np.allclose(output, expected_output, atol=1e-6), f"Forward output incorrect: Actual: {output}, Expected: {expected_output}") # Backward pass correctness self.assertTrue(np.allclose(dl_dinputs, expected_dl_dinputs, atol=1e-6), f"Inputs derivative incorrect Actual: {dl_dinputs}, expected: {expected_dl_dinputs}") self.assertTrue(np.allclose(layer.weights, expected_weights, atol=1e-6), f"Weight update incorrect Actual: {layer.weights}, expected: {expected_weights}") self.assertTrue(np.allclose(layer.biases, expected_biases, atol=1e-6), f"Bias update incorrect Actual: {layer.biases}, expected: {expected_biases}") def test_sigmoid_layer_2x2(self): ############## # Arrange # ############## inputs = np.array([[1.0, 2.0], [3.0, 4.0]]) weights = np.array([[0.5, 0.2], [0.3, 0.7]]) biases = np.array([0.1, -0.1]) learning_rate = 0.001 # Pre-activation value (z) # z = inputs.dot(weights) + biases z = np.dot(inputs, weights) + biases # Expected output using the sigmoid function expected_output = 1 / (1 + np.exp(-z)) # Loss gradient dL/dout (assuming a gradient of 1 for simplicity) dL_dout = np.array([[1.0, 1.0], [1.0, 1.0]]) # Activation derivative dout/dz dout_dz = expected_output * (1 - expected_output) # Expected gradients expected_dl_dweights = np.dot(inputs.T, dL_dout * dout_dz) expected_dL_dbias = np.sum(dL_dout * dout_dz, axis=0) expected_dl_dinputs = np.dot(dL_dout * dout_dz, weights.T) # Expected updated weights and biases expected_weights = weights - learning_rate * expected_dl_dweights expected_biases = biases - learning_rate * expected_dL_dbias # Initialize SigmoidLayer (assuming SigmoidLayer class exists) layer = SigmoidLayer(weights.shape[0], weights.shape[1], weights=weights, biases=biases) ############## # Act # ############## # Forward pass output = layer.forward(inputs) # Backward pass dl_dinputs = layer.backward(dL_dout, learning_rate) ############## # Assert # ############## # Forward output correctness self.assertTrue(np.allclose(output, expected_output, atol=1e-6), f"Forward output incorrect: Actual: {output}, Expected: {expected_output}") # Backward pass correctness self.assertTrue(np.allclose(dl_dinputs, expected_dl_dinputs, atol=1e-6), f"Inputs derivative incorrect Actual: {dl_dinputs}, expected: {expected_dl_dinputs}") self.assertTrue(np.allclose(layer.weights, expected_weights, atol=1e-6), f"Weight update incorrect Actual: {layer.weights}, expected: {expected_weights}") self.assertTrue(np.allclose(layer.biases, expected_biases, atol=1e-6), f"Bias update incorrect Actual: {layer.biases}, expected: {expected_biases}")