Deep learning and neural networksLoading progress…

Reverse accumulation

Apply the chain rule through a graph and implement the core of a scalar reverse-mode autodiff engine.

Start at the output and walk backwards

The loss changes one-for-one with itself, so the backward pass starts with ∂L/∂L = 1. Going backwards, each node multiplies the gradient it received by its local derivatives and passes the results on. Multiplying along a path is the chain rule.

A value that affects the loss along more than one path receives a contribution from each path, and the contributions are added. This is the most common source of mistakes when doing backpropagation by hand.

x3∂ 2x + x6∂ 1
x feeds the add node twice. Each edge carries local derivative 1 × upstream 1 = 1. The gradient of x is the SUM of both: 1 + 1 = 2. (Check: x + x = 2x, whose slope is 2.)
Same idea, two graphs. In x + x the single input is used twice, so its gradient is 1 + 1. In a·b + a·c the input a collects a share from each product.

Predict first. If x is used twice in x + x, what is the derivative of the output with respect to x?

Reading the numbers

A gradient of zero does not mean the input is unimportant in general. It means that at this point a small change does nothing, for instance because a multiplying weight is zero or because the upstream gradient is zero.

Chapter context

Backpropagation organizes repeated applications of the chain rule. The graph stores how values depend on earlier values. Reverse traversal accumulates how the final loss changes with each intermediate quantity.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

If u=ab+cu=ab+c and L=u2L=u^2, then

∂L∂a=2ub,∂L∂b=2ua,∂L∂c=2u.\frac{\partial L}{\partial a}=2ub,\quad \frac{\partial L}{\partial b}=2ua,\quad \frac{\partial L}{\partial c}=2u.

At a shared node x, add all downstream contributions: ∂L/∂x=∑j(∂L/∂uj)(∂uj/∂x)\partial L/\partial x=\sum_j(\partial L/\partial u_j)(\partial u_j/\partial x).

Worked example

Take a=2,b=3,c=1. Then u=7 and L=49. The upstream gradient into u is 14, giving dL/da=14×3=42, dL/db=14×2=28 and dL/dc=14. These are sensitivities, not new parameter values; an optimizer still needs a step size.

Reference code

The following example uses the Python standard library and does not call a remote service.

from math import isclose
a,b,c = 2.,3.,1.
u = a*b+c
assert u*u == 49
assert (2*u*b,2*u*a,2*u) == (42,28,14)
h = 1e-5
f = lambda x: (x*b+c)**2
assert isclose((f(a+h)-f(a-h))/(2*h), 42, rel_tol=1e-7)

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Forward values and gradients are different objects. Shared branches require addition of gradient contributions. Backpropagation computes derivatives; the optimizer changes parameters.