5. Calculus for learning

Why this layer exists

Training a model means choosing its weights. Chapter 4 fixed what “good weights” means: low cross-entropy on the training data, equivalently high likelihood. For a categorical distribution the best weights had a closed form, the normalised counts. For a network they do not, because every output probability depends on all the weights at once, and there are far too many weights to search. What is cheap to compute, at the current weights, is how the loss would change if each weight moved a little. That vector of sensitivities is the gradient, and it points in the direction in which the loss rises fastest. Gradient descent takes a small step the other way and repeats.

Every model trained in this book is trained this way: the bigram, MLP and transformer language models of Part II, the chat fine-tune of chapter 13, and the QLoRA fine-tune of chapter 46 on dixie’s 12 GB RTX 3060, where the base weights stay frozen and only a small set of added low-rank weights receives the steps. The optimizers used on real models rescale and smooth the step, per coordinate and over time; the step is still built from the gradient. The one knob this chapter explains fully is the learning rate, the step size. Too small and training crawls; too large and the loss oscillates and then grows without bound until it overflows to inf and turns into nan. The threshold between the two is a number you can compute, and the lab computes it for a real problem and watches the runs on either side of it.

Chapter 6 builds an engine that computes gradients automatically. The only way to trust such an engine is to compare its answers with a slow, independent estimate, and the numerical gradient built here is that estimate.

Mechanism

The derivative is a local linear approximation

Zoom in far enough on the graph of a smooth function and it looks like a straight line. The slope of that line at xx is the derivative f′(x)f'(x), and the line itself is the approximation

f(x+h)≈f(x)+f′(x)h, f(x + h) \approx f(x) + f'(x)\,h ,

good for small hh. Precisely, f′(x)f'(x) is the number that makes the error of this approximation small compared with hh itself:

f′(x)=limh→0f(x+h)−f(x)h, f'(x) = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h} ,

when the limit exists. The ratio is the slope of the chord from xx to x+hx + h; the limit is the slope the chords approach as the second point slides in. Write the error as

r(h)=f(x+h)−f(x)−f′(x)h. r(h) = f(x + h) - f(x) - f'(x)\,h.

The definition says exactly that r(h)/h→0r(h)/h \to 0.

The reading that matters for learning: if f′(x)>0f'(x) > 0, a small decrease in xx lowers ff; if f′(x)<0f'(x) < 0, a small increase does; and the size of f′(x)f'(x) says how much ff changes per unit of xx, to first order. f′(x)=0f'(x) = 0 says nothing to first order, which is what happens at a minimum.

Derivatives from the definition

The functions that appear in the losses and layers of this book are built from powers, the exponential and the logarithm. Each derivative follows from the limit.

Squares. Expand and cancel:

(x+h)2−x2h=x2+2xh+h2−x2h=2x+h→2x. \frac{(x + h)^2 - x^2}{h} = \frac{x^2 + 2xh + h^2 - x^2}{h} = 2x + h \;\to\; 2x .

Positive integer powers. Multiplying out (x+h)n(x + h)^n gives xnx^n, then nn terms with one factor of hh (each xn−1hx^{n-1}h), then terms with h2h^2 or higher powers. This is the binomial expansion:

(x+h)n=xn+nxn−1h+n(n−1)2xn−2h2+⋯+hn. (x + h)^n = x^n + n\,x^{n-1}h + \frac{n(n-1)}{2}\,x^{n-2}h^2 + \cdots + h^n .

Subtract xnx^n and divide by hh: the first remaining term is nxn−1n\,x^{n-1} and every other term still carries at least one factor of hh, so

ddxxn=nxn−1. \frac{d}{dx}\,x^n = n\,x^{n-1} .

The exponential. ex+h=exehe^{x + h} = e^x e^h, so

ex+h−exh=ex⋅eh−1h. \frac{e^{x + h} - e^x}{h} = e^x \cdot \frac{e^h - 1}{h} .

Everything rests on the second factor. Take the power series eh=1+h+h2/2!+h3/3!+⋯e^h = 1 + h + h^2/2! + h^3/3! + \cdots as the definition of the exponential; then

eh−1h=1+h2!+h23!+⋯, \frac{e^h - 1}{h} = 1 + \frac{h}{2!} + \frac{h^2}{3!} + \cdots ,

and for ∣h∣≤1\lvert h \rvert \le 1 the terms after the 1 add up to at most ∣h∣(1/2!+1/3!+⋯)=∣h∣(e−2)\lvert h \rvert\,(1/2! + 1/3! + \cdots) = \lvert h \rvert\,(e - 2), which goes to 0 with hh. The limit is 1, and

ddxex=ex. \frac{d}{dx}\,e^x = e^x .

ee is the base for which the exponential’s slope at 0 is exactly 1; for any other base aa, ax=exlogaa^x = e^{x \log a} has slope loga\log a there. At h=0.001h = 0.001 the ratio is 1.0005.

The logarithm. y=logxy = \log x means ey=xe^y = x, for x>0x > 0. Let k=log(x+h)−logxk = \log(x + h) - \log x, the change in the log; then x+h=ey+kx + h = e^{y + k}, so h=ey+k−eyh = e^{y + k} - e^y, and

log(x+h)−logxh=key+k−ey=1/ey+k−eyk. \frac{\log(x + h) - \log x}{h} = \frac{k}{e^{y + k} - e^{y}} = 1 \Big/ \frac{e^{y + k} - e^{y}}{k} .

As h→0h \to 0, k→0k \to 0 too, since the logarithm is continuous, and the denominator is a chord slope of the exponential tending to ey=xe^y = x. So

ddxlogx=1x. \frac{d}{dx}\,\log x = \frac{1}{x} .

Sum, product and chain rules

Sums and constant multiples. The difference quotient of af+bga f + b g is aa times that of ff plus bb times that of gg, and limits respect sums, so (af+bg)′=af′+bg′(af + bg)' = af' + bg'. Differentiation is linear.

Products. Write f(x+h)=f+f′h+rff(x + h) = f + f'h + r_f and g(x+h)=g+g′h+rgg(x + h) = g + g'h + r_g, with everything on the right evaluated at xx and both remainders small compared with hh. Multiply:

f(x+h)g(x+h)=fg+(f′g+fg′)h+[f′g′h2+(f+f′h)rg+(g+g′h)rf+rfrg]. f(x + h)\,g(x + h) = fg + (f'g + fg')\,h + \big[\,f'g'h^2 + (f + f'h)\,r_g + (g + g'h)\,r_f + r_f r_g\,\big] .

