Math Relearn/ Core tools/ Module 03

Calculus: change, direction, chain

Derivatives, gradients and the chain rule β€” the machinery that turns "make the error smaller" into a number you can compute. Why gradient descent works, and what backpropagation actually computes.

On this page
    Track 1 Β· Core tools

    03 Β· Calculus: change, direction, chain

    ⏱ ~1 h 50πŸ“Ž NumPy🎯 Explain why gradient descent works and what backpropagation actually computes.

    Every training loop is the same sentence: look at how wrong you are, work out which way is less wrong, step that way. The middle part is calculus. This module builds the chain β€” derivative, gradient, curvature, Jacobian, chain rule β€” and spends its best section on the payoff: a full backpropagation worked with real numbers, so "autograd" becomes arithmetic you can do on paper.

    By the end you can

    • Read a derivative three ways β€” slope, sensitivity, local linear approximation β€” and say which one ML uses.
    • Compute partials and the gradient, and explain why the gradient is the direction of steepest increase.
    • Run gradient descent by hand and say exactly what the learning rate $\eta$ controls.
    • Derive $\sigma'(x)=\sigma(x)(1-\sigma(x))$ in four steps, and say what it does to deep gradients.
    • Do a backpropagation by hand on a 2-layer net, then verify it with a finite-difference check.
    • Recall the gradients of MSE, sigmoid+BCE, softmax+CE and the $L_2$ penalty on sight.

    Why this module exists

    "Learning" in machine learning is a physical claim: parameters moved in a direction that made the error smaller. Without a derivative there is no direction β€” you would search the parameter space by random trial, and random search dies exponentially in dimension. Calculus converts one scalar (how wrong am I?) into a direction (which way is less wrong?), and that conversion is the entire content of every optimiser you will ever run.

    The second reason is diagnostic: frameworks differentiate for you, so people skip this module and then cannot explain a loss that will not go down. Vanishing gradients, a saturated activation, a silently wrong loss β€” those are calculus bugs wearing a software costume. The third reason is that it is short. Almost all of what ML uses fits on one page, and probability, statistics and optimisation (Modules 04–07) all sit on top of it.

    Pass 1

    Remember β€” retrieve before you read

    Six questions, cold, before any scrolling

    Cold questions β€” answer from memory, in writing

    1. What is the derivative of $3x^2$?
    2. What is the derivative of $x^3 + 2x$?
    3. What is the derivative of $e^x$?
    4. A function has $f'(3) = 6$. In plain words: what does that promise near $x=3$, and what does it refuse to promise about $x = 100$?
    5. Expand $(f \circ g)'(x)$ β€” the derivative of $f(g(x))$ β€” from memory.
    6. What is the gradient of $g(x,y) = x^2 + y^2$ at $(1,2)$? Which way does it point?

    Model answers

    Only open these after your own answers are written down.

    1–3. Derivatives of $3x^2$, $x^3+2x$, $e^x$
    $6x$; $3x^2+2$; $e^x$. The power rule $nx^{n-1}$ handles the first two: $3x^2 \to 6x$, and $x^3 \to 3x^2$ while $2x \to 2$. The exponential is its own derivative β€” that is why $e$ exists as a constant at all, and why anything that grows in proportion to its current size (compound interest, a saturated activation) keeps reappearing in ML.
    4. What does $f'(3) = 6$ mean in words?
    Sensitivity at that point. Near $x=3$ each extra $0.01$ of input buys roughly $0.06$ of output; the tangent there has slope $6$ and the function is well approximated by $6(x-3)+f(3)$ locally. It says nothing about $x=100$: a derivative is a local statement, and the function may bend anywhere far away β€” the caveat in Section 7's pitfall.
    5. Expand $(f\circ g)'(x)$
    $(f\circ g)'(x) = f'(g(x))\,g'(x)$ β€” the chain rule: outer derivative evaluated at the inner value, times the inner derivative. Read as sensitivities, they multiply along a chain of causes. Backpropagation is this sentence applied millions of times in the right order.
    6. Gradient of $x^2+y^2$ at $(1,2)$?
    $(2x,2y) = (2,4)$ β€” one partial derivative per coordinate. It points at steepest increase, so descent steps against it: $(1,2)-0.1\,(2,4) = (0.8,\,1.6)$, closer to the minimum at the origin.
    What is a partial derivative, and how is it different from a gradient?
    One number vs the whole vector. $\partial f/\partial x_i$ nudges coordinate $i$ alone and measures the response, holding the rest fixed. The gradient $\nabla f$ collects all $n$ of them β€” the object that gets subtracted in a descent step, shaped like the parameter vector.
    Why does the sigmoid have the derivative it has?
    Because its output is its own raw material: $\sigma'(x)=\sigma(x)(1-\sigma(x))$ (four derivation steps, Section 3). It peaks at $0.25$ when $\sigma=0.5$ and collapses toward $0$ as the output saturates β€” so a chain of sigmoids multiplies small numbers and the gradient dies. That is vanishing gradients, in one formula.
    Name the four loss gradients you should not have to look up
    MSE: $\tfrac{2}{n}X^\top(Xw-y)$. Sigmoid + binary cross-entropy: $\sigma(z)-y$. Softmax + cross-entropy: $\mathbf{p}-\mathbf{y}$. $L_2$ penalty: $2\lambda w$. Derivations in Section 8 β€” the second and third are why sigmoid/softmax are paired with log losses.
    Pass 2

    Understand β€” the shape of the idea

    Ten sections: what a derivative is, what a gradient buys, why backprop is the chain rule
    Machine learning is minimisation; minimisation needs derivatives; deep networks need the chain rule applied backwards. That is the entire module β€” everything else is detail.

    1 Β· Why change, and why now

    A training step says: the parameters moved in a direction that made the error smaller. Three words do the work β€” "moved" (change), "direction" (a vector, not a number), "smaller" (a scalar to compare). Calculus was invented to talk about exactly those three words. ML did not borrow a tool from physics; it rediscovered the same problem with different nouns.

    Newton and Leibniz built calculus in the 1600s to describe motion β€” position changes, velocity changes β€” because physics had become a question of rates. Two and a half centuries later the same machinery runs the same play: the loss is a function of the parameters, the gradient is its derivative, and the optimiser is a body rolling downhill under a force (the gradient) instead of gravity.

    History worth keeping

    Leibniz gave us the notation we still use ($d$ for differentials, $\int$ for sums), and it survived because it is programmable: $\frac{dy}{dx}$ reads as a ratio of two tiny changes, and when you chain several, the $dx$'s cancel like units in physics. You will watch exactly that cancellation happen in Section 7 β€” it is not a trick, it is why the notation was chosen.

    2 Β· The derivative, from the limit to the intuition

    Start with what you can compute: a secant through two points on a curve, slope = rise/run. Then push the points together. The secant pivots toward the curve and becomes the tangent β€” the straight line that best touches the curve at that one point. That limit is the derivative.

    $$f'(x) = \lim_{h\to 0}\frac{f(x+h)-f(x)}{h}$$ the slope of the tangent at x β€” change in output per unit change in input, at one specific point

    Worked example, $f(x)=x^2$ at $x=3$. The quotient simplifies before the limit β€” the $h$ in the denominator cancels, which is why the limit exists:

    $$\frac{(3+h)^2 - 9}{h} = \frac{9 + 6h + h^2 - 9}{h} = \frac{6h+h^2}{h} = 6+h \;\xrightarrow{\;h\to 0\;}\; 6$$ so fβ€²(3) = 6: near x = 3 the function climbs six units for every unit of input

    One number, three readings. Keep all three β€” they get used in different places:

    • Slope of the tangent. The geometric picture: draw the curve, draw the line grazing it at $x$, read its steepness. The sign is a statement about direction of increase β€” negative means downhill as $x$ grows.
    • Sensitivity. Output per input, $\Delta\text{out}/\Delta\text{in}$. The engineer's reading: "how much does the loss react to a nudge in this parameter?" A parameter with derivative near $0$ is one the loss does not care about.
    • Best local linear approximation. Near $x$ the function is almost the line $f(x_0) + f'(x_0)(x-x_0)$. Everything downstream β€” gradients, step sizes, convergence arguments β€” runs on this approximation, not on $f$ itself.
    Local linear approximation is why gradients work at all

    A neural loss is not a plane; it is a wildly curved surface in a million dimensions. So why does subtracting a gradient ever help? Because locally the surface is flat to first order: $f(\mathbf{x}+\Delta)\approx f(\mathbf{x}) + \nabla f^\top\Delta$, and that linear function has an obvious downhill direction, $-\nabla f$. The approximation only holds nearby β€” which explains two everyday behaviours: take small steps, and re-evaluate the gradient after each one. Momentum, Adam and Newton's method are refinements of "how far and how often to step", never a replacement for "the gradient is a local linear model".

    3 Β· Rules, each with a reason

    You memorised these once for an exam and lost them. This time attach each rule to the reason it exists.

    $$\frac{d}{dx}\big(x^n\big) = n x^{n-1}, \qquad \frac{d}{dx}\big(c\,f\big) = c\,f', \qquad \frac{d}{dx}\big(f+g\big) = f' + g'$$ power rule, constant multiple, sum β€” differentiation is linear, which is why sums of terms (and sums of samples) differentiate term by term

    Product rule β€” where the two terms come from. Nudge both factors and expand, exactly like $(a+b)^2$: each factor gets a turn to change while the other sits still.

    $$(f+df)(g+dg) = fg + f\,dg + g\,df + df\,dg$$ keep fg; drop dfΒ·dg (two tiny things multiplied is negligible); the two middle terms are the derivative
    $$\frac{d}{dx}\big(f(x)g(x)\big) = f'(x)\,g(x) + f(x)\,g'(x)$$ f changes while g holds, plus g changes while f holds β€” two middle terms, two terms in the rule

    Quotient rule (losses and normalisations divide), and the chain rule for composition β€” the two you will use most:

    $$\frac{d}{dx}\frac{f}{g} = \frac{f'g - fg'}{g^2}, \qquad \frac{d}{dx}f\big(g(x)\big) = f'\big(g(x)\big)\,g'(x)$$ the minus sign is the one everyone forgets; and for composition, outer derivative at the inner value times the inner derivative

    The table is limited to the functions ML actually differentiates:

    FunctionDerivativeWhere you meet it
    $x^n$$n x^{n-1}$Power losses, $L_2$ penalties, polynomial features
    $1/x$$-1/x^2$Every log-derivative, normalisation terms
    $e^x$$e^x$Exponentials in softmax, natural parameterisations
    $\ln x$$1/x$The most important row. Log-likelihoods: products become sums and derivatives become $1/x$
    $\log_b x$$1/(x \ln b)$Base-2 cross-entropy, bits (Module 06)
    $\sigma(x) = \frac{1}{1+e^{-x}}$$\sigma(x)(1-\sigma(x))$Logistic regression, gates, attention masks

    The $\ln x$ row deserves one sentence: because $\frac{d}{dx}\ln x = \frac{1}{x}$, differentiating a log-likelihood replaces nasty products with sums and simple ratios. That is why we maximise log-likelihoods β€” the maths gets cheap exactly where it matters.

    The sigmoid derivative, in four steps. Reproduce this without notes; it appears in every derivation of logistic regression:

    1. Write $\sigma(x) = (1+e^{-x})^{-1}$ β€” a power of something, so the chain rule applies.
    2. Differentiate: $\sigma'(x) = -(1+e^{-x})^{-2}\cdot(-e^{-x}) = \frac{e^{-x}}{(1+e^{-x})^2}$ (the minus signs cancel).
    3. Split the numerator with $e^{-x} = (1+e^{-x})-1$, giving $\frac{1}{1+e^{-x}} - \frac{1}{(1+e^{-x})^2}$.
    4. Both pieces are powers of $\sigma$: $\sigma'(x) = \sigma(x) - \sigma(x)^2 = \sigma(x)(1-\sigma(x))$.
    $$\sigma'(x) = \frac{e^{-x}}{(1+e^{-x})^2} = \sigma(x) - \sigma(x)^2 = \sigma(x)\big(1-\sigma(x)\big)$$ the derivative is written in the output itself β€” no x needed at runtime, so the forward pass stores Οƒ(z) and reuses it backwards
    Read the sigmoid derivative as a warning

    $\sigma(1-\sigma)$ peaks at $0.25$ (when $\sigma=0.5$) and falls to nearly $0$ as $\sigma$ saturates at $0$ or $1$. Every saturated sigmoid kills up to 75% of the gradient passing through, and ten in a row leave $0.25^{10}\approx 0.000001$. Nothing is wrong with your code β€” that is the multiplication of sensitivities doing what Section 7 says it does. It is why ReLU took over deep nets, and why you should always ask "what is the derivative of this activation?" before stacking it.

    4 Β· Partial derivatives and the gradient

    Models have millions of parameters, so $f:\mathbb{R}^n \to \mathbb{R}$. A partial derivative freezes all but one coordinate and asks the single-variable question about that one:

    $$\frac{\partial f}{\partial x_i} = \lim_{h\to 0}\frac{f(x_1,\dots,x_i+h,\dots,x_n)-f(\mathbf{x})}{h}$$ nudge coordinate i alone, hold the rest still, measure the response

    The gradient collects all of them into one vector β€” a direction with one entry per parameter, the same shape as the weights, which is what makes subtraction legal:

    $$\nabla f = \left(\frac{\partial f}{\partial x_1},\ \frac{\partial f}{\partial x_2},\ \dots,\ \frac{\partial f}{\partial x_n}\right)$$ all the local slopes at once β€” this is the object printed as tensor.grad

    Three geometric facts, each sketchable in ten seconds:

    • Steepest increase. The gradient points where $f$ grows fastest at that point; descent goes against it.
    • Perpendicular to level sets. Draw the contour where $f$ is constant β€” the gradient sticks straight out of it, because moving along a contour does not change $f$.
    • Everything else is a projection. The rate of change along a unit direction $\mathbf{u}$ is $\nabla f\cdot\mathbf{u} = \|\nabla f\|\cos\theta$, largest when $\theta=0$ β€” that proof of "steepest", not a claim.
    $$D_{\mathbf{u}} f = \nabla f \cdot \mathbf{u} = \|\nabla f\|\,\|\mathbf{u}\|\cos\theta = \|\nabla f\|\cos\theta \quad (\|\mathbf{u}\|=1)$$ the directional derivative along u β€” maximised by choosing u parallel to the gradient

    Worked example. $g(x,y)=x^2+y^2$ at $(1,2)$: differentiating $x^2$ with $y$ held fixed gives $2x$, and vice versa.

    $$\nabla g = (2x,\ 2y) = (2,\ 4) \ \text{ at } (1,2)$$ uphill at (1,2): more x, even more y β€” pointing away from the minimum at the origin

    One gradient descent step subtracts a scaled version. $\eta$ (eta) is the learning rate: it converts a gradient (slope units) into a step (parameter units).

    $$\mathbf{x} \leftarrow \mathbf{x} - \eta\,\nabla f(\mathbf{x}), \qquad \eta = 0.1:\quad (1,2) - 0.1\,(2,4) = (0.8,\ 1.6)$$ distance to the minimum shrinks from √5 β‰ˆ 2.236 to √3.2 β‰ˆ 1.789
    The gradient is a direction, not a step size

    Two opposite errors. Using the raw gradient as the step ($\eta = 1$) overshoots: on $x^2+y^2$ each update multiplies the position by $(1-2\eta) = -1$, so you bounce between $(1,2)$ and $(-1,-2)$ forever. Choosing $\eta$ too large makes $|1-2\eta| \gt 1$ and the iterates run away β€” the loss you were minimising explodes, and the fix is a smaller learning rate, not a different loss. Pass 3 runs $\eta = 0.1$, $0.001$ and $1.1$ so you can watch convergence, crawling and divergence on one plot.

    5 Β· Second derivatives: curvature, minima and saddles

    The first derivative says which way is downhill right now; the second says whether downhill is getting steeper or flatter β€” the curvature, and with it the local shape:

    $$f''(x) > 0 \Rightarrow \text{cup (local min)} \qquad f''(x) < 0 \Rightarrow \text{cap (local max)} \qquad f''(x) = 0 \Rightarrow \text{flat, inconclusive}$$ the second-derivative test at a critical point where fβ€² = 0

    With many variables there is a second derivative per pair of coordinates, so they organise into a symmetric matrix β€” the Hessian:

    $$H_{ij} = \frac{\partial^2 f}{\partial x_i \partial x_j}, \qquad H \in \mathbb{R}^{n\times n}$$ a matrix, not a vector: how the slope in direction i changes when you move in direction j

    At a critical point ($\nabla f = 0$) the test generalises: $H$ positive definite β‡’ every direction curves upward β‡’ local minimum; negative definite β‡’ maximum; mixed signs β‡’ a saddle point, which looks like a minimum from one angle and a maximum from another. In high dimensions saddles are abundant and local minima are rare β€” one sentence now, developed in Module 07.

    $$f(\mathbf{x}+\Delta) \approx f(\mathbf{x}) + \nabla f^\top \Delta + \tfrac{1}{2}\,\Delta^\top H \Delta$$ value + slope term + curvature term β€” the whole function, locally, in three pieces

    In plain English: the first term is where you are, the second is the tilt under your feet (zero at a critical point β€” which is why first-order methods stall there), the third is how the ground bends. Newton's method uses all three to jump to the bottom of a quadratic bowl; gradient descent uses only the second and inches downhill. And a large Hessian means the gradient changes fast over short distances β€” a narrow valley demanding small steps β€” while a flat plain tolerates big ones. That is the whole intuition behind Adam: rescale each coordinate's step by an estimate of its curvature.

    6 Β· Jacobian: gradients of vector-valued functions

    Gradients are for scalar outputs; layers return vectors. For $f:\mathbb{R}^d \to \mathbb{R}^k$, stack the $k$ output gradients as rows and you get the Jacobian, a $k \times d$ matrix:

    $$J = \begin{bmatrix} \frac{\partial f_1}{\partial x_1} & \cdots & \frac{\partial f_1}{\partial x_d} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_k}{\partial x_1} & \cdots & \frac{\partial f_k}{\partial x_d} \end{bmatrix} \in \mathbb{R}^{k\times d}$$ row i is the gradient of output i β€” one row per output, one column per input

    Shapes, because shapes are 80% of the bugs: outputs along rows, inputs down columns. When $k=1$ the Jacobian collapses to one row β€” the gradient written as a $1\times d$ matrix. A gradient is a degenerate Jacobian, not a different species. And for a single linear layer $f(\mathbf{x}) = W\mathbf{x}$ with $W \in \mathbb{R}^{k\times d}$, the Jacobian is the weight matrix itself: $J = W$.

    Why you should care: a whole layer's local behaviour is a matrix, so the vector chain rule is a product of these matrices (Section 7). But you never want to build them β€” a million-by-million layer has $10^{12}$ entries. Frameworks instead compute only $J$ times a vector (a Jacobian–vector product), in time proportional to the function itself. That trick is reverse-mode automatic differentiation, named in Section 10.

    7 Β· The chain rule is backpropagation

    Backpropagation is not a new algorithm: it is the chain rule applied backwards, once per layer, reusing whatever the forward pass already stored.

    Scalar case first β€” the formula from the warm-up, stated again on purpose:

    $$\frac{d}{dx}f\big(g(x)\big) = f'\big(g(x)\big)\,g'(x)$$ sensitivities multiply along a chain of causes

    Now the vector version, which is what backprop runs. If scalar $L$ is computed from an intermediate vector $\mathbf{y} = \mathbf{y}(\mathbf{x})$, the gradient w.r.t. $\mathbf{x}$ is the incoming gradient multiplied by the transposed Jacobian of the local step:

    $$\nabla_{\mathbf{x}} L = \left(\frac{\partial \mathbf{y}}{\partial \mathbf{x}}\right)^{\!\top} \nabla_{\mathbf{y}} L$$ the transpose matters: the upstream gradient arrives "row-shaped" and must be aligned with the shapes of x

    A full backpropagation, with numbers. The smallest network that still has a hidden non-linearity: one input $x$, one sigmoid, one output, squared loss.

    $$z_1 = w_1 x,\qquad a_1 = \sigma(z_1),\qquad z_2 = w_2 a_1,\qquad L = (z_2 - y)^2$$ the forward pass, left to right β€” save all four values, you need them all backwards

    Walk the chain backwards, symbols first, one factor per edge. For $w_2$: it reaches $L$ only through $z_2$, and $z_2 = w_2 a_1$ so $\partial z_2/\partial w_2 = a_1$:

    $$\frac{\partial L}{\partial w_2} = \frac{\partial L}{\partial z_2}\cdot\frac{\partial z_2}{\partial w_2} = 2(z_2-y)\,a_1$$ upstream factor 2(zβ‚‚ βˆ’ y), local factor a₁ β€” only saved forward values are needed

    For $w_1$ the path is longer β€” through $z_2$, then $a_1$, then $z_1$ β€” so four factors multiply:

    $$\frac{\partial L}{\partial w_1} = \underbrace{2(z_2-y)}_{\partial L/\partial z_2}\cdot\underbrace{w_2}_{\partial z_2/\partial a_1}\cdot\underbrace{\sigma(z_1)\big(1-\sigma(z_1)\big)}_{\partial a_1/\partial z_1}\cdot\underbrace{x}_{\partial z_1/\partial w_1}$$ the entire backward pass for w₁: one factor per edge, multiplied in reverse order
    $\dfrac{\partial L}{\partial z_2}$loss β†’ zβ‚‚ β†’ $\times\, w_2$zβ‚‚ β†’ a₁ β†’ $\times\, \sigma'(z_1)$a₁ β†’ z₁ β†’ $\times\, x$z₁ β†’ w₁ β†’ $\dfrac{\partial L}{\partial w_1}$done

    Numbers: $x=1$, $w_1=0.5$, $w_2=1.2$, $y=1$. Forward first β€” these are the values the backward pass reuses:

    $$\begin{aligned} z_1 &= 0.5 \times 1 = 0.5 \\ a_1 &= \sigma(0.5) = 0.622459 \\ z_2 &= 1.2 \times 0.622459 = 0.746951 \\ L &= (0.746951 - 1)^2 = 0.064034 \end{aligned}$$ forward pass, computed once
    $$\begin{aligned} \frac{\partial L}{\partial z_2} &= 2(0.746951 - 1) = -0.506098 \\[3pt] \frac{\partial L}{\partial w_2} &= -0.506098 \times 0.622459 = \mathbf{-0.315025} \\[3pt] \frac{\partial L}{\partial w_1} &= -0.506098 \times 1.2 \times 0.235004 \times 1 = \mathbf{-0.142722} \end{aligned}$$ Οƒβ€²(0.5) = 0.622459 Γ— 0.377541 = 0.235004; x = 1 contributes only its own value

    Both gradients are negative: increasing either weight lowers $L$ here, which is exactly what a descent step will do. Pass 3 checks both numbers against finite differences.

    Why backprop is cheap β€” and why it sometimes is not enough

    The backward pass consumed $a_1$, $z_1$, $z_2$, $w_2$, $x$ β€” every value already computed on the way forward and merely reused. Each edge costs one multiplication, so the backward pass costs about one forward pass: training β‰ˆ 3Γ— inference, not 3Γ— per parameter. The counterweight: the same multiplications are how gradients vanish. Chain $\sigma' \le 0.25$ ten times and you are multiplying by $\approx 10^{-6}$; chain large derivatives and they explode. Same mechanism, opposite sign β€” residual connections (Module 10) are the architectural fix.

    The gradient is computed at a point β€” it says nothing far away

    A gradient is local: it describes the surface within a step or two of where you stand. It does not say where the minimum is, what the loss will be after 100 updates, or whether a better basin exists elsewhere. That is why you recompute it every iteration, why an oversized learning rate "suddenly" diverges (you left the region where the local model held), and why "the gradient says this direction is bad" really means "bad from here". $f'(3)=6$, all over again, in $n$ dimensions.

    8 Β· Gradients of the losses you will actually differentiate

    These four are the payoff of the module. Learn to re-derive them, not just recite them β€” each is five lines of Section 3's rules plus a chain rule.

    $$L(w) = \frac{1}{n}\|Xw - y\|^2, \qquad \nabla_w L = \frac{2}{n}\,X^\top\!\left(Xw - y\right)$$ the squared-error gradient: residuals, pulled back into parameter space by Xα΅€

    Derivation, shapes checked. Expand: $L = \frac{1}{n}\sum_{i=1}^{n}(x_i^\top w - y_i)^2$. Differentiating w.r.t. $w_j$, the chain rule brings down $2(x_i^\top w - y_i)$ and $\partial(x_i^\top w)/\partial w_j = x_{ij}$ (only $x_i$'s $j$-th coordinate touches $w_j$). So $\frac{\partial L}{\partial w_j} = \frac{2}{n}\sum_i (Xw-y)_i x_{ij}$ β€” column $j$ of $X$ times the residual vector, i.e. row $j$ of $X^\top(Xw-y)$. Shapes: $X$ is $n\times d$, $w$ is $d$, $y$ is $n$; so $Xw-y$ is $n$ and $X^\top$ ($d\times n$) times it is $d$ β€” a gradient the size of $w$, as it must be. In code: (2/n) * X.T @ (X @ w - y).

    $$L(z) = -\big[y\ln\sigma(z) + (1-y)\ln(1-\sigma(z))\big]$$ binary cross-entropy β€” the log loss maximum likelihood forces on a Bernoulli output

    Sigmoid + binary cross-entropy. Differentiate with respect to $z$ (we stop at the pre-activation and let backprop carry it on), using $\frac{d}{dz}\ln\sigma = \frac{\sigma'}{\sigma}$ and $\frac{d}{dz}\ln(1-\sigma) = \frac{-\sigma'}{1-\sigma}$:

    $$\frac{\partial L}{\partial z} = \sigma(z)\left[\frac{1-y}{1-\sigma(z)} - \frac{y}{\sigma(z)}\right]$$ Οƒβ€² = Οƒ(1βˆ’Οƒ) substituted β€” it supplies exactly the denominators

    Those denominators are the cancellation to watch: $\sigma'/\sigma = 1-\sigma$ and $\sigma'/(1-\sigma) = \sigma$. Multiply out and two terms remain:

    $$\frac{\partial L}{\partial z} = \sigma(1-y) - y(1-\sigma) = \sigma(z) - y$$ prediction minus label β€” a subtraction, with every transcendental function gone

    The beautiful cancellation: an activation whose derivative is built from its own output, paired with a loss containing that output inside logarithms, cancels to the simplest possible gradient. Not luck β€” maximum likelihood wearing a nice outfit β€” and the reason sigmoid and cross-entropy are never casually swapped for other pairings.

    $$\mathbf{p} = \operatorname{softmax}(\mathbf{z}),\quad L = -\sum_c y_c \ln p_c \qquad \Rightarrow \qquad \frac{\partial L}{\partial \mathbf{z}} = \mathbf{p} - \mathbf{y}$$ softmax + cross-entropy: the multi-class twin of the line above, one entry per class

    Sketch. The softmax Jacobian is $\frac{\partial p_c}{\partial z_j} = p_c(\mathbb{1}[c=j] - p_j)$. Substitute into $\frac{\partial L}{\partial z_j} = -\sum_c \frac{y_c}{p_c}\frac{\partial p_c}{\partial z_j}$: the $p_c$ in the denominator cancels the one in the Jacobian, leaving $-y_j + p_j\sum_c y_c = p_j - y_j$, since $\sum_c y_c = 1$. Same cancellation, one dimension up.

    $$\nabla_w\ \lambda\|w\|^2 = 2\lambda w$$ the L2 penalty's gradient is proportional to the weights themselves β€” it pulls big weights down hardest

    One line: $\|w\|^2 = \sum_j w_j^2$ and each coordinate gives $2w_j$. Practical consequence: the weight-decay step is $w \leftarrow w - \eta\,2\lambda w = (1-2\eta\lambda)w$ β€” a multiplicative shrink every iteration, which is what "decay" should mean.

    Keep these four within reach

    $\tfrac{2}{n}X^\top(Xw-y)$, $\sigma(z)-y$, $\mathbf{p}-\mathbf{y}$, $2\lambda w$. Between them: linear regression, logistic regression, every neural classifier, $L_2$ regularisation. When a loss misbehaves, the fastest debug is re-deriving its gradient on paper and comparing with the code β€” exactly the finite-difference check in Pass 3.

    9 Β· Integrals: the other half, used more rarely but needed

    Differentiation splits a change into rates; integration rebuilds a whole from rates β€” addition in the limit. Geometrically $\int_a^b f(x)\,dx$ is the signed area under the curve, defined as the limit of summing ever-thinner rectangles: $\int_a^b f(x)\,dx = \lim_{k\to\infty}\sum_{i=1}^{k} f(x_i^*)\,\Delta x$.

    $$F'(x) = f(x) \quad\Longrightarrow\quad \int_a^b f(x)\,dx = F(b) - F(a)$$ the fundamental theorem: differentiation and integration are inverse operations

    No proof β€” this notebook does proofs only when they pay rent β€” but the paragraph worth remembering: if $F$ is an antiderivative of $f$, the accumulated area from $a$ to $x$ has derivative $f(x)$, because it grows exactly as fast as the curve is tall. Two functions with the same derivative differ by a constant; fixing it at one endpoint gives $F(b)-F(a)$. That is the whole theorem, and it is why "find the antiderivative" solves "find the area".

    The most common integral in ML is the expectation β€” the continuous version of the probability-weighted average $\sum_x x\,P(X=x)$, with sums replaced by areas:

    $$\mathbb{E}[X] = \int_{-\infty}^{\infty} x\,p(x)\,dx$$ the weighted average of a continuous random variable β€” Module 04 uses this constantly

    Where else ML meets integrals, in increasing order of frequency:

    • Expectation of a function. $\mathbb{E}[g(X)] = \int g(x)\,p(x)\,dx$ β€” variance, risk, and every "average over the data distribution" statement you will meet.
    • Probability is volume. $P(a \le X \le b) = \int_a^b p(x)\,dx$: a probability is the area under the density, so densities must integrate to $1$ β€” the constraint that produces every normalising constant in every distribution.
    • The Gaussian integral, once. $\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$ β€” a beautiful fact about a boring-looking integrand, and the origin of the Gaussian's $\sqrt{2\pi\sigma^2}$.
    $$\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi} \qquad\text{hence}\qquad \int_{-\infty}^{\infty} e^{-x^2/(2\sigma^2)}\,dx = \sqrt{2\pi\sigma^2}$$ substituting x β†’ √(2)σ·u stretches the area by √(2)Οƒ; the normaliser 1/√(2πσ²) is whatever makes the total area 1

    So the "mysterious" $\sqrt{2\pi\sigma^2}$ is not decoration β€” it is exactly the number required to make the integral equal $1$. Module 04 completes the picture: expectation, variance, and where least squares comes from.

    10 Β· Symbolic vs numeric vs automatic differentiation

    You need derivatives in code. There are three ways to get them; knowing all three lets you choose, and lets you recognise which one your framework is running.

    MethodHowFailure mode
    Numeric (finite differences)Approximate the limit: $\frac{f(x+h)-f(x)}{h}$Subtractive cancellation: too small an $h$ and $f(x+h)-f(x)$ loses digits to float noise; too large and truncation error dominates. Ceiling β‰ˆ $10^{-8}$
    Symbolic (SymPy)Apply algebra rules to the expression, like a personExpression swell on real models; cannot differentiate arbitrary Python control flow
    Automatic (PyTorch, JAX)Apply the chain rule to a recorded computation graphNeeds differentiable ops; memory for saved intermediates

    Autodiff is what ML uses, and it is neither of the two things people assume. It is not numerical β€” no approximation, no cancellation, exact to floating-point precision. And it is not symbolic β€” it never builds a giant expression. It stores the operations you ran (the graph), then walks that graph backwards applying the chain rule one node at a time, exactly as you did in Section 7, at a small constant factor over the forward pass whatever the dimension. requires_grad=True starts the recording; loss.backward() runs the walk. The word you will see is autograd β€” now you know it means "the chain rule on a computation graph".

    What a real training loop looks like with this

    loss = criterion(model(x), y) builds the graph and computes $L$ (Section 7's forward pass). opt.zero_grad() clears old gradients, because they accumulate. loss.backward() fills param.grad with $\nabla_\theta L$. opt.step() applies $\theta \leftarrow \theta - \eta\nabla_\theta L$. Nothing else happens in training β€” everything else in deep learning is about making those four lines behave.

    Pass 3

    Implement β€” check the math against the machine

    Type these; do not copy. Then change one number and predict the result

    Block 1 Β· The gradient check every engineer runs

    Analytic gradient vs finite differences. If they disagree by more than about $10^{-8}$, one of them is wrong β€” in practice, your code.

    import numpy as np
    rng = np.random.default_rng(0)
    
    # central difference: symmetric error is O(h^2), twice as good as the naive version
    def fd_grad(fn, p, h=1e-5):
        g = np.zeros_like(p)
        for i in range(p.size):
            e = np.zeros_like(p); e[i] = h
            g[i] = (fn(p + e) - fn(p - e)) / (2 * h)
        return g
    
    # 1) a toy function you can differentiate by eye
    f      = lambda p: p[0]**2 + p[1]**2
    grad_f = lambda p: np.array([2*p[0], 2*p[1]])
    p = np.array([1.5, -2.0])
    print("f  analytic:", grad_f(p), " numeric:", fd_grad(f, p),
          " err:", np.abs(grad_f(p) - fd_grad(f, p)).max())   # expect < 1e-8
    
    # 2) the real thing: MSE on synthetic data
    n, d = 20, 4
    X = rng.normal(size=(n, d))
    y = X @ rng.normal(size=d) + 0.1 * rng.normal(size=n)
    w = rng.normal(size=d)
    mse      = lambda w: np.mean((X @ w - y)**2)
    grad_mse = lambda w: (2/n) * X.T @ (X @ w - y)
    print("MSE analytic:", np.round(grad_mse(w), 6))
    print("MSE numeric :", np.round(fd_grad(mse, w), 6))
    print("max error   :", np.abs(grad_mse(w) - fd_grad(mse, w)).max())   # expect < 1e-8

    Both errors land near $10^{-11}$. If you ever see $10^{-3}$, you have found a real bug β€” a missing factor of $2$, a forgotten $1/n$, a transposed matrix. This is a gradient check, and it is how any new custom layer gets validated before anyone trusts a training run to it.

    Block 2 Β· Gradient descent from scratch, three learning rates

    import numpy as np
    import matplotlib.pyplot as plt
    
    grad = lambda p: 2 * p                      # nabla f for f(x,y) = x^2 + y^2
    
    def descend(eta, n_steps=30, start=(2.5, -2.0)):
        p = np.array(start, float); path = [p.copy()]
        for _ in range(n_steps):
            p = p - eta * grad(p)               # the update rule, nothing else
            path.append(p.copy())
        return np.array(path)
    
    # contour map of the loss, with each trajectory drawn on top
    xs = np.linspace(-3, 3, 200); X, Y = np.meshgrid(xs, xs)
    plt.contour(X, Y, X**2 + Y**2, levels=12)
    
    for eta, style in [(0.1, '-'), (0.001, '--'), (1.1, ':')]:
        path = descend(eta)
        plt.plot(path[:, 0], path[:, 1], style, marker='o', ms=3, label=f"eta={eta}")
        print(f"eta={eta:<6} after 30 steps: {np.round(path[-1], 4)}")
    
    # eta=0.1   -> [ 0.0031 -0.0025]  converged: factor per step = 1-2*eta = 0.8
    # eta=0.001 -> [ 2.3543 -1.8834]  crawling: factor 0.998, barely moves
    # eta=1.1   -> [ 593.44 -474.75]  diverging: factor -1.2, |factor| > 1
    plt.legend(); plt.axis('equal'); plt.show()

    On this function each step multiplies the position by $(1-2\eta)$, so all three outcomes read off one number: $0.8$ converges, $0.998$ crawls, $-1.2$ diverges. Real losses are not quadratics, but the local picture is the same β€” and this plot is what to sketch when someone asks why their loss exploded after they raised the learning rate.

    Block 3 Β· Backprop by hand, then verified

    import numpy as np
    sig = lambda z: 1 / (1 + np.exp(-z))
    
    def loss_and_grad(w1, w2, x, y):
        z1 = w1 * x
        a1 = sig(z1)                     # save it: sigma'(z1) is built from a1
        z2 = w2 * a1
        L  = (z2 - y)**2
        # backward pass β€” one factor per edge, in reverse
        dL_dz2 = 2 * (z2 - y)
        dL_dw2 = dL_dz2 * a1
        dL_dz1 = dL_dz2 * w2 * a1 * (1 - a1)   # chain through the sigmoid
        dL_dw1 = dL_dz1 * x
        return L, np.array([dL_dw1, dL_dw2])
    
    x, y, w = 1.0, 1.0, np.array([0.5, 1.2])
    L, g = loss_and_grad(*w, x, y)
    print(f"L = {L:.6f}   grad = {np.round(g, 6)}")
    # L = 0.064034   grad = [-0.142722 -0.315025]  β€” matches the paper derivation
    
    # now force the machine to agree
    loss_only = lambda w1, w2: loss_and_grad(w1, w2, x, y)[0]
    h = 1e-6
    fd = np.array([(loss_only(*(w + h*np.eye(2)[i])) - loss_only(*(w - h*np.eye(2)[i]))) / (2*h)
                   for i in range(2)])
    print("finite diff:", np.round(fd, 6), " max error:", np.abs(fd - g).max())  # ~1e-11

    The two agree to twelve decimal places. That is the whole trust story of deep learning: the framework's backward() performs the same multiplications you just did, and you can audit it on any function you write.

    Exercise Β· the BCE gradient, and the bug you will one day hit
    • Implement $\frac{\partial L}{\partial z} = \sigma(z) - y$ for binary cross-entropy and verify it against a finite-difference check of the summed loss on $z = (-2.0,\, 0.0,\, 1.5)$, $y = (0, 1, 1)$. Expected agreement: $\approx 10^{-10}$.
    • Then break it. Average the loss over the $n$ samples (L.mean()) but keep the summed gradient $\sigma(z) - y$. Re-run the finite difference on the mean loss and divide your gradient by theirs: every ratio is exactly $n$ β€” here $3.0$.
    • Why this matters: mean-vs-sum mismatches are invisible in unit tests (the direction is still right, so the loss still falls) and surface weeks later as a run that only works at $\eta = 10^{-4}$. Your effective learning rate silently became $n\times\eta$. The fix is one line β€” divide by $n$ β€” if you know to look.
    Checkpoint

    Prove it, then start Module 04

    Answer first, then open the feedback

    Multiple choice β€” instant feedback

    1. You have computed $\nabla f$ at a point. Which statement is true?

    2. Gradient descent on $g(x,y) = x^2 + y^2$ at $(1,2)$ with $\eta = 0.1$. Where do you land after one step?

    3. What does backpropagation actually compute?

    4. At a critical point of $f$, the Hessian $H$ is positive definite. What follows?

    5. You find a point where $f'(x_0) = 0$ and $f''(x_0) = 0$. What can you conclude?

    6. Which statement about plain gradient descent is true?

    Open questions β€” answer aloud, without notes

    1. Explain $f'(3) = 6$ to a colleague: what does it promise about $x = 3.01$, and what does it refuse to promise about $x = 100$?
    2. Derive $\sigma'(x) = \sigma(x)(1-\sigma(x))$ from the definition, in four lines, without looking.
    3. Why does the product rule have two terms? Build them from $(f+df)(g+dg)$.
    4. For $g(x,y) = x^2+y^2$ at $(1,2)$: gradient, descent direction, and one step with $\eta = 0.05$.
    5. What is the Jacobian of a layer $f(\mathbf{x}) = W\mathbf{x}$? When does reverse-mode autodiff beat forward-mode, and why?
    6. Write $\partial L/\partial w_1$ for the 2-layer sigmoid net as a product of four factors, saying what each corresponds to in the graph.
    7. Why does sigmoid + cross-entropy give exactly $\sigma(z) - y$? Name the two factors that cancel.
    8. You set $h = 10^{-12}$ in a finite-difference check and the error got worse than with $h = 10^{-6}$. Explain why.

    Re-answer these in 1 / 3 / 7 days β€” cold, no notes. The derivatives you cannot re-derive are the ones you have not learned yet.

    Continue in your ML notebook

    This module is the "why"; the companion notebook is the "what it does". ML module 05 Β· Regression & gradient descent runs the update rule on a real dataset; ML module 06 Β· Logistic regression uses the $\sigma(z)-y$ gradient you derived in Section 8; and ML module 10 Β· Neural networks & deep learning is where backpropagation stops being a worked example and becomes the thing you train with.