Before this lesson:lesson 03 (Jacobians, Hessians,
Taylor expansion), lesson 04 (chain rule, VJPs of
Wx and of elementwise maps, fan-out), lesson 05
(reverse mode). Outer products from linear-algebra lesson 01.
The softmax Jacobian is derived in linear-algebra lesson 16.
Time: about 90 minutes, plus the notebook. This is the heaviest lesson of the track; take a break
after section 3.3.
Every training step of every neural network runs a backward pass. Frameworks do it for you, but
you need the formulas when you write a custom layer or a fused GPU kernel, when a loss turns into
nan, when gradients vanish in a deep model, or when an interviewer asks you to "derive backprop
for an MLP" or "the gradient of softmax cross-entropy" on a whiteboard. These are among the most
common technical interview questions in ML.
The losses of this lesson are most of the loss code in practice: least squares in regression,
logistic loss in binary classification, softmax cross-entropy in every classifier and every large
language model (LLM). Layer normalization (LayerNorm) sits in every transformer block. In ML for
science, weighted least squares is the data-misfit term of data assimilation and seismic inversion,
and LayerNorm is inside transformer-based weather models.
Backprop is reverse mode on a chain of layers. An MLP computes
x→z1→a1→z2→⋯→zL→ℓ. Lesson 05 showed that reverse mode runs
the chain once forward (and stores the values), then once backward with the seed
∂ℓ/∂ℓ=1. On the way back we carry the error signal of each layer,
δl=∂zl∂ℓ(a vector with the shape of zl).
Each layer does two jobs on the backward sweep:
Pass the error down:δl is the VJP of the layer above. Multiply δl+1 by
(Wl+1)⊤, then multiply entrywise by the slope φ′(zl) of the activation.
Pay the weights: the gradient of Wl is the outer product δl(al−1)⊤ of the
error signal and the input of the layer.
Interactive figure: loading…
Step through one training step. Press Next step. The forward pass fills the nodes with values
(blue); the backward pass fills them with error signals δ
(orange). A grey hidden unit is "off" (ReLU with z≤0): its slope is 0, so
no error passes through it, and its row of ∂ℓ/∂W1 is zero. Move x with the
sliders to switch units on and off, and switch to the softmax loss to see δ2=p−ey.
Prediction minus target. Let z be the score (or the vector of scores) that a model outputs,
and y the target. The three common losses have the same gradient form:
Loss
Formula in the score z
Prediction
∂ℓ/∂z
squared error
21(z−y)2
z
z−y
logistic, y∈{0,1}
log(1+ez)−yz
σ(z)=1/(1+e−z)
σ(z)−y
softmax cross-entropy, C classes
log∑kezk−zy
p=softmax(z)
p−ey
Here ey is the one-hot vector of the true class y: 1 in entry y, 0 elsewhere. If the
prediction equals the target, the gradient is zero. For the logistic and softmax losses, a model that
is sure and wrong gets gradient entries close to ±1, the largest possible size, so the next step
corrects it strongly.
Why the same form? (generalized linear models)
Each loss has the form ℓ(z)=A(z)−y⊤z (plus a constant), with A(z)=21z2,A(z)=log(1+ez) and A(z)=log∑kezk (in the last case the target is the one-hot
vector ey). These functions A are the log-partition functions of the Gaussian (unit variance),
Bernoulli and categorical distributions. A standard fact about exponential families: ∇A(z)
is the mean of the distribution (the prediction) and ∇2A(z) is its covariance: 1,σ(1−σ) and diag(p)−pp⊤. A covariance matrix is positive
semidefinite (PSD), so every one of these losses is convex in the scores. These are the
canonical-link generalized linear models of the
ML fundamentals track (coming later).
Interactive figure: loading…
Move the logits and watch the gradient. The bars show the probabilities p
(blue) and the gradient p−ey (orange). The
gradient entries always sum to 0 and stay in [−1,1]. Then move the shift slider: adding the same
number to every logit changes nothing in p, but the naive formula (magenta)
breaks once a logit passes about 710, while log-sum-exp stays correct.
Quick check
A 3-class classifier outputs p=softmax(z)=(0.7,0.2,0.1), and the true class is y=1. What is ∂ℓ/∂z for ℓ=−logpy?
Why.
The gradient is p−ey=(0.7−1,0.2,0.1). The option (−1/0.7,0,0) is the derivative with respect to p, not z: it forgets the softmax. The option (−0.3,0,0) forgets that the wrong classes also get a gradient (their logits are pushed down). The entries of the right answer sum to 0.
We use column vectors for one sample (the same notation as lessons 03–05).
Input a0=x∈Rn0.
Hidden layers l=1,…,L−1: the pre-activationzl=Wlal−1+bl and the
activational=φ(zl), with Wl∈Rnl×nl−1,bl∈Rnl and an
elementwise function φ (ReLU, tanh, ...).
Output layer: the logitszL=WLaL−1+bL∈RC, one score per class.
Loss: softmax cross-entropy ℓ=−logsoftmax(zL)y for the true class
y∈{1,…,C}.
Error signalδl=∂ℓ/∂zl∈Rnl.
⊙ is the entrywise product. Two facts from lesson 04:
the VJP of z=Wa is u↦W⊤u, and the VJP of an elementwise map a=φ(z) is
u↦φ′(z)⊙u.
3.2 Theorem 1 (backpropagation for an L-layer MLP) #
With the notation of 3.1:
(a) δL=p−ey, where p=softmax(zL);
(b) δl=φ′(zl)⊙(Wl+1)⊤δl+1 for l=L−1,…,1;
(c) ∂Wl∂ℓ=δl(al−1)⊤ (an outer product, nl×nl−1) and ∂bl∂ℓ=δl;
(d) ∂x∂ℓ=(W1)⊤δ1.
Proof.
Output.ℓ=−zyL+log∑kezkL. So
∂ℓ/∂zkL=−[k=y]+ezkL/∑jezjL, which is pk−[k=y].
Here [k=y] is 1 if k=y and 0 otherwise. This is (a). (Another loss changes only this step: for
ℓ=21∥zL−t∥2 we get δL=zL−t.)
Layer Jacobian.ℓ depends on zl only through zl+1=Wl+1φ(zl)+bl+1.
By the chain rule, ∂zl+1/∂zl=Wl+1diag(φ′(zl)).
Recursion. The gradient through a map is the VJP with its Jacobian. So
δl=(Wl+1diag(φ′(zl)))⊤δl+1. A diagonal matrix
times a vector is an entrywise product, so this is φ′(zl)⊙(Wl+1)⊤δl+1: rule (b).
Weights.ℓ depends on Wl only through zl, and zkl=∑jWkjlajl−1+bkl.
So ∂zkl/∂Wijl=[k=i]ajl−1. In the sum
∂ℓ/∂Wijl=∑kδkl[k=i]ajl−1 only k=i survives, so
∂ℓ/∂Wijl=δilajl−1.
In matrix form this is δl(al−1)⊤. In the same way, ∂zkl/∂bil=[k=i]
gives ∂ℓ/∂bl=δl. This is (c).
Input.ℓ depends on x only through z1=W1x+b1, so ∂ℓ/∂x=(W1)⊤δ1. This is (d).
Order. Step 1 gives δL. Step 3 gives each δl from δl+1, for l=L−1
down to 1. Steps 4 and 5 then give all parameter and input gradients. ■
Read it. The backward pass is reverse accumulation on the chain x→z1→⋯→zL→ℓ.
Unrolled, it is a product of transposed layer Jacobians:
Step 1 differentiates the softmax and the loss together. So the C×C softmax Jacobian
diag(p)−pp⊤ is never formed; the backward pass costs O(C) at the output.
What must be stored. Rule (b) needs φ′(zl) and rule (c) needs al−1. So the forward
pass keeps every activation until the backward pass reaches its layer. For tanh,
φ′(z)=1−tanh2z=1−a2, and for ReLU, 1[z>0]=1[a>0], so storing al is enough.
Answer to question 1 of section 1.∂ℓ/∂W=δa⊤ has the shape of W,512×256. Its rank is at most 1, because it is an outer product: every column is a multiple
of δ. It is exactly 1 when δ=0 and a=0.
Quick check
One sample goes through z=Wa with W∈R512×256,a=0 and δ=∂ℓ/∂z=0. What are the shape and the rank of ∂ℓ/∂W?
Why.
The gradient of a parameter always has the shape of the parameter. Here it is δa⊤ with δ∈R512 and a∈R256: an outer product, so every column is ajδ and the rank is 1. It does not depend on W at all, only on the error signal and the input.
Now take a batch of B samples and the mean loss ℓ=B1∑i=1Bℓi. Put one
sample per row: A0=X∈RB×n0 and
Zl=Al−1(Wl)⊤+1(bl)⊤,Al=φ(Zl),
where 1∈RB is the vector of ones. Row i of Zl is (zil)⊤, the
pre-activation of sample i. This is the layout of PyTorch: nn.Linear stores Wl with shape
(out, in) and computes A @ W.T + b. Let Δl=∂ℓ/∂Zl∈RB×nl.
Its row i is (δil)⊤/B, because ℓi enters the mean with weight 1/B.
ΔL=(P−Y)/B, with P the row-wise softmax of ZL and Y the one-hot labels as rows.
Δl=(Δl+1Wl+1)⊙φ′(Zl): rule (b), transposed and stacked as rows.
∂ℓ/∂Wl=(Δl)⊤Al−1. By the outer-product view of the matrix
product (linear-algebra lesson 01), this is
∑i(row i of Δl)⊤(row i of Al−1): the sum of the
per-sample outer products.
∂ℓ/∂bl=(Δl)⊤1: the column sums over the batch.
The factor 1/B enters once, in ΔL. After that every step is a plain product or sum.
Shapes are your test.∂ℓ/∂Wl must have the shape of Wl(nl×nl−1).
Some code stores the transposed matrix W=(Wl)⊤ with shape (in, out) and computes
Z=AW+1b⊤. Then step 3 reads A⊤Δ and step 2 multiplies by W⊤.
Derive in one layout, then check the shapes in the other.
Cost and memory. Per layer, the forward pass does one matrix product. The backward pass does two
of the same size: ΔlWl (the error for the layer below) and (Δl)⊤Al−1 (the
weight gradient). So the backward pass costs about twice the forward pass, and a full training
step about three forward passes. This is the "small constant" of reverse mode in lesson 05. The
price is memory: all Al−1 and φ′(Zl) stay stored until the backward pass uses them.
Activation memory grows with depth and batch size; checkpointing (lesson 05) trades it for recomputation.
Quick check
In the batch formulas above (ℓ is the mean loss and Δl=∂ℓ/∂Zl), where does the factor 1/B appear?
Why.
ℓi enters the mean with weight 1/B, so the seed of the backward pass is (P−Y)/B. Steps 2–4 are linear in ΔL, so the 1/B is carried along automatically. Dividing again (per layer, or with a mean for ∂ℓ/∂b) makes the gradient B times too small; forgetting it makes it B times too large, like a B times larger learning rate.
Now three losses on a linear model. Notation: n samples with d features; X∈Rn×d
has one sample xi⊤ per row; w∈Rd are the weights; the scores are z=Xw; the loss is
a mean over the samples. (Here the batch is the whole data set; for a mini-batch replace n by B.)
We use one fact from the optimization track: a twice continuously differentiable
function on Rd is convex if and only if its Hessian is PSD at every point.
Theorem 2.L(w)=2n1∥Xw−y∥2 has
∇L(w)=n1X⊤(Xw−y),∇2L=n1X⊤X⪰0.
So L is convex. It is strictly convex if and only if X has full column rank.
Proof.
Let r=Xw−y (the residual). For a step h∈Rd, expand the square in
L(w+h)=2n1∥r+Xh∥2. This gives
L(w+h)=L(w)+n1r⊤Xh+2n1h⊤X⊤Xh.
L is quadratic, so this Taylor expansion (lesson 03) is
exact. The linear term is ∇L⊤h, so ∇L=n1X⊤r. The quadratic term
is 21h⊤∇2Lh, so ∇2L=n1X⊤X.
h⊤X⊤Xh=∥Xh∥2≥0, so the Hessian is PSD and L is convex.
∥Xh∥2=0 exactly for h in the null space of X. So the Hessian is positive definite if and
only if that null space is {0}, that is, X has full column rank. ■
∇L=0 gives the normal equationsX⊤Xw=X⊤y. Solve them with QR or lstsq,
not with (X⊤X)−1 (linear-algebra lesson 12).
A quadratic equals its own second-order Taylor model, so one Newton stepw−(∇2L)−1∇L lands exactly on the minimizer.
Labels yi∈{0,1} and scores zi=xi⊤w. The model says P(y=1)=σ(z). The
negative log-likelihood of one sample is ℓ(z)=−ylogσ(z)−(1−y)log(1−σ(z)). It
simplifies to log(1+ez)−yz, because logσ(z)=z−log(1+ez) and
log(1−σ(z))=−log(1+ez). So
Chain rule through z=Xw (Jacobian X):∇L=n1X⊤(σ(z)−y).
The gradient is n1∑i(σ(zi)−yi)xi. Its Jacobian in w is
n1∑isixixi⊤=n1X⊤SX.
For any v:v⊤∇2Lv=n1∑isi(xi⊤v)2≥0, since every si>0.
If X has full column rank and v=0, some xi⊤v=0, so the sum is >0.■
Two remarks on the curvature. First, si≤41, so
∇2L⪯4n1X⊤X⪯4nλmax(X⊤X)I: the loss is
smooth, and gradient descent with the fixed step η=4n/λmax(X⊤X) decreases it at
every step (the optimization track proves this). Second, si→0 when ∣zi∣→∞:
the curvature vanishes far from the origin. This matters on separable data.
Separable data: no minimizer. The data are linearly separable if some wsep has
xi⊤wsep>0 for every yi=1 and xi⊤wsep<0 for every yi=0.
Along the ray twsep, each sample loss is log(1+e−t∣xi⊤wsep∣). It
decreases to 0 as t→∞ but never reaches 0. So no finite minimizer exists, and gradient
descent lets ∥w∥ grow without bound. Strict convexity says only "at most one minimizer", not
"a minimizer exists". An L2 penalty 2λ∥w∥2 adds λI to the Hessian and gives
a unique minimizer. (Newton's method on this loss is called IRLS, iteratively reweighted least squares.)
Interactive figure: loading…
Drag the weight vector w or press Run gradient descent. The aqua lines
are level sets of the logistic loss L(w) in the plane of weights; the blue
arrow is one gradient-descent step −η∇L(w) with the step
η=4n/λmax(X⊤X) of 3.5. The inset shows the data and the decision line w⊤x=0. On the data that are not
separable, the level sets are closed curves around one minimizer w∗. Switch to separable: the level sets open up, gradient descent runs off along a ray,
the loss decreases slowly towards 0, and the Hessian eigenvalues (readout) shrink towards 0.
Stable computation.log(1+e800) overflows to inf in float64: ez overflows above
z≈709.8 (in float32 above z≈88.7). Use the softplus form
log(1+ez)=max(z,0)+log(1+e−∣z∣) (np.logaddexp(0, z)): the exponent is never positive.
Compute σ(z) as 1/(1+e−z) for z≥0 and as ez/(1+ez) for z<0.
Quick check
Why is the logistic-regression Hessian n1X⊤SX positive semidefinite?
Why.
Write the quadratic form: it is a weighted sum of squares with positive weights, so it is ≥0 for every v. Full column rank is needed only for the stronger statement "positive definite". A function can be ≥0 without being convex, and a diagonal matrix with a negative entry is not PSD.
For a batch with logits Z∈RB×C (one row zb per sample), labels yb and the mean
loss L=B1∑bℓ(zb):
∂Z∂L=B1(P−Y),
where P is the row-wise softmax of Z and Y holds the one-hot labels as rows.
Proof.
∂lse/∂zj=ezj/∑kezk=pj: the gradient of log-sum-exp is the softmax.
∂(−zy)/∂zj=−1 if j=y and 0 otherwise. So ∇zℓ=p−ey.
Row b of Z enters only the term ℓ(zb), with the factor 1/B. So row b of
∂L/∂Z is (pb−eyb)⊤/B. This is the matrix formula.
The Hessian is the Jacobian of p:∂pi/∂zj=pi([i=j]−pj), that is,
diag(p)−pp⊤ (linear-algebra lesson 16).
For any v:v⊤(diag(p)−pp⊤)v equals ∑kpkvk2−(∑kpkvk)2.
This is the variance of vk when the index k is drawn with probabilities p. So it is ≥0,
and ℓ is convex in z. It is 0 for v=1: adding a constant to all logits changes
nothing. ■
Properties you will use. The entries of p−ey sum to 0 (shift invariance). Each entry lies
in [−1,1], however large the logits are. For softmax regression, Z=XW with W∈Rd×C,
the VJP of a linear map gives ∂L/∂W=X⊤(P−Y)/B.
Stable computation. Let m=maxkzk. Since ∑kezk=em∑kezk−m,
lse(z)=m+logk∑ezk−m.
Every exponent is ≤0, so nothing overflows. The largest term is e0=1, so the sum is
≥1 and its log is never log0. Then compute ℓ=lse(z)−zy (this is
−log_softmax). Do not compute −logpy from the probabilities: even a stable softmax gives
py=0 when zy is more than about 745 below the largest logit (e−746 rounds to 0 in
float64), and then −log0=∞. This is why torch.nn.functional.cross_entropy takes logits,
not probabilities. (See linear-algebra lesson 13
for overflow and underflow.)
Answer to question 2 of section 1. For y=1:e1000 overflows to inf, and inf/inf is
nan, so the loss is nan. For y=2:p2=1/∞=0 and −log0=∞. The true losses
are log(1+e−1000)≈0 and 1000+log(1+e−1000)≈1000. The log-sum-exp
form gives both.
v is the biased variance (divide by D), as in PyTorch's layer_norm. ε≥0 is a
small constant (PyTorch default 10−5); we need v+ε>0.γ,β∈RD
are learned.
The picture: two invariances. Adding a constant c to every entry of x does not change
x^. So a step along 1 does not change the loss, and the gradient has no component along
1: its entries sum to 0. With ε=0, multiplying x by a positive number does not
change x^ either, so the gradient also has no component along x−μ1 (that is, along
x^). The backward pass removes these two components from the incoming gradient.
Theorem 5. Let g=∂L/∂y be the upstream gradient and g^=γ⊙g. Then
where the last formula holds for each row, and mean averages over the D features.
Proof of Theorem 5 (two simple maps and the chain rule)
Affine part.yj=γjx^j+βj. So for one row ∂L/∂βj=gj,∂L/∂γj=gjx^j and ∂L/∂x^=γ⊙g=g^.
The same γ,β act on every row, so their gradients are summed over the rows (fan-out, lesson 04).
Split the rest.x^=N(Πx), with the centering matrixΠ=I−D111⊤
(so Πx=x−μ1) and N(c)=c/ρ(c),ρ(c)=∥c∥2/D+ε. At
c=Πx we have ∥c∥2/D=v, so ρ=σ and c/σ=x^.
Jacobian of N.∂ρ/∂cj=cj/(Dρ). So by the quotient rule
∂Ni/∂cj=[i=j]/ρ−cicj/(Dρ3). In matrix form,
JN=σ1(I−D1x^x^⊤), a symmetric matrix.
VJP through N.u=JN⊤g^=σ1(g^−x^(x^⊤g^)/D).
Since (x^⊤g^)/D=mean(g^⊙x^), this is
u=σ1(g^−x^mean(g^⊙x^)).
VJP through Π.Π is symmetric and Πu=u−mean(u)1. Since
mean(x^)=0, we get mean(u)=mean(g^)/σ.
So ∂L/∂x=Πu is the formula. ■
Checks.
Row sums: sum the formula over j. The first two terms give ∑jg^j−Dmean(g^)=0.
The third gives mean(g^⊙x^)∑jx^j=0, because ∑jx^j=0.
So ∑j∂L/∂xj=0, for every ε.
Orthogonality to x^: using ∥x^∥2=Dv/(v+ε), one finds that
x^⊤∂L/∂x equals σ1(x^⊤g^) times ε/(v+ε). This is
exactly 0 for ε=0 (scale invariance) and small when v≫ε. When the row
variance v is close to ε, it is not small.
Cost.O(D) per row. Cache x^ and 1/σ from the forward pass. Never build the
D×D Jacobian.
Answer to question 3 of section 1.∑j∂L/∂xj=0. Layer norm of x+c1
equals layer norm of x, so 0=dcdL(x+c1)c=0=1⊤∇xL.
Interactive figure: loading…
Compare the full backward pass with a naive one. The grey bars are g^=γ⊙g, which
is what you get (times 1/σ) if you treat μ and σ as constants. The
orange bars are the true gradient, times σ. The true one always sums to 0,
and it is orthogonal to x^ while the row variance is much larger than ε=10−5.
Move the scale slider down until the variance is close to ε and watch the cosine readout.
Quick check
Layer normalization (with ε=10−5) gets a row x∈R5, and the backward pass returns dx=∂L/∂x. What is dx1+dx2+⋯+dx5?
Why.
Adding c1 to x does not change the output for any ε (the mean absorbs it), so the directional derivative along 1 is 0. The statement that needs ε=0 is the other invariance: orthogonality to x^ (scale invariance).
The unrolled form of Theorem 1 shows that δ1 is δL multiplied by L−1 factors
Jl⊤=diag(φ′(zl))(Wl+1)⊤. Norms of products are at most products of
norms, so
∥δ1∥≤(l=1∏L−1∥diag(φ′(zl))∥∥Wl+1∥)∥δL∥.
If every factor has norm at most q<1, then ∥δ1∥≤qL−1∥δL∥: the gradient
of the first layers shrinks exponentially with depth (vanishing gradients). The sigmoid has
σ′≤1/4, so a deep sigmoid network vanishes unless the weights are large. When the factors
stretch vectors on average by more than 1, the norm typically grows exponentially
(exploding gradients). Careful initialization (variance 1/n for tanh, 2/n for ReLU), residual
connections (y=x+F(x) adds an identity path, so its Jacobian is I+DF) and normalization
layers keep this product near norm 1. The deep learning track treats these
methods in detail.
Interactive figure: loading…
Change the depth, the activation and the weight scale. The orange line is
the norm of the error signal ∥δl∥ at each layer of a random MLP of width 64, on a log
scale; the backward pass starts at the right with ∥δL∥=1. With tanh and scale 1 the norm
decreases slowly; with scale 0.5 it vanishes fast. With ReLU, scale 2≈1.41 (He
initialization) keeps it nearly flat, and scale 2 explodes. The sigmoid vanishes even at scale 1.
NumPy checks examples (b), (c) and (d) with hand-written backward passes. The comments show the
printed output.
importnumpyasnpfromscipy.specialimportlogsumexp# (a) + (b): the 2-3-1 ReLU network of section 4, batch form (one sample per row)W1=np.array([[2.,1.],[1.,3.],[0.,-1.]]);b1=np.zeros(3)W2=np.array([[1.,-1.,2.]]);b2=np.array([0.5])X=np.array([[1.,-1.],[0.,1.]]);T=np.array([[1.5],[-0.5]])B=len(X)Z1=X@W1.T+b1;A1=np.maximum(Z1,0)# forward, store Z1 and A1Z2=A1@W2.T+b2;L=0.5*np.mean(np.sum((Z2-T)**2,axis=1))D2=(Z2-T)/B# Delta^2: the only 1/BdW2,db2=D2.T@A1,D2.sum(0)D1=(D2@W2)*(Z1>0)# Delta^1dW1,db1=D1.T@X,D1.sum(0)print(L,dW2,db2)# 1.25 [[ 0.5 -1.5 1. ]] [0.5]print(dW1.tolist())# [[1.0, -1.5], [0.0, 0.5], [2.0, -2.0]]# (c) softmax cross-entropy: (P - Y) / B, computed with log-sum-expZ=np.array([[np.log(4),np.log(2),np.log(2)],[0.,0.,0.]])y=np.array([1,2])# classes 2 and 3 (code counts from 0)lse=logsumexp(Z,axis=1)loss=np.mean(lse-Z[np.arange(2),y])P=np.exp(Z-lse[:,None]);Y=np.eye(3)[y]print(round(loss,4),((P-Y)/2).round(4).tolist())# 1.2425 [[0.25, -0.375, 0.125], [0.1667, 0.1667, -0.3333]]# naive vs stable at z = (1000, 0)z=np.array([1000.,0.])withnp.errstate(over="ignore",invalid="ignore",divide="ignore"):print(-np.log(np.exp(z)/np.exp(z).sum()))# [nan inf]print(logsumexp(z)-z)# [ 0. 1000.]# (d) layer norm backward (eps = 0), and its two checksdeflayernorm_backward(x,gamma,g,eps=0.0):mu,var=x.mean(),x.var()# biased variance, as in PyTorchsigma=np.sqrt(var+eps);xh=(x-mu)/sigmagh=gamma*greturn(gh-gh.mean()-xh*np.mean(gh*xh))/sigma,xhx=np.array([1.,1.,5.,5.])gamma=np.array([1.,1.,2.,1.])g=np.array([1.,2.,1.,0.])# upstream gradient dL/dydx,xh=layernorm_backward(x,gamma,g)print(dx,dx.sum(),xh@dx)# [-0.25 0.25 0.5 -0.5 ] 0.0 0.0
In PyTorch, autograd gives the same numbers. Note the weight layout of nn.Linear (out, in) and
that cross_entropy takes logits:
importtorchimporttorch.nn.functionalasFtorch.set_default_dtype(torch.float64)# The same 2-3-1 network as nn.Linear layers: weight shape (out, in), Z = A W^T + b.net=torch.nn.Sequential(torch.nn.Linear(2,3),torch.nn.ReLU(),torch.nn.Linear(3,1))withtorch.no_grad():net[0].weight.copy_(torch.tensor([[2.,1.],[1.,3.],[0.,-1.]]));net[0].bias.zero_()net[2].weight.copy_(torch.tensor([[1.,-1.,2.]]));net[2].bias.fill_(0.5)X=torch.tensor([[1.,-1.],[0.,1.]]);T=torch.tensor([[1.5],[-0.5]])loss=0.5*F.mse_loss(net(X),T)# mse_loss is the mean of squaresloss.backward()print(loss.item(),net[0].weight.grad.tolist())# 1.25 [[1.0, -1.5], [0.0, 0.5], [2.0, -2.0]]# Softmax cross-entropy takes LOGITS; its gradient is (P - Y) / B.Z=torch.tensor([[1000.,0.,0.],[0.,0.,0.]],requires_grad=True)ce=F.cross_entropy(Z,torch.tensor([1,2]))# finite: log-sum-exp insidece.backward()print(round(ce.item(),4),Z.grad.numpy().round(4).tolist())# 500.5493 [[0.5, -0.5, 0.0], [0.1667, 0.1667, -0.3333]]
The loss of the first row is lse(1000,0,0)−0≈1000, finite, and its
gradient row is (p−e2)/2≈(1,−1,0)/2.
Every autodiff framework. Each operation in PyTorch or JAX has a VJP rule like the ones in
this lesson, and backward() runs them in reverse order. A custom torch.autograd.Function
asks you to write the VJP by hand. Fused kernels (for example for LayerNorm or cross-entropy) use
the closed forms of Theorems 4 and 5 to save memory and time.
Classifiers and language models. The logit gradient of every classifier and LLM is
(P−Y)/B. With a vocabulary of 105 tokens, P is the largest tensor of the step, so
memory-efficient kernels compute lse and p−ey in chunks of the vocabulary.
Soft targets. For any target distribution q(∑kqk=1), the loss
−∑kqklogpk=lse(z)−q⊤z has the gradient p−q. This covers
label smoothing and knowledge distillation. With a temperature T (logits z/T) the
gradient is (softmax(z/T)−q)/T. When q is the teacher's softmax at the same
temperature, this scales like 1/T2 for large T, so distillation losses are often multiplied by T2.
Outer-product structure. A per-sample weight gradient δa⊤ has rank 1 and Frobenius
norm ∥δ∥∥a∥. So per-sample gradient norms (needed for clipping in differentially
private SGD) cost almost nothing. A batch gradient has rank at most B. K-FAC approximates the
curvature of a layer with the Kronecker factors E[aa⊤] and E[δδ⊤].
Newton, Fisher and Gauss–Newton. The logistic Hessian n1X⊤SX does not depend on
y, so it equals the Fisher information. For a nonlinear model with these losses, the
Gauss–Newton matrix J⊤∇z2ℓJ uses exactly the score Hessians of this lesson:
I,S or diag(p)−pp⊤ (lesson 08).
ML for science. Variational data assimilation and seismic inversion minimize a weighted least
squares loss J(u)=21(Gu−d)⊤Σo−1(Gu−d)+21(u−ub)⊤Σb−1(u−ub)
(observation operator G, data d, background state ub, covariances Σo and Σb). The proof of Theorem 2 gives
∇J=G⊤Σo−1(Gu−d)+Σb−1(u−ub). For a nonlinear G, the
transposed Jacobian DG(u)⊤ is the adjoint model: one backward run gives the whole gradient.
LayerNorm (or its variant RMSNorm) is in every block of transformer-based weather and
Earth-system models.
Missing transposes. The recursion uses (Wl+1)⊤δl+1, not Wl+1δl+1,
and ∂ℓ/∂Wl=δl(al−1)⊤ has the shape of Wl. Shapes catch both
mistakes, unless the layer is square.
The mask of the wrong layer.δl uses φ′(zl), the pre-activation of layer l
itself, not φ′(zl+1).
Mixing the two layouts.z=Wa (one column per sample) and Z=AW⊤ or Z=AW (one row
per sample) give transposed formulas. Derive in one layout, then check shapes in the other.
1/B twice, or not at all. It enters once, in ΔL. Then ∂ℓ/∂b is a
sum over the batch, not another mean. A sum instead of a mean makes the gradient B times larger.
Naive logs and exps.log(1+ez) and log∑kezk overflow when an exponent is above
about 710 in float64 (about 89 in float32). Use logaddexp, softplus, log_sigmoid, logsumexp.
−log of a probability. Even a stable softmax underflows for unlikely classes. Use
lse(z)−zy; this is why cross_entropy takes logits.
Sigmoid plus squared error for classification.21(σ(z)−y)2 has the gradient
(σ−y)σ(1−σ), which vanishes when the model is confidently wrong. At z=−5
and y=1 it is −0.0066; cross-entropy gives σ−y=−0.9933, about 150 times larger.
Layer norm with μ and σ treated as constants. That gives dx=g^/σ and
misses two terms. Test: the entries of dx must sum to 0.
Biased vs unbiased variance. Layer norm uses the biased variance. np.var is biased by default,
but torch.var is unbiased by default (correction=1).
Forgetting to sum dγ and dβ over all rows (all tokens of all sequences in the batch).
"Strictly convex, so a minimizer exists." Not on separable data: strict convexity gives at most
one minimizer, not existence.
ℓ=21∥σ(Wx+b)−t∥2 with the elementwise sigmoid σ. Give δ=∂ℓ/∂z
for z=Wx+b, and ∂ℓ/∂W,∂ℓ/∂b,∂ℓ/∂x.Answerδ=(σ(z)−t)⊙σ(z)⊙(1−σ(z)), using σ′=σ(1−σ). Then ∂ℓ/∂W=δx⊤,∂ℓ/∂b=δ and ∂ℓ/∂x=W⊤δ.
A batch of B=8 samples passes through a layer with Wl∈R512×256. What is the
largest possible rank of ∂ℓ/∂Wl?Answer8. (Δl)⊤Al−1 has inner dimension B: it is a sum of 8 outer products, so its rank is at most min(8,512,256)=8.
A residual block computes y=x+F(x), and the loss depends on x only through y. With
yˉ=∂ℓ/∂y, give ∂ℓ/∂x. Why does this help deep networks?Answer∂ℓ/∂x=yˉ+DF(x)⊤yˉ=(I+DF(x))⊤yˉ: two paths, and at a fan-out the contributions add (lesson 04). The identity path passes yˉ back unchanged, so the gradient does not have to go through a long product of layer Jacobians that can shrink to zero.
Label smoothing with α=0.3 uses the target q=(1−α)ey+Cα1.
For z=(ln4,ln2,ln2) and y=2, what is ∇zℓ?Answerq=(0.1,0.8,0.1) and p=(0.5,0.25,0.25), so ∇zℓ=p−q=(0.4,−0.55,0.15). The entries still sum to 0.
Compute ℓ for z=(1000,1000,998) and y=3 without overflow.Answerm=1000. Then lse(z)=1000+log(e0+e0+e−2), which is 1000+log2.1353=1000.7586. So ℓ=1000.7586−998=2.7586.
In the logistic example of 3.5, change the labels to y=(1,1,0). Is the Hessian at
w=(0,ln3) still positive definite? Does L have a minimizer?AnswerThe Hessian n1X⊤SX does not depend on y, so it is the same positive definite matrix. But no minimizer exists: wsep=(0.5,1) gives the scores (0.5,1.5,−0.5), positive exactly for the label-1 samples. So L(twsep)→0 as t→∞ without reaching 0 (for t=1,10,30:0.38,4.5⋅10−3,2.0⋅10−7).
Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
What is δL=∂ℓ/∂zL for softmax cross-entropy?
p−ey: the softmax probabilities minus the one-hot vector of the true class.
Write the backprop recursion for the error signal δl of a hidden layer.
δl=φ′(zl)⊙(Wl+1)⊤δl+1.
What is ∂ℓ/∂Wl for one sample, and what is its rank?
δl(al−1)⊤, an outer product of the error signal and the layer input. Rank at most 1.
Batch form (one sample per row, mean loss): what are ΔL and ∂ℓ/∂Wl?
ΔL=(P−Y)/B and ∂ℓ/∂Wl=(Δl)⊤Al−1.
Gradient and Hessian of L(w)=2n1∥Xw−y∥2?
∇L=n1X⊤(Xw−y) and ∇2L=n1X⊤X.
Gradient and Hessian of the mean logistic loss with scores z=Xw?
∇L=n1X⊤(σ(z)−y) and ∇2L=n1X⊤SX,S=diag(σ(zi)(1−σ(zi))).
Why has logistic regression no minimizer on linearly separable data?
Along twsep every sample loss is log(1+e−t∣xi⊤wsep∣), which tends to 0 as t→∞ but never reaches it. So the infimum 0 is not attained.
Write the stable log-sum-exp formula and say why it does not overflow.
lse(z)=m+log∑kezk−m with m=maxkzk. Every exponent is ≤0, and the sum is ≥1.
Why do the entries of the layer-norm input gradient sum to 0?
Layer norm of x+c1 equals layer norm of x, so the derivative along 1 is 0: 1⊤∇xL=0.
Why do gradients vanish in a deep sigmoid network?
δ1 is a product of L−1 factors diag(σ′(zl))(Wl+1)⊤, and σ′≤1/4. Unless the weights are large, each factor shrinks the vector, so the product shrinks exponentially with depth.
"Derive backprop for an MLP." — Define δl=∂ℓ/∂zl. Then δL=p−ey,δl=φ′(zl)⊙(Wl+1)⊤δl+1,∂ℓ/∂Wl=δl(al−1)⊤,∂ℓ/∂bl=δl. Check every shape. Then give the batch form with ΔL=(P−Y)/B.
"Derive the gradient of softmax cross-entropy." — Write ℓ=lse(z)−zy. The
gradient of lse is the softmax, so ∇ℓ=p−ey; for a batch mean, (P−Y)/B.
"Why subtract the max? Why does cross_entropy take logits?" — ez overflows, and −logpy is
∞ when py underflows. The shifted lse(z)−zy avoids both; the shift is
exact because the softmax is shift invariant.
"Is logistic regression convex? Is a neural network?" — Logistic: yes, the Hessian
n1X⊤SX is PSD. A network: no. Permuting the hidden units of a minimizer gives another
minimizer with the same loss; for a convex loss the midpoint of the two would also be a minimizer,
but the averaged weights are in general worse.
"Why cross-entropy and not squared error for classification?" — It is the maximum-likelihood loss
of the categorical model, it is convex in the logits, and its gradient p−ey stays large when
the model is confidently wrong (squared error on a sigmoid output vanishes there).
"What is the backward pass of layer norm?" — The formula of Theorem 5; it removes the mean and the
x^ component of γ⊙g, then divides by σ.
"Why do gradients vanish or explode?" — The gradient at the input side is a product of L−1
layer Jacobians; initialization, residual connections and normalization keep it near norm 1.
The notebook for this lesson runs in Colab. Each exercise has a check cell (it compares
your gradient with a central-difference gradient) and a folded solution:
softmax_cross_entropy(Z, y): the stable mean loss and its gradient (P−Y)/B, finite at logits ±1000.
logistic_regression(X, y, w): loss, gradient and Hessian; then Newton's method on the breast
cancer data set of scikit-learn.
layernorm_forward and layernorm_backward: check the gradient and its two invariances.
mlp_forward and mlp_backward for a 2-layer MLP on the scikit-learn digits; gradient check, then
train a few epochs.
Measure the gradient norm per layer of a deep tanh MLP at two weight scales.
The chain rule through one neuron per layer, then the indices for many neurons: the same recursion as Theorem 1.
Open on YouTube · the player loads from youtube-nocookie.com when you press play
The picture before the formulas: how each weight's gradient depends on the activation that feeds it.
Open on YouTube · the player loads from youtube-nocookie.com when you press play
A full backward pass by hand, tensor by tensor, including cross-entropy and batch normalization; compare each step with sections 3.3 and 3.6.
Open on YouTube · the player loads from youtube-nocookie.com when you press play
Computational graphs, local gradients and the vectorized backward pass of a linear layer.
Open on YouTube · the player loads from youtube-nocookie.com when you press play
Kevin Murphy, Probabilistic Machine Learning: An Introduction,
free PDF: §13.3 (backpropagation, VJPs of common layers), §13.4.2 (vanishing and exploding
gradients), §10.2.3 (logistic regression gradient and Hessian), §11.2.2 (least squares).