Mathematical Foundations

VI.10 · Optimization

What does an optimizer's stopping reason actually establish?

Read raw gradients, optional clipped gradients, finite updates, loss changes, and budgets in their actual evaluation order. Each reason explains why computation stopped; its meaning depends on the objective and the evidence retained.

Inspect why this numerical run stopped

Parameter space: θ₁ and θ₂

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Stop: gradient-norm threshold (not a global-optimality certificate). Parameters outside the displayed window are retained in the table; the view never changes the path. Raw gradient threshold = 0.1; absolute loss-change threshold = 0; clip norm = 0 (0 disables each). Parameter norm boundary = 1000000. Numerical stopping and clipping do not certify a global minimum.

Loss slice: θ₂ = 0

This is a slice of the objective in parameter space, with θ₂ fixed at zero.

Current parameter vector
(2, 1)
Current objective value
4
Actual gradient components
(2, 4)
Learning rate
0.250000
Update vector
(−0.5, −1)
Next parameter vector
(1.5, 0)
New objective value
1.125000
Selected raw gradient norm
4.472136
Final raw gradient norm at stop
0.084470
Raw gradient stopping threshold
0.1
Absolute loss-change stopping threshold
disabled (0)
Last accepted absolute loss change
0.002775
Explicit divergence norm boundary
1000000
Actual termination reason
gradient-norm threshold (not a global-optimality certificate)

≈ binary64 calculation · = analytic classification. Replay selects the same evaluated step in every view.

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Stop: gradient-norm threshold (not a global-optimality certificate). Parameters outside the displayed window are retained in the table; the view never changes the path. Raw gradient threshold = 0.1; absolute loss-change threshold = 0; clip norm = 0 (0 disables each). Parameter norm boundary = 1000000. Numerical stopping and clipping do not certify a global minimum.

L=a2(θ1−c)2+2θ22L=\tfrac a2(\theta_1-c)^2+2\theta_2^2
θk+1=θk−η∇L(θk)\theta_{k+1}=\theta_k-\eta\nabla L(\theta_k)
θ1≈[21]+[−0.5−1]≈[1.50]\theta_{1}\approx \begin{bmatrix}2\\1\end{bmatrix}+\begin{bmatrix}-0.5\\-1\end{bmatrix}\approx \begin{bmatrix}1.5\\0\end{bmatrix}
∥∇L(θk)∥≤τg,∣Lk+1−Lk∣≤τL\|\nabla L(\theta_k)\|\le\tau_g,\quad|L_{k+1}-L_k|\le\tau_L
Evaluated binary64 history. Replay changes the selected row, never the stored mathematics.
kθ(k)L(k)gradientηupdateθ(k+1)L(k+1)raw gradient normclip scaleapplied gradientunclipped updateabsolute loss change
0(2, 1)4(2, 4)0.25(−0.5, −1)(1.5, 0)1.1254.4721361(2, 4)(−0.5, −1)2.875
1(1.5, 0)1.125(1.5, 0)0.25(−0.375, 0)(1.125, 0)0.6328131.51(1.5, 0)(−0.375, 0)0.492188
2(1.125, 0)0.632813(1.125, 0)0.25(−0.28125, 0)(0.84375, 0)0.3559571.1251(1.125, 0)(−0.28125, 0)0.276855
3(0.84375, 0)0.355957(0.84375, 0)0.25(−0.210938, 0)(0.632813, 0)0.2002260.843751(0.84375, 0)(−0.210938, 0)0.155731
4(0.632813, 0)0.200226(0.632813, 0)0.25(−0.158203, 0)(0.474609, 0)0.1126270.6328131(0.632813, 0)(−0.158203, 0)0.087599
5(0.474609, 0)0.112627(0.474609, 0)0.25(−0.118652, 0)(0.355957, 0)0.0633530.4746091(0.474609, 0)(−0.118652, 0)0.049274
6(0.355957, 0)0.063353(0.355957, 0)0.25(−0.088989, 0)(0.266968, 0)0.0356360.3559571(0.355957, 0)(−0.088989, 0)0.027717
7(0.266968, 0)0.035636(0.266968, 0)0.25(−0.066742, 0)(0.200226, 0)0.0200450.2669681(0.266968, 0)(−0.066742, 0)0.015591
8(0.200226, 0)0.020045(0.200226, 0)0.25(−0.050056, 0)(0.150169, 0)0.0112750.2002261(0.200226, 0)(−0.050056, 0)0.00877
9(0.150169, 0)0.011275(0.150169, 0)0.25(−0.037542, 0)(0.112627, 0)0.0063420.1501691(0.150169, 0)(−0.037542, 0)0.004933
10(0.112627, 0)0.006342(0.112627, 0)0.25(−0.028157, 0)(0.08447, 0)0.0035680.1126271(0.112627, 0)(−0.028157, 0)0.002775

Evaluated iteration 0

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss.

θ1≈[21]+[−0.5−1]≈[1.50]\theta_{1}\approx \begin{bmatrix}2\\1\end{bmatrix}+\begin{bmatrix}-0.5\\-1\end{bmatrix}\approx \begin{bmatrix}1.5\\0\end{bmatrix}

from −3 to 3, up to 2 decimal places

from −3 to 3, up to 2 decimal places

from 0.25 to 4, up to 2 decimal places

from −2 to 2, up to 2 decimal places

from 0.01 to 2, up to 2 decimal places

from 1 to 40, whole numbers only

from 0 to 1, up to 2 decimal places

from 0 to 1, up to 2 decimal places

from 0 to 5, up to 2 decimal places

Inspect the raw gradient and its norm before an update, then compare unclipped and applied displacements. Replay finite loss changes and read the precise stopping reason with its configured thresholds.

Reset starts at (2,1)(2,1) on L=x2/2+2y2L=x^2/2+2y^2, with η=1/4\eta=1/4, K=12K=12, raw-gradient tolerance 0.10.1, loss-change tolerance 0, clipping threshold 0, and divergence norm bound 10610^6. Predict whether the first move or the first gradient check ends the run. Then compare the small-loss preset, which stops after two finite updates while its gradient is still far from zero.

Intuition: a diagnostic is evidence about a computation

An optimizer may stop because its measured slope is small, its objective changes little, its work budget is used, or its arithmetic becomes unsafe. These are distinct observations. The reason should name the event and show the values used to make the decision.

