ai theory
CS231n Notes (4) — Backpropagation
Junyoung Park · 2022-11-05 · 10 min
Introduction
The preceding articles introduced support vector machines and softmax for linear classification, along with the hinge and cross-entropy losses used to optimize them. We also saw score function , parameterized by weight and bias , and gradient descent as a way to optimize the score and loss. Because calculating an analytic gradient over every sample is computationally expensive, we introduced stochastic gradient descent and mini-batch gradient descent, which reduces update noise while remaining efficient.
This article explains how to optimize parameters efficiently in multilayer networks, beginning with a single perceptron. Along the way, it traces a central episode in the early history, decline, and revival of artificial intelligence.
Perceptron
The idea of an artificial neural network first appeared in the 1943 paper A Logical Calculus of the Ideas Immanent in Nervous Activity. McCulloch and Pitts described the human nervous system as a network of connected logical switches. Their work did not yet produce a practical learning algorithm. A more applied proposal arrived with Frank Rosenblatt's 1958 perceptron, which helped open the first era of AI enthusiasm.
Rosenblatt's perceptron was a feed-forward neural network for linear classification. It multiplied inputs by weights, applied an activation function, and returned or according to whether the result crossed a threshold.
Modern deep learning differs in detail, but its fundamental structure remains a collection of these functions arranged into many nodes and layers. Like deep learning today, the perceptron attracted enormous academic and media attention, along with predictions that AI would soon replace much of the world.
Enthusiasm declined sharply after Marvin Minsky and Seymour Papert mathematically analyzed its limits in the 1969 book Perceptrons.
They pointed out that a single linear perceptron cannot implement even XOR. Failure on such a basic logical function implied that it could not solve many ordinary nonlinear problems. Minsky noted that a multilayer perceptron might represent XOR, but no effective method was then available to train all of its parameters.
Multilayer Perceptron
Interest in neural networks faded, but some researchers continued. The 1986 book Parallel Distributed Processing popularized multilayer perceptrons with hidden layers and the backpropagation algorithm. A single perceptron is restricted to linear classification; a multilayer perceptron (MLP) adds hidden layers that can compose multiple decision boundaries and represent functions such as XOR.
More layers also mean many more weights and biases, making their optimization difficult. Backpropagation solves this by sending an input forward through the network, comparing the prediction with ground truth, and propagating the resulting error backward to update every parameter.
Backpropagation
Let us make that concept concrete. For a single-layer linear classifier, score function contains weights and biases; by folding the bias into an augmented weight and input, we can write it using only :
Loss metric compares prediction with ground truth and produces error . The error tells us in which direction the output must move. Because the prediction depends on inputs and , derivatives tell us how those inputs affect the error. For the parameter , we need
This derivative is only the local slope of the loss surface; it does not reveal a global minimum directly. Gradient descent still takes small steps along the negative gradient.
By the chain rule,
Now suppose there are many layers, with activation function :
Let denote the activation after layer , so . The last layer's weight gradient is a product of local derivatives:
For the preceding layer,
The pattern continues: values already calculated in the forward pass supply local derivatives, and backward propagation multiplies them according to the chain rule.
Consider the simple computational graph below.
Green values are forward inputs and intermediate results; red values are gradients. Let and . The gradient arriving at output is . Because , the gradient at is . Addition has local derivative one for both inputs, so and . Finally, .
The example illustrates two principles:
- Values calculated during the forward pass are reused to calculate gradients for inputs during the backward pass.
- Starting from the final output gradient, each earlier gradient is obtained by multiplying local derivatives according to the chain rule.
Interpreting Backpropagation
Backpropagation is a local process. Every gate in a computational graph receives inputs and calculates two kinds of result: its forward output and local derivatives of that output with respect to its inputs. These calculations depend only on the gate's own operation, regardless of the surrounding graph.
After one forward pass, backpropagation can therefore compute the derivative of the final output with respect to every parameter. The chain rule multiplies the gradient arriving from downstream by each local input derivative. Multiplication along all backward paths is what allows otherwise independent perceptrons in a complicated network to learn together.
Return to the graph:
The addition gate receives and and outputs . Its local derivatives with respect to both inputs are . The complete graph outputs , and the backward gradient arriving at is .
If we wanted the final graph output to increase, a negative gradient at says that itself should decrease. The gate multiplies this arriving gradient by its local derivative for every input, giving for both and . Decreasing either input decreases ; because is negative, that increases final output .
In general, multiplying a gate's incoming gradient by its local input gradients tells us how far and in which direction each input should change to move toward the desired output. Backpropagation lets all gates communicate, with chain-rule derivatives as the message.
Example: A Two-Dimensional Neuron
This is one neuron with a two-dimensional input and sigmoid activation. Its computational graph uses the following elementary derivatives:
The figure records forward values in green and gradients in red. Start at the output with gradient . The reciprocal gate has local derivative . At input , the backward value is approximately
Adding one has derivative one, so passes through unchanged. The exponential gate's local derivative equals its output, , producing . Multiplication by flips the sign to .
The path now branches through additions. Addition passes the same to both inputs. At multiplication gates, each input's local derivative is the other input: if , then and . Thus the weight gradient for is , while the input gradient for is . Small numerical differences in the diagram come from rounding. The same calculation gives gradients and for and .
Sigmoid Gradient
The graph above decomposes sigmoid into elementary operations, but its analytic derivative is simpler:
Rearranging,
In the example, the preactivation gives , so the derivative is immediately
Conclusion
We traced how multilayer networks overcame the linear limits of a single perceptron, and how backpropagation made those networks trainable. Local gradients connect every component of a network through the chain rule. This was the mechanism that allowed neural networks to move beyond the problems a perceptron could not solve and begin the path toward modern deep learning.
Appendix
Question: Draw a computational graph for the following equation and calculate its gradients in Python.
Answer
x = 3 # example values
y = -4
# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator #(1)
num = x + sigy # numerator #(2)
sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # denominator #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)
# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
Answer Walkthrough
Matching the code's values and gradients to the diagram gives the following result.
Red values were computed in the forward pass; green values are backward gradients. The final gate multiplies num and invden. Since the output gradient is one, each input receives the other input as its gradient:
dnum = invden
dinvden = num
The reciprocal gate multiplies by local derivative at input den:
dden = (-1.0 / (den**2)) * dinvden
Because den adds sigx and xpysqr, its gradient passes unchanged to both:
dsigx = (1) * dden
dxpysqr = (1) * dden
The square gate uses derivative :
dxpy = (2 * xpy) * dxpysqr
The addition xpy = x + y passes that gradient to both variables:
dx = (1) * dxpy
dy = (1) * dxpy
The sigmoid derivative derived above adds another contribution to dx:
dx += ((1 - sigx) * sigx) * dsigx
The numerator addition likewise passes dnum to x and sigy. Notice that dx already contains a contribution from the denominator path, so gradients from the two paths are accumulated:
dx += (1) * dnum
dsigy = (1) * dnum
Finally, the second sigmoid contributes to dy:
dy += ((1 - sigy) * sigy) * dsigy