Every term in the brackets, divided by hh, goes to 0: h2/h=hh^2/h = h, and rf/hr_f/h, rg/hr_g/h vanish by definition. What is left is the product rule, (fg)′=f′g+fg′(fg)' = f'g + fg'.

Composition. For g(f(x))g(f(x)), first approximate the inner function, then the outer one at the point u=f(x)u = f(x). The inner function moves by k=f(x+h)−f(x)=f′(x)h+rf(h)k = f(x + h) - f(x) = f'(x)\,h + r_f(h), and the outer function, moved from uu by kk, changes by g′(u)k+rg(k)g'(u)\,k + r_g(k) with rg(k)/k→0r_g(k)/k \to 0 as k→0k \to 0. Substituting kk:

g(f(x+h))=g(f(x))+g′(f(x))f′(x)h+[g′(f(x))rf(h)+rg(k)]. g(f(x + h)) = g(f(x)) + g'(f(x))\,f'(x)\,h + \big[\,g'(f(x))\,r_f(h) + r_g(k)\,\big] .

The bracket, divided by hh, goes to 0. Its first term does because rf(h)/h→0r_f(h)/h \to 0. For the second, k→0k \to 0 as h→0h \to 0, and k/h=f′(x)+rf(h)/hk/h = f'(x) + r_f(h)/h stays bounded, so rg(k)/h=(rg(k)/k)(k/h)→0r_g(k)/h = \big(r_g(k)/k\big)\,(k/h) \to 0; when k=0k = 0 the term is rg(0)=0r_g(0) = 0 outright. The coefficient of hh is the derivative:

(g∘f)′(x)=g′(f(x))f′(x). \big(g \circ f\big)'(x) = g'\big(f(x)\big)\,f'(x) .

This is the chain rule. A composition of local linear approximations is a local linear approximation whose slope is the product of the slopes. Chapter 3 showed that composing linear maps multiplies their matrices; the chain rule for functions of many variables is the same statement with each slope replaced by a matrix, and chapter 6 is built on it.

Two consequences come at once. Any real power: for x>0x > 0, xr=erlogxx^r = e^{r \log x}, so the chain rule gives erlogx⋅r/x=rxr−1e^{r \log x} \cdot r/x = r\,x^{r - 1}, extending the integer rule. And the quotient rule is not needed separately: 1/g=g−11/g = g^{-1}, whose derivative is −g′/g2-g'/g^2 by the power rule and the chain rule.

A worked composite. Take

f(w)=log(1+e−w). f(w) = \log\big(1 + e^{-w}\big) .

This is the cross-entropy of a two-outcome model whose probability for the observed outcome is 1/(1+e−w)1/(1 + e^{-w}), since −log11+e−w=log(1+e−w)-\log \frac{1}{1 + e^{-w}} = \log(1 + e^{-w}). Peel it from the outside. The outer function is logu\log u with u=1+e−wu = 1 + e^{-w}, contributing 1/u1/u. The inner function’s derivative is the derivative of e−we^{-w}, itself a composite: outer eve^v, inner v=−wv = -w, so e−w⋅(−1)e^{-w} \cdot (-1). Multiply:

f′(w)=11+e−w⋅(−e−w)=−e−w1+e−w=−11+ew, f'(w) = \frac{1}{1 + e^{-w}} \cdot \big(-e^{-w}\big) = -\frac{e^{-w}}{1 + e^{-w}} = -\frac{1}{1 + e^{w}} ,

the last step multiplying top and bottom by ewe^{w}. At w=0w = 0 this is −1/2-1/2, and the chord slope (f(0.001)−f(−0.001))/0.002(f(0.001) - f(-0.001))/0.002 also comes out −0.5-0.5. The derivative is always negative and tends to 0 as ww grows: raising the logit of the correct outcome always lowers the loss, by less and less once the model is confident. Exercise (a) meets the same function from the other side.

Partial derivatives

A loss depends on many numbers. For f(x1,…,xd)f(x_1, \ldots, x_d), the partial derivative ∂f/∂xi\partial f / \partial x_i is the ordinary derivative in xix_i with every other coordinate held fixed. For f(x,y)=x2y+eyf(x, y) = x^2 y + e^{y}:

∂f∂x=2xy,∂f∂y=x2+ey, \frac{\partial f}{\partial x} = 2xy , \qquad \frac{\partial f}{\partial y} = x^2 + e^{y} ,

treating yy as a constant in the first and xx in the second.

Partials combine into a local linear approximation in dd dimensions. Move from x\mathbf{x} by a small step δ\delta, shape (d,), one coordinate at a time. The first move changes ff by about (∂f/∂x1)δ1(\partial f / \partial x_1)\,\delta_1; the second by about (∂f/∂x2)δ2(\partial f / \partial x_2)\,\delta_2, with that partial evaluated at the already-shifted point, which differs from its value at x\mathbf{x} by an amount that vanishes as δ→0\delta \to \mathbf{0} as long as the partials are continuous, as they are for every function in this book. Summing the dd moves,

f(x+δ)≈f(x)+∑i=1d∂f∂xiδi. f(\mathbf{x} + \delta) \approx f(\mathbf{x}) + \sum_{i=1}^{d} \frac{\partial f}{\partial x_i}\,\delta_i .

The gradient points uphill

Collect the partials into a vector, the gradient:

∇f(x)=(∂f∂x1,…,∂f∂xd), \nabla f(\mathbf{x}) = \left( \frac{\partial f}{\partial x_1}, \ldots, \frac{\partial f}{\partial x_d} \right) ,

shape (d,), the same shape as x\mathbf{x}. The sum above is a dot product, so the local approximation is

f(x+δ)≈f(x)+∇f(x)⋅δ. f(\mathbf{x} + \delta) \approx f(\mathbf{x}) + \nabla f(\mathbf{x}) \cdot \delta .

This is the form chapter 4 used, ahead of time, to find the maximum-likelihood distribution. Now ask which direction increases ff fastest. Take a unit vector u\mathbf{u} and step a distance ss along it, δ=su\delta = s\,\mathbf{u}. The rate of change of ff along u\mathbf{u}, the directional derivative, is

Duf=lims→0f(x+su)−f(x)s=∇f⋅u=∥∇f∥cosφ, D_{\mathbf{u}} f = \lim_{s \to 0} \frac{f(\mathbf{x} + s\,\mathbf{u}) - f(\mathbf{x})}{s} = \nabla f \cdot \mathbf{u} = \lVert \nabla f \rVert \cos\varphi ,

where φ\varphi is the angle between ∇f\nabla f and u\mathbf{u}, by chapter 2’s identity a⋅b=∥a∥∥b∥cosφ\mathbf{a} \cdot \mathbf{b} = \lVert \mathbf{a} \rVert\,\lVert \mathbf{b} \rVert \cos\varphi with ∥u∥=1\lVert \mathbf{u} \rVert = 1. Only cosφ\cos\varphi depends on the direction. It is largest, 1, at φ=0\varphi = 0: ff rises fastest along ∇f/∥∇f∥\nabla f / \lVert \nabla f \rVert, at rate ∥∇f∥\lVert \nabla f \rVert. It is smallest, −1-1, at φ=π\varphi = \pi: ff falls fastest along −∇f-\nabla f. At φ=π/2\varphi = \pi/2 the rate is 0: directions perpendicular to the gradient are, to first order, level, and the gradient is perpendicular to the contour through x\mathbf{x}.

Gradient descent

Given a loss L(θ)L(\theta) over parameters θ\theta, shape (P,), gradient descent repeats

θt+1=θt−η∇L(θt), \theta_{t+1} = \theta_t - \eta\, \nabla L(\theta_t) ,

with a learning rate η>0\eta > 0. The local approximation with δ=−η∇L\delta = -\eta \nabla L says what one step does:

L(θt+1)≈L(θt)−η∥∇L(θt)∥2, L(\theta_{t+1}) \approx L(\theta_t) - \eta\, \lVert \nabla L(\theta_t) \rVert^2 ,

a decrease whenever the gradient is nonzero and η\eta is small enough for the approximation to hold. Descent stops making progress where ∇L=0\nabla L = \mathbf{0}, a stationary point. In general that can be a minimum, a maximum or a saddle, and a minimum need not be the lowest one; for the loss this chapter fits, there is a single stationary point and it is the minimum. The only question left is what “small enough” means.

How large a step: one dimension

Take the simplest function with a minimum, f(x)=a2x2f(x) = \frac{a}{2} x^2 with a>0a > 0. Its derivative is axa x, so one step of gradient descent is

xt+1=xt−ηaxt=(1−ηa)xt,hencext=(1−ηa)tx0. x_{t+1} = x_t - \eta\, a\, x_t = (1 - \eta a)\,x_t , \qquad \text{hence} \qquad x_t = (1 - \eta a)^t\, x_0 .

The iterates go to the minimum at 0 exactly when ∣1−ηa∣<1\lvert 1 - \eta a \rvert < 1, that is, −1<1−ηa<1-1 < 1 - \eta a < 1, which for positive η\eta and aa is

0<η<2a. 0 < \eta < \frac{2}{a} .

Within that range there are two regimes, split at η=1/a\eta = 1/a, where one step lands exactly on the minimum. Below it the factor is between 0 and 1: every step moves toward the minimum without passing it, and a small ηa\eta a means many short steps. Above it the factor is between −1-1 and 0: each step overshoots to the other side, landing closer than it started, and the iterates zig-zag inward. At η=2/a\eta = 2/a the factor is −1-1 and the iterate bounces between x0x_0 and −x0-x_0 forever; beyond it each overshoot lands farther out, and ∣xt∣\lvert x_t \rvert grows geometrically.

Gradient descent on f(x) = a x squared over 2, started from the same point with three learning rates. With eta a = 0.25 the steps are short and all on one side, creeping toward the minimum. With eta a = 0.8 the first step lands close to the minimum and the next nearly on it. With eta a = 2.1 each step overshoots to the other side and lands higher than it started, so the iterates zig-zag outward and diverge. too small ηa = 0.25 0 x ← (0.75) x each step start well chosen ηa = 0.8 0 x ← (0.20) x each step start too large ηa = 2.1 0 x ← (−1.10) x each step start

The constant aa is the curvature, the second derivative f′′(x)=af''(x) = a: how fast the slope changes. Any smooth function looks like this near a minimum x∗x^*. There f′(x∗)=0f'(x^*) = 0, so the local approximation applied to f′f' gives f′(x∗+e)≈f′′(x∗)ef'(x^* + e) \approx f''(x^*)\,e, and a gradient step from x∗+ex^* + e moves the error to e−ηf′′(x∗)e=(1−ηf′′(x∗))ee - \eta f''(x^*)\,e = (1 - \eta f''(x^*))\,e: the same recursion with a=f′′(x∗)a = f''(x^*). The stability limit is 2 divided by the curvature.

Many parameters: curvature by direction

The loss of this chapter’s lab is exactly quadratic in its parameters, of the form

L(θ)=L∗+12e⊤He,e=θ−θ∗, L(\theta) = L^* + \frac{1}{2}\, \mathbf{e}^\top \mathbf{H}\, \mathbf{e} , \qquad \mathbf{e} = \theta - \theta^* ,

with θ\theta, θ∗\theta^* and e\mathbf{e} of shape (P,), and H\mathbf{H} a symmetric (P, P) matrix. The double sum is 12∑i∑jeiHijej\frac12 \sum_{i}\sum_{j} e_i H_{ij} e_j; its partial derivative in eke_k collects the terms with i=ki = k and those with j=kj = k, giving 12(∑jHkjej+∑ieiHik)=(He)k\frac12 \big(\sum_j H_{kj} e_j + \sum_i e_i H_{ik}\big) = (\mathbf{H}\mathbf{e})_k by symmetry. So ∇L=He\nabla L = \mathbf{H}\mathbf{e}, and differentiating once more, ∂2L/∂θj∂θk=Hjk\partial^2 L / \partial \theta_j \partial \theta_k = H_{jk}: H\mathbf{H} is the matrix of second partial derivatives, the Hessian. A gradient step subtracts ηHe\eta \mathbf{H} \mathbf{e} from the error:

et+1=(I−ηH)et. \mathbf{e}_{t+1} = (\mathbf{I} - \eta \mathbf{H})\, \mathbf{e}_t .

In general He\mathbf{H}\mathbf{e} points in a different direction from e\mathbf{e}, so the coordinates are coupled. But some directions v\mathbf{v} are only stretched: Hv=λv\mathbf{H}\mathbf{v} = \lambda \mathbf{v} for a scalar λ\lambda. Such a v\mathbf{v} is an eigenvector of H\mathbf{H}, and λ\lambda its eigenvalue. Along an eigenvector the step is (I−ηH)v=(1−ηλ)v(\mathbf{I} - \eta \mathbf{H})\mathbf{v} = (1 - \eta\lambda)\,\mathbf{v}, which is the one-dimensional recursion with a=λa = \lambda: λ\lambda is the curvature in that direction. A symmetric (P, P) matrix has PP mutually perpendicular eigenvectors, the spectral theorem (exercise (c) finds them by hand for P=2P = 2), so any error splits into PP components that evolve independently, each multiplied by its own 1−ηλi1 - \eta\lambda_i per step.

Three facts follow. The loss has a unique minimum at θ∗\theta^* only if every λi>0\lambda_i > 0, a bowl in every direction; a zero eigenvalue is a direction along which the loss is flat. The iteration converges only if every factor has magnitude below 1, so

0<η<2λmax: 0 < \eta < \frac{2}{\lambda_{\max}} :

the steepest direction sets the largest usable learning rate. And the flattest direction sets the pace: its factor is 1−ηλmin1 - \eta\lambda_{\min}, and with η\eta below 2/λmax2/\lambda_{\max} that is at least 1−2λmin/λmax1 - 2\lambda_{\min}/\lambda_{\max}. When the ratio λmax/λmin\lambda_{\max}/\lambda_{\min}, the condition number, is large, the safe step is tiny for the flat directions and plain gradient descent is slow. np.linalg.eigvalsh returns the eigenvalues of a symmetric matrix in ascending order.

The analysis is exact only for a quadratic, but near a minimum every smooth loss is close to one, for the reason the one-dimensional case gave. A learning rate that makes a real training run blow up is, locally, above 2/λmax2/\lambda_{\max} of the loss surface where the run happens to be.

The linear-regression gradient

The lab fits the model y^i=xi⋅w+b\hat{y}_i = \mathbf{x}_i \cdot \mathbf{w} + b to nn examples. X\mathbf{X}, shape (n, d), holds the inputs as rows xi\mathbf{x}_i; w\mathbf{w} has shape (d,), bb is a scalar and the targets y\mathbf{y} have shape (n,). The loss is the mean squared error

L(w,b)=1n∑i=1nri2,ri=xi⋅w+b−yi=∑j=1dXijwj+b−yi, L(\mathbf{w}, b) = \frac{1}{n} \sum_{i=1}^{n} r_i^2 , \qquad r_i = \mathbf{x}_i \cdot \mathbf{w} + b - y_i = \sum_{j=1}^{d} X_{ij} w_j + b - y_i ,

where rir_i is the residual of example ii. In index notation first. rir_i is linear in the parameters, with ∂ri/∂wj=Xij\partial r_i / \partial w_j = X_{ij} and ∂ri/∂b=1\partial r_i / \partial b = 1. By the chain rule with outer function u2u^2, ∂(ri2)/∂wj=2riXij\partial (r_i^2) / \partial w_j = 2 r_i X_{ij}, and by linearity

∂L∂wj=2n∑i=1nriXij,∂L∂b=2n∑i=1nri. \frac{\partial L}{\partial w_j} = \frac{2}{n} \sum_{i=1}^{n} r_i\, X_{ij} , \qquad \frac{\partial L}{\partial b} = \frac{2}{n} \sum_{i=1}^{n} r_i .

The first sum runs over the row index of XijX_{ij}, which is how an entry of X⊤r\mathbf{X}^\top \mathbf{r} is formed: (X⊤r)j=∑i(X⊤)jiri=∑iXijri(\mathbf{X}^\top \mathbf{r})_j = \sum_i (\mathbf{X}^\top)_{ji}\, r_i = \sum_i X_{ij}\, r_i. In matrix form,

∇wL=2nX⊤(Xw+b−y),∂L∂b=2n1⋅(Xw+b−y). \nabla_{\mathbf{w}} L = \frac{2}{n}\, \mathbf{X}^\top (\mathbf{X}\mathbf{w} + b - \mathbf{y}) , \qquad \frac{\partial L}{\partial b} = \frac{2}{n}\, \mathbf{1} \cdot (\mathbf{X}\mathbf{w} + b - \mathbf{y}) .

Shapes: Xw\mathbf{X}\mathbf{w} is (n, d) @ (d,), giving (n,); the scalar bb broadcasts across it; the residual vector r\mathbf{r} is (n,); X⊤\mathbf{X}^\top is (d, n), so X⊤r\mathbf{X}^\top\mathbf{r} is (d,), the shape of w\mathbf{w}, as a gradient must be. Read as a sum, the gradient is the average over examples of 2rixi2 r_i \mathbf{x}_i: each example pulls w\mathbf{w} along its own input, in proportion to how wrong its prediction is.

The bias fits the same pattern if it is treated as a weight on an input that is always 1. Append a column of ones to get X~\tilde{\mathbf{X}}, shape (n, d+1), and stack the parameters into θ=(w,b)\theta = (\mathbf{w}, b), shape (d+1,). Then r=X~θ−y\mathbf{r} = \tilde{\mathbf{X}}\theta - \mathbf{y} and both results above are one formula, ∇L=2nX~⊤(X~θ−y)\nabla L = \frac{2}{n} \tilde{\mathbf{X}}^\top (\tilde{\mathbf{X}}\theta - \mathbf{y}). It is linear in θ\theta with matrix

H=2nX~⊤X~,shape (d+1,d+1), \mathbf{H} = \frac{2}{n}\, \tilde{\mathbf{X}}^\top \tilde{\mathbf{X}} , \qquad \text{shape } (d+1, d+1) ,

which is symmetric, since (X~⊤X~)⊤=X~⊤X~(\tilde{\mathbf{X}}^\top \tilde{\mathbf{X}})^\top = \tilde{\mathbf{X}}^\top \tilde{\mathbf{X}}, and is the Hessian. The minimum θ∗\theta^* is where the gradient vanishes, X~⊤X~θ∗=X~⊤y\tilde{\mathbf{X}}^\top \tilde{\mathbf{X}}\, \theta^* = \tilde{\mathbf{X}}^\top \mathbf{y}, and subtracting that from the gradient at any θ\theta gives ∇L(θ)=H(θ−θ∗)\nabla L(\theta) = \mathbf{H}(\theta - \theta^*): the previous section’s quadratic exactly, with no approximation. The largest stable learning rate for linear regression is 2/λmax2/\lambda_{\max} of this matrix, which depends only on the inputs. For one input feature,

H=2(x2‾x‾x‾1), \mathbf{H} = 2 \begin{pmatrix} \overline{x^2} & \overline{x} \\ \overline{x} & 1 \end{pmatrix} ,

with bars denoting averages over the nn examples: the entries of X~⊤X~\tilde{\mathbf{X}}^\top \tilde{\mathbf{X}} are ∑ixi2\sum_i x_i^2, ∑ixi\sum_i x_i and ∑i1=n\sum_i 1 = n. Exercise (c) evaluates it for the lab data.

Numerical gradients

The definition of the derivative suggests computing it directly: pick a small hh and evaluate the forward difference (f(x+h)−f(x))/h(f(x + h) - f(x))/h. How wrong is it? Extend the local approximation to more terms, f(x+h)=c0+c1h+c2h2+c3h3+⋯f(x + h) = c_0 + c_1 h + c_2 h^2 + c_3 h^3 + \cdots. Setting h=0h = 0 gives c0=f(x)c_0 = f(x); differentiating kk times in hh and then setting h=0h = 0 leaves only k!ckk!\,c_k on the right, so ck=f(k)(x)/k!c_k = f^{(k)}(x)/k!, the kk-th derivative over k!k!. This is the Taylor expansion:

f(x+h)=f(x)+f′(x)h+f′′(x)2h2+f′′′(x)6h3+⋯. f(x + h) = f(x) + f'(x)\,h + \frac{f''(x)}{2}\,h^2 + \frac{f'''(x)}{6}\,h^3 + \cdots .

