Md. Asif Uddin
    Problem I.6.B01

    One full backward pass through a 2-2-1 network

    numeric▲△△

    Eight decimal places throughout; the finite-difference check uses a step of 1e-6.

    STATEMENT

    Run one forward pass and one backward pass through a small network by hand, producing every intermediate and every parameter gradient. Then check the result against central differences, which is the only way to know a hand-derived gradient is right rather than merely consistent with itself.

    GIVEN

    A network with two inputs, two sigmoid hidden units and one linear output. One example, so every activation is a row.

    x=(12),y=1\vec{x} = \begin{pmatrix} 1 & 2 \end{pmatrix}, \qquad y = 1

    W(1)=(0.10.30.20.4),b(1)=(00.1)\mat{W}^{(1)} = \begin{pmatrix} 0.1 & 0.3 \\ 0.2 & 0.4 \end{pmatrix}, \qquad \vec{b}^{(1)} = \begin{pmatrix} 0 & 0.1 \end{pmatrix}W(2)=(0.5−0.6),b(2)=0.2\mat{W}^{(2)} = \begin{pmatrix} 0.5 \\ -0.6 \end{pmatrix}, \qquad b^{(2)} = 0.2

    The hidden nonlinearity is σ(z)=1/(1+e−z)\sigma(z) = 1/(1 + e^{-z}), the head carries none, and the loss is L=12(y^−y)2L = \tfrac{1}{2}(\hat{y} - y)^2.

    FIND

    Every intermediate of the forward pass; the adjoints δ(2)\delta^{(2)} and δ(1)\delta^{(1)}; the gradients with respect to all nine parameters; and the largest disagreement between those gradients and a central-difference estimate.

    STRATEGY

    Forward first, keeping everything — by Definition 3 the backward pass consumes the stored values, so discarding them means starting again. Then apply (I.6.2) from the sink: the loss adjoint is 1, and each node passes its own adjoint times its local Jacobian to its producers. For this architecture that is exactly the recurrence (I.6.3) and the outer product (I.6.4).

    SOLUTION

    Step 1 — forward. The pre-activations are z(1)=xW(1)+b(1)\vec{z}^{(1)} = \vec{x}\mat{W}^{(1)} + \vec{b}^{(1)}:

    z1(1)=1(0.1)+2(0.2)+0=0.5,z2(1)=1(0.3)+2(0.4)+0.1=1.2z^{(1)}_1 = 1(0.1) + 2(0.2) + 0 = 0.5, \qquad z^{(1)}_2 = 1(0.3) + 2(0.4) + 0.1 = 1.2

    a(1)=σ(z(1))=(0.622459330.76852478)\vec{a}^{(1)} = \sigma(\vec{z}^{(1)}) = \begin{pmatrix} 0.62245933 & 0.76852478 \end{pmatrix}

    y^=z(2)=0.62245933(0.5)+0.76852478(−0.6)+0.2=0.05011480\hat{y} = z^{(2)} = 0.62245933(0.5) + 0.76852478(-0.6) + 0.2 = 0.05011480

    L=12(0.05011480−1)2=0.45114095L = \tfrac{1}{2}(0.05011480 - 1)^2 = 0.45114095

    Step 2 — the adjoint at the sink. Lˉ=1\bar{L} = 1 by definition, and the head is linear, so

    δ(2)=∂L∂z(2)=y^−y=−0.94988520\delta^{(2)} = \frac{\partial L}{\partial z^{(2)}} = \hat{y} - y = -0.94988520

    The sign says the prediction is below the target, which it is.

    Step 3 — the second layer’s parameters. By (I.6.4) the weight gradient is the outer product of what entered the layer with what came back:

    ∂L∂W(2)=(a(1))Tδ(2)=(−0.59126491−0.73001032),∂L∂b(2)=−0.94988520\frac{\partial L}{\partial \mat{W}^{(2)}} = (\vec{a}^{(1)})^{\mathsf{T}} \delta^{(2)} = \begin{pmatrix} -0.59126491 \\ -0.73001032 \end{pmatrix}, \qquad \frac{\partial L}{\partial b^{(2)}} = -0.94988520

    The bias gradient is the adjoint, because a bias enters its node with coefficient one.

    Step 4 — travel back through the layer. By (I.6.3), multiply by (W(2))T(\mat{W}^{(2)})^{\mathsf{T}} and gate by the slope of the nonlinearity:

    δ(2)(W(2))T=(−0.474942600.56993112)\delta^{(2)}(\mat{W}^{(2)})^{\mathsf{T}} = \begin{pmatrix} -0.47494260 & 0.56993112 \end{pmatrix}

    For the logistic function σ′=σ(1−σ)\sigma' = \sigma(1 - \sigma), so it is read off the stored activations rather than recomputed:

    σ′(z(1))=(0.235003710.17789444)\sigma'(\vec{z}^{(1)}) = \begin{pmatrix} 0.23500371 & 0.17789444 \end{pmatrix}

    δ(1)=(−0.474942600.56993112)⊙(0.235003710.17789444)=(−0.111613270.10138758)\delta^{(1)} = \begin{pmatrix} -0.47494260 & 0.56993112 \end{pmatrix} \odot \begin{pmatrix} 0.23500371 & 0.17789444 \end{pmatrix} = \begin{pmatrix} -0.11161327 & 0.10138758 \end{pmatrix}

    Step 5 — the first layer’s parameters. The same outer product, one layer down:

    ∂L∂W(1)=xTδ(1)=(−0.111613270.10138758−0.223226550.20277516),∂L∂b(1)=δ(1)\frac{\partial L}{\partial \mat{W}^{(1)}} = \vec{x}^{\mathsf{T}} \delta^{(1)} = \begin{pmatrix} -0.11161327 & 0.10138758 \\ -0.22322655 & 0.20277516 \end{pmatrix}, \qquad \frac{\partial L}{\partial \vec{b}^{(1)}} = \delta^{(1)}

    The second row is exactly twice the first, because the second input is twice the first. A weight’s gradient carries the size of the thing that fed it.

    Step 6 — the check. Central differences with h=10−6h = 10^{-6}, evaluating the loss twice per parameter and taking (L(θ+h)−L(θ−h))/2h(L(\theta + h) - L(\theta - h))/2h, agree with all nine analytic gradients. The largest disagreement is 5.79×10−115.79 \times 10^{-11}.

    That agreement is worth more than any of the individual numbers. The analytic gradient came from the chain rule; the numeric one came from evaluating the forward function eighteen times and never differentiating anything. Two unrelated computations landing on the same nine numbers is evidence the derivation is correct.

    Answer

    quantityvalue
    z(1)\vec{z}^{(1)}0.5, 1.20.5,\ 1.2
    a(1)\vec{a}^{(1)}0.62245933, 0.768524780.62245933,\ 0.76852478
    y^\hat{y}0.050114800.05011480
    LL0.451140950.45114095
    δ(2)\delta^{(2)}−0.94988520-0.94988520
    δ(1)\delta^{(1)}−0.11161327, 0.10138758-0.11161327,\ 0.10138758
    ∂L/∂W(2)\partial L/\partial \mat{W}^{(2)}−0.59126491, −0.73001032-0.59126491,\ -0.73001032
    ∂L/∂W(1)\partial L/\partial \mat{W}^{(1)}(−0.11161327, 0.10138758; −0.22322655, 0.20277516)(-0.11161327,\ 0.10138758;\ -0.22322655,\ 0.20277516)
    largest gradient-check gap5.79×10−115.79 \times 10^{-11}

    Check — sanity

    The bias gradients equal the adjoints. ∂L/∂b(2)=δ(2)\partial L/\partial b^{(2)} = \delta^{(2)} and ∂L/∂b(1)=δ(1)\partial L/\partial \vec{b}^{(1)} = \delta^{(1)}, to the last digit. A bias adds into its node with coefficient one, so its local Jacobian is the identity and (I.6.2) leaves the adjoint unchanged. Any discrepancy here is an arithmetic slip, not a modelling question.

    The rows of ∂L/∂W(1)\partial L/\partial \mat{W}^{(1)} are in the ratio of the inputs. −0.22322655/−0.11161327=2-0.22322655 / -0.11161327 = 2 exactly, matching x2/x1=2x_2/x_1 = 2. The outer product in (I.6.4) says the gradient of a weight scales with the activation entering it, so this ratio is forced.

    The sigmoid slopes are below 1/41/4. 0.235003710.23500371 and 0.177894440.17789444, against the maximum σ′(0)=0.25\sigma'(0) = 0.25. Every layer of this kind multiplies the travelling adjoint by something no larger than a quarter, which is the observation I.6.B04 turns into a depth limit.

    Where this breaks

    The finite-difference check that validates the gradient here does not scale, and it is worth being precise about why. It costs two forward passes per parameter, so checking a model with NN parameters costs 2N2N evaluations — for N=567 434N = 567\,434, the network audited in I.5.B03, that is over a million forward passes to verify one gradient that reverse mode produced in a single traversal. By (I.6.6) the analytic gradient cost about four forward passes. The ratio of a million to four is the cheap gradient principle, seen from the other side.

    The step size has its own trap. Too large and the truncation error of the difference dominates; too small and floating-point cancellation does, because L(θ+h)L(\theta + h) and L(θ−h)L(\theta - h) agree in their leading digits and subtracting them discards exactly those. At h=10−12h = 10^{-12} in fp64 this same check returns visible garbage. Gradient checking is a debugging tool for small cases, not a verification method for a real model.

    Variation

    Replace the linear head with a second sigmoid, so y^=σ(z(2))\hat{y} = \sigma(z^{(2)}), and keep everything else. Which of the nine gradients change, and by what factor? Predict the factor before recomputing — one number in Step 2 is responsible for all of it.

    Draws on