ML Foundations Lecture 12 of 14
Part IV — Multivariable Calculus · Lecture 12

Gradients, Jacobians, and Hessians

Calculus in one variable, plus linear algebra, equals the calculus that machine learning actually runs on. The gradient points uphill, the Jacobian is a derivative that is a matrix, the Hessian is curvature — and the multivariable chain rule is backpropagation.

By the end you can

  • Compute partial derivatives and assemble a gradient, with correct shapes
  • Prove that \(\grad f\) is the direction of steepest ascent and is perpendicular to level sets
  • Use the multivariable chain rule, and see it as the backpropagation recursion
  • Derive the matrix-calculus identities used on credit in Lecture 10
  • Classify a critical point in \(d\) dimensions using the eigenvalues of the Hessian

Assumed

  • Lectures 4–7: derivatives, chain rule, Taylor, optimisation
  • Lectures 8–11: vectors, matrices, quadratic forms, eigenvalues

1Partial derivatives

A loss depends on millions of parameters at once. The trick is to change one at a time.

Definition — partial derivative

For \(f:\RR^d\to\RR\), the partial derivative with respect to \(x_i\) is

\[ \frac{\partial f}{\partial x_i}(\vx) = \lim_{h\to0}\frac{f(\vx+h\ve_i)-f(\vx)}{h}, \]

where \(\ve_i\) is the \(i\)-th standard basis vector. In practice: differentiate with respect to \(x_i\), treating every other variable as a constant.

It is Lecture 4's definition verbatim, with the increment restricted to one axis. Every rule from Lecture 5 applies unchanged. For \(f(x,y)=x^2y+3y\):

\[\frac{\partial f}{\partial x} = 2xy, \qquad \frac{\partial f}{\partial y} = x^2+3.\]

Definition — gradient

The gradient collects all the partials into one vector:

\[ \grad f(\vx) = \begin{bmatrix}\partial f/\partial x_1 \\ \vdots \\ \partial f/\partial x_d\end{bmatrix} \in \RR^{d}. \]

Shapes matter: \(f\) maps \(\RR^d\to\RR\), and \(\grad f\) maps \(\RR^d\to\RR^d\). The gradient of a scalar function has the same shape as its input — which is what makes \(\vw \leftarrow \vw - \eta\grad L\) type-check.

2Directional derivatives, and why the gradient points uphill

Partials tell you the rate of change along the axes. But you can move in any direction. Let \(\vu\) be a unit vector; the rate of change of \(f\) along \(\vu\) is

\[D_{\vu}f(\vx) = \lim_{h\to0}\frac{f(\vx+h\vu)-f(\vx)}{h}.\]