Subtract f(x)f(x) and divide by hh:

f(x+h)−f(x)h=f′(x)+f′′(x)2h+O(h2). \frac{f(x + h) - f(x)}{h} = f'(x) + \frac{f''(x)}{2}\,h + O(h^2) .

The error shrinks in proportion to hh: O(h)O(h). The central difference evaluates on both sides. Replacing hh by −h-h flips the sign of the odd-power terms:

f(x+h)−f(x−h)=2f′(x)h+f′′′(x)3h3+O(h5), f(x + h) - f(x - h) = 2 f'(x)\,h + \frac{f'''(x)}{3}\,h^3 + O(h^5) ,

the even-power terms cancelling in the subtraction. Divide by 2h2h:

f(x+h)−f(x−h)2h=f′(x)+f′′′(x)6h2+O(h4). \frac{f(x + h) - f(x - h)}{2h} = f'(x) + \frac{f'''(x)}{6}\,h^2 + O(h^4) .

The error is now O(h2)O(h^2): dividing hh by 10 divides it by 100. For the same two evaluations per coordinate, the central difference is far more accurate, and it is what numerical_grad uses.

Taylor’s error says a smaller hh is always better. Floating point says otherwise. A float64 stores about 16 significant decimal digits; the unit roundoff is ϵmach=2−53≈1.1×10−16\epsilon_{\text{mach}} = 2^{-53} \approx 1.1 \times 10^{-16}, and each computed value of ff carries an error of order ϵmach∣f∣\epsilon_{\text{mach}} \lvert f \rvert. For small hh, f(x+h)f(x + h) and f(x−h)f(x - h) agree in most of their leading digits, and subtracting them cancels those digits and leaves the rounding errors behind at the same absolute size. Dividing by 2h2h then magnifies them: the rounding part of the error is of order ϵmach∣f∣/h\epsilon_{\text{mach}} \lvert f \rvert / h and grows as hh shrinks. The total error of the central difference is roughly

