1. Why this session exists
Sessions 2.5 and 2.6 gave us δ(ℓ) at every layer. The last step of backprop is to convert these deltas into the gradients we update with: ∂L/∂W(ℓ) and ∂L/∂b(ℓ). The conversion is one line each, and the pattern is the same "residual × input" outer product we saw in 1.3 and 1.8 — just applied at every layer of the network.[1]
2. Per-example formula
At layer ℓ, the pre-activation is
z(ℓ)=W(ℓ)a(ℓ−1)+b(ℓ).
Entry by entry, zi(ℓ)=∑jWi,j(ℓ)aj(ℓ−1)+bi(ℓ). So
∂Wi,j(ℓ)∂zi(ℓ)=aj(ℓ−1),∂Wi,j(ℓ)∂zp(ℓ)=0 for p=i.
By the chain rule with δ(ℓ)=∂L/∂z(ℓ),
∂Wi,j(ℓ)∂L=p∑∂zp(ℓ)∂L⋅∂Wi,j(ℓ)∂zp(ℓ)=δi(ℓ)⋅aj(ℓ−1).
Stacking entries (i,j) into a matrix,
∂W(ℓ)∂L=δ(ℓ)(a(ℓ−1))⊤∈Rdℓ×dℓ−1.
Outer product: delta column at layer ℓ times activation row at layer ℓ−1.
For the bias, ∂zp(ℓ)/∂bi(ℓ)=δpi, so
∂b(ℓ)∂L=δ(ℓ)∈Rdℓ.
3. Batched form
For a mini-batch of B examples, with Δ(ℓ)∈RB×dℓ and A(ℓ−1)∈RB×dℓ−1 as before, the weight gradient is the sum of per-example outer products, scaled by 1/B:
∂W(ℓ)∂Lbatch=B1(Δ(ℓ))⊤A(ℓ−1)∈Rdℓ×dℓ−1.
(Recover the per-example case by inspecting one row of Δ and one row of A; the matrix product over B rows is the sum of outer products.)
The bias gradient is the column-wise average of Δ(ℓ):
∂b(ℓ)∂Lbatch=B1i=1∑BΔi,:(ℓ)∈Rdℓ.
In PyTorch this is Delta.T @ A / B for the weight and Delta.mean(dim=0) for the bias.
4. Shape sanity check
- W(ℓ) is dℓ×dℓ−1.
- Δ(ℓ) is B×dℓ, A(ℓ−1) is B×dℓ−1.
- (Δ(ℓ))⊤A(ℓ−1) is dℓ×dℓ−1 ✓ (matches W(ℓ)).
- Δ(ℓ) summed over rows is dℓ ✓ (matches b(ℓ)).
A gradient must be the same shape as the parameter it differentiates. If you ever see a transposed result, you've swapped the order in the outer product.
5. The full forward + backward picture
End-to-end picture of one training step on a mini-batch.
Forward (vectorised): for ℓ=1,…,L,
Z(ℓ)=A(ℓ−1)(W(ℓ))⊤+b(ℓ),A(ℓ)=g(ℓ)(Z(ℓ)).
Compute Lbatch.
Backward (vectorised):
- Initialise Δ(L) at the output layer (2.5; for softmax + CE it is B1(Y^−Y)).
- For ℓ=L−1,L−2,…,1, recurse Δ(ℓ)=(Δ(ℓ+1)W(ℓ+1))⊙g′(ℓ)(Z(ℓ)) (2.6).
- For each ℓ, compute ∂L/∂W(ℓ)=(Δ(ℓ))⊤A(ℓ−1) and ∂L/∂b(ℓ)=Δ(ℓ)sum over batch axis.
(The 1/B factor lives wherever you decide; standard practice folds it into the output delta or the loss itself.)
Update. For each layer,
W(ℓ)←W(ℓ)−α∂W(ℓ)∂L,b(ℓ)←b(ℓ)−α∂b(ℓ)∂L.
One training step ≈ three forward passes' worth of FLOPs. Convergence is the topic of Chapter 3.
6. Takeaways
- ∂L/∂W(ℓ)=δ(ℓ)(a(ℓ−1))⊤. Outer product: delta × input.
- ∂L/∂b(ℓ)=δ(ℓ).
- Batched: ∂L/∂W(ℓ)=B1(Δ(ℓ))⊤A(ℓ−1), bias is the batch-mean of Δ(ℓ).
- Combined with 2.5 and 2.6, you now have the complete backprop algorithm.
Self-test(a) Derive the weight gradient identity from scratch using the chain rule on z(ℓ)=W(ℓ)a(ℓ−1)+b(ℓ). Check that the result is an outer product.
(b) For the MNIST MLP of 2.4 §4 (widths (784,256,128,10)) and batch B=32, compute the FLOP count of one forward pass and of the corresponding backward pass. Verify backward ≈ forward.
(c) Suppose two examples in the same mini-batch have very different deltas — one δ=+5, another δ=−5. How does the mean gradient over the batch behave? Why is this a feature, not a bug? (Hint: noise averaging in SGD.)
(d) For layer 1, the input is the data itself: a(0)=x. Write out ∂L/∂W(1) explicitly in terms of δ(1) and the raw input.