Deep learning and neural networksLoading progress…

Gradient checks

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

Trust, but check numerically

Backpropagation bugs are quiet: the code runs, the loss even goes down a little, but a derivative rule is wrong. A gradient check catches this by comparing each analytic gradient with an estimate from the function itself, the central difference:

numeric ≈ ( L(w + h) − L(w − h) ) / (2h)
relative error = |analytic − numeric| / (|analytic| + |numeric|)

Try it on a tanh neuron. With a correct engine every parameter passes. Plant a wrong tanh rule and the parameters that flow through it fail, while the ones that do not pass through it still pass. That pattern tells you where to look.

log10 h−5
w−0.3∂ −3.085x2∂ 0.463w·x−0.6∂ −1.543b0.8∂ −1.543m + b0.2∂ −1.543tanh(z)0.197∂ −1.605y1∂ 1.605t − y−0.803∂ −1.605e²0.644∂ 1
parambackpropnumeric (central diff)relative errorverdict
w−3.085427−3.0854271.3e-11pass
x0.4628140.4628142.6e-12pass
b−1.542714−1.5427145.2e-12pass
y1.6052491.6052493.0e-12pass

h = 10^-5. A very large h measures the average slope over a wide step; a tiny h drowns in rounding error. Around 1e-5 the correct engine passes.

Change the planted bug and the step size h. A good h is around 1e-5: much larger and the estimate is too coarse, much smaller and rounding error takes over.

Predict first. Is demanding exact floating-point equality a good gradient check?

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.