∣f′′′∣6h2+ϵmach∣f∣h, \frac{\lvert f''' \rvert}{6}\, h^2 + \frac{\epsilon_{\text{mach}} \lvert f \rvert}{h} ,

minimised where the two terms are of comparable size, around h≈ϵmach1/3≈5×10−6h \approx \epsilon_{\text{mach}}^{1/3} \approx 5 \times 10^{-6} when ff and f′′′f''' are of order 1. For the forward difference the same balance, hh against ϵmach/h\epsilon_{\text{mach}}/h, puts the best hh near ϵmach1/2≈10−8\epsilon_{\text{mach}}^{1/2} \approx 10^{-8} and the best error near 10−810^{-8} as well. numerical_grad defaults to h=10−6h = 10^{-6}, near the central-difference optimum; exercise (d) measures both curves.

A numerical gradient costs two evaluations of ff per parameter. For a single (1000, 1000) weight matrix that is two million evaluations of the loss, which rules it out for training. Chapter 6 computes exact gradients by the chain rule instead, and uses numerical_grad on small inputs to check them; chapter 10 checks its attention layer the same way.

Walkthrough

The reference module is py/tinygpt/descent.py.

def numerical_grad(f, x, eps=1e-6):
    """Central-difference estimate of the gradient of scalar f at x: (f(x+h) - f(x-h)) / 2h per coordinate."""
    x = np.array(x, dtype=np.float64)  # a private copy we can perturb
    grad = np.zeros_like(x)
    for idx in np.ndindex(x.shape):
        orig = x[idx]
        x[idx] = orig + eps
        hi = f(x)
        x[idx] = orig - eps
        lo = f(x)
        x[idx] = orig
        grad[idx] = (hi - lo) / (2 * eps)
    return grad

np.array always copies, so the function perturbs a private array and the caller’s x is never modified; converting to float64 also matters, because adding 10−610^{-6} to an entry of an integer array would be truncated away. np.ndindex(x.shape) yields every index tuple of an array of any shape, so the same function differentiates with respect to a vector, a bias, or a (in, out) weight matrix. Each coordinate is perturbed up, then down, then restored before the next, so every partial derivative is taken with the other coordinates at their original values, which is what a partial derivative is. f must return a scalar; grad[idx] = ... assigns one number per entry.

def mse(pred, y):
    """Mean squared error."""
    r = np.asarray(pred, dtype=np.float64) - np.asarray(y, dtype=np.float64)
    return float((r * r).mean())


def linreg_grad(w, b, x, y):
    """Gradient of mse(x @ w + b, y) with respect to w (shape (d,)) and b (scalar)."""
    n = x.shape[0]
    r = x @ w + b - y          # (n,)
    dw = (2.0 / n) * (x.T @ r)  # (d, n) @ (n,) -> (d,)
    db = (2.0 / n) * r.sum()
    return dw, float(db)

mse returns a Python float rather than a numpy scalar, like the functions of chapters 2 and 4. linreg_grad is the matrix form of the derivation line for line: the residual r, shape (n,), then 2nX⊤r\frac{2}{n}\mathbf{X}^\top\mathbf{r} for the weights and 2n∑iri\frac{2}{n}\sum_i r_i for the bias. x @ w + b relies on the broadcasting of chapter 3 to add the scalar bias to every prediction. The lab test test_linreg_grad_matches_numerical checks this function against numerical_grad at a random point to a relative tolerance of 10−510^{-5}, the check chapter 6 uses to verify its engine.

def fit_linreg(x, y, lr, steps):
    """Plain gradient descent from w = 0, b = 0. losses[t] is the loss before step t, plus the final loss."""
    w, b = np.zeros(x.shape[1]), 0.0
    losses = [mse(x @ w + b, y)]
    for _ in range(steps):
        dw, db = linreg_grad(w, b, x, y)
        w, b = w - lr * dw, b - lr * db
        losses.append(mse(x @ w + b, y))
    return w, b, losses

The tuple assignment w, b = w - lr * dw, b - lr * db evaluates both right-hand sides before either name is rebound, and linreg_grad computed both gradients at the old parameters, so the step is the simultaneous update of the mechanism. losses records the loss at the starting point and after each step, steps + 1 values in all, so losses[0] is the loss of the all-zero model and losses[-1] the loss of the returned one.

Exercises

(a) Differentiate the logistic sigmoid σ(x)=1/(1+e−x)\sigma(x) = 1/(1 + e^{-x}) and show that σ′(x)=σ(x)(1−σ(x))\sigma'(x) = \sigma(x)\,(1 - \sigma(x)).

Answer

Write σ=u−1\sigma = u^{-1} with u=1+e−xu = 1 + e^{-x}. The chain rule gives −u−2u′-u^{-2}\,u', and u′=−e−xu' = -e^{-x} by the chain rule again (outer eve^v, inner v=−xv = -x). So

σ′(x)=−1(1+e−x)2⋅(−e−x)=e−x(1+e−x)2=11+e−x⋅e−x1+e−x. \sigma'(x) = -\frac{1}{(1 + e^{-x})^2} \cdot \big(-e^{-x}\big) = \frac{e^{-x}}{(1 + e^{-x})^2} = \frac{1}{1 + e^{-x}} \cdot \frac{e^{-x}}{1 + e^{-x}} .

The first factor is σ(x)\sigma(x). The second is 1−σ(x)1 - \sigma(x), since 1−11+e−x=1+e−x−11+e−x1 - \frac{1}{1 + e^{-x}} = \frac{1 + e^{-x} - 1}{1 + e^{-x}}. Hence σ′=σ(1−σ)\sigma' = \sigma(1 - \sigma).

Two things follow. The derivative can be computed from the function’s output alone, with no further exponential, which is useful when the output is already stored. And since σ(1−σ)\sigma(1 - \sigma) is a product of two numbers in (0,1)(0, 1) that sum to 1, it is at most 12⋅12=14\frac12 \cdot \frac12 = \frac14, reached at x=0x = 0, and it tends to 0 as ∣x∣\lvert x \rvert grows: a sigmoid unit far into either tail passes almost no gradient back through the chain rule. The worked composite of the mechanism was −logσ(w)-\log \sigma(w), and its derivative −1/(1+ew)-1/(1 + e^{w}) is −(1−σ(w))-(1 - \sigma(w)), which is −σ′(w)/σ(w)-\sigma'(w)/\sigma(w), as the chain rule through the logarithm says it must be.

(b) Express the derivative of tanhx=ex−e−xex+e−x\tanh x = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}} in terms of tanhx\tanh x itself.