A small gradient describes local first-order behavior. A tiny loss change may result from a tiny learning rate, rounding, or motion along an almost flat direction. A finite budget says how much work was allowed. None of those observations, by itself, proves a global optimum.

Gradient clipping changes a numerical update while retaining the raw gradient for diagnosis. It can limit one step’s magnitude, but it does not turn an invalid objective into a valid one or establish that repeated steps converge.

Definition: two optional thresholds and one update intervention

This chapter uses plain gradient descent, with no momentum, Adam history, stochastic sampling, projection, or penalty. For the quadratic family L=a(x−c)2/2+2y2L=a(x-c)^2/2+2y^2, the raw gradient is g=(a(x−c),4y)g=(a(x-c),4y).

A positive raw-gradient tolerance τg\tau_g stops before an update when

∥gk∥2≤τg.\norm{g_k}_2\leq\tau_g.

A positive absolute loss-change tolerance τL\tau_L stops after a finite update when

∣L(θk+1)−L(θk)∣≤τL.\abs{L(\theta_{k+1})-L(\theta_k)}\leq\tau_L.

Zero disables the corresponding tolerance test. It does not turn exact zero into an enabled threshold or disable finite-value checks. An exactly zero raw gradient has its own explicit stop when the gradient-tolerance test is disabled.

For clipping threshold C>0C>0 and nonzero raw g, define

s=min⁡(1,C∥g∥2),gapplied=sg,Δunclipped=−ηg,Δapplied=−ηsg.s=\min\left(1,\frac C{\norm g_2}\right),\qquad g_{\mathrm{applied}}=sg,\qquad \Delta_{\mathrm{unclipped}}=-\eta g,\qquad \Delta_{\mathrm{applied}}=-\eta sg.

At C=0C=0, clipping is disabled and s=1s=1. A zero raw gradient stops before the quotient is formed. The positive divergence bound B triggers when the next finite point has ∥θk+1∥2>B\norm{\theta_{k+1}}_2>B.

Mechanics: the policy order is part of the meaning

At each permitted plain-descent iteration, apply these checks in order:

  1. Evaluate the current objective on its domain and its raw gradient. Stop if the objective or required gradient is undefined or non-finite.
  2. If τg>0\tau_g>0, compare the raw gradient norm with τg\tau_g before any update.
  3. If the raw gradient is exactly zero, stop explicitly. This reason carries no automatic optimum claim.
  4. Form the applied gradient, displacement, and next point. Guard the actual transition for finite values.
  5. Evaluate the next objective and required gradient. A non-finite next evaluation stops before a loss difference is used.
  6. Check the next finite point against the configured divergence norm bound.
  7. If τL>0\tau_L>0, compare the finite endpoint losses using their absolute difference.
  8. If K updates have been completed, stop for the iteration budget.

The first applicable reason wins. A positive gradient tolerance also catches exact zero before the separate zero-gradient rule. Divergence takes precedence over a small loss change on the same finite transition. Loss-change stopping takes precedence over budget exhaustion on the last allowed update.

Keep raw g, its norm, the clipping scale, applied g, and both displacement vectors in the record. The raw-gradient test must not use the capped applied norm: otherwise lowering the clipping threshold could manufacture a misleading tolerance stop.

Replay selects these recorded evaluations and interventions. A failed candidate remains an attempted transition with its failure reason; it must not be presented as a successful finite loss evaluation.

Derivation: interpret each measured quantity separately

For the default quadratic and rate 1/41/4, the error factors are (3/4,0)(3/4,0). After the first update,

xk=2(3/4)k,yk=0,Lk=2(3/4)2k,k≥1.x_k=2(3/4)^k,\qquad y_k=0,\qquad L_k=2(3/4)^{2k},\qquad k\geq1.

The gradient norm is then ∣xk∣\abs{x_k}. The loss changes with a different scale:

Lk−Lk+1=(1−916)Lk=716Lk.L_k-L_{k+1}=\left(1-\frac9{16}\right)L_k =\frac7{16}L_k.

Thus a loss-change threshold and gradient threshold generally stop at different states. Rescaling the objective scales both gradients and loss differences; changing parameter units also changes Euclidean gradient norms. Tolerances need a stated scale and purpose.

Clipping preserves the direction of a nonzero raw gradient and yields

∥gapplied∥2=min⁡(∥g∥2,C),∥Δapplied∥2=ηmin⁡(∥g∥2,C).\norm{g_{\mathrm{applied}}}_2=\min(\norm g_2,C),\qquad \norm{\Delta_{\mathrm{applied}}}_2 =\eta\min(\norm g_2,C).

It limits displacement at that iteration. It is not a coordinate clamp, a projection onto a parameter region, or a change to the objective. A very large finite gradient can still reveal trouble even when its applied update is small.

The absolute loss-change rule does not require a decrease. A small increase also satisfies the tolerance. A divergence bound is likewise a configured stop for excessive parameter norm, not a proof that the exact sequence tends to infinity.

Worked examples: one path, different stopping reasons

The canonical first gradient and update are

g0=(2,4),∥g0∥2=20>0.1,Δ0=(−1/2,−1),θ1=(3/2,0),L1=9/8.g_0=(2,4),\quad \norm{g_0}_2=\sqrt{20}>0.1,\quad \Delta_0=(-1/2,-1),\quad \theta_1=(3/2,0),\quad L_1=9/8.

The next point is (9/8,0)(9/8,0) with loss 81/12881/128. For the canonical raw-gradient tolerance, after ten updates the norm is 2(3/4)10≈0.112627>0.12(3/4)^{10}\approx0.112627>0.1. After eleven it is

2(3/4)11=1771472097152≈0.0844703.2(3/4)^{11}=\frac{177147}{2097152}\approx0.0844703.

The next pre-update check stops at that state. Eleven updates were completed; a twelfth update is not taken. The positive final loss means the exact zero-loss minimizer has not been reached.

Worked example: Inspect why this numerical run stopped

Parameter space: θ₁ and θ₂

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Stop: gradient-norm threshold (not a global-optimality certificate). Parameters outside the displayed window are retained in the table; the view never changes the path. Raw gradient threshold = 0.1; absolute loss-change threshold = 0; clip norm = 0 (0 disables each). Parameter norm boundary = 1000000. Numerical stopping and clipping do not certify a global minimum.

Loss slice: θ₂ = 0

This is a slice of the objective in parameter space, with θ₂ fixed at zero.