D_u f = ∇f · u, and the steepest-ascent theorem0 / 6
\[g(h) = f(\vx + h\vu), \qquad D_{\vu}f(\vx) = g'(0)\]
Reduce to one variable. Walking along a straight line through \(\vx\) in direction \(\vu\) makes \(f\) a function of the single parameter \(h\). The directional derivative is just \(g'(0)\), an ordinary Lecture-4 derivative.
\[g'(h) = \sum_{i=1}^{d} \frac{\partial f}{\partial x_i}\big(\vx+h\vu\big)\cdot \frac{d}{dh}\big(x_i+hu_i\big) = \sum_{i=1}^{d}\frac{\partial f}{\partial x_i}\,u_i\]
Multivariable chain rule. \(f\)'s input changes in every coordinate simultaneously, so the total rate is the sum of (sensitivity to coordinate \(i\)) × (rate of change of coordinate \(i\)). We state and use this rule properly in the next section; here it is the natural generalisation of Lecture 5.
\[D_{\vu}f(\vx) = \grad f(\vx)\cdot\vu\]
Recognise the dot product. The sum \(\sum_i (\partial f/\partial x_i) u_i\) is precisely \(\grad f\cdot\vu\). So the gradient is exactly the object that converts a direction into a rate of change.
\[D_{\vu}f = \grad f\cdot\vu = \|\grad f\|\,\|\vu\|\cos\theta = \|\grad f\|\cos\theta\]
Apply Lecture 8's geometric formula, using \(\|\vu\|=1\). Now everything is decided by the angle \(\theta\) between the direction of travel and the gradient.
\[\max_{\|\vu\|=1} D_{\vu}f = \|\grad f\| \quad\text{attained at}\quad \vu = \frac{\grad f}{\|\grad f\|}\]
The steepest-ascent theorem. \(\cos\theta\) is maximised at \(\theta=0\), i.e. pointing along the gradient. So: \(\grad f\) points in the direction of steepest ascent, and its length is the rate of ascent in that direction. Correspondingly \(-\grad f\) is steepest descent — which is the entire justification for the name and for the update rule \(\vw\leftarrow\vw-\eta\grad L\). ∎
\[\theta = 90^\circ \;\Rightarrow\; D_{\vu}f = 0 \;\Rightarrow\; \grad f \perp \text{level set}\]
A free corollary. Moving perpendicular to the gradient changes \(f\) at rate zero — that is, it keeps you on a level set. So the gradient is always perpendicular to the contour through the point. This is why gradient descent on a long thin valley zig-zags: it steps perpendicular to the contours, which is across the valley rather than along it.
Contours, the gradient field, and the directional derivativeInteractive
level sets of f∇f at the probe pointyour direction ugradient field
1
-1.60
1.20
0
Rotate u and watch the readout. The directional derivative peaks exactly when u lines up with the blue gradient arrow, hits zero when u is tangent to the contour, and is most negative pointing straight downhill. Notice on the 'valley' surface how the gradient points across the valley rather than along it — that mismatch, quantified by the condition number in Lecture 11, is what makes narrow valleys slow.

3Jacobians, and the multivariable chain rule

Definition — Jacobian

For a vector-valued \(\vf:\RR^n\to\RR^m\), the Jacobian is the \(m\times n\) matrix of all partials:

\[ \mJ_{\vf}(\vx) = \frac{\partial \vf}{\partial \vx} = \begin{bmatrix} \partial f_1/\partial x_1 & \cdots & \partial f_1/\partial x_n\\ \vdots & \ddots & \vdots\\ \partial f_m/\partial x_1 & \cdots & \partial f_m/\partial x_n \end{bmatrix}, \qquad (\mJ)_{ij} = \frac{\partial f_i}{\partial x_j}. \]

Row \(i\) is \(\grad f_i^\top\). When \(m=1\) the Jacobian is a single row — the gradient, transposed. That row-versus-column distinction is a notational nuisance and the source of endless sign-and-shape confusion; the rule to hold on to is the Jacobian has as many rows as outputs and as many columns as inputs.

Intuition

The Jacobian is the best linear approximation to \(\vf\) near \(\vx\): \(\;\vf(\vx+\vh) \approx \vf(\vx) + \mJ\vh\). Compare Lecture 4's \(f(x+h)\approx f(x)+f'(x)h\) — the derivative was a number that multiplied the nudge; now it is a matrix that multiplies the nudge vector. Same idea, richer type.

Theorem — multivariable chain rule

If \(\vf:\RR^n\to\RR^m\) and \(\vg:\RR^m\to\RR^p\), then for \(\vh = \vg\circ\vf\),

\[ \mJ_{\vh}(\vx) = \mJ_{\vg}\big(\vf(\vx)\big)\;\mJ_{\vf}(\vx). \]

Shapes: \((p\times m)(m\times n) = (p\times n)\) ✓. The chain rule is matrix multiplication. In coordinates,

\[ \frac{\partial h_i}{\partial x_k} = \sum_{j=1}^{m}\frac{\partial g_i}{\partial u_j}\,\frac{\partial f_j}{\partial x_k}, \]

summing over every path from input \(k\) to output \(i\) through the intermediate layer.

Where this shows up in ML

Now look at a network. With \(\vz^{(\ell)} = f_\ell(\vz^{(\ell-1)})\) and a scalar loss \(L\) at the end, the chain rule gives

\[ \frac{\partial L}{\partial \vz^{(0)}} = \frac{\partial L}{\partial \vz^{(L)}}\,\mJ_{L}\,\mJ_{L-1}\cdots\mJ_{1}. \]

A product of Jacobians, exactly as Lecture 5's scalar chain rule was a product of derivatives. Two observations that explain the entire design of a deep learning framework:

4The matrix-calculus identities we owe you

Lecture 10 used two gradient rules on credit. Here they are, proved by writing out coordinates — which is always the safe way to settle a matrix-calculus question you are unsure about.

∇(aᵀx) = a and ∇(xᵀAx) = (A + Aᵀ)x0 / 6
\[f(\vx) = \va^\top\vx = \sum_{j=1}^{d} a_j x_j\]
Write it as a sum. Whenever matrix calculus feels slippery, drop to indices. Every identity below is elementary once you do.
\[\frac{\partial f}{\partial x_i} = \frac{\partial}{\partial x_i}\sum_j a_jx_j = a_i \quad\Longrightarrow\quad \grad f = \va\]
Differentiate one coordinate. Every term with \(j\neq i\) is constant with respect to \(x_i\) and dies; the \(j=i\) term contributes \(a_i\). Stacking gives \(\va\). This is the analogue of \(\frac{d}{dx}(ax)=a\). ∎
\[q(\vx) = \vx^\top\mA\vx = \sum_{j}\sum_{k} A_{jk}\,x_jx_k\]
The quadratic form in indices. A double sum over all pairs of coordinates.
\[\frac{\partial q}{\partial x_i} = \sum_{k}A_{ik}x_k \;+\; \sum_{j}A_{ji}x_j\]
Product rule on each term. \(x_i\) appears in two places: as \(x_j\) with \(j=i\) (giving \(\sum_k A_{ik}x_k\), i.e. row \(i\) of \(\mA\) dotted with \(\vx\)) and as \(x_k\) with \(k=i\) (giving \(\sum_j A_{ji}x_j\), i.e. column \(i\)). The diagonal term \(A_{ii}x_i^2\) is correctly counted twice, giving \(2A_{ii}x_i\).
\[\grad q = \mA\vx + \mA^\top\vx = (\mA+\mA^\top)\vx\]
Reassemble. The first sum is entry \(i\) of \(\mA\vx\); the second is entry \(i\) of \(\mA^\top\vx\). ∎
\[\mA \text{ symmetric} \;\Longrightarrow\; \grad(\vx^\top\mA\vx) = 2\mA\vx\]
The case we needed. Exactly analogous to \(\frac{d}{dx}(ax^2)=2ax\). In Lecture 10, \(\mA=\mX^\top\mX\) is symmetric, so \(\grad(\vw^\top\mX^\top\mX\vw)=2\mX^\top\mX\vw\) ✓ — the step we deferred.
Now derive the least-squares gradient yourself

Using the two identities above, differentiate

\[ J(\vw) = \|\mX\vw-\vy\|^2 \]

with respect to \(\vw\), and confirm you recover the normal equations. Check the shape of every term as you go.

Expand (as in Lecture 10):

\[J(\vw) = \vw^\top\underbrace{(\mX^\top\mX)}_{d\times d,\ \text{symmetric}}\vw \;-\;2\,\vw^\top\underbrace{(\mX^\top\vy)}_{d\times 1}\;+\;\|\vy\|^2 .\]

Differentiate term by term, using linearity:

  • First term: \(\grad(\vw^\top\mA\vw)=2\mA\vw = 2\mX^\top\mX\vw\). Shape \(d\times1\) ✓.
  • Second term: \(\grad(-2\vw^\top\vb) = -2\vb = -2\mX^\top\vy\). Shape \(d\times1\) ✓.
  • Third term: constant in \(\vw\), gradient \(\vzero\).
\[ \grad J = 2\mX^\top\mX\vw - 2\mX^\top\vy \;\stackrel{!}{=}\;\vzero \quad\Longrightarrow\quad \mX^\top\mX\vw = \mX^\top\vy. \qquad\blacksquare\]

Alternative route, worth knowing. Treat it as a composition: \(\vr(\vw)=\mX\vw-\vy\) with Jacobian \(\mJ_{\vr}=\mX\) (\(n\times d\)), and \(J=\vr^\top\vr\) with \(\grad_{\vr}J = 2\vr\) (\(n\times1\)). The chain rule for a scalar output reads \(\grad_{\vw}J = \mJ_{\vr}^\top\,\grad_{\vr}J = \mX^\top(2\vr) = 2\mX^\top(\mX\vw-\vy)\) ✓. Same answer, and note the transpose: pulling a gradient backwards through a linear map multiplies by \(\mJ^\top\), which is exactly what a backward pass does.

5The Hessian

Definition — Hessian

For \(f:\RR^d\to\RR\), the Hessian is the \(d\times d\) matrix of second partials:

\[ \mH = \grad^2 f, \qquad H_{ij} = \frac{\partial^2 f}{\partial x_i \partial x_j}. \]

It is the Jacobian of the gradient. Clairaut's theorem: if the second partials are continuous then \(\frac{\partial^2 f}{\partial x_i\partial x_j} = \frac{\partial^2 f}{\partial x_j\partial x_i}\), so \(\mH\) is symmetric — and therefore everything from Lecture 11 applies to it: real eigenvalues, orthogonal eigenvectors, a definiteness classification.

The second-order Taylor expansion in \(d\) dimensions is exactly what you would guess:

\[f(\vx+\vh) \;\approx\; f(\vx) + \grad f(\vx)^\top\vh + \tfrac12\,\vh^\top\mH(\vx)\,\vh.\]

Three terms: a number, a linear form, a quadratic form. Compare Lecture 6's \(f+f'h+\tfrac12 f''h^2\) — identical, with the scalars promoted to a vector and a matrix. And now the second-derivative test writes itself, using Lecture 11's classification.

Theorem — second derivative test in d dimensions

Let \(\grad f(\vx^\star)=\vzero\) and let \(\mH=\grad^2f(\vx^\star)\), with eigenvalues \(\lambda_1,\dots,\lambda_d\).

Why: at a critical point the linear term vanishes, so \(f(\vx^\star+\vh)-f(\vx^\star) \approx \tfrac12\vh^\top\mH\vh\), and Lecture 11 showed the sign of that quadratic form is decided entirely by the signs of the eigenvalues.

Gradient descent in two dimensions — and what the Hessian says about where it landsInteractive
contoursdescent pathstartend
1
-2.20
2.20
0.080
40
Try the saddle: start almost exactly on the y-axis and descent slides towards the origin, hovers there where the gradient is nearly zero, then eventually escapes along the negative-curvature direction. In high dimensions this is the typical situation — critical points of a deep network's loss are overwhelmingly saddles, not local minima, which is why 'stuck in a local minimum' is usually the wrong diagnosis.
Where this shows up in ML

Saddle points dominate in high dimensions. Here is a counting argument that is worth internalising. At a critical point of a random smooth function in \(\RR^d\), think of each of the \(d\) Hessian eigenvalues as being positive or negative roughly independently. For a local minimum you need all \(d\) to be positive — probability about \(2^{-d}\). For \(d=10^6\) that number is indistinguishable from zero.

So essentially every critical point of a large network's loss is a saddle. Consequences:

Check yourself
For \(f(x,y) = x^2 - y^2\) at the origin: \(\grad f = (2x,-2y)\) vanishes there. What kind of point is it?
Correct. Correct. Along the \(x\)-axis, \(f(x,0)=x^2\) has a minimum at 0; along the \(y\)-axis, \(f(0,y)=-y^2\) has a maximum. Both statements are true simultaneously, which is precisely what a saddle is.

Note that examining a few directions is not enough in general — you could check the \(x\) and \(y\) axes of some function and see minima in both while a diagonal direction descends. The eigenvalue test is exhaustive because the eigenvectors span every direction, and Lecture 11's Rayleigh bound says nothing can be worse than \(\lambda_{\min}\).
Not quite. It is not a sum of squares — the second term is \(-y^2\), which is negative for every \(y\neq0\). Check the value: \(f(0,0)=0\) but \(f(0,0.1)=-0.01<0\), so points arbitrarily close have smaller values. Not a minimum. Vanishing gradient tells you it is critical; only the Hessian tells you which kind.
Not quite. Same objection in the other direction: \(f(0.1,0)=+0.01>0\), so nearby points have larger values. It is neither. When a critical point is neither a max nor a min, "saddle" is the name.
Numerical gradients, Jacobians, Hessians — and how to gradcheckPython · NumPy

6Exercises

★

Compute \(\grad f\) and \(\mH\) for each, and classify every critical point:

(a) \(f(x,y)=x^2+3y^2-4x+6y\)    (b) \(f(x,y)=x^3-3xy+y^3\)    (c) \(f(x,y)=e^{-(x^2+y^2)}\)

(a) \(\grad f = (2x-4,\ 6y+6)\), zero at \((2,-1)\). \(\mH=\begin{bmatrix}2&0\\0&6\end{bmatrix}\), constant. Eigenvalues \(2,6\), both positive → strict local (and, since \(\mH\) is constant and positive definite, global) minimum, value \(f(2,-1)=4+3-8-6=-7\).

(b) \(\grad f = (3x^2-3y,\ -3x+3y^2)\). Setting both to zero: \(y=x^2\) and \(x=y^2\). Substituting, \(x=x^4\), so \(x(x^3-1)=0\), giving \(x=0\) or \(x=1\). Critical points \((0,0)\) and \((1,1)\).
\(\mH=\begin{bmatrix}6x&-3\\-3&6y\end{bmatrix}\).
At \((0,0)\): \(\begin{bmatrix}0&-3\\-3&0\end{bmatrix}\), \(\det=-9<0\). A negative determinant in 2D means \(\lambda_1\lambda_2<0\), hence mixed signs → saddle. (Eigenvalues \(\pm3\).)
At \((1,1)\): \(\begin{bmatrix}6&-3\\-3&6\end{bmatrix}\), \(\tr=12>0\) and \(\det=36-9=27>0\), so both eigenvalues are positive (\(3\) and \(9\)) → local minimum, value \(1-3+1=-1\).
Useful 2×2 shortcut: \(\det<0\) ⟹ saddle; \(\det>0\) with \(\tr>0\) ⟹ minimum; \(\det>0\) with \(\tr<0\) ⟹ maximum.

(c) \(\grad f = -2e^{-(x^2+y^2)}(x,y)\), zero only at \((0,0)\) since the exponential never vanishes.
At the origin, \(\mH = \begin{bmatrix}-2&0\\0&-2\end{bmatrix}\) (differentiate again and evaluate; the cross terms vanish by symmetry). Both eigenvalues negative → local maximum, value 1. It is also global, since \(0<f\le1\) everywhere.

★★

Prove that \(\grad f(\vx^\star)=\vzero\) is necessary for \(\vx^\star\) to be a local minimum of a differentiable \(f:\RR^d\to\RR\).

Hint: reduce to the one-dimensional case using \(g(t)=f(\vx^\star+t\vu)\) and Lecture 7's Fermat rule.

Then explain why the converse fails in a way that is worse in high dimensions than in one.

Proof. Suppose \(\vx^\star\) is a local minimum, so \(f(\vx^\star)\le f(\vx)\) for all \(\vx\) within some \(\delta\). Fix any unit direction \(\vu\) and define \(g(t)=f(\vx^\star+t\vu)\), a function of one real variable, differentiable at 0 with \(g'(0)=D_{\vu}f(\vx^\star)=\grad f(\vx^\star)^\top\vu\) by the directional-derivative theorem.

For \(|t|<\delta\) the point \(\vx^\star+t\vu\) is within \(\delta\) of \(\vx^\star\), so \(g(t)\ge g(0)\): the point \(t=0\) is a local minimum of \(g\), and it is interior. By Fermat's rule (Lecture 7), \(g'(0)=0\), i.e.

\[\grad f(\vx^\star)^\top\vu = 0.\]

This holds for every unit \(\vu\). Take \(\vu=\ve_i\) for each \(i\): every component of the gradient is zero. Hence \(\grad f(\vx^\star)=\vzero\). ∎

(Equivalently: a vector orthogonal to every vector must be the zero vector — take \(\vu\) parallel to the gradient itself and you get \(\|\grad f\|^2=0\).)

Why the converse fails worse in high dimensions. In one dimension a critical point is a minimum, a maximum, or an inflection — and generically (with \(f''\neq0\)) it is one of the first two, so a critical point has roughly a 50% chance of being a minimum.

In \(d\) dimensions the classification is by the sign pattern of \(d\) eigenvalues. A minimum needs all of them positive. If the signs were independent and equally likely, minima would have probability \(2^{-d}\), while saddles — anything with at least one sign of each — would have probability \(1-2^{-d+1}\).

The independence assumption is crude, and random-matrix theory sharpens it considerably (the semicircle law, and results of Bray–Dean and Dauphin et al. on spin glasses and neural losses), but the qualitative conclusion survives and is well supported empirically: in high dimensions almost all critical points are saddles, and the ones with low loss are overwhelmingly the ones that are minima. This reframes what optimisation is fighting. It is not searching a landscape littered with bad local minima; it is navigating long flat plateaus around saddles.

★★

Derive the gradient of the logistic regression loss

\[ L(\vw) = -\sum_{i=1}^{n}\Big[y_i\log\sigma(\vw^\top\vx_i) + (1-y_i)\log\big(1-\sigma(\vw^\top\vx_i)\big)\Big] \]

and show it simplifies to \(\;\grad L = \mX^\top(\hat{\vp}-\vy)\), where \(\hat p_i = \sigma(\vw^\top\vx_i)\).

Comment on the resemblance to the least-squares gradient.

Work with one term. Write \(z=\vw^\top\vx\), \(p=\sigma(z)\), and \(\ell = -[y\log p + (1-y)\log(1-p)]\).

Step 1 — differentiate the loss with respect to \(p\):

\[\frac{\partial \ell}{\partial p} = -\frac{y}{p} + \frac{1-y}{1-p} = \frac{-y(1-p)+p(1-y)}{p(1-p)} = \frac{p-y}{p(1-p)}.\]

Step 2 — the sigmoid derivative (Lecture 5): \(\dfrac{dp}{dz} = p(1-p)\).

Step 3 — chain rule, and the beautiful cancellation:

\[\frac{\partial \ell}{\partial z} = \frac{p-y}{p(1-p)}\cdot p(1-p) = p - y.\]

Step 4 — chain rule again, with \(\dfrac{\partial z}{\partial \vw}=\vx\):

\[\grad_{\vw}\ell = (p-y)\,\vx.\]

Summing over the dataset and stacking, \(\grad L = \sum_i (\hat p_i-y_i)\vx_i = \mX^\top(\hat{\vp}-\vy)\). ∎

The resemblance. Least squares gave \(\grad J = 2\mX^\top(\mX\vw-\vy) = 2\mX^\top (\hat{\vy}-\vy)\). Both are design matrix transposed, times prediction error. This is not a coincidence: both losses are negative log-likelihoods of exponential-family models with a canonical link function (identity for the Gaussian, logit for the Bernoulli), and for that whole family the gradient always takes this form. It is why the same optimiser code works for both, and why the update rule \(\vw \leftarrow \vw - \eta\mX^\top(\hat{\vy}-\vy)\) feels like the natural thing to write.

Note also that the messy \(p(1-p)\) cancelled exactly. Had we used squared error on top of a sigmoid instead, it would not have — the gradient would carry a factor of \(\sigma'\), which vanishes when the model is confidently wrong. That is a concrete reason cross-entropy trains better than squared error for classification: it removes the saturation from the gradient.

★★★

Coding. Implement reverse-mode automatic differentiation for a two-layer network and verify it against numerical gradients.

  1. Forward: \(\vz_1=\mW_1\vx+\vb_1\), \(\va_1=\tanh(\vz_1)\), \(\vz_2=\mW_2\va_1+\vb_2\), \(L=\tfrac12\|\vz_2-\vy\|^2\).
  2. Derive and implement the backward pass by hand, using the chain rule as a product of vector-Jacobian products. Recall \(\tanh'=1-\tanh^2\).
  3. Check every gradient against num_grad to at least \(10^{-7}\) relative error.
  4. Count the operations. How does the cost of the backward pass compare with the forward pass, and how would it compare if you had used forward mode instead?

2. The backward pass. Work from the loss backwards, carrying \(\bar{\vv} := \partial L/\partial \vv\) at each node. Each step is a vector-Jacobian product.

\[\bar{\vz}_2 = \vz_2-\vy, \qquad \bar{\mW}_2 = \bar{\vz}_2\va_1^\top, \qquad \bar{\vb}_2 = \bar{\vz}_2,\] \[\bar{\va}_1 = \mW_2^\top\bar{\vz}_2, \qquad \bar{\vz}_1 = \bar{\va}_1 \odot (1-\va_1^2), \] \[\bar{\mW}_1 = \bar{\vz}_1\vx^\top, \qquad \bar{\vb}_1 = \bar{\vz}_1.\]

Two patterns to notice, and they are the whole of backpropagation. Pulling a gradient back through a linear map multiplies by the transpose of the weight matrix. Pulling it back through an elementwise map multiplies elementwise by the local derivative. The gradient with respect to a weight matrix is always an outer product of (incoming gradient) and (stored input).

import numpy as np
rng = np.random.default_rng(0)
din, dh, dout = 5, 7, 3
W1 = rng.normal(size=(dh, din))*0.5;  b1 = rng.normal(size=dh)*0.1
W2 = rng.normal(size=(dout, dh))*0.5; b2 = rng.normal(size=dout)*0.1
x  = rng.normal(size=din);            y  = rng.normal(size=dout)

def forward(W1, b1, W2, b2):
    z1 = W1 @ x + b1;  a1 = np.tanh(z1)
    z2 = W2 @ a1 + b2
    return 0.5*np.sum((z2 - y)**2), (z1, a1, z2)

def backward(W1, b1, W2, b2):
    _, (z1, a1, z2) = forward(W1, b1, W2, b2)
    gz2 = z2 - y                       # dL/dz2
    gW2 = np.outer(gz2, a1);  gb2 = gz2
    ga1 = W2.T @ gz2                   # transpose: linear layer
    gz1 = ga1 * (1 - a1**2)            # elementwise: tanh
    gW1 = np.outer(gz1, x);   gb1 = gz1
    return gW1, gb1, gW2, gb2

def num_grad(f, p, h=1e-6):
    g = np.zeros_like(p); it = np.nditer(p, flags=['multi_index'])
    while not it.finished:
        i = it.multi_index; old = p[i]
        p[i] = old + h; fp = f()
        p[i] = old - h; fm = f()
        p[i] = old;     g[i] = (fp - fm)/(2*h)
        it.iternext()
    return g

gW1, gb1, gW2, gb2 = backward(W1, b1, W2, b2)
for name, param, ana in [("W1",W1,gW1), ("b1",b1,gb1), ("W2",W2,gW2), ("b2",b2,gb2)]:
    num = num_grad(lambda: forward(W1,b1,W2,b2)[0], param)
    rel = np.abs(num-ana).max() / max(np.abs(ana).max(), 1e-12)
    print(f"{name}: max abs diff {np.abs(num-ana).max():.3e}   rel {rel:.3e}"
          f"   {'OK' if rel < 1e-7 else 'MISMATCH'}")

3. All four should report relative error around \(10^{-9}\) or better. If one is wrong and the others are right, the error is almost always a missing transpose or a missing elementwise factor — the two patterns above.

4. Cost. The forward pass does two matrix-vector products: \(d_h d_{in} + d_{out}d_h\) multiply-adds. The backward pass does two matrix-vector products (\(\mW_2^\top\bar{\vz}_2\), and the analogous one if there were more layers) plus two outer products of the same total size. So the backward pass costs roughly two forward passes, independent of the number of parameters. This is the central fact that makes deep learning possible: you obtain all \(P\) partial derivatives for the price of a small constant number of forward evaluations.

Forward mode (the Dual class from Lecture 5) propagates one perturbation direction at a time, so it needs \(P\) sweeps to get \(P\) partials — here \(P = d_hd_{in}+d_h+d_{out}d_h+ d_{out} = 35+7+21+3 = 66\), so about \(66\times\) the work. For a model with \(10^9\) parameters the ratio becomes \(10^9\). Reverse mode wins whenever outputs are few and inputs are many; forward mode wins in the opposite case (for example, sensitivity of many outputs to one hyperparameter). Both are the same chain rule, associated in opposite orders.


Next: Part V. Everything so far has been deterministic. Real data is noisy, losses are expectations, and generalisation is a probabilistic claim. Lecture 13 builds random variables, expectation and variance from the ground up.