Deep learning and neural networksLoading progress…

Topological order

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

Why the order is not optional

The forward pass must compute a node only after all of its inputs. The backward pass is the mirror image: a node may send its gradient to its inputs only after every node that uses it has already sent its share. Otherwise it passes along an incomplete number, and the error spreads to everything upstream.

A topological order is any ordering of the nodes where every node comes after its inputs. Run it forwards to compute values, and in reverse to send gradients.

Forward pass. Click nodes to compute them (blue = ready).

aba·bp + ap·qr + q
forward order: none

Backward pass. Click the operation nodes in the order you want to push gradients back. A node that goes too early pushes an incomplete gradient and turns red.

No backward order chosen yet.
Try the forward pass, then try different backward orders. A node that goes too early turns red and the final gradients come out wrong.

Predict first. Why is an arbitrary node order unsafe for the backward pass?

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.