You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 line
5.7KB

  1. import unittest
  2. import numpy as np
  3. from neural_net.activation_layers.sigmoid_layer import SigmoidLayer
  4. # noinspection PyMethodMayBeStatic
  5. class SigmoidLayerTests(unittest.TestCase):
  6. def test_sigmoid_layer_1x1(self):
  7. ##############
  8. # Arrange #
  9. ##############
  10. inputs = np.array([[1.0]])
  11. weights = np.array([[0.5]])
  12. biases = np.array([0.0])
  13. learning_rate = 0.001
  14. # Pre-activation value (z)
  15. # This is the intermediate value calculated as the weighted sum of inputs plus the bias.
  16. z = np.dot(inputs, weights) + biases
  17. # Ouput
  18. # The result of applying the activation function to the pre-activation value z
  19. # Sigmoid activation formula: 1 / (1 + e^-z)
  20. expected_output = 1 / (1 + np.exp(-z))
  21. # Loss gradient dL/dout
  22. # Represents how much the loss changes when the output changes.
  23. dL_dout = np.array([[1.0]])
  24. # Activation derivative dout/dz
  25. # This tells you how much the output of the activation function changes with respect to the pre-activation value z.
  26. # Sigmoid derivative formula: σ(z) * (1 - σ(z))
  27. dout_dz = expected_output * (1.0 - expected_output)
  28. # Gradient of the loss with respect to weights (dL/dweights)
  29. # This represents how much the loss changes when the weights change.
  30. # Formula: dL/dweights = inputs × dL/dout × σ′(z)
  31. expected_dl_dweights = inputs * dL_dout * dout_dz
  32. # Gradient of the loss with respect to the bias (dL/dbias)
  33. expected_dL_dbias = np.sum(dL_dout * dout_dz)
  34. # Gradient of the loss with respect to inputs (dL/dinputs)
  35. # 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.
  36. # Formula: dL / dinputs = dL/dout × σ′(z) × weights
  37. expected_dl_dinputs = dL_dout * dout_dz * weights
  38. # Calculate expected new weights and biases
  39. expected_weights = weights - learning_rate * expected_dl_dweights
  40. expected_biases = biases - learning_rate * expected_dL_dbias
  41. # Initialize SigmoidLayer
  42. layer = SigmoidLayer(weights.shape[0], weights.shape[1], weights=weights, biases=biases)
  43. ##############
  44. # Act #
  45. ##############
  46. # Forward pass
  47. output = layer.forward(inputs)
  48. # Backward pass
  49. dl_dinputs = layer.backward(dL_dout, learning_rate)
  50. ##############
  51. # Assert #
  52. ##############
  53. # Forward output correctness
  54. self.assertTrue(np.allclose(output, expected_output, atol=1e-6),
  55. f"Forward output incorrect: Actual: {output}, Expected: {expected_output}")
  56. # Backward pass correctness
  57. self.assertTrue(np.allclose(dl_dinputs, expected_dl_dinputs, atol=1e-6),
  58. f"Inputs derivative incorrect Actual: {dl_dinputs}, expected: {expected_dl_dinputs}")
  59. self.assertTrue(np.allclose(layer.weights, expected_weights, atol=1e-6),
  60. f"Weight update incorrect Actual: {layer.weights}, expected: {expected_weights}")
  61. self.assertTrue(np.allclose(layer.biases, expected_biases, atol=1e-6),
  62. f"Bias update incorrect Actual: {layer.biases}, expected: {expected_biases}")
  63. def test_sigmoid_layer_2x2(self):
  64. ##############
  65. # Arrange #
  66. ##############
  67. inputs = np.array([[1.0, 2.0],
  68. [3.0, 4.0]])
  69. weights = np.array([[0.5, 0.2],
  70. [0.3, 0.7]])
  71. biases = np.array([0.1, -0.1])
  72. learning_rate = 0.001
  73. # Pre-activation value (z)
  74. # z = inputs.dot(weights) + biases
  75. z = np.dot(inputs, weights) + biases
  76. # Expected output using the sigmoid function
  77. expected_output = 1 / (1 + np.exp(-z))
  78. # Loss gradient dL/dout (assuming a gradient of 1 for simplicity)
  79. dL_dout = np.array([[1.0, 1.0],
  80. [1.0, 1.0]])
  81. # Activation derivative dout/dz
  82. dout_dz = expected_output * (1 - expected_output)
  83. # Expected gradients
  84. expected_dl_dweights = np.dot(inputs.T, dL_dout * dout_dz)
  85. expected_dL_dbias = np.sum(dL_dout * dout_dz, axis=0)
  86. expected_dl_dinputs = np.dot(dL_dout * dout_dz, weights.T)
  87. # Expected updated weights and biases
  88. expected_weights = weights - learning_rate * expected_dl_dweights
  89. expected_biases = biases - learning_rate * expected_dL_dbias
  90. # Initialize SigmoidLayer (assuming SigmoidLayer class exists)
  91. layer = SigmoidLayer(weights.shape[0], weights.shape[1], weights=weights, biases=biases)
  92. ##############
  93. # Act #
  94. ##############
  95. # Forward pass
  96. output = layer.forward(inputs)
  97. # Backward pass
  98. dl_dinputs = layer.backward(dL_dout, learning_rate)
  99. ##############
  100. # Assert #
  101. ##############
  102. # Forward output correctness
  103. self.assertTrue(np.allclose(output, expected_output, atol=1e-6),
  104. f"Forward output incorrect: Actual: {output}, Expected: {expected_output}")
  105. # Backward pass correctness
  106. self.assertTrue(np.allclose(dl_dinputs, expected_dl_dinputs, atol=1e-6),
  107. f"Inputs derivative incorrect Actual: {dl_dinputs}, expected: {expected_dl_dinputs}")
  108. self.assertTrue(np.allclose(layer.weights, expected_weights, atol=1e-6),
  109. f"Weight update incorrect Actual: {layer.weights}, expected: {expected_weights}")
  110. self.assertTrue(np.allclose(layer.biases, expected_biases, atol=1e-6),
  111. f"Bias update incorrect Actual: {layer.biases}, expected: {expected_biases}")