Skip to content

Commit ffa3058

Browse files
raman77768peRFectBeliever
authored andcommitted
Add 2-hidden layer neural network with back propagation using sigmoid activation function (TheAlgorithms#4037)
* added neural network with 2 hidden layers * Revert "added neural network with 2 hidden layers" This reverts commit fa4e2ac. * added neural network with 2 hidden layers * passing pre-commit requirements * doctest completed * added return hints * added example * example added * completed doctest's * changes made as per the review * changes made * changes after review * changes * spacing * changed return type * changed dtype
1 parent a1833ad commit ffa3058

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

Diff for: neural_network/2_hidden_layers_neural_network.py

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
"""
2+
References:
3+
- http://neuralnetworksanddeeplearning.com/chap2.html (Backpropagation)
4+
- https://en.wikipedia.org/wiki/Sigmoid_function (Sigmoid activation function)
5+
- https://en.wikipedia.org/wiki/Feedforward_neural_network (Feedforward)
6+
"""
7+
8+
import numpy
9+
10+
11+
class TwoHiddenLayerNeuralNetwork:
12+
def __init__(self, input_array: numpy.ndarray, output_array: numpy.ndarray) -> None:
13+
"""
14+
This function initializes the TwoHiddenLayerNeuralNetwork class with random
15+
weights for every layer and initializes predicted output with zeroes.
16+
17+
input_array : input values for training the neural network (i.e training data) .
18+
output_array : expected output values of the given inputs.
19+
"""
20+
21+
# Input values provided for training the model.
22+
self.input_array = input_array
23+
24+
# Random initial weights are assigned where first argument is the
25+
# number of nodes in previous layer and second argument is the
26+
# number of nodes in the next layer.
27+
28+
# Random initial weights are assigned.
29+
# self.input_array.shape[1] is used to represent number of nodes in input layer.
30+
# First hidden layer consists of 4 nodes.
31+
self.input_layer_and_first_hidden_layer_weights = numpy.random.rand(
32+
self.input_array.shape[1], 4
33+
)
34+
35+
# Random initial values for the first hidden layer.
36+
# First hidden layer has 4 nodes.
37+
# Second hidden layer has 3 nodes.
38+
self.first_hidden_layer_and_second_hidden_layer_weights = numpy.random.rand(
39+
4, 3
40+
)
41+
42+
# Random initial values for the second hidden layer.
43+
# Second hidden layer has 3 nodes.
44+
# Output layer has 1 node.
45+
self.second_hidden_layer_and_output_layer_weights = numpy.random.rand(3, 1)
46+
47+
# Real output values provided.
48+
self.output_array = output_array
49+
50+
# Predicted output values by the neural network.
51+
# Predicted_output array initially consists of zeroes.
52+
self.predicted_output = numpy.zeros(output_array.shape)
53+
54+
def feedforward(self) -> numpy.ndarray:
55+
"""
56+
The information moves in only one direction i.e. forward from the input nodes,
57+
through the two hidden nodes and to the output nodes.
58+
There are no cycles or loops in the network.
59+
60+
Return layer_between_second_hidden_layer_and_output
61+
(i.e the last layer of the neural network).
62+
63+
>>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float)
64+
>>> output_val = numpy.array(([0], [0], [0]), dtype=float)
65+
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
66+
>>> res = nn.feedforward()
67+
>>> array_sum = numpy.sum(res)
68+
>>> numpy.isnan(array_sum)
69+
False
70+
"""
71+
# Layer_between_input_and_first_hidden_layer is the layer connecting the
72+
# input nodes with the first hidden layer nodes.
73+
self.layer_between_input_and_first_hidden_layer = sigmoid(
74+
numpy.dot(self.input_array, self.input_layer_and_first_hidden_layer_weights)
75+
)
76+
77+
# layer_between_first_hidden_layer_and_second_hidden_layer is the layer
78+
# connecting the first hidden set of nodes with the second hidden set of nodes.
79+
self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid(
80+
numpy.dot(
81+
self.layer_between_input_and_first_hidden_layer,
82+
self.first_hidden_layer_and_second_hidden_layer_weights,
83+
)
84+
)
85+
86+
# layer_between_second_hidden_layer_and_output is the layer connecting
87+
# second hidden layer with the output node.
88+
self.layer_between_second_hidden_layer_and_output = sigmoid(
89+
numpy.dot(
90+
self.layer_between_first_hidden_layer_and_second_hidden_layer,
91+
self.second_hidden_layer_and_output_layer_weights,
92+
)
93+
)
94+
95+
return self.layer_between_second_hidden_layer_and_output
96+
97+
def back_propagation(self) -> None:
98+
"""
99+
Function for fine-tuning the weights of the neural net based on the
100+
error rate obtained in the previous epoch (i.e., iteration).
101+
Updation is done using derivative of sogmoid activation function.
102+
103+
>>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float)
104+
>>> output_val = numpy.array(([0], [0], [0]), dtype=float)
105+
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
106+
>>> res = nn.feedforward()
107+
>>> nn.back_propagation()
108+
>>> updated_weights = nn.second_hidden_layer_and_output_layer_weights
109+
>>> (res == updated_weights).all()
110+
False
111+
"""
112+
113+
updated_second_hidden_layer_and_output_layer_weights = numpy.dot(
114+
self.layer_between_first_hidden_layer_and_second_hidden_layer.T,
115+
2
116+
* (self.output_array - self.predicted_output)
117+
* sigmoid_derivative(self.predicted_output),
118+
)
119+
updated_first_hidden_layer_and_second_hidden_layer_weights = numpy.dot(
120+
self.layer_between_input_and_first_hidden_layer.T,
121+
numpy.dot(
122+
2
123+
* (self.output_array - self.predicted_output)
124+
* sigmoid_derivative(self.predicted_output),
125+
self.second_hidden_layer_and_output_layer_weights.T,
126+
)
127+
* sigmoid_derivative(
128+
self.layer_between_first_hidden_layer_and_second_hidden_layer
129+
),
130+
)
131+
updated_input_layer_and_first_hidden_layer_weights = numpy.dot(
132+
self.input_array.T,
133+
numpy.dot(
134+
numpy.dot(
135+
2
136+
* (self.output_array - self.predicted_output)
137+
* sigmoid_derivative(self.predicted_output),
138+
self.second_hidden_layer_and_output_layer_weights.T,
139+
)
140+
* sigmoid_derivative(
141+
self.layer_between_first_hidden_layer_and_second_hidden_layer
142+
),
143+
self.first_hidden_layer_and_second_hidden_layer_weights.T,
144+
)
145+
* sigmoid_derivative(self.layer_between_input_and_first_hidden_layer),
146+
)
147+
148+
self.input_layer_and_first_hidden_layer_weights += (
149+
updated_input_layer_and_first_hidden_layer_weights
150+
)
151+
self.first_hidden_layer_and_second_hidden_layer_weights += (
152+
updated_first_hidden_layer_and_second_hidden_layer_weights
153+
)
154+
self.second_hidden_layer_and_output_layer_weights += (
155+
updated_second_hidden_layer_and_output_layer_weights
156+
)
157+
158+
def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None:
159+
"""
160+
Performs the feedforwarding and back propagation process for the
161+
given number of iterations.
162+
Every iteration will update the weights of neural network.
163+
164+
output : real output values,required for calculating loss.
165+
iterations : number of times the weights are to be updated.
166+
give_loss : boolean value, If True then prints loss for each iteration,
167+
If False then nothing is printed
168+
169+
>>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float)
170+
>>> output_val = numpy.array(([0], [1], [1]), dtype=float)
171+
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
172+
>>> first_iteration_weights = nn.feedforward()
173+
>>> nn.back_propagation()
174+
>>> updated_weights = nn.second_hidden_layer_and_output_layer_weights
175+
>>> (first_iteration_weights == updated_weights).all()
176+
False
177+
"""
178+
for iteration in range(1, iterations + 1):
179+
self.output = self.feedforward()
180+
self.back_propagation()
181+
if give_loss:
182+
loss = numpy.mean(numpy.square(output - self.feedforward()))
183+
print(f"Iteration {iteration} Loss: {loss}")
184+
185+
def predict(self, input: numpy.ndarray) -> int:
186+
"""
187+
Predict's the output for the given input values using
188+
the trained neural network.
189+
190+
The output value given by the model ranges in-between 0 and 1.
191+
The predict function returns 1 if the model value is greater
192+
than the threshold value else returns 0,
193+
as the real output values are in binary.
194+
195+
>>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float)
196+
>>> output_val = numpy.array(([0], [1], [1]), dtype=float)
197+
>>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val)
198+
>>> nn.train(output_val, 1000, False)
199+
>>> nn.predict([0,1,0])
200+
1
201+
"""
202+
203+
# Input values for which the predictions are to be made.
204+
self.array = input
205+
206+
self.layer_between_input_and_first_hidden_layer = sigmoid(
207+
numpy.dot(self.array, self.input_layer_and_first_hidden_layer_weights)
208+
)
209+
210+
self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid(
211+
numpy.dot(
212+
self.layer_between_input_and_first_hidden_layer,
213+
self.first_hidden_layer_and_second_hidden_layer_weights,
214+
)
215+
)
216+
217+
self.layer_between_second_hidden_layer_and_output = sigmoid(
218+
numpy.dot(
219+
self.layer_between_first_hidden_layer_and_second_hidden_layer,
220+
self.second_hidden_layer_and_output_layer_weights,
221+
)
222+
)
223+
224+
return int(self.layer_between_second_hidden_layer_and_output > 0.6)
225+
226+
227+
def sigmoid(value: numpy.ndarray) -> numpy.ndarray:
228+
"""
229+
Applies sigmoid activation function.
230+
231+
return normalized values
232+
233+
>>> sigmoid(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64))
234+
array([[0.73105858, 0.5 , 0.88079708],
235+
[0.73105858, 0.5 , 0.5 ]])
236+
"""
237+
return 1 / (1 + numpy.exp(-value))
238+
239+
240+
def sigmoid_derivative(value: numpy.ndarray) -> numpy.ndarray:
241+
"""
242+
Provides the derivative value of the sigmoid function.
243+
244+
returns derivative of the sigmoid value
245+
246+
>>> sigmoid_derivative(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64))
247+
array([[ 0., 0., -2.],
248+
[ 0., 0., 0.]])
249+
"""
250+
return (value) * (1 - (value))
251+
252+
253+
def example() -> int:
254+
"""
255+
Example for "how to use the neural network class and use the
256+
respected methods for the desired output".
257+
Calls the TwoHiddenLayerNeuralNetwork class and
258+
provides the fixed input output values to the model.
259+
Model is trained for a fixed amount of iterations then the predict method is called.
260+
In this example the output is divided into 2 classes i.e. binary classification,
261+
the two classes are represented by '0' and '1'.
262+
263+
>>> example()
264+
1
265+
"""
266+
# Input values.
267+
input = numpy.array(
268+
(
269+
[0, 0, 0],
270+
[0, 0, 1],
271+
[0, 1, 0],
272+
[0, 1, 1],
273+
[1, 0, 0],
274+
[1, 0, 1],
275+
[1, 1, 0],
276+
[1, 1, 1],
277+
),
278+
dtype=numpy.float64,
279+
)
280+
281+
# True output values for the given input values.
282+
output = numpy.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=numpy.float64)
283+
284+
# Calling neural network class.
285+
neural_network = TwoHiddenLayerNeuralNetwork(input_array=input, output_array=output)
286+
287+
# Calling training function.
288+
# Set give_loss to True if you want to see loss in every iteration.
289+
neural_network.train(output=output, iterations=10, give_loss=False)
290+
291+
return neural_network.predict(numpy.array(([1, 1, 1]), dtype=numpy.float64))
292+
293+
294+
if __name__ == "__main__":
295+
example()

0 commit comments

Comments
 (0)