Current parameter vector
(2, 1)
Current objective value
4
Actual gradient components
(2, 4)
Learning rate
0.250000
Update vector
(−0.5, −1)
Next parameter vector
(1.5, 0)
New objective value
1.125000
Selected raw gradient norm
4.472136
Final raw gradient norm at stop
0.084470
Raw gradient stopping threshold
0.1
Absolute loss-change stopping threshold
disabled (0)
Last accepted absolute loss change
0.002775
Explicit divergence norm boundary
1000000
Actual termination reason
gradient-norm threshold (not a global-optimality certificate)

≈ binary64 calculation · = analytic classification. Replay selects the same evaluated step in every view.

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss. Stop: gradient-norm threshold (not a global-optimality certificate). Parameters outside the displayed window are retained in the table; the view never changes the path. Raw gradient threshold = 0.1; absolute loss-change threshold = 0; clip norm = 0 (0 disables each). Parameter norm boundary = 1000000. Numerical stopping and clipping do not certify a global minimum.

L=a2(θ1−c)2+2θ22L=\tfrac a2(\theta_1-c)^2+2\theta_2^2
θk+1=θk−η∇L(θk)\theta_{k+1}=\theta_k-\eta\nabla L(\theta_k)
θ1≈[21]+[−0.5−1]≈[1.50]\theta_{1}\approx \begin{bmatrix}2\\1\end{bmatrix}+\begin{bmatrix}-0.5\\-1\end{bmatrix}\approx \begin{bmatrix}1.5\\0\end{bmatrix}
∥∇L(θk)∥≤τg,∣Lk+1−Lk∣≤τL\|\nabla L(\theta_k)\|\le\tau_g,\quad|L_{k+1}-L_k|\le\tau_L
Evaluated binary64 history. Replay changes the selected row, never the stored mathematics.
kθ(k)L(k)gradientηupdateθ(k+1)L(k+1)raw gradient normclip scaleapplied gradientunclipped updateabsolute loss change
0(2, 1)4(2, 4)0.25(−0.5, −1)(1.5, 0)1.1254.4721361(2, 4)(−0.5, −1)2.875
1(1.5, 0)1.125(1.5, 0)0.25(−0.375, 0)(1.125, 0)0.6328131.51(1.5, 0)(−0.375, 0)0.492188
2(1.125, 0)0.632813(1.125, 0)0.25(−0.28125, 0)(0.84375, 0)0.3559571.1251(1.125, 0)(−0.28125, 0)0.276855
3(0.84375, 0)0.355957(0.84375, 0)0.25(−0.210938, 0)(0.632813, 0)0.2002260.843751(0.84375, 0)(−0.210938, 0)0.155731
4(0.632813, 0)0.200226(0.632813, 0)0.25(−0.158203, 0)(0.474609, 0)0.1126270.6328131(0.632813, 0)(−0.158203, 0)0.087599
5(0.474609, 0)0.112627(0.474609, 0)0.25(−0.118652, 0)(0.355957, 0)0.0633530.4746091(0.474609, 0)(−0.118652, 0)0.049274
6(0.355957, 0)0.063353(0.355957, 0)0.25(−0.088989, 0)(0.266968, 0)0.0356360.3559571(0.355957, 0)(−0.088989, 0)0.027717
7(0.266968, 0)0.035636(0.266968, 0)0.25(−0.066742, 0)(0.200226, 0)0.0200450.2669681(0.266968, 0)(−0.066742, 0)0.015591
8(0.200226, 0)0.020045(0.200226, 0)0.25(−0.050056, 0)(0.150169, 0)0.0112750.2002261(0.200226, 0)(−0.050056, 0)0.00877
9(0.150169, 0)0.011275(0.150169, 0)0.25(−0.037542, 0)(0.112627, 0)0.0063420.1501691(0.150169, 0)(−0.037542, 0)0.004933
10(0.112627, 0)0.006342(0.112627, 0)0.25(−0.028157, 0)(0.08447, 0)0.0035680.1126271(0.112627, 0)(−0.028157, 0)0.002775

Evaluated iteration 0

Iteration 0: at θ ≈ (2, 1), L ≈ 4 and gradient ≈ (2, 4). Rate η ≈ 0.25 gives update ≈ (−0.5, −1). Adding that update gives θ next ≈ (1.5, 0) and L next ≈ 1.125. The step follows the negative gradient; a finite step can still increase loss.

θ1≈[21]+[−0.5−1]≈[1.50]\theta_{1}\approx \begin{bmatrix}2\\1\end{bmatrix}+\begin{bmatrix}-0.5\\-1\end{bmatrix}\approx \begin{bmatrix}1.5\\0\end{bmatrix}

Replay canonical plain descent: (2,1) to (3/2,0) to (9/8,0). The positive raw-gradient tolerance ends the run before a twelfth update, after eleven completed updates.

The small-loss preset disables gradient stopping and sets τL=1/2\tau_L=1/2. The first loss change is 4−9/8=23/84-9/8=23/8, above the threshold. The second is

9/8−81/128=63/128<1/2.9/8-81/128=63/128<1/2.

It stops after two updates at (9/8,0)(9/8,0), where the raw gradient norm is still 9/89/8. The budget preset uses the same path with both tolerances disabled and K=2K=2; it ends at the same point for the different reason of iteration budget.

For the clipping preset, retain start (2,1)(2,1) and rate 1/41/4, disable gradient stopping, and set C=1C=1. At the first step,

s=120,gapplied=(2,4)20,Δapplied=(−1/2,−1)20.s=\frac1{\sqrt{20}},\qquad g_{\mathrm{applied}}=\frac{(2,4)}{\sqrt{20}},\qquad \Delta_{\mathrm{applied}}=\frac{(-1/2,-1)}{\sqrt{20}}.

The next point is (2−1/(220),1−1/20)(2-1/(2\sqrt{20}),1-1/\sqrt{20}), and the displacement norm is 1/41/4. The unclipped vector remains (−1/2,−1)(-1/2,-1) in the record, and the raw norm remains 20\sqrt{20}.

The divergent preset uses the quadratic, η=2\eta=2, K=12K=12, both tolerances 0, no clipping, and bound 10610^6. Its factors are (−1,−7)(-1,-7): x alternates between ±2\pm2, while ∣yk∣=7k\abs{y_k}=7^k. The eighth next point is (2,5764801)(2,5764801), exceeding the bound, so the run stops after eight finite updates.