Answer

Write N=ex−e−xN = e^{x} - e^{-x} and D=ex+e−xD = e^{x} + e^{-x}. Their derivatives swap: N′=ex+e−x=DN' = e^{x} + e^{-x} = D and D′=ex−e−x=ND' = e^{x} - e^{-x} = N. By the product rule on N⋅D−1N \cdot D^{-1}, with (D−1)′=−D′/D2(D^{-1})' = -D'/D^2 from the chain rule,

tanh′x=N′D−ND′D2=DD−N2D2=1−tanh2x. \tanh' x = \frac{N'}{D} - \frac{N D'}{D^2} = \frac{D}{D} - \frac{N^2}{D^2} = 1 - \tanh^2 x .

Like the sigmoid, the derivative comes from the output: it is 1 at x=0x = 0 and tends to 0 as tanhx→±1\tanh x \to \pm 1. The two functions are related by tanhx=2σ(2x)−1\tanh x = 2\sigma(2x) - 1, and differentiating that gives the same result: 4σ′(2x)=4σ(1−σ)4\sigma'(2x) = 4\sigma(1 - \sigma), and with t=2σ−1t = 2\sigma - 1, σ=(1+t)/2\sigma = (1 + t)/2 and 1−σ=(1−t)/21 - \sigma = (1 - t)/2, so 4σ(1−σ)=(1+t)(1−t)=1−t24\sigma(1 - \sigma) = (1 + t)(1 - t) = 1 - t^2.

(c) The lab tests generate data as x∼N(0,1)x \sim N(0, 1), n=100n = 100, seed 0, with y=3x−2y = 3x - 2 plus small noise. Estimate the largest stable learning rate for fit_linreg on this data from the quadratic analysis, and check it against test_fit_linreg_diverges_when_lr_too_large, which expects divergence at 2.0.

Answer

The Hessian needs two averages of the inputs. Computed from the test data (x.mean() and (x ** 2).mean()), x‾=0.0811\overline{x} = 0.0811 and x2‾=0.9323\overline{x^2} = 0.9323, so

H=2(0.93230.08110.08111)=(1.86450.16220.16222). \mathbf{H} = 2 \begin{pmatrix} 0.9323 & 0.0811 \\ 0.0811 & 1 \end{pmatrix} = \begin{pmatrix} 1.8645 & 0.1622 \\ 0.1622 & 2 \end{pmatrix} .

Its eigenvalues, by hand: for a symmetric (pqqs)\begin{pmatrix} p & q \\ q & s \end{pmatrix}, Hv=λv\mathbf{H}\mathbf{v} = \lambda\mathbf{v} is the pair of equations (p−λ)v1+qv2=0(p - \lambda) v_1 + q v_2 = 0 and qv1+(s−λ)v2=0q v_1 + (s - \lambda) v_2 = 0. Multiply the first by s−λs - \lambda, the second by qq, and subtract: [(p−λ)(s−λ)−q2]v1=0\big[(p - \lambda)(s - \lambda) - q^2\big] v_1 = 0. Doing the same to eliminate v1v_1 gives the same bracket times v2v_2, so a nonzero v\mathbf{v} needs the bracket to vanish: λ2−(p+s)λ+ps−q2=0\lambda^2 - (p + s)\lambda + ps - q^2 = 0, whose roots are

λ=p+s2±(p−s2)2+q2=1.9323±0.06772+0.16222=1.9323±0.1758, \lambda = \frac{p + s}{2} \pm \sqrt{\left(\frac{p - s}{2}\right)^2 + q^2} = 1.9323 \pm \sqrt{0.0677^2 + 0.1622^2} = 1.9323 \pm 0.1758 ,

so λmin=1.7565\lambda_{\min} = 1.7565 and λmax=2.1080\lambda_{\max} = 2.1080. The two eigenvectors are perpendicular, the spectral theorem for this case: λ1(v1⋅v2)=(Hv1)⋅v2=v1⋅(Hv2)=λ2(v1⋅v2)\lambda_1\,(\mathbf{v}_1 \cdot \mathbf{v}_2) = (\mathbf{H}\mathbf{v}_1) \cdot \mathbf{v}_2 = \mathbf{v}_1 \cdot (\mathbf{H}\mathbf{v}_2) = \lambda_2\,(\mathbf{v}_1 \cdot \mathbf{v}_2), using H⊤=H\mathbf{H}^\top = \mathbf{H}, and with λ1≠λ2\lambda_1 \ne \lambda_2 that forces v1⋅v2=0\mathbf{v}_1 \cdot \mathbf{v}_2 = 0. The bound is

η<2λmax=22.1080=0.9487. \eta < \frac{2}{\lambda_{\max}} = \frac{2}{2.1080} = 0.9487 .

At η=2.0\eta = 2.0 both directions are unstable, with factors 1−2.0×1.7565=−2.511 - 2.0 \times 1.7565 = -2.51 and 1−2.0×2.1080=−3.221 - 2.0 \times 2.1080 = -3.22, so the test is far over the line: the loss goes from 11.4 at the start to 72.1 after one step and 1.17×10171.17 \times 10^{17} after twenty.

Two ways to get the bound wrong are instructive. Ignoring the bias, the curvature in ww alone is 2x2‾=1.86452\overline{x^2} = 1.8645 and the bound would be 1.07261.0726, but the bias adds a direction of curvature and couples to ww through x‾\overline{x}; a run at η=1.0\eta = 1.0 diverges, reaching a loss of 1.4×10211.4 \times 10^{21} after 300 steps. And a run just over the true bound does not blow up at once. The starting error θ0−θ∗=(−3.0006,2.0006)\theta_0 - \theta^* = (-3.0006, 2.0006) happens to be nearly perpendicular to the steep eigenvector, about (0.554,0.832)(0.554, 0.832): its component along it is 0.0016, against 3.6 along the flat one. At η=0.96\eta = 0.96 the steep factor is −1.024-1.024 and that tiny component needs many steps to matter; the loss falls below 10−310^{-3} at step 13 and is back up to 3.25 at step 300.

(d) For f(x)=exf(x) = e^x at x=1x = 1, compute the error of the forward and central differences for h=10−1,10−2,…,10−12h = 10^{-1}, 10^{-2}, \ldots, 10^{-12}, plot both against hh on log–log axes, and explain both slopes and the floor.

Answer
import math

x = 1.0
exact = math.exp(x)
print("   eps     forward     central")
for k in range(1, 13):
    h = 10.0 ** -k
    fwd = (math.exp(x + h) - math.exp(x)) / h
    cen = (math.exp(x + h) - math.exp(x - h)) / (2 * h)
    print(f"1e-{k:02d}  {abs(fwd - exact):.2e}  {abs(cen - exact):.2e}")
   eps     forward     central
1e-01  1.41e-01  4.53e-03
1e-02  1.36e-02  4.53e-05
1e-03  1.36e-03  4.53e-07
1e-04  1.36e-04  4.53e-09
1e-05  1.36e-05  5.86e-11
1e-06  1.36e-06  1.63e-10
1e-07  1.40e-07  5.86e-11
1e-08  6.60e-09  6.60e-09
1e-09  2.15e-07  6.60e-09
1e-10  1.55e-06  6.73e-07
1e-11  3.26e-05  1.04e-05
1e-12  4.32e-04  2.10e-04

Each row is one decade of hh, so on log–log axes the slope is the change in the error’s exponent per row. For large hh the forward error drops one decade per row, slope 1, and matches f′′(1)2h=e2h=1.359h\frac{f''(1)}{2} h = \frac{e}{2} h = 1.359\,h; the central error drops two decades per row, slope 2, and matches f′′′(1)6h2=0.453h2\frac{f'''(1)}{6} h^2 = 0.453\,h^2. These are the leading error terms of the Taylor analysis.

Both then hit a floor and turn up, with slope about −1-1: the rounding error, of order ϵmache/h\epsilon_{\text{mach}} e / h, grows as hh shrinks. At h=10−12h = 10^{-12} that estimate is 1.1×10−16×2.718/10−12=3×10−41.1 \times 10^{-16} \times 2.718 / 10^{-12} = 3 \times 10^{-4}, the size of both measured errors. The central difference reaches its floor sooner and lower, about 6×10−116 \times 10^{-11} near h=10−5h = 10^{-5} to 10−710^{-7}, where its truncation error 0.45h20.45 h^2 meets the rounding error; the forward difference bottoms out near h=10−8h = 10^{-8}, around 10−810^{-8}. Near the floor the values jump around, since the rounding error depends on exactly how 1+h1 + h and e1±he^{1 \pm h} round, not smoothly on hh. The default h=10−6h = 10^{-6} of numerical_grad gives 1.6×10−101.6 \times 10^{-10} here, a relative error of 6×10−116 \times 10^{-11}.

(e) Why does the gradient of the mean loss not grow with the size of the dataset? What changes if the loss is the sum of squared residuals instead of their mean?

Answer

The gradient of a mean is the mean of the per-example gradients, by linearity: ∇L=1n∑i2rix~i\nabla L = \frac{1}{n} \sum_i 2 r_i \tilde{\mathbf{x}}_i. For examples drawn from the same distribution, that average estimates a fixed expectation, and by chapter 4 it concentrates around it as nn grows rather than growing with nn. The Hessian 2nX~⊤X~\frac{2}{n}\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}} is also an average, of 2x~ix~i⊤2\tilde{\mathbf{x}}_i\tilde{\mathbf{x}}_i^\top, so its eigenvalues, and with them the stability bound, do not depend on nn either. A learning rate tuned on one dataset size carries over to another.

With the sum, the gradient and the Hessian are both nn times larger. The minimiser is the same, but every eigenvalue is multiplied by nn and the stability bound divided by nn. On the test data the bound falls from 0.9487 to 0.009487: gradient descent on the summed loss converges at η=0.009\eta = 0.009, to a sum of squares of 0.0091 (100 times the mean-loss minimum of 9.1×10−59.1 \times 10^{-5}), and diverges at η=0.01\eta = 0.01, to 1.4×10231.4 \times 10^{23} after 300 steps. Training loops average the loss over each batch for this reason: a change of batch size then changes how noisy the gradient is, not its scale.

Lab

Implement every function in py/labs/ch05/starter.py, then run

make lab CH=05 IMPL=mine

until it passes. Read py/labs/ch05/test_lab.py first. It pins what the prose leaves open: numerical_grad must return 2x2\mathbf{x} for ∑ixi2\sum_i x_i^2 to within 10−610^{-6} and must leave its input unchanged, including a 2-D input; mse of [1, 2] against [1, 4] must be exactly 2.0; linreg_grad must agree with numerical_grad at a random point; and fit_linreg must recover w=3w = 3 and b=−2b = -2 to within 0.01 in 500 steps at η=0.1\eta = 0.1, produce a non-increasing loss at η=0.05\eta = 0.05, and end with a higher loss than it started with at η=2.0\eta = 2.0. Derive linreg_grad on paper before writing it; the numerical test will say whether the derivation is right.

Then sweep the learning rate. Save this script outside the repository, for example as /tmp/lr_sweep.py:

import numpy as np

from tinygpt.descent import fit_linreg

rng = np.random.default_rng(0)
x = rng.normal(size=(100, 1))
y = 3.0 * x[:, 0] - 2.0 + 0.01 * rng.normal(size=100)

xb = np.hstack([x, np.ones((100, 1))])  # (100, 2): the bias is a weight on a constant input
hessian = (2 / 100) * xb.T @ xb          # (2, 2)
curvatures = np.linalg.eigvalsh(hessian)
print("curvatures", curvatures.round(4), " stability bound 2/max =", round(2 / curvatures.max(), 4))

for lr in [0.01, 0.1, 0.5, 0.9, 1.1]:
    w, b, losses = fit_linreg(x, y, lr=lr, steps=300)
    hit = next((t for t, loss in enumerate(losses) if loss < 1e-3), None)
    if hit is not None:
        print(f"lr={lr:<4}  loss < 1e-3 after {hit:3d} steps   w={w[0]:.4f}  b={b:.4f}")
    else:
        low = int(np.argmin(losses))
        print(f"lr={lr:<4}  diverged: lowest loss {losses[low]:.3g} at step {low}, {losses[-1]:.3g} at step 300")

and run it from py/ with PYTHONPATH=. uv run python /tmp/lr_sweep.py. The data is the test data. Each run takes 300 steps; the script reports the first step at which the loss is below 10−310^{-3}, with the parameters after all 300 steps, or, for a run that never gets there, its lowest loss and its final loss. Change the import to from labs.ch05.starter import fit_linreg to run yours; the output should be the same:

curvatures [1.7565 2.108 ]  stability bound 2/max = 0.9487
lr=0.01  loss < 1e-3 after 267 steps   w=2.9858  b=-1.9907
lr=0.1   loss < 1e-3 after  25 steps   w=3.0006  b=-2.0006
lr=0.5   loss < 1e-3 after   3 steps   w=3.0006  b=-2.0006
lr=0.9   loss < 1e-3 after   9 steps   w=3.0006  b=-2.0006
lr=1.1   diverged: lowest loss 0.851 at step 20, 3.31e+66 at step 300

Match each line to the analysis before moving on.

  • The bound is 0.9487. The four learning rates below it converge and the one above it does not. The minimum loss is 9.1×10−59.1 \times 10^{-5}, the variance of the noise the data was generated with (0.0120.01^2) up to sampling, and it sits at w=3.0006w = 3.0006, b=−2.0006b = -2.0006, not exactly at 3 and −2-2, because the least-squares fit also fits the noise.
  • Small rates are slow at the rate the flat direction sets. At η=0.01\eta = 0.01 the flat direction’s factor is 1−0.01×1.7565=0.98241 - 0.01 \times 1.7565 = 0.9824, and the excess loss in that direction, 12λc2\frac12 \lambda c^2 for a component cc, shrinks by its square, 0.9651, per step. Starting from an excess of 11.42, reaching 10−310^{-3} (an excess of 9.1×10−49.1 \times 10^{-4}) takes log(11.42/9.1×10−4)/(−log0.9651)≈266\log(11.42 / 9.1 \times 10^{-4}) / (-\log 0.9651) \approx 266 steps; the run took 267, and after 300 it has still not settled to four decimal places. At η=0.1\eta = 0.1 the same estimate gives 24.4 and the run took 25.
  • The fastest rate is not the largest stable one. At η=0.5\eta = 0.5 the factors are 0.1220.122 and −0.054-0.054, both small, and three steps suffice. The best single learning rate for a quadratic makes the two extreme factors equal and opposite, η=2/(λmin+λmax)=0.5175\eta = 2/(\lambda_{\min} + \lambda_{\max}) = 0.5175, factors ±0.091\pm 0.091. At η=0.9\eta = 0.9, still stable, both factors are negative, −0.581-0.581 and −0.897-0.897: the iterates overshoot on every step, the zig-zag of the figure, and convergence takes 9 steps.
  • Divergence can hide. At η=1.1\eta = 1.1 the steep factor is −1.319-1.319, so that component grows, and the flat factor is −0.932-0.932, so that one shrinks. The loss falls for 20 steps, to 0.851, before it turns. The reason is the starting point: as exercise (c) found, the initial error has a component of only 0.0016 along the steep direction and 3.6 along the flat one, so the unstable part starts about 4.5×1064.5 \times 10^6 times smaller in loss terms and gains on it by a factor of 2.0 per step (1.3192/0.93221.319^2 / 0.932^2), so it needs about 22 steps to overtake it; the total loss turns at step 20, as the two become comparable. After that the loss grows by 1.3192=1.741.319^2 = 1.74 per step, reaching 3.3×10663.3 \times 10^{66} at step 300. A training curve that falls and then explodes need not mean the run went somewhere new; an unstable direction may have been growing from the start.

Finally, add 0.94, 0.95 and 0.96 to the list, straddling the bound. All three reach 10−310^{-3} in 12 or 13 steps, because the flat direction does most of the work. After 300 steps the run at 0.94 has converged to the minimum, the one at 0.95 has started to climb back up (to 1.03×10−41.03 \times 10^{-4} from a low of 9.4×10−59.4 \times 10^{-5} at step 25), and the one at 0.96 is back at 3.25. Stopping at the first step below a threshold would have scored all three as successes.

Further reading

  • Gilbert Strang, Calculus (3rd ed., 2017; free from MIT OpenCourseWare), chapters 2–4 and 6. Derivatives from the definition, their applications and the chain rule (chapters 2–4), and the exponential and logarithm (chapter 6).
  • Marc Peter Deisenroth, A. Aldo Faisal and Cheng Soon Ong, Mathematics for Machine Learning (Cambridge University Press, 2020), chapter 5. Partial derivatives, gradients of vector- and matrix-valued functions, the multivariate chain rule, and Taylor series, in the notation of machine learning.
  • Jorge Nocedal and Stephen J. Wright, Numerical Optimization (2nd ed., 2006), chapter 2. Optional depth: conditions for a minimum, Taylor’s theorem in many variables, and an overview of the line-search and trust-region methods that go beyond a fixed step.

results matching ""

    No results matching ""