Intro To Deep Learning - Lab
Intro To Deep Learning - Lab
Introduction
In this lab, we will build a neural network from scratch and code how it performs predictions using forward propagation. Please
note that all deep learning libraries have the entire training and prediction processes implemented, and so in practice you
wouldn't really need to build a neural network from scratch. However, hopefully completing this lab will help you understand
neural networks and how they work even better.
4. Access your Flask app via a webpage anywhere using a custom link.
Table of Contents
1. Recap
2. Initalize a Network
3. Compute Weighted Sum at Each Node
4. Compute Node Activation
5. Forward Propagation
Recap
From the videos, let's recap how a neural network makes predictions through the forward propagation process. Here is a neural
network that takes two inputs, has one hidden layer with two nodes, and an output layer with one node.
Let's start by randomly initializing the weights and the biases in the network. We have 6 weights and 3 biases, one for each
node in the hidden layer as well as for each node in the output layer.
In [15]: # All Libraries required for this lab are listed below. The libraries pre-installed on Skills Network Labs are co
# If you run this notebook on a different environment, e.g. your desktop, you may need to uncomment and install c
In [17]: print(weights)
print(biases)
Now that we have the weights and the biases defined for the network, let's compute the output for a given input, $x_1$ and
$x_2$.
Let's start by computing the wighted sum of the inputs, $z_{1, 1}$, at the first node of the hidden layer.
print('The weighted sum of the inputs at the first node in the hidden layer is {}'.format(z_11))
The weighted sum of the inputs at the first node in the hidden layer is 0.8964999999999999
Next, let's compute the weighted sum of the inputs, $z_{1, 2}$, at the second node of the hidden layer. Assign the value to z_12.
In [21]: print('The weighted sum of the inputs at the second node in the hidden layer is {}'.format(np.around(z_12, decima
The weighted sum of the inputs at the second node in the hidden layer is 1.61
Next, assuming a sigmoid activation function, let's compute the activation of the first node, $a_{1, 1}$, in the hidden layer.
print('The activation of the first node in the hidden layer is {}'.format(np.around(a_11, decimals=4)))
Let's also compute the activation of the second node, $a_{1, 2}$, in the hidden layer. Assign the value to a_12.
In [24]: print('The activation of the second node in the hidden layer is {}'.format(np.around(a_12, decimals=4)))
Now these activations will serve as the inputs to the output layer. So, let's compute the weighted sum of these inputs to the
node in the output layer. Assign the value to z_2.
Print the weighted sum of the inputs at the node in the output layer.
In [26]: print('The weighted sum of the inputs at the node in the output layer is {}'.format(np.around(z_2, decimals=4)))
The weighted sum of the inputs at the node in the output layer is 0.8302
Finally, let's compute the output of the network as the activation of the node in the output layer. Assign the value to a_2.
Print the activation of the node in the output layer which is equivalent to the prediction made by the network.
In [28]: print('The output of the network for x1 = 0.5 and x2 = 0.85 is {}'.format(np.around(a_2, decimals=4)))
Obviously, neural networks for real problems are composed of many hidden layers and many more nodes in each layer. So, we
can't continue making predictions using this very inefficient approach of computing the weighted sum at each node and the
activation of each node manually.
In order to code an automatic way of making predictions, let's generalize our network. A general network would take $n$
inputs, would have many hidden layers, each hidden layer having $m$ nodes, and would have an output layer. Although the
network is showing one hidden layer, but we will code the network to have many hidden layers. Similarly, although the network
shows an output layer with one node, we will code the network to have more than one node in the output layer.
Initialize a Network
Let's start by formally defining the structure of the network.
Now that we defined the structure of the network, let's go ahead and inititailize the weights and the biases in the network to
random numbers. In order to be able to initialize the weights and the biases to random numbers, we will need to import the
Numpy library.
# loop through each layer and randomly initialize the weights and biases associated with each node
# notice how we are adding 1 to the number of hidden layers in order to include the output layer
for layer in range(num_hidden_layers + 1):
# initialize weights and biases associated with each node in the current layer
network[layer_name] = {}
for node in range(num_nodes):
node_name = 'node_{}'.format(node+1)
network[layer_name][node_name] = {
'weights': np.around(np.random.uniform(size=num_nodes_previous), decimals=2),
'bias': np.around(np.random.uniform(size=1), decimals=2),
}
num_nodes_previous = num_nodes
Awesome! So now with the above code, we are able to initialize the weights and the biases pertaining to any network of any
number of hidden layers and number of nodes in each layer. But let's put this code in a function so that we are able to
repetitively execute all this code whenever we want to construct a neural network.
network = {}
# loop through each layer and randomly initialize the weights and biases associated with each layer
for layer in range(num_hidden_layers + 1):
if layer == num_hidden_layers:
layer_name = 'output' # name last layer in the network output
num_nodes = num_nodes_output
else:
layer_name = 'layer_{}'.format(layer + 1) # otherwise give the layer a number
num_nodes = num_nodes_hidden[layer]
num_nodes_previous = num_nodes
np.random.seed(12)
inputs = np.around(np.random.uniform(size=5), decimals=2)
The inputs to the network are [0.15 0.74 0.26 0.53 0.01]
Use the compute_weighted_sum function to compute the weighted sum at the first node in the first
hidden layer.
node_weights = small_network['layer_1']['node_1']['weights']
node_bias = small_network['layer_1']['node_1']['bias']
The weighted sum at the first node in the hidden layer is 1.2118
Use the node_activation function to compute the output of the first node in the first hidden layer.
Forward Propagation
The final piece of building a neural network that can perform predictions is to put everything together. So let's create a function
that applies the compute_weighted_sum and node_activation functions to each node in the network and propagates the data all
the way to the output layer and outputs a prediction for each node in the output layer.
The way we are going to accomplish this is through the following procedure:
1. Start with the input layer as the input to the first hidden layer.
2. Compute the weighted sum at the nodes of the current layer.
3. Compute the output of the nodes of the current layer.
4. Set the output of the current layer to be the input to the next layer.
5. Move to the next layer in the network.
6. Repeat steps 2 - 4 until we compute the output of the output layer.
layer_inputs = list(inputs) # start with the input layer as the input to the first hidden layer
layer_data = network[layer]
layer_outputs = []
for layer_node in layer_data:
node_data = layer_data[layer_node]
# compute the weighted sum and the output of each node at the same time
node_output = node_activation(compute_weighted_sum(layer_inputs, node_data['weights'], node_data['bia
layer_outputs.append(np.around(node_output[0], decimals=4))
if layer != 'output':
print('The outputs of the nodes in hidden layer number {} is {}'.format(layer.split('_')[1], layer_ou
layer_inputs = layer_outputs # set the output of this layer to be the input to next layer
network_predictions = layer_outputs
return network_predictions
Use the forward_propagate function to compute the prediction of our small network
The outputs of the nodes in hidden layer number 1 is [0.7706, 0.7911, 0.8848]
The outputs of the nodes in hidden layer number 2 is [0.7874, 0.8881]
The outputs of the nodes in hidden layer number 3 is [0.8941, 0.8485, 0.7074]
The predicted value by the network for the given input is 0.8872
So we built the code to define a neural network. We can specify the number of inputs that a neural network can take, the
number of hidden layers as well as the number of nodes in each hidden layer, and the number of nodes in the output layer.
We first use the initialize_network to create our neural network and define its weights and biases.