The non-finite preset instead uses L=x4+y4L=x^4+y^4, start (3,1)(3,1), η=2\eta=2, K=12K=12, both tolerances 0, no clipping, and the explicitly configured bound 1030010^{300}. Its recurrence is xk+1=xk−8xk3x_{k+1}=x_k-8x_k^3, likewise for y. The first two next points are (−213,−7)(-213,-7) and (77308563,2737)(77308563,2737). The fifth proposed point is finite in binary64, but its quartic objective overflows. The next-evaluation guard wins before divergence or a loss comparison; the failed loss is not replaced by zero.

Executable reference code

The standalone standard-library program records the actual binary64 plain-descent recurrence and its ordered reasons. Independent Fraction calculations check the canonical path, raw-gradient stopping count, two-step loss-change values, and budget endpoint. An 80-digit Decimal calculation checks the clipped first step independently.

Literal assertions also cover an explicit zero gradient at a saddle, loss-change precedence on the last budgeted step, divergence before loss-change stopping, a non-finite transition, and a non-finite next objective with finite candidate coordinates. The NumPy reference computes actual two-component array gradients and updates, then compares valid recorded values with a separate high-precision scalar recurrence. Its checked binary64 tolerance is relative and absolute 10−1210^{-12}; stop reasons and counts must match exactly.

code/python/optimization_diagnostics.py
"""Plain binary64 descent with ordered stops; independent Fraction and Decimal checks."""
from decimal import Decimal as D, localcontext
from fractions import Fraction as F
from math import hypot, isfinite


def evaluate(point, objective, a, c):
    x, y = point
    if objective == "quadratic":
        loss, gradient = a*(x-c)*(x-c)/2+2*y*y, (a*(x-c), 4*y)
    elif objective == "flat":
        loss, gradient = x*x*x*x+y*y*y*y, (4*x*x*x, 4*y*y*y)
    elif objective == "saddle":
        loss, gradient = x*x-y*y, (2*x, -2*y)
    else:
        raise ValueError("Objective must be quadratic, flat, or saddle")
    return (loss, gradient) if all(isfinite(value) for value in (loss, *gradient)) else None


def run(start=(2, 1), eta=0.25, iterations=12, a=1.0, c=0.0, objective="quadratic",
        gradient_tol=0.1, loss_tol=0.0, clip=0.0, norm_bound=1e6):
    if len(start) != 2 or type(iterations) is not int or not 1 <= iterations <= 40:
        raise ValueError("Require two coordinates and an integer budget in [1,40]")
    requested = point = tuple(map(float, start))
    eta, a, c, gradient_tol, loss_tol, clip, norm_bound = map(
        float, (eta, a, c, gradient_tol, loss_tol, clip, norm_bound))
    if not all(isfinite(value) for value in (*point, eta, a, c, gradient_tol, loss_tol, clip, norm_bound)):
        raise ValueError("Require finite inputs")
    if eta <= 0 or not 0.25 <= a <= 4 or not -2 <= c <= 2 or min(gradient_tol, loss_tol, clip) < 0 or norm_bound <= 0:
        raise ValueError("Require eta,bound>0, a in [0.25,4], c in [-2,2], and nonnegative thresholds")
    if objective not in ("quadratic", "flat", "saddle"):
        raise ValueError("Objective must be quadratic, flat, or saddle")
    frames, reason = [], "iteration budget"
    for k in range(iterations):
        current = evaluate(point, objective, a, c)
        if current is None:
            reason = "non-finite current evaluation"
            break
        loss, raw = current
        norm = hypot(*raw)
        if not isfinite(norm):
            reason = "non-finite current evaluation"
            break
        if gradient_tol > 0 and norm <= gradient_tol:
            reason = "gradient tolerance"
            break
        if norm == 0:
            reason = "zero gradient"
            break
        scale = min(1.0, clip/norm) if clip > 0 else 1.0
        applied = (scale*raw[0], scale*raw[1])
        raw_update = (-eta*raw[0], -eta*raw[1])
        raw_proposal = (point[0]+raw_update[0], point[1]+raw_update[1])
        update = (-eta*applied[0], -eta*applied[1])
        next_point = (point[0]+update[0], point[1]+update[1])
        frame = {"k": k, "point": point, "loss": loss, "raw_gradient": raw, "gradient_norm": norm,
                 "scale": scale, "applied_gradient": applied, "raw_update": raw_update,
                 "raw_proposal": raw_proposal, "applied_update": update, "next": next_point,
                 "next_loss": None, "next_gradient": None}
        frames.append(frame)
        if not all(isfinite(value) for value in (scale, *applied, *update, *next_point)):
            reason = "non-finite transition"
            break
        next_evaluation = evaluate(next_point, objective, a, c)
        if next_evaluation is None:
            reason = "non-finite next evaluation"
            break
        next_loss, next_gradient = next_evaluation
        frame["next_loss"], frame["next_gradient"] = next_loss, next_gradient
        point = next_point
        if hypot(*point) > norm_bound:
            reason = "divergence bound"
            break
        if loss_tol > 0 and abs(next_loss-loss) <= loss_tol:
            reason = "loss change tolerance"
            break
    return {"start": requested, "point": point, "frames": frames, "reason": reason,
            "final_evaluation": evaluate(point, objective, a, c)}


