Deep learning and neural networksLoading progress…

Computation graphs

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

Break the formula into small steps

Training a network means finding how the loss changes when each weight changes. Writing that out for a whole network at once is messy. Backpropagation avoids the mess by first splitting the computation into tiny operations (add, multiply, square, tanh) and recording how they connect. That record is a computation graph.

Each node stores its forward value and its immediate inputs, the nodes with an arrow straight into it. Inputs such as a and b are leaves: they have no inputs of their own.

aba·bcp + cu²
Blue outlines are ready to compute. Click one.
order so far: none
The graph for L = (a·b + c)². Click nodes to compute them. A node can only be computed after its inputs, so the blue outlines show what is ready.

Predict first. For z = a*b + c, what are z's immediate inputs?

Sharing and counting

A value can feed more than one node: when a appears twice in a formula it has two arrows leaving it. The graph never loops back on itself, so there is always an order in which every node comes after its inputs. That order is what the forward pass follows and the next lessons reverse.

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.