2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
References:
|
|
|
|
- http://neuralnetworksanddeeplearning.com/chap2.html (Backpropagation)
|
|
|
|
- https://en.wikipedia.org/wiki/Sigmoid_function (Sigmoid activation function)
|
|
|
|
- https://en.wikipedia.org/wiki/Feedforward_neural_network (Feedforward)
|
|
|
|
"""
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
import numpy as np
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TwoHiddenLayerNeuralNetwork:
|
2024-03-28 18:03:23 +00:00
|
|
|
def __init__(self, input_array: np.ndarray, output_array: np.ndarray) -> None:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
This function initializes the TwoHiddenLayerNeuralNetwork class with random
|
|
|
|
weights for every layer and initializes predicted output with zeroes.
|
|
|
|
|
|
|
|
input_array : input values for training the neural network (i.e training data) .
|
|
|
|
output_array : expected output values of the given inputs.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Input values provided for training the model.
|
|
|
|
self.input_array = input_array
|
|
|
|
|
|
|
|
# Random initial weights are assigned where first argument is the
|
|
|
|
# number of nodes in previous layer and second argument is the
|
|
|
|
# number of nodes in the next layer.
|
|
|
|
|
|
|
|
# Random initial weights are assigned.
|
|
|
|
# self.input_array.shape[1] is used to represent number of nodes in input layer.
|
|
|
|
# First hidden layer consists of 4 nodes.
|
2024-04-01 19:39:31 +00:00
|
|
|
rng = np.random.default_rng()
|
|
|
|
self.input_layer_and_first_hidden_layer_weights = rng.random(
|
|
|
|
(self.input_array.shape[1], 4)
|
2020-12-26 16:13:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# Random initial values for the first hidden layer.
|
|
|
|
# First hidden layer has 4 nodes.
|
|
|
|
# Second hidden layer has 3 nodes.
|
2024-04-01 19:39:31 +00:00
|
|
|
self.first_hidden_layer_and_second_hidden_layer_weights = rng.random((4, 3))
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
# Random initial values for the second hidden layer.
|
|
|
|
# Second hidden layer has 3 nodes.
|
|
|
|
# Output layer has 1 node.
|
2024-04-01 19:39:31 +00:00
|
|
|
self.second_hidden_layer_and_output_layer_weights = rng.random((3, 1))
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
# Real output values provided.
|
|
|
|
self.output_array = output_array
|
|
|
|
|
|
|
|
# Predicted output values by the neural network.
|
|
|
|
# Predicted_output array initially consists of zeroes.
|
2024-03-28 18:03:23 +00:00
|
|
|
self.predicted_output = np.zeros(output_array.shape)
|
2020-12-26 16:13:20 +00:00
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
def feedforward(self) -> np.ndarray:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
The information moves in only one direction i.e. forward from the input nodes,
|
|
|
|
through the two hidden nodes and to the output nodes.
|
|
|
|
There are no cycles or loops in the network.
|
|
|
|
|
|
|
|
Return layer_between_second_hidden_layer_and_output
|
|
|
|
(i.e the last layer of the neural network).
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> input_val = np.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float)
|
|
|
|
>>> output_val = np.array(([0], [0], [0]), dtype=float)
|
2020-12-26 16:13:20 +00:00
|
|
|
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
|
|
|
|
>>> res = nn.feedforward()
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> array_sum = np.sum(res)
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> bool(np.isnan(array_sum))
|
2020-12-26 16:13:20 +00:00
|
|
|
False
|
|
|
|
"""
|
|
|
|
# Layer_between_input_and_first_hidden_layer is the layer connecting the
|
|
|
|
# input nodes with the first hidden layer nodes.
|
|
|
|
self.layer_between_input_and_first_hidden_layer = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(self.input_array, self.input_layer_and_first_hidden_layer_weights)
|
2020-12-26 16:13:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# layer_between_first_hidden_layer_and_second_hidden_layer is the layer
|
|
|
|
# connecting the first hidden set of nodes with the second hidden set of nodes.
|
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_input_and_first_hidden_layer,
|
|
|
|
self.first_hidden_layer_and_second_hidden_layer_weights,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
# layer_between_second_hidden_layer_and_output is the layer connecting
|
|
|
|
# second hidden layer with the output node.
|
|
|
|
self.layer_between_second_hidden_layer_and_output = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer,
|
|
|
|
self.second_hidden_layer_and_output_layer_weights,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return self.layer_between_second_hidden_layer_and_output
|
|
|
|
|
|
|
|
def back_propagation(self) -> None:
|
|
|
|
"""
|
|
|
|
Function for fine-tuning the weights of the neural net based on the
|
|
|
|
error rate obtained in the previous epoch (i.e., iteration).
|
|
|
|
Updation is done using derivative of sogmoid activation function.
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> input_val = np.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float)
|
|
|
|
>>> output_val = np.array(([0], [0], [0]), dtype=float)
|
2020-12-26 16:13:20 +00:00
|
|
|
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
|
|
|
|
>>> res = nn.feedforward()
|
|
|
|
>>> nn.back_propagation()
|
|
|
|
>>> updated_weights = nn.second_hidden_layer_and_output_layer_weights
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> bool((res == updated_weights).all())
|
2020-12-26 16:13:20 +00:00
|
|
|
False
|
|
|
|
"""
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
updated_second_hidden_layer_and_output_layer_weights = np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer.T,
|
|
|
|
2
|
|
|
|
* (self.output_array - self.predicted_output)
|
|
|
|
* sigmoid_derivative(self.predicted_output),
|
|
|
|
)
|
2024-03-28 18:03:23 +00:00
|
|
|
updated_first_hidden_layer_and_second_hidden_layer_weights = np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_input_and_first_hidden_layer.T,
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
2
|
|
|
|
* (self.output_array - self.predicted_output)
|
|
|
|
* sigmoid_derivative(self.predicted_output),
|
|
|
|
self.second_hidden_layer_and_output_layer_weights.T,
|
|
|
|
)
|
|
|
|
* sigmoid_derivative(
|
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer
|
|
|
|
),
|
|
|
|
)
|
2024-03-28 18:03:23 +00:00
|
|
|
updated_input_layer_and_first_hidden_layer_weights = np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.input_array.T,
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
2
|
|
|
|
* (self.output_array - self.predicted_output)
|
|
|
|
* sigmoid_derivative(self.predicted_output),
|
|
|
|
self.second_hidden_layer_and_output_layer_weights.T,
|
|
|
|
)
|
|
|
|
* sigmoid_derivative(
|
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer
|
|
|
|
),
|
|
|
|
self.first_hidden_layer_and_second_hidden_layer_weights.T,
|
|
|
|
)
|
|
|
|
* sigmoid_derivative(self.layer_between_input_and_first_hidden_layer),
|
|
|
|
)
|
|
|
|
|
|
|
|
self.input_layer_and_first_hidden_layer_weights += (
|
|
|
|
updated_input_layer_and_first_hidden_layer_weights
|
|
|
|
)
|
|
|
|
self.first_hidden_layer_and_second_hidden_layer_weights += (
|
|
|
|
updated_first_hidden_layer_and_second_hidden_layer_weights
|
|
|
|
)
|
|
|
|
self.second_hidden_layer_and_output_layer_weights += (
|
|
|
|
updated_second_hidden_layer_and_output_layer_weights
|
|
|
|
)
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
def train(self, output: np.ndarray, iterations: int, give_loss: bool) -> None:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
Performs the feedforwarding and back propagation process for the
|
|
|
|
given number of iterations.
|
|
|
|
Every iteration will update the weights of neural network.
|
|
|
|
|
|
|
|
output : real output values,required for calculating loss.
|
|
|
|
iterations : number of times the weights are to be updated.
|
|
|
|
give_loss : boolean value, If True then prints loss for each iteration,
|
|
|
|
If False then nothing is printed
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> input_val = np.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float)
|
|
|
|
>>> output_val = np.array(([0], [1], [1]), dtype=float)
|
2020-12-26 16:13:20 +00:00
|
|
|
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
|
|
|
|
>>> first_iteration_weights = nn.feedforward()
|
|
|
|
>>> nn.back_propagation()
|
|
|
|
>>> updated_weights = nn.second_hidden_layer_and_output_layer_weights
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> bool((first_iteration_weights == updated_weights).all())
|
2020-12-26 16:13:20 +00:00
|
|
|
False
|
|
|
|
"""
|
|
|
|
for iteration in range(1, iterations + 1):
|
|
|
|
self.output = self.feedforward()
|
|
|
|
self.back_propagation()
|
|
|
|
if give_loss:
|
2024-03-28 18:03:23 +00:00
|
|
|
loss = np.mean(np.square(output - self.feedforward()))
|
2020-12-26 16:13:20 +00:00
|
|
|
print(f"Iteration {iteration} Loss: {loss}")
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
def predict(self, input_arr: np.ndarray) -> int:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
Predict's the output for the given input values using
|
|
|
|
the trained neural network.
|
|
|
|
|
|
|
|
The output value given by the model ranges in-between 0 and 1.
|
|
|
|
The predict function returns 1 if the model value is greater
|
|
|
|
than the threshold value else returns 0,
|
|
|
|
as the real output values are in binary.
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> input_val = np.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float)
|
|
|
|
>>> output_val = np.array(([0], [1], [1]), dtype=float)
|
2020-12-26 16:13:20 +00:00
|
|
|
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
|
|
|
|
>>> nn.train(output_val, 1000, False)
|
2023-10-15 23:41:45 +00:00
|
|
|
>>> nn.predict([0, 1, 0]) in (0, 1)
|
2021-03-22 18:24:05 +00:00
|
|
|
True
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
# Input values for which the predictions are to be made.
|
2022-10-13 14:23:59 +00:00
|
|
|
self.array = input_arr
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
self.layer_between_input_and_first_hidden_layer = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(self.array, self.input_layer_and_first_hidden_layer_weights)
|
2020-12-26 16:13:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_input_and_first_hidden_layer,
|
|
|
|
self.first_hidden_layer_and_second_hidden_layer_weights,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
self.layer_between_second_hidden_layer_and_output = sigmoid(
|
2024-03-28 18:03:23 +00:00
|
|
|
np.dot(
|
2020-12-26 16:13:20 +00:00
|
|
|
self.layer_between_first_hidden_layer_and_second_hidden_layer,
|
|
|
|
self.second_hidden_layer_and_output_layer_weights,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-10-15 23:41:45 +00:00
|
|
|
return int((self.layer_between_second_hidden_layer_and_output > 0.6)[0])
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
def sigmoid(value: np.ndarray) -> np.ndarray:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
Applies sigmoid activation function.
|
|
|
|
|
|
|
|
return normalized values
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> sigmoid(np.array(([1, 0, 2], [1, 0, 0]), dtype=np.float64))
|
2020-12-26 16:13:20 +00:00
|
|
|
array([[0.73105858, 0.5 , 0.88079708],
|
|
|
|
[0.73105858, 0.5 , 0.5 ]])
|
|
|
|
"""
|
2024-03-28 18:03:23 +00:00
|
|
|
return 1 / (1 + np.exp(-value))
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
def sigmoid_derivative(value: np.ndarray) -> np.ndarray:
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
Provides the derivative value of the sigmoid function.
|
|
|
|
|
|
|
|
returns derivative of the sigmoid value
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
>>> sigmoid_derivative(np.array(([1, 0, 2], [1, 0, 0]), dtype=np.float64))
|
2020-12-26 16:13:20 +00:00
|
|
|
array([[ 0., 0., -2.],
|
|
|
|
[ 0., 0., 0.]])
|
|
|
|
"""
|
|
|
|
return (value) * (1 - (value))
|
|
|
|
|
|
|
|
|
|
|
|
def example() -> int:
|
|
|
|
"""
|
|
|
|
Example for "how to use the neural network class and use the
|
|
|
|
respected methods for the desired output".
|
|
|
|
Calls the TwoHiddenLayerNeuralNetwork class and
|
|
|
|
provides the fixed input output values to the model.
|
|
|
|
Model is trained for a fixed amount of iterations then the predict method is called.
|
|
|
|
In this example the output is divided into 2 classes i.e. binary classification,
|
|
|
|
the two classes are represented by '0' and '1'.
|
|
|
|
|
2021-03-22 18:24:05 +00:00
|
|
|
>>> example() in (0, 1)
|
|
|
|
True
|
2020-12-26 16:13:20 +00:00
|
|
|
"""
|
|
|
|
# Input values.
|
2024-03-28 18:03:23 +00:00
|
|
|
test_input = np.array(
|
2020-12-26 16:13:20 +00:00
|
|
|
(
|
|
|
|
[0, 0, 0],
|
|
|
|
[0, 0, 1],
|
|
|
|
[0, 1, 0],
|
|
|
|
[0, 1, 1],
|
|
|
|
[1, 0, 0],
|
|
|
|
[1, 0, 1],
|
|
|
|
[1, 1, 0],
|
|
|
|
[1, 1, 1],
|
|
|
|
),
|
2024-03-28 18:03:23 +00:00
|
|
|
dtype=np.float64,
|
2020-12-26 16:13:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# True output values for the given input values.
|
2024-03-28 18:03:23 +00:00
|
|
|
output = np.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=np.float64)
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
# Calling neural network class.
|
2022-10-13 14:23:59 +00:00
|
|
|
neural_network = TwoHiddenLayerNeuralNetwork(
|
|
|
|
input_array=test_input, output_array=output
|
|
|
|
)
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
# Calling training function.
|
|
|
|
# Set give_loss to True if you want to see loss in every iteration.
|
|
|
|
neural_network.train(output=output, iterations=10, give_loss=False)
|
|
|
|
|
2024-03-28 18:03:23 +00:00
|
|
|
return neural_network.predict(np.array(([1, 1, 1]), dtype=np.float64))
|
2020-12-26 16:13:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
example()
|