def main():
    canonical = run()
    assert canonical["reason"] == "gradient tolerance" and len(canonical["frames"]) == 11
    assert canonical["point"] == (float(F(177147, 2097152)), 0)
    assert 2*F(3, 4)**10 > F(1, 10) and 2*F(3, 4)**11 < F(1, 10)
    for k, frame in enumerate(canonical["frames"]):
        current = (F(2), F(1)) if k == 0 else (2*F(3, 4)**k, F(0))
        next_point = (2*F(3, 4)**(k+1), F(0))
        assert frame["point"] == tuple(map(float, current))
        assert frame["next"] == tuple(map(float, next_point))
        assert frame["loss"] == float(current[0]**2/2+2*current[1]**2)
        assert frame["next_loss"] == float(next_point[0]**2/2)
        assert frame["raw_update"] == frame["applied_update"]
        assert frame["raw_proposal"] == frame["next"]
    early = run(start=(0.05, 0))
    assert early["reason"] == "gradient tolerance" and not early["frames"]
    assert early["point"] == (0.05, 0)
    inclusive = run(start=(1.5, 0), gradient_tol=1.5)
    assert inclusive["reason"] == "gradient tolerance" and not inclusive["frames"]
    small_loss = run(gradient_tol=0, loss_tol=0.5)
    assert small_loss["reason"] == "loss change tolerance" and len(small_loss["frames"]) == 2
    assert small_loss["point"] == (1.125, 0)
    assert small_loss["frames"][1]["next_loss"] == float(F(81, 128))
    assert abs(small_loss["frames"][1]["next_loss"]-small_loss["frames"][1]["loss"]) == float(F(63, 128))
    budget = run(iterations=2, gradient_tol=0)
    assert budget["reason"] == "iteration budget" and budget["point"] == (1.125, 0)
    assert len(budget["frames"]) == 2
    assert run(iterations=2, gradient_tol=0, loss_tol=0.5)["reason"] == "loss change tolerance"

    clipped = run(gradient_tol=0, clip=1)
    assert clipped["reason"] == "iteration budget" and len(clipped["frames"]) == 12
    first = clipped["frames"][0]
    assert first["raw_gradient"] == (2, 4) and first["gradient_norm"] == hypot(2, 4)
    assert first["raw_update"] == (-0.5, -1) and first["raw_proposal"] == (1.5, 0)
    with localcontext() as context:
        context.prec = 80
        scale = 1/D(20).sqrt()
        for actual, expected in zip(first["applied_gradient"], (2*scale, 4*scale)):
            assert abs(actual-float(expected)) < 1e-15
        for actual, expected in zip(first["next"], (2-D("0.5")*scale, 1-scale)):
            assert abs(actual-float(expected)) < 1e-15
    assert abs(hypot(*first["applied_update"])-0.25) < 1e-15
    assert all(frame["gradient_norm"] > 0 and hypot(*frame["applied_gradient"]) <= 1+1e-15 for frame in clipped["frames"])
    assert run(gradient_tol=1.1, clip=1)["frames"]  # The capped norm must not trigger a raw-gradient stop.
    hand = run(start=(3, 1), gradient_tol=0, clip=2, iterations=1)["frames"][0]
    assert hand["gradient_norm"] == 5 and hand["scale"] == 0.4
    assert abs(hand["next"][0]-2.7) < 1e-15 and abs(hand["next"][1]-0.6) < 1e-15

    divergent = run(eta=2, gradient_tol=0)
    assert divergent["reason"] == "divergence bound" and len(divergent["frames"]) == 8
    assert divergent["point"] == (2, 5764801)
    precedence = run(eta=2, gradient_tol=0, loss_tol=1e100, norm_bound=1)
    assert precedence["reason"] == "divergence bound" and len(precedence["frames"]) == 1
    saddle = run(start=(0, 0), objective="saddle", gradient_tol=0)
    assert saddle["reason"] == "zero gradient" and not saddle["frames"]
    assert evaluate((0, 1), "saddle", 1, 0)[0] == -1
    assert run(start=(0, 0))["reason"] == "gradient tolerance"
    nonfinite = run(start=(3, 1), objective="flat", eta=2, gradient_tol=0, norm_bound=1e300)
    assert nonfinite["reason"] == "non-finite next evaluation" and len(nonfinite["frames"]) == 5
    assert nonfinite["frames"][0]["next"] == (-213, -7)
    assert nonfinite["frames"][1]["next"] == (77308563, 2737)
    failed = nonfinite["frames"][-1]
    assert all(isfinite(value) for value in failed["next"])
    assert hypot(*failed["next"]) < 1e300 and failed["next_loss"] is None
    assert nonfinite["point"] == failed["point"]  # Preserve the last valid point, alongside the failed candidate.
    assert run(eta=1e308, gradient_tol=0)["reason"] == "non-finite transition"
    invalid_current = run(start=(1e100, 0), objective="flat", gradient_tol=1e308)
    assert invalid_current["reason"] == "non-finite current evaluation" and not invalid_current["frames"]

    for settings in ({"gradient_tol": -1}, {"clip": -1}, {"loss_tol": -1}, {"norm_bound": 0},
                     {"iterations": 41}, {"eta": 0}, {"start": (float("nan"), 0)}, {"objective": "unknown"}):
        try:
            run(**settings)
        except ValueError:
            pass
        else:
            raise AssertionError("Invalid input accepted")
    print("Canonical gradient stop: 11 updates; small-loss stop: 2; budget stop: 2.")
    print("Clipping: raw norm sqrt(20), scale 1/sqrt(20), applied displacement norm 1/4.")
    print("Divergence: 8 finite updates; quartic: 5 attempts, last finite candidate has non-finite next evaluation.")
    print("Saddle: explicit zero-gradient stop after 0 updates; no optimum claim. Ordered precedence checks passed.")


if __name__ == "__main__":
    main()
code/python/optimization_diagnostics_numpy.py
"""Actual NumPy plain descent and ordered stops, cross-checked with 80-digit scalar arithmetic."""
from decimal import Decimal as D, localcontext
from fractions import Fraction as F
import numpy as np


def evaluate(point, objective, a, c):
    x, y = point
    if objective == "quadratic":
        loss, gradient = a*(x-c)*(x-c)/2+2*y*y, np.array([a*(x-c), 4*y])
    elif objective == "flat":
        loss, gradient = np.sum(point*point*point*point), 4*point*point*point
    elif objective == "saddle":
        loss, gradient = x*x-y*y, np.array([2*x, -2*y])
    else:
        raise ValueError("Objective must be quadratic, flat, or saddle")
    return (float(loss), gradient) if np.all(np.isfinite([loss, *gradient])) else None


