A four-part walkthrough of neural networks: matrix-calculus review, forward propagation, the derivatives behind a neuron, and the four equations of backpropagation derived by hand.
2.1 The Neuron Function
2.2 Weight and Bias Indexing
2.3 A Layer of Neurons
Part 3: Derivatives of Neural Networks & Gradient Descent#
3.1 Motivation & Cost Function
3.2 Differentiating a Neuron's Operations
3.2.1 Binary Elementwise Functions
3.2.2 Hadamard Product
3.2.3 Scalar Expansion
3.2.4 Sum
3.3 Derivation of an Activation
3.4 Derivation of the cost
3.5 Intuition: derivative of the cost w.r.t the weights
3.6 Differentiating w.r.t the bias
3.7 Gradient Descent Intuition
3.8 The Gradient Descent Algorithm & stochastic Gradient Descent
3.9 Derivation of a layer (and why it doesn't work)
4.1 The Error of a Node
4.2 The four equations of Backpropagation
4.2.1 E1: Error of aL
4.2.2 E2: Error of al
4.2.3 E3: Cost w.r.t biases
4.2.4 E4: Cost w.r.t weights
4.2.5 Equation 4 vectorized
4.3 Tying Part 3 and Part 4 together
4.4 The Backpropagation Algorithm
4.5 Looking forward
The stigma that Neural Networks are hard is very wrong. It is just a huge function. In this function you are going to be working to reduce the cost function. This reducing of cost function is done by Calculus. That's about it.
Lets take a good look at this above diagram. The Green circle is the Neuron. Each Neuron has a bias (b), and a weights matrix(W∈RN) which contain N number of values. N is the number of neurons in the previous layer. The math that happens before storing a value in the neuron is:
σ(i=1∑N(Xi⋅Wi)+b)
The σ here is a sigmoid function. It is our activation function This is a squishify function, which takes values from negative infinity to positive infinity and plots them between 0 and 1. The idea is that each neuron will now store a scalar value. Now lets talk about how to efficiently work on the summation of ∑i=1NxiWi. We know that X and W are stored in the form of matrices and both of them have N values. you could just take the dot product of X and W to get this summation. Remember to transpose one of them!
So essentially, each neuron stores a value calculates with the inputs given by all the neurons of the previous layer using the formula:
We know that all the Neurons store a singular scalar value. So, we just have a column matrix containing all the Neurons' values. The Neuron's value is called its Activation. The number of values in the column matrix will be the number of Neurons in that layer, l. Let us call this column matrix Sl, where l corresponds to its layer.
Lets talk about the storage of Weights matrix. We know that each Neuron will have a connection with all the previous layer's Neurons. This means: if the number of neurons in (l−1)th layer is pand the number of neurons in the lth layer is q. Then the number of values in the Weights matrix just for layer l will be (p×q).
Now with this you could directly get the values of the Sl.
Sl=σ(Wl⋅a(l−1)+bl)
Now that you have Sl, you could do the same and find out S(l+1) by treating Sl as al.
You could imagine a machine with a lot of knobs. Each knob affects the final output by a certain amount. Keep in mind that some knobs affect the final output more than the others. Now how do you go about finding how each of these knobs change the final output and to what extent do they affect them?
That is what this entire section deals with.
Error Function: The error function that we use here is MSE.
MSE=m1i=1∑m(y^i−yi)2
Keep in mind that the calculation of the loss is done in the Feedforward half of the NN. The gradient of the loss is done in the backpropagation half of the NN.
3.1.1 Derivation of a Binary element-wise operation#
A good place to start would be: What is a binary element-wise operation? It is an operation which takes in two vectors and performs some element wise operation on them and gives out a singular vector. This is a quick explanation as to why Jacobians here are always going to be diagonal matrices. Emphasis on the HERE, and by HERE, we mean when we are working with element wise functions.
F(v,w)=v1v2⋮vn+w1w2⋮wn
Here we have a function F(v, w). It is a function which is undergoing a binary element-wise operation (+). Each row of F(v, w) is given names like F1,F2,…,Fn. Also, you need to keep note that F1 contains V1 and no other V's. Same with the other rows.
so now on differentiating, you can clearly observe why we get a diagonal matrix.
Now we will use all the above stuff: Here is what we want to do:
a=σ(WTX+b)
We could rewrite this as:
a=σ(∑(W⊕X)+b)
Where ⊕ is the symbol for Hadamard product. With the chain rule: keep in mind H = W ⊕ X and S = sum(H)
∂W∂a=∂Z∂a⋅∂S∂Z⋅∂H∂S⋅∂W∂H
∂b∂a=∂Z∂a⋅∂b∂Z
Lets work on the first the weights first:
∂W∂H=x10 ⋅⋅0 0 x2⋅⋅0 ⋅⋅⋱⋅⋅⋅⋱⋅00⋅⋅xn
Similarly using derivatives of sums:
∂H∂S=1T
And lets assume σ is relu here because it makes out life easier and is more popular now. Hence:
∂Z∂a={1,0,if z>0otherwise
Now lets substitute all these values. We wont be assuming that ∂Z∂a is 0, because that just cancels out everything.
∂W∂a=1⋅1⋅1T⋅[x1x2⋅⋅xn]
Working this out:
∂W∂a=[x1x2⋅⋅xn]
Now if Z<0, then:
∂W∂a=0
Working on bias now
∂b∂a=∂Z∂a⋅∂b∂Z
We already calculated ∂Z∂a, and ∂b∂Z=1, hence we get: if Z>0
∂b∂a=1 if Z<0
∂b∂a=0
3.4 Derivative of the cost for a simple neural network#
Here we use the MSE for the Loss function.
Before this we used to have inputs to a neuron. X, the neuron itself, which stored the weights W and the bias B. Using W, B and X, we get Z, passing it through an activation function, we get A. Now we add onto this, and after we get A, we subtract it by the actual value we expect the neuron to get, Y^. Then we tend to normalize this error, using a tool like MSE or MAE(Mean absolute error). This is what we call the cost function. It is represented by J(θ).
Now that we know this, it shouldn't be too hard. Until now we had seen how the different aspects of a neuron like Z, or its activation function changes with respect to the weights and biases. Now we add onto that and see how the cost function changes with respect to the Weights and the Bias.
∂W∂J,∂b∂J
This is the final piece to the puzzle, the last link to the chain:
∂W∂J=∂a∂J×∂W∂a
∂b∂J=∂a∂J×∂b∂a
Now let us look at a dataset, We have the training examples as the columns and the feature numbers as the rows. Training example number will be shown by the superscript and the feature number as the subscript.
The essence of this is very beautiful, Lets take a look at it: Each row represents how much the error J changes when we tweak that corresponding weight W1,W2,W3,...,Wn.
In simpler words, it tells you how sensitive the cost function is to each of the weights to that neuron.
For a single training example with 2 features. Imagine a huge blanket as such:
Then the gradient always points to the highest point of the blanket from any point. Hence: Negative gradient points to lowest point of the blanket. The gradient is a vector of N dimensions pointing to the topmost point of the function. So a more accurate way of representing the above function would be as such:
∇wJ=[X1.e1X2.e1]
Where ∇wJ is read as the gradient of J wrt W. It is a vector (2 dimensional here), pointing upward. So With the gradient function, we could tell on changing which weight do we get the most bang-for-buck with respect to the cost function. So each step needs to be in the direction of −∇wJ to reduce the overall loss.
3.8 Gradient descent algorithm and Stochastic Gradient Descent#
Now that we have the gradient of the cost function with respect to the weights and biases.
∇W,bJ=∂W1∂J∂W2∂J.∂b1∂J..∂Wn∂J
We can plug this into the traditional SGD algorithm:
θ=θ−α×∇W,bJ
Where θ is the weights vector, with each weights corresponding to the same differential in the gradient vector. α is the learning rate.
This is Gradient descent, what is Stochastic Gradient descent though? Lets say we have a Training set of m = 10,000,000 training examples. Then we would have to calculate:
This would be painful to calculate, hence we break these down into batches, each of lets say.. 120 values. We then perform gradient descent over these batches. This would be computationally efficient. Now how do we stitch all of these batches values together. We just take one step per each batch. Remember to shuffle the training examples though.
3.9 Finding Derivative of an entire layer (and why it doesn't work the same way we did with one neuron)#
Here we shall delve deeper into why we do not use the forward pass to calculate the change of the cost with respect to the weights of the first layer, the second layer and so on.
The loss with respect to each node of a certain layer l and a certain node of that layer j: δjl
Now why is backpropagation better? Backpropagation makes sure you calculate all the values, including C, then we move backwards. By backwards, we calculate: ∂a3∂C.
We store this value as we will need it later too. Then we move a step back to calculate how the weight of the node W12 affects Cost. This is going to be:
∂W112∂C=∂a3∂C×∂W112∂a3
Now we just use the previously stored value and get this answer easily.
Let us get some notations clear before getting started with this:
δjl:The error of the jthnode of the lth layer,
Zjl:The Z of the jth node of the lth layer.
Δ: The small change made to Z.
We know that:
ajl=σ(Zjl)
When we want to make a small change to this activation, we do not mess with the activation as a whole instead we add a small change to Z.
Zjl=WT.X+b+Δ
Then pass it through the activation function. And then we calculate the error of the node:
δjl=∂Zjl∂C
Why does this make sense though? This is because if the value of error of the node is tiny, then on changing the node's parameters will not change the cost to a high extent. On the other hand, if it were high, that would mean, on changing the parameters of the node, we could drastically reduce the cost. Basically the same bang-per-buck idea.
On observing closely, This is just the gradient vector of the cost with respect to the activation of the Lth layer multiplied with the derivative of the activation function of Z of that node.
δL=∇aLJ.σ′(ZL)
This is possible to calculate for the last layer, because we already have the values of the expected outputs (The labels of the model). So we use this as the layer that comes after the last hidden layer and calculate the error of the Lth layer.
With this equation, if we know the error of the (l+1)th layer,δl+1. Then we can find the error of the lth layer, δl.
δl=((Wl+1)Tδl+1).σ′(Zl)
That is, if we take the dot product of the weights matrix of the (l+1)th and its errors. Then we element wise multiply it with the derivative of the activation function of the lth layer, we get the lthlayer errors.
Keep in mind that:
The weights matrix of a layer is of dimensions (input size x output size)
The error of a layer is a vector of J elements (where J is the number of neurons in that layer)
σ′(Zl) is a vector of J elements where J is the number of neurons in the layer.
Hence using this simple equation, we could just backpropagate and get the activations for all the layers.
4.2.3 Equation 3: Finding the derivative of the cost w.r.t any bias#
The derivative of cost with respect any bias is given by the:
δl=∂bl∂C
Now we could just use the chain rule again to get this:
δl=∂bl∂C=∂bl∂Zl×∂Zl∂C
But,
∂bl∂Zl=1
Hence,
δl=∂Zl∂C
4.2.4 Equation 4: Finding the derivative of the cost w.r.t any Weight#
∂wjkl∂c=akl−1.δjl
For the derivation, let us take 2 layers. The (l−1)th and the lth layers. Let take the weight going from the kthneuron to the jth neuron of the previous to the next layer respectively.
With the chain rule:
∂wjkl∂c=∂wjkl∂Zjl×∂Zjl∂C
The second term here is the error of the node. δjl
4.2.5 Finding the derivative of the cost w.r.t any Weight VECTORIZED#
Using the formula that we derived above, we can transform the weights matrix into a derivative. As we know, the weights matrix for a neural network which has j neurons in the lth layer. and kneurons in the (l−1)th layer.
Using the above formula, we get the element wise cost function derivation. Do not confuse this for matrix differentiation, which is completely different.