Type at least 2 letters. Enter opens the first result, Esc closes.

    Calculus for ML · Lesson 4 · Chain rule, automatic differentiation and backpropagation

    The chain rule: products of Jacobians and computational graphs

    Intermediate 70 min 4 interactive figures 3 videos Colab notebook

    Before this lesson: lesson 01 (the 1-D chain rule and linear approximation), lesson 02 (partial derivatives, the gradient), lesson 03 (the derivative as a linear map, the Jacobian and its shape), and linear algebra lesson 01 (the product ABAB is the map "first BB, then AA"). Time: about 70 minutes, plus the notebook.


    Why this matters #

    Backpropagation is the chain rule, applied in a clever order. Every deep-learning framework (PyTorch, JAX, TensorFlow) stores your model as a computational graph and runs the chain rule on it. When you understand the chain rule as a product of Jacobians, you can:

    • derive the gradient of any new layer, loss or custom operation by hand (and check it);
    • see why a residual connection y=x+F(x)y = x + F(x) helps the gradient reach early layers: the two paths add, and the identity path passes the gradient unchanged;
    • see why a weight used in many places (an RNN cell, tied input and output embeddings) gets the sum of the gradients of all its uses;
    • understand the adjoint models of science (weather data assimilation, seismic imaging): they are the same backward sweep, run through a simulator.

    1. Predict first #

    2. The picture #

    One variable first: rates multiply. Take y=(3x+1)2y = (3x+1)^2. Write it as two steps: u=3x+1u = 3x+1 and y=u2y = u^2. At x=1x = 1: u=4u = 4, du/dx=3du/dx = 3, and dy/du=2u=8dy/du = 2u = 8. A small step Δx\Delta x gives Δu≈3 Δx\Delta u \approx 3\,\Delta x, and this step in uu gives Δy≈8 Δu\Delta y \approx 8\,\Delta u. So

    Δy≈8⋅3 Δx=24 Δx,dydx=dydu dudx=24.\Delta y \approx 8 \cdot 3\,\Delta x = 24\,\Delta x, \qquad \frac{dy}{dx} = \frac{dy}{du}\,\frac{du}{dx} = 24 .

    Check: y=9x2+6x+1y = 9x^2 + 6x + 1 gives dy/dx=18x+6=24dy/dx = 18x + 6 = 24 at x=1x = 1.

    Interactive figure: loading…

    Move the Δx slider toward 0. A small step on the xx line becomes a step on the uu line and then on the yy line. The violet bars are the true changes; the dashed blue bars are the linear predictions u′(x)Δxu'(x)\Delta x and y′(u) u′(x)Δxy'(u)\,u'(x)\Delta x. The magenta gap shrinks faster than Δx\Delta x, so Δy/Δx\Delta y/\Delta x tends to the product y′(u) u′(x)y'(u)\,u'(x). Try the other presets: the rates are now different at every point.

    Many variables: linear maps compose. Near a point xx, a differentiable map ff acts on a small step hh like its Jacobian (lesson 03): f(x+h)≈f(x)+Df(x) hf(x + h) \approx f(x) + Df(x)\,h. Near u=f(x)u = f(x), the map gg acts like Dg(u)Dg(u). So the step hh goes through two matrices:

    h  →  Df(x)    Df(x) h  →  Dg(f(x))    Dg(f(x)) Df(x) h.h \;\xrightarrow{\;Df(x)\;}\; Df(x)\,h \;\xrightarrow{\;Dg(f(x))\;}\; Dg(f(x))\,Df(x)\,h .

    Composing two linear maps multiplies their matrices (linear algebra lesson 01). The map applied first stands on the right. The figure shows this for two maps of the plane.

    Interactive figure: loading…

    Make the square smaller with the ε slider. The violet square around xx is mapped by ff (a rotation and scaling near each point) and then by gg (a shear). The true images are slightly curved. The dashed blue parallelograms are the Jacobian predictions Df⋅squareDf\cdot\text{square} and Dg Df⋅squareDg\,Df\cdot\text{square}; for small ε\varepsilon they match the true images. Press show wrong order: the magenta parallelogram Df Dg⋅squareDf\,Dg\cdot\text{square} does not match, even for tiny squares. Each panel is zoomed around its point.

    Quick check

    f:R4→R3f:\R^4\to\R^3 and g:R3→R2g:\R^3\to\R^2. What is the shape of D(g∘f)(x)D(g\circ f)(x)?

    A program is a graph. A formula like f(x1,x2)=ln⁡x1+x1x2−sin⁡x2f(x_1, x_2) = \ln x_1 + x_1x_2 - \sin x_2 is computed in small steps. Give each step a name: v1=ln⁡x1v_1 = \ln x_1, v2=x1x2v_2 = x_1x_2, v3=sin⁡x2v_3 = \sin x_2, v4=v1+v2v_4 = v_1 + v_2, v5=v4−v3v_5 = v_4 - v_3. Draw one node per step and an arrow from each value to the steps that use it. This is the computational graph of ff (the example is from Baydin et al. 2018).

    To get the derivatives, sweep the graph twice. The forward run computes all values from the inputs to the output. The backward run starts at the output with the number 1 and walks back. At each node it multiplies by the local derivative on the edge (the derivative of one small step). Where a value feeds two nodes, the two contributions add.

    Interactive figure: loading…

    Press Next step and predict each number before it appears. The first 7 steps compute the values (first number under each node). Then the backward run fills the orange adjoints vˉi=∂f/∂vi\bar v_i = \partial f/\partial v_i from right to left. Watch x2x_2 and x1x_1: each is used twice, so it receives two contributions, and the second one is added (magenta). At the end, xˉ1=5.5\bar x_1 = 5.5 and xˉ2=1.716\bar x_2 = 1.716 are the two partial derivatives. Switch to forward (tangents) to get one directional derivative instead.

    Quick check

    For ff and gg as above, which expression is the Jacobian of g∘fg\circ f at xx?

    3. Definitions and main results #

    We use the notation of lesson 03. Vectors are columns. For f:Rn→Rmf:\R^n\to\R^m, the Jacobian Df(x)∈Rm×nDf(x)\in\R^{m\times n} has entries [Df(x)]ij=∂fi/∂xj[Df(x)]_{ij} = \partial f_i/\partial x_j (outputs × inputs). ff is differentiable at xx if

    f(x+h)=f(x)+Df(x) h+r(h),∥r(h)∥∥h∥→0 as h→0.f(x + h) = f(x) + Df(x)\,h + r(h), \qquad \frac{\|r(h)\|}{\|h\|} \to 0 \text{ as } h \to 0 .

    In words: the error r(h)r(h) of the linear approximation is small even compared with the step hh. We write this as r(h)=o(∥h∥)r(h) = o(\|h\|).

    Theorem (chain rule) #

    Let f:Rn→Rmf:\R^n\to\R^m be differentiable at xx, and g:Rm→Rpg:\R^m\to\R^p differentiable at u=f(x)u = f(x). Then g∘fg\circ f is differentiable at xx, and

    D(g∘f)(x)=Dg(f(x)) Df(x),(p×m)⋅(m×n)=p×n.D(g\circ f)(x) = Dg(f(x))\,Df(x), \qquad (p\times m)\cdot(m\times n) = p\times n .

    Why it is true. Write A=Df(x)A = Df(x) and B=Dg(u)B = Dg(u). A step hh moves ff by k=Ah+rf(h)k = Ah + r_f(h). This step kk moves gg by Bk+rg(k)=BAh+(B rf(h)+rg(k))Bk + r_g(k) = BAh + \big(B\,r_f(h) + r_g(k)\big). The main part is BAhBAh, a linear function of hh. The rest is a sum of two errors, and both are o(∥h∥)o(\|h\|): B rf(h)B\,r_f(h) because BB is a fixed matrix, and rg(k)r_g(k) because kk is at most a constant times ∥h∥\|h\|. So BABA is the derivative. The full proof makes "the rest is small" precise.

    Full proof (six steps)

    Let ∥⋅∥\|\cdot\| be the Euclidean norm, and for a matrix the induced operator norm ∥A∥=max⁡h≠0∥Ah∥/∥h∥\|A\| = \max_{h\ne0}\|Ah\|/\|h\| (linear algebra lesson 11), so that ∥Ah∥≤∥A∥ ∥h∥\|Ah\| \le \|A\|\,\|h\|.

    1. By the definition of the derivative, f(x+h)=u+Ah+rf(h)f(x+h) = u + Ah + r_f(h) with ∥rf(h)∥/∥h∥→0\|r_f(h)\|/\|h\| \to 0, and g(u+k)=g(u)+Bk+rg(k)g(u+k) = g(u) + Bk + r_g(k) with ∥rg(k)∥/∥k∥→0\|r_g(k)\|/\|k\| \to 0.
    2. Set ρ(k)=∥rg(k)∥/∥k∥\rho(k) = \|r_g(k)\|/\|k\| for k≠0k \ne 0 and ρ(0)=0\rho(0) = 0. Then ∥rg(k)∥=ρ(k) ∥k∥\|r_g(k)\| = \rho(k)\,\|k\| for every kk (also for k=0k = 0, because rg(0)=0r_g(0) = 0), and ρ(k)→0\rho(k) \to 0 as k→0k \to 0. This trick avoids dividing by ∥k∥\|k\|, which can be 0 even when h≠0h \ne 0.
    3. Put k(h)=f(x+h)−u=Ah+rf(h)k(h) = f(x+h) - u = Ah + r_f(h). Then g(f(x+h))=g(u)+BAh+R(h)g(f(x+h)) = g(u) + BAh + R(h) with R(h)=B rf(h)+rg(k(h))R(h) = B\,r_f(h) + r_g(k(h)).
    4. First part: ∥B rf(h)∥≤∥B∥ ∥rf(h)∥\|B\,r_f(h)\| \le \|B\|\,\|r_f(h)\|, so ∥B rf(h)∥/∥h∥→0\|B\,r_f(h)\|/\|h\| \to 0.
    5. Second part: for small hh, ∥rf(h)∥≤∥h∥\|r_f(h)\| \le \|h\|, so ∥k(h)∥≤(∥A∥+1)∥h∥\|k(h)\| \le (\|A\| + 1)\|h\|. Hence k(h)→0k(h) \to 0 and ∥rg(k(h))∥/∥h∥=ρ(k(h)) ∥k(h)∥/∥h∥≤ρ(k(h)) (∥A∥+1)→0\|r_g(k(h))\|/\|h\| = \rho(k(h))\,\|k(h)\|/\|h\| \le \rho(k(h))\,(\|A\| + 1) \to 0.
    6. So R(h)=o(∥h∥)R(h) = o(\|h\|). By the definition (and the uniqueness) of the derivative, D(g∘f)(x)=BAD(g\circ f)(x) = BA. ■\blacksquare

    Shape check. Always check (p×m)(m×n)(p\times m)(m\times n): the inner sizes must agree, and the result has the outer sizes. If the shapes do not fit, the order is wrong. If the shapes fit by accident (square matrices), the order can still be wrong: matrix products do not commute.

    Longer chains. For F=fL∘⋯∘f1F = f_L\circ\cdots\circ f_1 with x0=xx_0 = x, xk=fk(xk−1)x_k = f_k(x_{k-1}) and Jk=Dfk(xk−1)J_k = Df_k(x_{k-1}), induction on LL gives DF(x)=JL JL−1⋯J1DF(x) = J_L\,J_{L-1}\cdots J_1. Each JkJ_k is evaluated at the input of its own step.

    The entry form #

    Read the product Dg DfDg\,Df entry by entry. Entry (i,j)(i, j) is row ii of DgDg times column jj of DfDf:

    ∂(g∘f)i∂xj=∑k=1m∂gi∂uk ∂fk∂xj.\frac{\partial (g\circ f)_i}{\partial x_j} = \sum_{k=1}^{m} \frac{\partial g_i}{\partial u_k}\,\frac{\partial f_k}{\partial x_j} .

    In words: sum over every intermediate variable uku_k through which xjx_j can change gig_i. Each term is the product of the rates along one path xj→uk→gix_j \to u_k \to g_i.

    The case most people learn first. Let f(x,y)f(x, y) be a function of two variables, and let x(t)x(t), y(t)y(t) be functions of time. Then t↦(x(t),y(t))t \mapsto (x(t), y(t)) has the 2×12\times1 Jacobian (x′(t),y′(t))⊤(x'(t), y'(t))^\top, and ff has the 1×21\times 2 Jacobian (∂xf,∂yf)(\partial_x f, \partial_y f). Their product is

    ddtf(x(t),y(t))=∂f∂x x′(t)+∂f∂y y′(t).\frac{d}{dt} f(x(t), y(t)) = \frac{\partial f}{\partial x}\,x'(t) + \frac{\partial f}{\partial y}\,y'(t) .

    Example. f(x,y)=x2yf(x, y) = x^2y with x(t)=cos⁡tx(t) = \cos t, y(t)=sin⁡ty(t) = \sin t, at t=0t = 0: x=1x = 1, y=0y = 0, x′=−sin⁡0=0x' = -\sin 0 = 0, y′=cos⁡0=1y' = \cos 0 = 1. So ddtf=2xy⋅0+x2⋅1=1\frac{d}{dt}f = 2xy\cdot 0 + x^2\cdot 1 = 1. Check: f(x(t),y(t))=cos⁡2t sin⁡tf(x(t), y(t)) = \cos^2 t\,\sin t has derivative −2cos⁡tsin⁡2t+cos⁡3t-2\cos t\sin^2 t + \cos^3 t, which is 1 at t=0t = 0.

    A gradient through a map is a vector-Jacobian product #

    In ML the last function is almost always a scalar loss ℓ:Rm→R\ell:\R^m\to\R. Its Jacobian is the row ∇ℓ⊤\nabla\ell^\top (lesson 02). The chain rule gives the 1×n1\times n row ∇ℓ(f(x))⊤ Df(x)\nabla\ell(f(x))^\top\,Df(x). Transpose it to get a column:

    ∇(ℓ∘f)(x)=Df(x)⊤ ∇ℓ(f(x)).\nabla(\ell\circ f)(x) = Df(x)^\top\,\nabla\ell(f(x)) .

    This is a vector-Jacobian product (VJP): a vector times the Jacobian, written as Df⊤uDf^\top u (or u⊤Dfu^\top Df as a row). We write the gradient of the loss with respect to any intermediate value uu as uˉ=∂ℓ/∂u\bar u = \partial\ell/\partial u. It has the same shape as uu and is called the adjoint of uu. The formula above says:

    xˉ=Df(x)⊤ uˉfor u=f(x).\bar x = Df(x)^\top\,\bar u \qquad\text{for } u = f(x).

    The adjoint of the output, multiplied by the transposed local Jacobian, gives the adjoint of the input. Three VJPs appear in every network.

    (a) Linear map, z=Wxz = Wx with W∈Rm×nW\in\R^{m\times n}. The Jacobian with respect to xx is WW itself, so xˉ=W⊤zˉ\bar x = W^\top \bar z.

    (b) Elementwise map, a=φ(z)a = \varphi(z) (for example tanh⁡\tanh or ReLU applied to each entry). The Jacobian is diag⁡(φ′(z))\operatorname{diag}(\varphi'(z)) (lesson 03), a diagonal matrix, so zˉ=φ′(z)⊙aˉ\bar z = \varphi'(z)\odot\bar a. Here ⊙\odot is the elementwise product. Never build the diagonal matrix in code: it has m2m^2 entries, and only mm of them are nonzero.

    (c) The weights, z=Wxz = Wx as a function of WW. Now WW is the variable and xx is fixed.

    Proposition. If z=Wxz = Wx and zˉ=∂ℓ/∂z\bar z = \partial\ell/\partial z, then Wˉ=∂ℓ/∂W=zˉ x⊤\bar W = \partial\ell/\partial W = \bar z\,x^\top, an m×nm\times n matrix (the shape of WW).

    Proof.

    1. zk=∑jWkjxjz_k = \sum_j W_{kj}x_j, so ∂zk/∂Wij=xj\partial z_k/\partial W_{ij} = x_j if k=ik = i, and 0 otherwise.
    2. By the entry form of the chain rule, ∂ℓ/∂Wij=∑kzˉk ∂zk/∂Wij=zˉi xj\partial\ell/\partial W_{ij} = \sum_k \bar z_k\,\partial z_k/\partial W_{ij} = \bar z_i\,x_j.
    3. The matrix with entries zˉixj\bar z_i x_j is the outer product zˉ x⊤\bar z\,x^\top. ■\blacksquare

    An outer product of two vectors has rank at most 1. So for one sample, the gradient of a linear layer is a rank-1 matrix. Lesson 05 returns to this fact.

    Quick check

    z=Wxz = Wx with W∈R3×5W\in\R^{3\times 5}, and the upstream gradient is zˉ∈R3\bar z\in\R^3. What is xˉ=∂ℓ/∂x\bar x = \partial\ell/\partial x?

    Fan-out: the contributions of all paths add #

    A value fans out when it is used by more than one later step. For example, xx feeds two maps, u=f(x)u = f(x) and w=g(x)w = g(x), and the loss is ℓ=F(u,w)\ell = F(u, w).

    Theorem (paths add). With uˉ=∇uF\bar u = \nabla_u F and wˉ=∇wF\bar w = \nabla_w F,

    xˉ=Df(x)⊤ uˉ+Dg(x)⊤ wˉ.\bar x = Df(x)^\top\,\bar u + Dg(x)^\top\,\bar w .

    Proof.

    1. Stack the two maps into one: s(x)=(f(x),g(x))s(x) = (f(x), g(x)). Its Jacobian is the stacked block matrix Ds=(DfDg)Ds = \begin{pmatrix} Df \\ Dg\end{pmatrix}, because the rows of DsDs are the rows of DfDf and then the rows of DgDg.
    2. ℓ=F(s(x))\ell = F(s(x)), so the VJP rule gives xˉ=Ds⊤ ∇F=(Df⊤Dg⊤)(uˉwˉ)\bar x = Ds^\top\,\nabla F = \begin{pmatrix} Df^\top & Dg^\top\end{pmatrix}\begin{pmatrix}\bar u\\ \bar w\end{pmatrix}.
    3. Multiply the blocks: xˉ=Df⊤uˉ+Dg⊤wˉ\bar x = Df^\top\bar u + Dg^\top\bar w. ■\blacksquare

    So the gradient through each path is computed as if the other path did not exist, and then the results are added. The same is true for any number of paths.

    Interactive figure: loading…

    Drag the violet point along the curve. Here xx feeds two branches, and yy depends on both. The bars show the path-1 and path-2 products and their sum. Only the tangent with the summed slope touches the curve; the magenta lines use one path only. Try residual: the identity path always contributes 1. Try shared weight: one parameter θ\theta is used in two places.

    Three places where fan-out matters:

    • Residual connections. y=x+F(x)y = x + F(x). The identity path and the FF path add: xˉ=yˉ+DF(x)⊤yˉ\bar x = \bar y + DF(x)^\top\bar y. Even if DF⊤yˉDF^\top\bar y is tiny, the gradient yˉ\bar y still reaches xx through the identity path.
    • Shared weights. A parameter used in several places (the same RNN weight at every time step, tied input and output embeddings) gets the sum of the gradients of all its uses.
    • A variable used twice in a formula. In y=x2sin⁡xy = x^2\sin x, the product rule 2xsin⁡x+x2cos⁡x2x\sin x + x^2\cos x is exactly "path through x2x^2" plus "path through sin⁡x\sin x".
    Quick check

    y=u wy = u\,w with u=x2u = x^2 and w=sin⁡xw = \sin x. What is dy/dxdy/dx at x=πx = \pi?

    Computational graphs #

    Definitions.

    • A primitive operation is a small function whose derivative we know in closed form: ++, −-, ×\times, //, exp⁡\exp, ln⁡\ln, sin⁡\sin, tanh⁡\tanh, a matrix product, a sum over an axis.
    • A program computes intermediate variables viv_i, one per primitive operation. We number the nn inputs v1−n,…,v0v_{1-n}, \dots, v_0 and the later variables v1,v2,…v_1, v_2, \dots in the order they are computed. This order is a topological order: every viv_i uses only variables with smaller index.
    • The computational graph has one node per variable and an edge j→ij \to i when the operation that makes viv_i uses vjv_j directly. Then jj is a parent of ii, and ii is a child of jj. The graph has no cycles: it is a directed acyclic graph (DAG).
    • The local derivative on edge j→ij\to i is ∂vi/∂vj\partial v_i/\partial v_j: the derivative of the one operation that makes viv_i, with respect to one of its arguments, the others fixed.

    The example. For f(x1,x2)=ln⁡x1+x1x2−sin⁡x2f(x_1, x_2) = \ln x_1 + x_1x_2 - \sin x_2:

    variable operation parents local derivatives
    v−1=x1v_{-1} = x_1 input
    v0=x2v_0 = x_2 input
    v1=ln⁡v−1v_1 = \ln v_{-1} ln⁡\ln v−1v_{-1} ∂v1/∂v−1=1/v−1\partial v_1/\partial v_{-1} = 1/v_{-1}
    v2=v−1 v0v_2 = v_{-1}\,v_0 ×\times v−1,v0v_{-1}, v_0 ∂v2/∂v−1=v0\partial v_2/\partial v_{-1} = v_0,  ∂v2/∂v0=v−1\ \partial v_2/\partial v_0 = v_{-1}
    v3=sin⁡v0v_3 = \sin v_0 sin⁡\sin v0v_0 ∂v3/∂v0=cos⁡v0\partial v_3/\partial v_0 = \cos v_0
    v4=v1+v2v_4 = v_1 + v_2 ++ v1,v2v_1, v_2 11, 11
    v5=v4−v3v_5 = v_4 - v_3 −- v4,v3v_4, v_3 11, −1-1

    x1x_1 has two children (v1v_1 and v2v_2) and x2x_2 has two children (v2v_2 and v3v_3): both fan out.

    Adjoints and tangents. Let yy be the output. For every node:

    • the adjoint vˉj=∂y/∂vj\bar v_j = \partial y/\partial v_j says how much yy changes when vjv_j changes and everything computed from vjv_j is computed again;
    • the tangent v˙i=ddtvi(x+tx˙)∣t=0\dot v_i = \frac{d}{dt}v_i(x + t\dot x)\big|_{t=0} says how fast viv_i changes when the inputs move in a chosen direction x˙\dot x.

    Theorem (the two sweeps).

    1. Backward sweep. Set yˉ=1\bar y = 1. Then, for jj in reverse topological order,
    vˉj=∑i ∈ children(j)vˉi ∂vi∂vj.\bar v_j = \sum_{i\,\in\,\text{children}(j)} \bar v_i\,\frac{\partial v_i}{\partial v_j}.

    At the end, vˉ1−n,…,vˉ0\bar v_{1-n}, \dots, \bar v_0 are the partial derivatives ∂y/∂x1,…,∂y/∂xn\partial y/\partial x_1, \dots, \partial y/\partial x_n. 2. Forward sweep. Set the input tangents to x˙\dot x. Then, for ii in topological order,

    v˙i=∑j ∈ parents(i)∂vi∂vj v˙j.\dot v_i = \sum_{j\,\in\,\text{parents}(i)} \frac{\partial v_i}{\partial v_j}\,\dot v_j.

    At the end, y˙=∇y⊤x˙\dot y = \nabla y^\top\dot x, the directional derivative of yy along x˙\dot x.

    Proof of the backward sweep.

    1. vjv_j enters the rest of the program only through its children. Give each use its own copy: child ii uses a copy cic_i, and all copies equal vjv_j. Then yy is a function HH of the copies (all other earlier variables stay fixed), and y(vj)=H(vj,…,vj)y(v_j) = H(v_j, \dots, v_j).
    2. By the fan-out rule (or the entry form with one input and several paths), ∂y/∂vj=∑i∂H/∂ci\partial y/\partial v_j = \sum_i \partial H/\partial c_i.
    3. The copy cic_i changes yy only through viv_i. By the 1-D chain rule, ∂H/∂ci=(∂y/∂vi) (∂vi/∂ci)=vˉi ∂vi/∂vj\partial H/\partial c_i = (\partial y/\partial v_i)\,(\partial v_i/\partial c_i) = \bar v_i\,\partial v_i/\partial v_j.
    4. Children come later in the topological order. In reverse order, all vˉi\bar v_i of the children are known when we reach jj. Induction from the output (where yˉ=1\bar y = 1) to the inputs gives the claim. ■\blacksquare

    Proof of the forward sweep. Move the inputs along x(t)=x+tx˙x(t) = x + t\dot x. Each viv_i is a function of its parents, so the entry form of the chain rule gives ddtvi=∑j∂vi∂vjddtvj\frac{d}{dt}v_i = \sum_j \frac{\partial v_i}{\partial v_j}\frac{d}{dt}v_j. The parents come earlier, so their tangents are known. At the output, the chain rule gives y˙=Dy x˙=∇y⊤x˙\dot y = Dy\,\dot x = \nabla y^\top\dot x. ■\blacksquare

    Graph and sweep are different things. The graph says what is computed. A sweep says in which order the local derivatives are combined. One backward sweep gives the derivatives of one output with respect to all inputs. One forward sweep gives the derivatives of all outputs along one input direction.

    Quick check

    In the backward sweep, a node vjv_j has two children, vav_a and vbv_b. What is vˉj\bar v_j?

    Why the order matters (a preview of lesson 05) #

    For a chain F=fL∘⋯∘f1F = f_L\circ\cdots\circ f_1, the Jacobian is JL⋯J1J_L\cdots J_1. Matrix multiplication is associative, so every way of placing the brackets gives the same matrix. But the costs differ a lot:

    • Forward: push an input direction vv through the chain, JL(⋯(J2(J1v)))J_L(\cdots(J_2(J_1 v))). Every step is a matrix-vector product. The result is DF vDF\,v, a Jacobian-vector product (JVP).
    • Backward: pull an output weight uu back, (⋯((u⊤JL)JL−1)⋯ )J1(\cdots((u^\top J_L)J_{L-1})\cdots)J_1. Every step is again a matrix-vector product. The result is u⊤DFu^\top DF, a VJP.

    For a scalar loss, the last Jacobian JLJ_L is a single row. Starting there, every intermediate result stays a single row, and one backward sweep gives the whole gradient. This is why training uses the backward order. Lesson 05 turns the two sweeps into the forward and reverse modes of automatic differentiation and counts their costs. Linear algebra lesson 15 gives a short first view of JVPs and VJPs.

    Answers to section 1. (1) 2×42\times 4, and Dg DfDg\,Df is the right product: (2×3)(3×4)(2\times3)(3\times4). The other order Df DgDf\,Dg would be (3×4)(2×3)(3\times 4)(2\times 3), which is not defined. (2) Add. x1x_1 reaches ff along two paths, through ln⁡x1\ln x_1 and through x1x2x_1x_2, and the two contributions add: ∂f/∂x1=1/x1+x2\partial f/\partial x_1 = 1/x_1 + x_2. At (2,5)(2, 5) this is 0.5+5=5.50.5 + 5 = 5.5.

    4. Worked example #

    (a) A chain rule with Jacobians. Let f:R2→R3f:\R^2\to\R^3 and g:R3→Rg:\R^3\to\R be

    f(x)=(x1x2x1+x2x22),g(u)=u1u2+u3,x=(12).f(x) = \begin{pmatrix} x_1x_2 \\ x_1 + x_2 \\ x_2^2\end{pmatrix}, \qquad g(u) = u_1u_2 + u_3, \qquad x = \begin{pmatrix}1\\2\end{pmatrix}.
    1. Forward: u=f(1,2)=(2,3,4)u = f(1, 2) = (2, 3, 4).
    2. Jacobian of ff (rows = outputs): Df(x)=(x2x11102x2)=(211104)Df(x) = \begin{pmatrix} x_2 & x_1 \\ 1 & 1 \\ 0 & 2x_2\end{pmatrix} = \begin{pmatrix} 2 & 1 \\ 1 & 1 \\ 0 & 4\end{pmatrix}, a 3×23\times 2 matrix.
    3. Gradient of gg: ∇g(u)=(u2,u1,1)=(3,2,1)\nabla g(u) = (u_2, u_1, 1) = (3, 2, 1).
    4. VJP: ∇(g∘f)(x)=Df⊤∇g=(210114)(321)=(6+2+03+2+4)=(89)\nabla(g\circ f)(x) = Df^\top\nabla g = \begin{pmatrix} 2 & 1 & 0 \\ 1 & 1 & 4\end{pmatrix}\begin{pmatrix}3\\2\\1\end{pmatrix} = \begin{pmatrix} 6 + 2 + 0 \\ 3 + 2 + 4\end{pmatrix} = \begin{pmatrix}8\\9\end{pmatrix}.
    5. Check directly: g(f(x))=x1x2(x1+x2)+x22=x12x2+x1x22+x22g(f(x)) = x_1x_2(x_1 + x_2) + x_2^2 = x_1^2x_2 + x_1x_2^2 + x_2^2. Then ∂1=2x1x2+x22=4+4=8\partial_1 = 2x_1x_2 + x_2^2 = 4 + 4 = 8 and ∂2=x12+2x1x2+2x2=1+4+4=9\partial_2 = x_1^2 + 2x_1x_2 + 2x_2 = 1 + 4 + 4 = 9. ✓

    (b) The graph of f(x1,x2)=ln⁡x1+x1x2−sin⁡x2f(x_1, x_2) = \ln x_1 + x_1x_2 - \sin x_2 at (2,5)(2, 5).

    Forward run (values).

    step value
    v−1=x1v_{-1} = x_1 22
    v0=x2v_0 = x_2 55
    v1=ln⁡v−1v_1 = \ln v_{-1} ln⁡2=0.693\ln 2 = 0.693
    v2=v−1v0v_2 = v_{-1}v_0 1010
    v3=sin⁡v0v_3 = \sin v_0 sin⁡5=−0.959\sin 5 = -0.959
    v4=v1+v2v_4 = v_1 + v_2 10.69310.693
    v5=v4−v3v_5 = v_4 - v_3 11.652=f(2,5)11.652 = f(2, 5)

    Backward run (adjoints), in reverse order. Each node sends vˉi\bar v_i times the local derivative to each parent.

    1. Seed: vˉ5=1\bar v_5 = 1.
    2. Node v5=v4−v3v_5 = v_4 - v_3: vˉ4=vˉ5⋅1=1\bar v_4 = \bar v_5\cdot 1 = 1 and vˉ3=vˉ5⋅(−1)=−1\bar v_3 = \bar v_5\cdot(-1) = -1.
    3. Node v4=v1+v2v_4 = v_1 + v_2: vˉ1=vˉ4⋅1=1\bar v_1 = \bar v_4\cdot 1 = 1 and vˉ2=vˉ4⋅1=1\bar v_2 = \bar v_4\cdot 1 = 1.
    4. Node v3=sin⁡v0v_3 = \sin v_0: vˉ0=vˉ3cos⁡v0=−cos⁡5=−0.284\bar v_0 = \bar v_3\cos v_0 = -\cos 5 = -0.284.
    5. Node v2=v−1v0v_2 = v_{-1}v_0: vˉ−1=vˉ2⋅v0=5\bar v_{-1} = \bar v_2\cdot v_0 = 5, and add to vˉ0\bar v_0: vˉ0=−0.284+vˉ2⋅v−1=−0.284+2=1.716\bar v_0 = -0.284 + \bar v_2\cdot v_{-1} = -0.284 + 2 = 1.716.
    6. Node v1=ln⁡v−1v_1 = \ln v_{-1}: add to vˉ−1\bar v_{-1}: vˉ−1=5+vˉ1/v−1=5+0.5=5.5\bar v_{-1} = 5 + \bar v_1/v_{-1} = 5 + 0.5 = 5.5.

    So ∂f/∂x1=5.5\partial f/\partial x_1 = 5.5 and ∂f/∂x2=1.716\partial f/\partial x_2 = 1.716. Check with the formulas: ∂f/∂x1=1/x1+x2=0.5+5=5.5\partial f/\partial x_1 = 1/x_1 + x_2 = 0.5 + 5 = 5.5 and ∂f/∂x2=x1−cos⁡x2=2−0.284=1.716\partial f/\partial x_2 = x_1 - \cos x_2 = 2 - 0.284 = 1.716. ✓ The two "add" steps are the fan-outs of x2x_2 (step 5) and x1x_1 (step 6).

    Forward sweep (tangents) with x˙=(1,0)\dot x = (1, 0). v˙−1=1\dot v_{-1} = 1, v˙0=0\dot v_0 = 0; v˙1=v˙−1/v−1=0.5\dot v_1 = \dot v_{-1}/v_{-1} = 0.5; v˙2=v˙−1v0+v−1v˙0=5\dot v_2 = \dot v_{-1}v_0 + v_{-1}\dot v_0 = 5; v˙3=cos⁡(v0) v˙0=0\dot v_3 = \cos(v_0)\,\dot v_0 = 0; v˙4=0.5+5=5.5\dot v_4 = 0.5 + 5 = 5.5; v˙5=5.5−0=5.5\dot v_5 = 5.5 - 0 = 5.5. One forward sweep gives one number, ∂f/∂x1=5.5\partial f/\partial x_1 = 5.5. For ∂f/∂x2\partial f/\partial x_2 you need a second sweep with x˙=(0,1)\dot x = (0, 1). The backward sweep gave both at once.

    (c) One neuron with a squared loss. z=w⊤x+bz = w^\top x + b, a=σ(z)a = \sigma(z), ℓ=12(a−t)2\ell = \tfrac12(a - t)^2, with the sigmoid σ(z)=1/(1+e−z)\sigma(z) = 1/(1 + e^{-z}) and σ′=σ(1−σ)\sigma' = \sigma(1-\sigma). Take x=(1,2)x = (1, 2), w=(0.5,−0.5)w = (0.5, -0.5), b=0.5b = 0.5, t=1t = 1.

    1. Forward: z=0.5−1+0.5=0z = 0.5 - 1 + 0.5 = 0, a=σ(0)=0.5a = \sigma(0) = 0.5, ℓ=12(0.5−1)2=0.125\ell = \tfrac12(0.5 - 1)^2 = 0.125.
    2. aˉ=∂ℓ/∂a=a−t=−0.5\bar a = \partial\ell/\partial a = a - t = -0.5.
    3. Elementwise VJP: zˉ=σ′(z) aˉ=0.5⋅0.5⋅(−0.5)=−0.125\bar z = \sigma'(z)\,\bar a = 0.5\cdot 0.5\cdot(-0.5) = -0.125.
    4. Weights (the 1-row case of zˉ x⊤\bar z\,x^\top): wˉ=zˉ x=(−0.125,−0.25)\bar w = \bar z\,x = (-0.125, -0.25), and bˉ=zˉ=−0.125\bar b = \bar z = -0.125.
    5. Input: xˉ=zˉ w=(−0.0625,0.0625)\bar x = \bar z\,w = (-0.0625, 0.0625).

    5. In code #

    NumPy checks the three hand computations with central differences (f(x+ηei)−f(x−ηei))/(2η)\big(f(x + \eta e_i) - f(x - \eta e_i)\big)/(2\eta) (lesson 07 explains this check).

    import numpy as np
    
    def num_grad(f, x, eta=1e-6):
        """Central differences, one input at a time."""
        g = np.zeros_like(x)
        for i in range(x.size):
            e = np.zeros_like(x); e[i] = eta
            g[i] = (f(x + e) - f(x - e)) / (2 * eta)
        return g
    
    # (a) gradient through a map is a VJP: Df^T grad g
    x = np.array([1.0, 2.0])
    f = lambda x: np.array([x[0] * x[1], x[0] + x[1], x[1] ** 2])
    g = lambda u: u[0] * u[1] + u[2]
    Df = np.array([[x[1], x[0]], [1.0, 1.0], [0.0, 2 * x[1]]])
    u = f(x)
    print(Df.T @ np.array([u[1], u[0], 1.0]))        # [8. 9.]
    print(num_grad(lambda x: g(f(x)), x))            # [8. 9.] (up to ~1e-9)
    
    # (b) backward sweep on the graph of ln x1 + x1 x2 - sin x2 at (2, 5)
    x1, x2 = 2.0, 5.0
    v1, v2, v3 = np.log(x1), x1 * x2, np.sin(x2)
    v4 = v1 + v2; v5 = v4 - v3
    b5 = 1.0
    b4, b3 = b5 * 1.0, b5 * -1.0
    b1, b2 = b4 * 1.0, b4 * 1.0
    b0 = b3 * np.cos(x2)              # from v3
    bm1 = b2 * x2                     # from v2
    b0 += b2 * x1                     # from v2: fan-out of x2, add
    bm1 += b1 / x1                    # from v1: fan-out of x1, add
    print(round(v5, 3), round(bm1, 3), round(b0, 3))   # 11.652 5.5 1.716
    F = lambda x: np.log(x[0]) + x[0] * x[1] - np.sin(x[1])
    print(num_grad(F, np.array([2.0, 5.0])).round(3))  # [5.5   1.716]
    
    # (c) one neuron, squared loss
    xn, w, b, t = np.array([1.0, 2.0]), np.array([0.5, -0.5]), 0.5, 1.0
    sig = lambda z: 1 / (1 + np.exp(-z))
    z = w @ xn + b; a = sig(z)
    zbar = (a - t) * a * (1 - a)
    print(zbar * xn, zbar, zbar * w)                 # [-0.125 -0.25 ] -0.125 [-0.0625  0.0625]
    loss_w = lambda w: 0.5 * (sig(w @ xn + b) - t) ** 2
    print(num_grad(loss_w, w).round(6))              # [-0.125 -0.25 ]
    

    6. Where it shows up #

    • Backpropagation. A neural network is a long chain of layers. Its gradient is the product of the transposed layer Jacobians, applied to ℓˉ=1\bar\ell = 1 from the loss backward. Lesson 06 and deep learning lesson 03 derive the full backward pass of an MLP.
    • Every autodiff framework. PyTorch, JAX and TensorFlow record the computational graph of your code. Each primitive operation comes with a VJP rule (its "backward" function), and loss.backward() runs the backward sweep of section 3.
    • Residual networks (He et al., 2016). In y=x+F(x)y = x + F(x) the gradient is yˉ+DF⊤yˉ\bar y + DF^\top\bar y. The identity path lets the gradient reach early layers of very deep networks. Transformers use a residual connection around every attention and MLP block.
    • Shared parameters. Recurrent networks use the same weights at every time step; convolutions use the same kernel at every position; many language models tie the input embedding and the output projection. In each case the gradient is the sum over all uses.
    • ML for science. In weather prediction, the backward sweep through a forecast model is the adjoint model; 4D-Var data assimilation gets the gradient of the misfit with respect to the initial state from one adjoint run. Seismic full-waveform inversion does the same through a wave simulation. See the ML for science track.

    7. Common mistakes #

    • Wrong order. D(g∘f)=Dg DfD(g\circ f) = Dg\,Df, not Df DgDf\,Dg. The map applied first stands on the right. The shape check catches this unless the matrices are square.
    • Wrong point. The outer Jacobian is evaluated at f(x)f(x), not at xx: Dg(f(x))Dg(f(x)). In a network, each layer's Jacobian uses that layer's own input, stored from the forward run.
    • Missing transpose. The gradient through z=Wxz = Wx is W⊤zˉW^\top\bar z, not WzˉW\bar z. The gradient for the weights is zˉ x⊤\bar z\,x^\top, with the shape of WW. Check shapes after every step.
    • Not adding at a fan-out. When a value is used twice (a residual, a shared weight, xx in x2sin⁡xx^2\sin x), its adjoint is the sum of the contributions. Overwriting instead of adding (= instead of +=) is a classic bug in hand-written backward passes.
    • Mixing up the local and the total derivative. On the edge x1→v2=x1x2x_1 \to v_2 = x_1x_2, the local derivative is x2=5x_2 = 5. The total derivative ∂f/∂x1=5.5\partial f/\partial x_1 = 5.5 also includes the path through ln⁡x1\ln x_1.
    • Building diagonal Jacobians. For an elementwise map, use φ′(z)⊙aˉ\varphi'(z)\odot\bar a. A diag⁡\operatorname{diag} matrix of size m×mm\times m wastes memory and time.

    8. Check yourself #

    1. f:R5→R2f:\R^5\to\R^2 and g:R2→R3g:\R^2\to\R^3. Give the shapes of Df(x)Df(x), Dg(f(x))Dg(f(x)) and D(g∘f)(x)D(g\circ f)(x).
      AnswerDfDf is 2×52\times 5, DgDg is 3×23\times 2, and D(g∘f)=Dg DfD(g\circ f) = Dg\,Df is (3×2)(2×5)=3×5(3\times2)(2\times5) = 3\times 5.
    2. f(x,y)=exyf(x, y) = e^{x}y, x(t)=t2x(t) = t^2, y(t)=3ty(t) = 3t. Compute ddtf(x(t),y(t))\frac{d}{dt}f(x(t), y(t)) at t=1t = 1 with the entry form, then check by substitution.
      AnswerAt t=1t = 1: x=1x = 1, y=3y = 3, x′=2t=2x' = 2t = 2, y′=3y' = 3. ∂xf=exy=3e\partial_x f = e^{x}y = 3e and ∂yf=ex=e\partial_y f = e^{x} = e. So ddtf=3e⋅2+e⋅3=9e≈24.46\frac{d}{dt}f = 3e\cdot 2 + e\cdot 3 = 9e \approx 24.46. Check: f=3t et2f = 3t\,e^{t^2}, derivative 3et2+6t2et2=9e3e^{t^2} + 6t^2e^{t^2} = 9e at t=1t = 1. ✓
    3. a=tanh⁡(z)a = \tanh(z) elementwise, with z∈R4z\in\R^4. Write zˉ\bar z in terms of aˉ\bar a and aa. Why is it enough to store aa from the forward run?
      Answerzˉ=(1−a⊙a)⊙aˉ\bar z = (1 - a\odot a)\odot\bar a, because tanh⁡′=1−tanh⁡2\tanh' = 1 - \tanh^2. The derivative is a function of the output aa, so zz is not needed.
    4. A residual block computes y=x+Wxy = x + Wx with a square matrix WW. Given yˉ\bar y, what is xˉ\bar x?
      AnswerTwo paths add: xˉ=yˉ+W⊤yˉ=(I+W)⊤yˉ\bar x = \bar y + W^\top\bar y = (I + W)^\top\bar y. The identity path passes yˉ\bar y unchanged.
    5. Draw the computational graph of f(x,y)=(x+y) exf(x, y) = (x + y)\,e^{x} and run the backward sweep at (1,0)(1, 0).
      Answerv1=x+y=1v_1 = x + y = 1, v2=ex=ev_2 = e^{x} = e, v3=v1v2=ev_3 = v_1v_2 = e. Backward: vˉ3=1\bar v_3 = 1; vˉ1=v2=e\bar v_1 = v_2 = e, vˉ2=v1=1\bar v_2 = v_1 = 1; yˉ=vˉ1⋅1=e\bar y = \bar v_1\cdot 1 = e; xˉ=vˉ1⋅1+vˉ2⋅ex=e+e=2e≈5.437\bar x = \bar v_1\cdot 1 + \bar v_2\cdot e^{x} = e + e = 2e \approx 5.437 (xx fans out to v1v_1 and v2v_2). Check: ∂xf=ex+(x+y)ex=2e\partial_x f = e^{x} + (x + y)e^{x} = 2e and ∂yf=ex=e\partial_y f = e^{x} = e at (1,0)(1, 0). ✓
    6. For one sample, why does ∂ℓ/∂W\partial\ell/\partial W of a linear layer z=Wxz = Wx have rank at most 1?
      AnswerIt equals zˉ x⊤\bar z\,x^\top, an outer product. Every column jj is xj zˉx_j\,\bar z, a multiple of the same vector zˉ\bar z.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. State the chain rule for g∘fg\circ f with Jacobians.

    2. f:R4→R3f:\R^4\to\R^3, g:R3→R2g:\R^3\to\R^2. What is the shape of D(g∘f)D(g\circ f)?

    3. Write the entry form of the chain rule.

    4. What is a vector-Jacobian product (VJP), and where does it appear?

    5. For z=Wxz = Wx, give xˉ\bar x and Wˉ\bar W in terms of zˉ\bar z.

    6. For an elementwise map a=φ(z)a = \varphi(z), what is zˉ\bar z?

    7. Why do gradients add when a value is used twice?

    8. What is the adjoint vˉj\bar v_j of a node in a computational graph, and how is it computed?

    9. For f(x1,x2)=ln⁡x1+x1x2−sin⁡x2f(x_1, x_2) = \ln x_1 + x_1x_2 - \sin x_2, what is ∂f/∂x1\partial f/\partial x_1 at (2,5)(2, 5), and which two paths give it?

    10. What does one forward sweep give, and what does one backward sweep give?

    Open the review →

    9. Interview angle #

    • "Explain the chain rule for vector functions." — D(g∘f)=Dg(f(x)) Df(x)D(g\circ f) = Dg(f(x))\,Df(x); show the shapes; the first map stands on the right because composition of linear maps is the matrix product.
    • "What is the gradient of ℓ(Wx)\ell(Wx) with respect to xx and with respect to WW?" — W⊤zˉW^\top\bar z and zˉ x⊤\bar z\,x^\top with zˉ=∇ℓ(Wx)\bar z = \nabla\ell(Wx). Mention the rank-1 structure of the weight gradient.
    • "Why do residual connections help training?" — fan-out: xˉ=yˉ+DF⊤yˉ\bar x = \bar y + DF^\top\bar y; the identity path carries the gradient back even when DFDF is small.
    • "Backprop through this small expression on the whiteboard." — draw the graph, compute the values, seed yˉ=1\bar y = 1, go backward, multiply by local derivatives, add at fan-outs, then check one partial derivative by hand or with finite differences.

    10. Practice #

    The notebook for this lesson runs in Colab. It has five exercises, each with a check cell and a folded solution:

    1. chain_jacobians: multiply a list of Jacobians in the right order, with shape checks.
    2. Forward and backward sweeps by hand on ln⁡x1+x1x2−sin⁡x2\ln x_1 + x_1x_2 - \sin x_2, compared with central differences.
    3. vjp_linear and vjp_elementwise: the two basic VJPs, without building any diagonal matrix.
    4. Fan-out: the derivative of g(x) h(x)g(x)\,h(x) by summing two paths, compared with the product rule.
    5. A second graph: one neuron σ(w⊤x+b)\sigma(w^\top x + b) with a squared loss, backpropagated by hand.

    11. Watch and read #

    The chain rule on a tiny network, with the "rates multiply" and "paths add" pictures of this lesson.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The entry form for f(x(t), y(t)), derived from small changes.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Computational graphs and the backward pass with local gradients, on small examples like section 4.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play