def run(start=(2, 1), eta=0.25, iterations=12, a=1.0, c=0.0, objective="quadratic",
        gradient_tol=0.1, loss_tol=0.0, clip=0.0, norm_bound=1e6):
    requested = point = np.asarray(start, dtype=np.float64).copy()
    if point.shape != (2,) or not np.all(np.isfinite(point)):
        raise ValueError("Require a finite two-coordinate start")
    if type(iterations) is not int or not 1 <= iterations <= 40:
        raise ValueError("Require an integer budget in [1,40]")
    eta, a, c, gradient_tol, loss_tol, clip, norm_bound = map(
        float, (eta, a, c, gradient_tol, loss_tol, clip, norm_bound))
    if not np.all(np.isfinite([eta, a, c, gradient_tol, loss_tol, clip, norm_bound])):
        raise ValueError("Require finite parameters")
    if eta <= 0 or not 0.25 <= a <= 4 or not -2 <= c <= 2 or min(gradient_tol, loss_tol, clip) < 0 or norm_bound <= 0:
        raise ValueError("Require eta,bound>0, a in [0.25,4], c in [-2,2], and nonnegative thresholds")
    if objective not in ("quadratic", "flat", "saddle"):
        raise ValueError("Objective must be quadratic, flat, or saddle")
    frames, reason = [], "iteration budget"
    with np.errstate(over="ignore", invalid="ignore"):
        for k in range(iterations):
            current = evaluate(point, objective, a, c)
            if current is None:
                reason = "non-finite current evaluation"
                break
            loss, raw = current
            norm = float(np.hypot(*raw))
            if not np.isfinite(norm):
                reason = "non-finite current evaluation"
                break
            if gradient_tol > 0 and norm <= gradient_tol:
                reason = "gradient tolerance"
                break
            if norm == 0:
                reason = "zero gradient"
                break
            scale = min(1.0, clip/norm) if clip > 0 else 1.0
            applied = scale*raw
            raw_update = -eta*raw
            raw_proposal = point+raw_update
            update = -eta*applied
            next_point = point+update
            frame = {"k": k, "point": point, "loss": loss, "raw_gradient": raw,
                     "gradient_norm": norm, "scale": scale, "applied_gradient": applied,
                     "raw_update": raw_update, "raw_proposal": raw_proposal, "applied_update": update,
                     "next": next_point, "next_loss": None, "next_gradient": None}
            frames.append(frame)
            if not np.all(np.isfinite([scale, *applied, *update, *next_point])):
                reason = "non-finite transition"
                break
            next_evaluation = evaluate(next_point, objective, a, c)
            if next_evaluation is None:
                reason = "non-finite next evaluation"
                break
            next_loss, next_gradient = next_evaluation
            frame["next_loss"], frame["next_gradient"] = next_loss, next_gradient
            point = next_point
            if np.hypot(*point) > norm_bound:
                reason = "divergence bound"
                break
            if loss_tol > 0 and abs(next_loss-loss) <= loss_tol:
                reason = "loss change tolerance"
                break
        final = evaluate(point, objective, a, c)
    return {"start": requested, "point": point, "frames": frames, "reason": reason, "final_evaluation": final}


def main():
    def check(actual, expected):
        np.testing.assert_allclose(actual, np.asarray(expected, dtype=np.float64), rtol=1e-12, atol=1e-12)

    cases = [
        ({}, "gradient tolerance", 11),
        ({"gradient_tol": 0, "loss_tol": 0.5}, "loss change tolerance", 2),
        ({"gradient_tol": 0, "iterations": 2}, "iteration budget", 2),
        ({"gradient_tol": 0, "clip": 1}, "iteration budget", 12),
        ({"gradient_tol": 0, "eta": 2}, "divergence bound", 8),
        ({"start": (3, 1), "objective": "flat", "eta": 2, "gradient_tol": 0, "norm_bound": 1e300},
         "non-finite next evaluation", 5),
        ({"start": (0, 0), "objective": "saddle", "gradient_tol": 0}, "zero gradient", 0),
        ({"a": 0.25, "c": -2, "eta": 0.1, "gradient_tol": 0}, "iteration budget", 12),
    ]
    with localcontext() as context:
        context.prec = 80
        for settings, reason, count in cases:
            result = run(**settings)
            assert result["reason"] == reason and len(result["frames"]) == count
            x, y = (D(str(value)) for value in settings.get("start", (2, 1)))
            a, c, eta, clip = (D(str(settings.get(key, default))) for key, default in
                               (("a", 1), ("c", 0), ("eta", 0.25), ("clip", 0)))
            objective = settings.get("objective", "quadratic")

            def scalar_evaluate(px, py):
                if objective == "flat":
                    return px**4+py**4, (4*px**3, 4*py**3)
                if objective == "saddle":
                    return px**2-py**2, (2*px, -2*py)
                return a*px*px/2-a*c*px+a*c*c/2+2*py*py, (a*px-a*c, 4*py)

            for frame in result["frames"]:
                loss, (gx, gy) = scalar_evaluate(x, y)
                norm = (gx*gx+gy*gy).sqrt()
                scale = min(D(1), clip/norm) if clip > 0 else D(1)
                ux, uy = -eta*scale*gx, -eta*scale*gy
                nx, ny = x+ux, y+uy
                check(frame["point"], (x, y))
                check(frame["loss"], loss)
                check(frame["raw_gradient"], (gx, gy))
                check(frame["gradient_norm"], norm)
                check(frame["scale"], scale)
                check(frame["applied_gradient"], (scale*gx, scale*gy))
                check(frame["raw_update"], (-eta*gx, -eta*gy))
                check(frame["raw_proposal"], (x-eta*gx, y-eta*gy))
                check(frame["applied_update"], (ux, uy))
                check(frame["next"], (nx, ny))
                next_loss, next_gradient = scalar_evaluate(nx, ny)
                if frame["next_loss"] is not None:
                    check(frame["next_loss"], next_loss)
                    check(frame["next_gradient"], next_gradient)
                else:
                    assert not np.isfinite(float(next_loss))
                x, y = nx, ny

    canonical = run()
    np.testing.assert_array_equal(canonical["point"], [float(F(177147, 2097152)), 0])
    for k, frame in enumerate(canonical["frames"], 1):
        np.testing.assert_array_equal(frame["next"], [float(2*F(3, 4)**k), 0])
    loss_stop = run(gradient_tol=0, loss_tol=0.5)
    assert loss_stop["frames"][1]["next_loss"] == float(F(81, 128))
    assert abs(loss_stop["frames"][1]["loss"]-loss_stop["frames"][1]["next_loss"]) == float(F(63, 128))
    assert run(gradient_tol=0, loss_tol=0.5, iterations=2)["reason"] == "loss change tolerance"
    assert run(gradient_tol=0, eta=2, loss_tol=1e100, norm_bound=1)["reason"] == "divergence bound"
    first = run(gradient_tol=0, clip=1)["frames"][0]
    np.testing.assert_array_equal(first["raw_gradient"], [2, 4])
    np.testing.assert_array_equal(first["raw_update"], [-0.5, -1])
    np.testing.assert_array_equal(first["raw_proposal"], [1.5, 0])
    check(first["scale"], 1/np.sqrt(20.0))
    check(np.hypot(*first["applied_update"]), 0.25)
    assert run(gradient_tol=1.1, clip=1)["frames"]
    hand = run(start=(3, 1), clip=2, gradient_tol=0, iterations=1)["frames"][0]
    assert hand["gradient_norm"] == 5 and hand["scale"] == 0.4
    check(hand["applied_gradient"], [1.2, 1.6])
    check(hand["next"], [2.7, 0.6])
    assert not run(start=(0.05, 0))["frames"]
    assert not run(start=(1.5, 0), gradient_tol=1.5)["frames"]
    assert run(start=(0, 0))["reason"] == "gradient tolerance"
    assert run(eta=1e308, gradient_tol=0)["reason"] == "non-finite transition"
    assert run(start=(1e100, 0), objective="flat", gradient_tol=1e308)["reason"] == "non-finite current evaluation"
    failure = run(start=(3, 1), objective="flat", eta=2, gradient_tol=0, norm_bound=1e300)
    np.testing.assert_array_equal(failure["frames"][0]["next"], [-213, -7])
    np.testing.assert_array_equal(failure["frames"][1]["next"], [77308563, 2737])
    last = failure["frames"][-1]
    assert np.all(np.isfinite(last["next"])) and last["next_loss"] is None
    assert np.hypot(*last["next"]) < 1e300
    np.testing.assert_array_equal(failure["point"], last["point"])
    for settings in ({"clip": -1}, {"gradient_tol": -1}, {"loss_tol": -1}, {"norm_bound": 0},
                     {"iterations": 41}, {"eta": 0}, {"start": (np.nan, 0)}, {"objective": "unknown"}):
        try:
            run(**settings)
        except ValueError:
            pass
        else:
            raise AssertionError("Invalid input accepted")
    print("NumPy stop counts: gradient 11, loss 2, budget 2, clipped budget 12, divergence 8, non-finite 5, saddle 0.")
    print("All valid recorded vectors, objective values, norms, and clipping scales match independent 80-digit scalars.")
    print("Relative/absolute tolerance 1e-12; ordered reasons and counts checked exactly.")


if __name__ == "__main__":
    main()

Explore: change a rule and explain the resulting stop

Compare reset, small-loss, and budget presets. Predict the stopping state and reason before reading the final row. The last two share a point, yet only one tested a small loss difference. Disable both thresholds and observe that finite checks, divergence, and the explicit zero-gradient reason remain active.

Choose clipping and compare both arrows at the same raw gradient. Lower C, keeping the start and rate fixed, and predict the new displacement norm. Ask whether the raw norm changed; clipping occurs after the raw evaluation and must not rewrite it.

Choose divergence, then the quartic non-finite preset. Read the displayed norm bound each time: 10610^6 and 1030010^{300} serve different diagnostic demonstrations. Leaving the plotting window is not itself a stopping policy. The table retains large finite states and the failed attempt’s reason.

For a local stationary counterexample, choose the saddle L=x2−y2L=x^2-y^2 at (0,0)(0,0) with gradient tolerance disabled. The explicit zero-gradient stop is correct. Along (0,t)(0,t) the loss is −t2<0-t^2<0, so declaring a minimum would be incorrect.

Common mistakes and counterexamples

Do not confuse the current gradient norm with the norm of an applied clipped gradient. If a raw norm is 100 and clipping sets it to 1 for the update, a raw-gradient tolerance of 2 has not been met.

A tiny loss change can reflect an excessively small step or a rounded-away update, rather than a small gradient. A small gradient can occur at a saddle or maximum. In this chapter’s saddle, the Hessian has one positive and one negative direction, so local stationarity supplies no minimum certificate.

Domain validity comes first. For −log⁡x+y2-\log x+y^2, a point with x≤0x\leq0 is outside the real objective’s domain. For ∣x∣+y2\abs x+y^2 at x=0x=0, the classical gradient is undefined. A tolerance cannot make either required evaluation valid, and silently substituting a subgradient would introduce a different algorithm.

Numerical considerations

Finite coordinates do not guarantee a finite loss or gradient. Quartic powers can overflow while the proposed point is still representable. Check the next evaluation before subtracting losses; subtracting infinities can produce a NaN that makes a threshold comparison silently false.

Use a scaled norm calculation such as hypot rather than first squaring very large components. Retain a finite-value guard even when clipping is enabled. Clipping is defined from a valid raw gradient; it cannot recover a direction already lost to overflow or an undefined derivative.

Near a tolerance boundary, binary64 rounding can decide which side of the comparison a value occupies. The policy uses the evaluated unrounded values and inclusive thresholds. The independent reference checks a bounded collection of examples; its numerical tolerance is a verification allowance, not an extra stopping threshold.

Engineering and ML connections

A useful training record includes the objective definition, parameter scale, rate, raw and applied gradients, clipping settings, tolerances, budget, and final reason. Without those, identical-looking endpoints can conceal different stopping events or interventions.

Learning rates affect both progress and measured loss change. Constraints and penalties change feasibility or the objective; clipping instead changes an update. The explicit zero-gradient rule here belongs to plain descent: momentum or Adam can retain nonzero optimizer history even when the current raw gradient vanishes.

Exercises

State whether a test runs before or after an update. Preserve raw values when clipping is enabled, and separate a computational reason from a mathematical optimum claim.

prediction

Apply the raw-gradient test before moving

On L=x²/2+2y², start at (1/20,0), set raw-gradient tolerance to 1/10, η=1/4, K=12, and disable loss-change stopping and clipping. Give the raw gradient norm, number of parameter updates, final x,y, and final loss.

calculation

Measure loss change after a finite update

On L=x²/2+2y² from (2,1), use η=1/4, K=12, disable gradient stopping and clipping, and set absolute loss-change tolerance to 1/2. Give the number of completed updates, final x,y, final loss, and last absolute loss change.

calculation

Clip the update without replacing the raw gradient

For L=x²/2+2y² at (3,1), the raw gradient is (3,4). Set clipping threshold C=2 and η=1/4, with gradient stopping disabled. Give the raw norm, clipping scale, both applied-gradient components, and next x,y.

construction

Treat the budget as a limit on work

From (2,1) on L=x²/2+2y², use η=1/4 and K=2, with gradient tolerance, loss-change tolerance, and clipping all zero. Give completed updates, final x,y, and final loss. The reason is iteration budget.

explanation

Interpret an explicit zero-gradient stop

For the saddle L=x²−y² at (0,0), use plain gradient descent with both tolerance thresholds and clipping disabled. What should the run report?

Choose one answer

Summary and checkpoints

Ordered diagnostics explain why plain descent stopped and what evidence remains valid. Check that you can distinguish a pre-update raw-gradient stop from a post-update loss-change stop, reproduce the clipped displacement without erasing the raw gradient, and explain why the two-step budget endpoint proves no optimum. Finite guards, divergence bounds, tolerances, and budgets each answer a specific numerical question.

The map of the book

97 of 97 chapters written · eight Parts

demo Foundation demo Try the live vector example Open as a page

Part I

Mathematical Language

The notation and mental objects everything else uses.

9 of 9 chapters written

  1. I.1 Scalars, quantities, units, powers and roots available
  2. I.2 Coordinates and points available
  3. I.3 Vectors: components, magnitude, and direction reference chapter available
  4. I.4 Algebraic expressions, equations and inequalities available
  5. I.5 Functions as input → transformation → output available
  6. I.6 Essential function families available
  7. I.7 Sets, intervals, and relations available
  8. I.8 Indexed quantities and summation available
  9. I.9 The notation map for ML available

Part II

Linear Algebra

Matrices as transformations: the strongest foundation in the book.

19 of 19 chapters written

  1. II.1 Vector addition and subtraction available
  2. II.2 Scalar multiplication and vector scaling available
  3. II.3 Norms, magnitude, and normalization available
  4. II.4 Dot product, angle, and orthogonality available
  5. II.5 Projection and distance to a direction available
  6. II.6 Linear combinations and span available
  7. II.7 Linear independence, basis, and coordinates available
  8. II.8 Matrices as linear transformations available
  9. II.9 Matrix × vector available
  10. II.10 Matrix × matrix as composition available
  11. II.11 Transpose and orthogonality available
  12. II.12 Determinant: orientation and area/volume scaling available
  13. II.13 Linear systems as intersecting constraints available
  14. II.14 Gaussian elimination and row operations available
  15. II.15 Rank and invertibility available
  16. II.16 Inverse matrices available
  17. II.17 Eigenvalues and eigenvectors available
  18. II.18 Orthogonal bases and least-squares geometry available
  19. II.19 Singular Value Decomposition available

Part III

Calculus

Change, local approximation, and sensitivity made visible.

15 of 15 chapters written

  1. III.1 Limits through approach available
  2. III.2 Continuity and local behavior available
  3. III.3 Derivative as secant → tangent available
  4. III.4 Derivative as rate of change and local linear model available
  5. III.5 Derivative rules available
  6. III.6 Chain rule available
  7. III.7 Multivariable functions and partial derivatives available
  8. III.8 Gradient and directional derivatives available
  9. III.9 Jacobian available
  10. III.10 Computational graphs and automatic differentiation available
  11. III.11 Hessian and curvature available
  12. III.12 Integrals as accumulation available
  13. III.13 Fundamental derivative/integral connection available
  14. III.14 Numerical differentiation available
  15. III.15 Numerical integration available

Part IV

Probability

Uncertainty through sample spaces, evidence, and repetition.

16 of 16 chapters written

  1. IV.1 Random experiments, outcomes, and sample spaces available
  2. IV.2 Events and probability mass available
  3. IV.3 Counting and finite probability where needed available
  4. IV.4 Conditional probability as restricted sample space available
  5. IV.5 Independence available
  6. IV.6 Bayes' theorem as probability-mass update available
  7. IV.7 Random variables available
  8. IV.8 PMF, PDF, and CDF available
  9. IV.9 Bernoulli and Binomial distributions available
  10. IV.10 Uniform and Gaussian distributions available
  11. IV.11 Expectation available
  12. IV.12 Variance and standard deviation available
  13. IV.13 Joint, marginal, and conditional distributions available
  14. IV.14 Covariance and correlation available
  15. IV.15 Law of Large Numbers available
  16. IV.16 Central Limit Theorem available

Part V

Statistics

From observed data to estimates, intervals, and likelihood.

10 of 10 chapters written

  1. V.1 Data summaries: mean, median, mode available
  2. V.2 Spread: variance, standard deviation, robust interpretation available
  3. V.3 Population, sample, and sampling variability available
  4. V.4 Estimators and estimation available
  5. V.5 Confidence intervals through repeated sampling available
  6. V.6 Covariance matrices and multivariate spread available
  7. V.7 Regression fundamentals available
  8. V.8 Likelihood available
  9. V.9 Maximum Likelihood Estimation available
  10. V.10 Maximum A Posteriori estimation available

Part VI

Optimization

Learning as repeated parameter updates governed by an objective.

10 of 10 chapters written

  1. VI.1 Objective functions and optimization landscapes available
  2. VI.2 Local vs global optima and stationary points available
  3. VI.3 Convexity available
  4. VI.4 Gradient descent flagship chapter available
  5. VI.5 Learning rate and convergence behavior available
  6. VI.6 Momentum available
  7. VI.7 Stochastic and mini-batch gradient descent available
  8. VI.8 Adam intuition available
  9. VI.9 Constraints and projected/penalized intuition available
  10. VI.10 Numerical stopping and optimization diagnostics available

Part VII

Information Theory

Logarithms, entropy, and the loss behind token prediction.

8 of 8 chapters written

  1. VII.1 Logarithms and log probabilities in computation available
  2. VII.2 Self-information available
  3. VII.3 Entropy available
  4. VII.4 Cross entropy and negative log-likelihood available
  5. VII.5 KL divergence available
  6. VII.6 Logits and softmax available
  7. VII.7 Numerical stability: log-sum-exp and stable softmax available
  8. VII.8 From token logits to language-model loss available

Part VIII

Numerical Computing

Why equivalent expressions behave differently on real machines.

10 of 10 chapters written

  1. VIII.1 Floating-point representation available
  2. VIII.2 Rounding and representation error available
  3. VIII.3 Error propagation and conditioning available
  4. VIII.4 Catastrophic cancellation available
  5. VIII.5 Overflow and underflow available
  6. VIII.6 Stable algorithms available
  7. VIII.7 Numerical linear algebra realities available
  8. VIII.8 Sparse matrices and sparse structure available
  9. VIII.9 Vectorized computation available
  10. VIII.10 Reproducibility, random seeds, and nondeterminism available