Why Momentum / Nesterov Matters
Plain gradient descent uses the current slope and forgets how it arrived. On a long, narrow loss valley, this can mean alternating across the steep direction while making slow progress along the shallow one. Momentum keeps a velocity vector so directions that persist over several updates can accumulate. Nesterov momentum measures the next gradient at a predicted position obtained from that velocity.
These methods are useful in smooth numerical optimization and in training models with full or mini-batch gradients. They add one persistent vector to the optimizer state. That small change makes the optimization a dynamical system: the same parameter vector can move differently depending on its velocity.
You will implement heavy-ball momentum, abbreviated HB, and a constant-coefficient look-ahead Nesterov form, abbreviated NAG. Both programs use an analytic diagonal quadratic, a fixed starting point, and zero initial velocity. This exposes every operation without an automatic-differentiation library or randomness. The short comparison illustrates the mechanics; it is not a claim that either method wins on every objective or every iteration. The displacement-velocity conventions follow the equations in Sutskever et al., Section 2.
Momentum carries a decayed displacement from the preceding step and corrects it using a gradient. Heavy ball evaluates that gradient at the current parameters; the Nesterov form here evaluates it at the predicted parameters. Track both parameters and velocity, and distinguish a correct recurrence from a guarantee of monotone loss or convergence.
Core Concepts and Terminology
We minimize a differentiable scalar function $f:\mathbb R^d\to\mathbb R$. Let $\mathbf x_t$ be the parameters after $t$ updates, $\mathbf v_t$ the stored displacement velocity, $\eta\gt0$ the learning rate, and $0\leq\beta\lt1$ the momentum coefficient. Initialize $\mathbf v_0=\mathbf0$. First form the retained motion $\mathbf m_t=\beta\mathbf v_t$.
Heavy ball evaluates the gradient at the old position:
$$\mathbf y_t=\mathbf x_t,\qquad \mathbf g_t=\nabla f(\mathbf y_t),$$ $$\mathbf v_{t+1}=\mathbf m_t-\eta\mathbf g_t,\qquad \mathbf x_{t+1}=\mathbf x_t+\mathbf v_{t+1}.$$
Nesterov changes the probe point, using the old velocity:
$$\mathbf y_t=\mathbf x_t+\mathbf m_t,\qquad \mathbf g_t=\nabla f(\mathbf y_t),$$ $$\mathbf v_{t+1}=\mathbf m_t-\eta\mathbf g_t,\qquad \mathbf x_{t+1}=\mathbf x_t+\mathbf v_{t+1}.$$
For Nesterov, $\mathbf x_{t+1}=\mathbf y_t-\eta\nabla f(\mathbf y_t)$ in real arithmetic. This alternative is a complete update, not a second update to perform after adding the new velocity. The programs retain the explicitly separated velocity form to make state and rounding order reproducible.
Velocity already includes learning-rate scaling: it has the same units as the parameters. Another common notation stores a positive gradient accumulator $\mathbf b_{t+1}=\beta\mathbf b_t+\mathbf g_t$ and writes $\mathbf x_{t+1}=\mathbf x_t-\eta\mathbf b_{t+1}$. For heavy ball with constant $\eta$, initialization zero, and no dampening, this corresponds to $\mathbf v_t=-\eta\mathbf b_t$. Changing learning rates or initialization rules changes that correspondence. Here there is no factor $1-\beta$ multiplying the gradient. A normalized exponential moving average is a different convention and requires a corresponding learning-rate interpretation.
The teaching objective is
$$f(\mathbf x)=\frac12\sum_{j=1}^d a_j(x_j-c_j)^2,\qquad [\nabla f(\mathbf x)]_j=a_j(x_j-c_j),\qquad a_j\geq0.$$
The curvatures are the diagonal entries of its Hessian. Write $L=\max_j a_j$. If all $a_j\gt0$, the unique minimizer is $\mathbf c$, the strong-convexity parameter is $\mu=\min_j a_j$, and the condition number is $\kappa=L/\mu$. A zero curvature makes that coordinate flat: its value need not reach $c_j$ to minimize $f$. Starting with zero velocity keeps that coordinate fixed.
The solver returns the final parameters, velocity, objective, counts, and snapshots of all states. It performs exactly $K$ updates and labels no state “converged.” For $L\gt0$, it conservatively requires $0\lt\eta\leq1/L$; if $L=0$, it skips this reciprocal test. This is a teaching guard for this quadratic family, rather than the largest possible stable step for every momentum method.
| Term | Example in the scalar run | Role |
|---|---|---|
| Parameters $\mathbf x_t$ | $x_1=2$ | Current optimization variables |
| Displacement velocity $\mathbf v_t$ | $v_1=-2$ | Stored update, including learning-rate scaling |
| Learning rate $\eta$ | $0.5$ | Scales the new gradient correction |
| Momentum $\beta$ | $0.5$ | Retains half of the old velocity |
| Carry $\mathbf m_t$ | $m_1=-1$ | Reused motion, computed before any mutation |
| Probe $\mathbf y_t$ | HB: $2$; NAG: $1$ at step $2$ | Location where the gradient is evaluated |
| Gradient $\mathbf g_t$ | HB: $2$; NAG: $1$ at step $2$ | Slope at the probe, not necessarily at the new point |
| Objective monitor | NAG: $f(x_2)=0.125$ | Reports the loss at committed parameters |
| Fixed point | $(x,v)=(0,0)$ for $f(x)=x^2/2$ | Both position and optimizer state remain unchanged |
| Flat coordinate | $a_j=0$ with $v_{0,j}=0$ | Remains at its initial parameter value |
How Momentum / Nesterov Works, Step by Step
Inputs are the initial point, center vector, nonnegative curvature vector, learning rate $\eta$, coefficient $\beta$, update count $K$, and mode HB or NAG. Use the following order for every step:
- Validate before computing. Check dimensions, finite numeric values, parameter bounds, integer update count, mode, and the reciprocal learning-rate guard. Copy the starting vector and initialize a zero velocity. Invalid inputs raise an exception; the caller receives no partial result.
- Record step zero. Evaluate $f(\mathbf x_0)$ once. Its probe and gradient fields are empty because no update gradient has yet been requested. The empty fields print
NONE, not a fabricated zero gradient. - Check the update cap. If $t=K$, return the final state and counters. A zero-step run therefore evaluates one starting loss and no gradients.
- Freeze the old state and compute the carry. Set $\mathbf m_t=\beta\mathbf v_t$. Do not overwrite the old velocity before choosing the probe.
- Choose exactly one probe. Heavy ball uses $\mathbf x_t$; Nesterov uses $\mathbf x_t+\mathbf m_t$. Obtain one full gradient there. Look-ahead does not inherently require a second gradient evaluation.
- Construct a whole new state. For each coordinate, compute the correction $\eta g_{t,j}$, subtract it from the carry to obtain $v_{t+1,j}$, and add that new velocity to the old $x_{t,j}$. Do not mutate parameters while evaluating a gradient that may couple multiple coordinates.
- Commit and monitor. Replace parameters and velocity together. Evaluate $f(\mathbf x_{t+1})$, store copies of the probe, gradient, and committed state, then repeat. The diagnostic objective does not accept, reject, or change an update.
A history row with iteration=k contains $\mathbf x_k$, $\mathbf v_k$, and $f(\mathbf x_k)$, but its nonempty probe and gradient describe the update into that row, namely $\mathbf y_{k-1}$ and $\mathbf g_{k-1}$. The gradient field is not a residual at $\mathbf x_k$.
There is intentionally no early exit for a zero probe gradient, one unchanged point, or increasing loss. For a production convergence test, evaluate the actual residual at the current parameters and consider both the residual and velocity, with appropriate absolute and relative tolerances. On a general nonconvex objective, even a small residual and velocity certify only approximate stationarity, not a global minimum.
flowchart TD
A["Validate mode, shapes, ranges, and step size"] --> B["Copy x; set v to zero; evaluate loss and record start"]
B --> C{"Completed K updates?"}
C -->|Yes| Z["Return final x, v, loss, counts, and history"]
C -->|No| D["Freeze old x and v; carry = beta times old v"]
D --> E{"NAG mode?"}
E -->|Yes| F["Probe = old x plus carry"]
E -->|No| G["Probe = old x"]
F --> H["Evaluate one full gradient at probe"]
G --> H
H --> I["New v = carry minus rate times gradient"]
I --> J["New x = old x plus new v; commit both"]
J --> L["Evaluate new loss; copy history row; count update"]
L --> C
Worked Example with a Full Trace
Use the one-dimensional function $f(x)=x^2/2$, gradient $f'(x)=x$, and inputs $x_0=4$, $v_0=0$, $\eta=\beta=1/2$, $K=4$. There is no random seed because the function, gradients, and updates are deterministic. Initially $f(4)=8$. All intermediate state numbers below are dyadic rationals and exactly representable in binary64 arithmetic.
Heavy ball. Here the probe is always the old $x$.
- From $(x_0,v_0)=(4,0)$: carry $m_0=0$, probe $y_0=4$, gradient $g_0=4$, correction $\eta g_0=2$. Thus $v_1=0-2=-2$, $x_1=4-2=2$, and $f(x_1)=2$.
- From $(2,-2)$: $m_1=-1$, $y_1=2$, $g_1=2$, and $\eta g_1=1$. Thus $v_2=-1-1=-2$, $x_2=2-2=0$, and $f(x_2)=0$.
- From $(0,-2)$: $m_2=-1$, $y_2=0$, $g_2=0$, and $\eta g_2=0$. Thus $v_3=-1$, $x_3=-1$, and $f(x_3)=1/2$. The stored velocity carries the method away from the exact minimizer.
- From $(-1,-1)$: $m_3=-1/2$, $y_3=-1$, $g_3=-1$, and $\eta g_3=-1/2$. Thus $v_4=-1/2-(-1/2)=0$, $x_4=-1$, and $f(x_4)=1/2$. An unchanged point is not a fixed state: the next gradient is still $-1$.
Nesterov. Now the probe includes the old carry.
- From $(4,0)$: $m_0=0$, $y_0=4+0=4$, $g_0=4$, and $\eta g_0=2$. Thus $v_1=-2$, $x_1=2$, and $f(x_1)=2$, matching the first heavy-ball update.
- From $(2,-2)$: $m_1=-1$, $y_1=2-1=1$, $g_1=1$, and $\eta g_1=1/2$. Thus $v_2=-3/2$, $x_2=1/2$, and $f(x_2)=1/8$.
- From $(1/2,-3/2)$: $m_2=-3/4$, $y_2=-1/4$, $g_2=-1/4$, and $\eta g_2=-1/8$. Thus $v_3=-3/4+1/8=-5/8$, $x_3=1/2-5/8=-1/8$, and $f(x_3)=1/128=0.0078125$.
- From $(-1/8,-5/8)$: $m_3=-5/16$, $y_3=-7/16$, $g_3=-7/16$, and $\eta g_3=-7/32$. Thus $v_4=-5/16+7/32=-3/32$, $x_4=-7/32=-0.21875$, and $f(x_4)=49/2048=0.02392578125$.
Each run requests four gradients and five objective values, including the initial loss. Nesterov has the smaller final loss for these inputs, while heavy ball reaches zero earlier and then leaves it. Nesterov's own loss increases on step $4$; neither sequence is monotone. The solver returns its last state, not the best state seen so far.
Trace It by Hand
Before continuing beyond the four-step limit, predict step $5$ for each method. Keep the coefficient and learning rate at $1/2$.
For heavy ball, start at $(x_4,v_4)=(-1,0)$. Carry is $0$, probe and gradient are $-1$, and the correction is $-1/2$. Therefore $v_5=1/2$, $x_5=-1/2$, and $f(x_5)=1/8$. The unchanged position on step $4$ did not justify stopping.
For Nesterov, start at $(-7/32,-3/32)$. Carry is $-3/64$, probe and gradient are $-17/64$, and the correction is $-17/128$. Therefore $v_5=-3/64+17/128=11/128$, $x_5=-17/128$, and $f(x_5)=289/32768=0.008819580078125$.
As a second check, set $\beta=0$ and recompute the first two steps. Both methods become ordinary gradient descent: $4\to2\to1$, with losses $8\to2\to1/2$.
Implementation in Python, C++, and Java
Each panel is a complete program with the same sample data and operation order. The callable momentum routine accepts vectors initial, center, weights, numbers rate, beta, an integer steps, and string mode. It supports $1\leq d\leq32$, $0\leq K\leq2000$, finite coordinates and centers with magnitude at most $10^6$, and finite curvatures in $[0,10^6]$. It requires $0\lt\eta\leq10^6$, $0\leq\beta\lt1$, and, when $L\gt0$, $\eta\leq1/L$ as checked in binary64. Python rejects booleans as numeric controls and requires an actual integer update count. The other languages express those types in the function signature.
The function always performs the requested number of updates. Zero curvature, a zero update count, a zero gradient, and a numerical no-op are supported cases. There is no callback objective, mini-batch sampling, learning-rate schedule, restart, line search, or hidden convergence flag. For another objective, replace the scalar value and gradient routines and reconsider the validation and stability assumptions.
Run Python 3 with python main.py. Compile C++17 with g++ -std=c++17 -ffp-contract=off main.cpp -o momentum and run ./momentum; avoid fast-math so operations retain the stated order. Save Java as Main.java, use Java 17 or later, and run javac Main.java then java Main. Java's decimal helper only formats the exact stored binary64 value to eight places with half-even rounding; optimization uses ordinary double arithmetic.
Output prints the objective at every recorded parameter vector. probe and g refer to the incoming update, so NONE on step zero is expected. The returned counters count full vector-gradient evaluations and scalar objective evaluations, not individual coordinates. All snapshots own their parameter and velocity arrays.
Expected standard output, identical in all three languages:
HBk=0 x=(4.00000000) v=(0.00000000) probe=NONE g=NONE f=8.00000000k=1 x=(2.00000000) v=(-2.00000000) probe=(4.00000000) g=(4.00000000) f=2.00000000k=2 x=(0.00000000) v=(-2.00000000) probe=(2.00000000) g=(2.00000000) f=0.00000000k=3 x=(-1.00000000) v=(-1.00000000) probe=(0.00000000) g=(0.00000000) f=0.50000000k=4 x=(-1.00000000) v=(0.00000000) probe=(-1.00000000) g=(-1.00000000) f=0.50000000steps=4 gradients=4 losses=5NAGk=0 x=(4.00000000) v=(0.00000000) probe=NONE g=NONE f=8.00000000k=1 x=(2.00000000) v=(-2.00000000) probe=(4.00000000) g=(4.00000000) f=2.00000000k=2 x=(0.50000000) v=(-1.50000000) probe=(1.00000000) g=(1.00000000) f=0.12500000k=3 x=(-0.12500000) v=(-0.62500000) probe=(-0.25000000) g=(-0.25000000) f=0.00781250k=4 x=(-0.21875000) v=(-0.09375000) probe=(-0.43750000) g=(-0.43750000) f=0.02392578steps=4 gradients=4 losses=5
Complexity, Correctness, and Invariants
Let $K$ be the requested number of updates and $d$ the dimension. Validation and initialization take $O(d)$. Every update evaluates one gradient, performs $O(d)$ vector arithmetic, evaluates one scalar objective for monitoring, and copies $O(d)$ state into history. For this diagonal quadratic both function routines cost $O(d)$, so best, average, and worst time are all $\Theta((K+1)d)$ for fixed valid $K,d$: no path exits early. The program makes exactly $K$ gradient calls and $K+1$ objective calls, including when the starting point is optimal.
Persistent optimizer state and temporary working vectors use $O(d)$ space. Retaining all snapshots raises the actual returned-storage requirement to $\Theta((K+1)d)$. Streaming or disabling the history gives $O(d)$ working space, but printed output and serialization still cost time. For a general objective with costs $C_g(d)$ and $C_f(d)$, total time is $O(d+C_f(d)+K(C_g(d)+C_f(d)+d))$. The constant full-loss monitoring policy here may be prohibitively expensive for a large training dataset.
State invariant and induction. Before update $t$, the stored pair is exactly $(\mathbf x_t,\mathbf v_t)$ from the chosen recurrence. Initially $\mathbf v_0=\mathbf0$, and the initial history row stores copies of the inputs and their objective. Assuming the invariant, the code forms $\beta\mathbf v_t$, selects the prescribed probe from the old state, and evaluates its gradient before mutation. Computing the new velocity and adding it once to the old parameters produces precisely the next recurrence state. Committing together and copying snapshots preserves the invariant. The counts follow because each loop performs one gradient call and one loss call. In floating-point arithmetic this means the specified, rounded sequence of operations; it does not imply exact real-arithmetic iterates on arbitrary inputs.
Why these guarded quadratics converge in exact arithmetic. Analyze one positive-curvature coordinate $a\gt0$, with error $e_t=x_t-c$. Set the fictitious $e_{-1}=e_0$ so $v_0=e_0-e_{-1}=0$. Eliminating velocity gives
$$e_{t+1}=(1+\beta-\eta a)e_t-\beta e_{t-1}\quad\text{(HB)},$$ $$e_{t+1}=q(1+\beta)e_t-q\beta e_{t-1},\qquad q=1-\eta a\quad\text{(NAG)}.$$
For a real characteristic polynomial $r^2-Ar+B$, both roots lie strictly inside the unit circle when $\lvert B\rvert\lt1$, $1-A+B\gt0$, and $1+A+B\gt0$. For heavy ball, $A=1+\beta-\eta a$ and $B=\beta$. Under $0\lt\eta a\leq1$ and $0\leq\beta\lt1$, these tests become $\beta\lt1$, $\eta a\gt0$, and $2+2\beta-\eta a\gt0$. For Nesterov, $A=q(1+\beta)$ and $B=q\beta$, with $0\leq q\lt1$; the tests become $q\beta\lt1$, $1-q\gt0$, and $1+q(1+2\beta)\gt0$. Thus every positive-curvature error tends to zero. A zero-curvature coordinate has gradient zero and, from zero initial velocity, remains fixed; it already contributes zero to the objective.
This establishes convergence of the infinite exact-arithmetic recurrence for this objective family under the stated mathematical guard. A finite run or rounded recurrence can stall or finish away from the minimum. Loss need not decrease at every iteration; the history above supplies counterexamples. Quadratic stability and implementation correctness do not establish a global convergence theorem for arbitrary nonlinear losses.
Acceleration needs assumptions and tuning. For smooth $\mu$-strongly convex deterministic objectives, a standard Nesterov choice is $\eta=1/L$ and $\beta=(\sqrt L-\sqrt\mu)/(\sqrt L+\sqrt\mu)$. Appropriate accelerated methods improve the condition-number dependence of iteration bounds from order $\kappa$ to order $\sqrt\kappa$, up to logarithmic accuracy factors. General smooth convex $O(1/t^2)$ guarantees use a suitable Nesterov parameter schedule; they do not follow from an arbitrary fixed $\beta$. Heavy ball's favorable quadratic tuning does not supply the same general strongly convex guarantee. Roger Grosse's momentum lecture discusses these distinctions. The fixed four-step demonstration is a recurrence trace, not a measurement of asymptotic rates.
| Method / reporting policy | Per-update time | Persistent state, excluding history | Main assumption or distinction |
|---|---|---|---|
| Full gradient descent | $O(C_g+d)$ | $O(d)$ parameters | No velocity; monotone descent needs a suitable smoothness bound and step |
| Heavy-ball momentum | $O(C_g+d)$ | $O(d)$ parameters plus $O(d)$ velocity | Gradient at current point; arbitrary nonlinear losses need further analysis |
| Look-ahead Nesterov | $O(C_g+d)$ | $O(d)$ parameters plus $O(d)$ velocity | One gradient at the extrapolated point; accelerated guarantees need appropriate tuning |
| Mini-batch momentum | $O(C_{g,b}+d)$ | $O(d)$ parameters plus $O(d)$ velocity | Noisy gradients; deterministic acceleration bounds do not automatically apply |
| Adam-style adaptive optimizer | $O(C_g+d)$ | $O(d)$ parameters and two $O(d)$ moment vectors | Adds coordinatewise second-moment scaling; a different algorithm |
| These programs, with full history | $\Theta(d)$ gradient, value, arithmetic, and copying | $\Theta((K+1)d)$ including stored snapshots | Exactly $K$ updates and $K+1$ objective evaluations; no early exit |
The invariant is the full parameter–velocity pair: form a carry from the old velocity, evaluate one gradient at the mode's prescribed probe, then commit the new velocity and parameters together. The induction proves the implementation follows its recurrence; the positive-curvature characteristic roots separately prove exact-arithmetic convergence for the guarded quadratic family. Neither result says every recorded loss decreases or the finite returned state is optimal.
Variants, Pitfalls, and Optimizations
Do not count carry twice. With displacement velocity, compute the new point as old point plus new velocity. Equivalently for Nesterov in exact arithmetic, use probe minus the gradient correction. Adding the new velocity to the probe is wrong because that velocity already contains the carry. The two correct algebraic forms can also round differently, so choose one operation order for cross-language reproduction.
Parameter stationarity and optimizer state are different. Heavy ball reaches the minimizer at step $2$ while its velocity is $-2$. It then moves away. On step $4$, its point is unchanged but its gradient is nonzero; step $5$ moves again. A proper fixed point of the full deterministic map must have $\mathbf v=\mathbf0$ and $\nabla f(\mathbf x)=\mathbf0$ for $\eta\gt0$. A small gradient at a look-ahead probe is not necessarily a small gradient at the current parameters. Finite precision adds underflow, cancellation, and numerical no-ops; the code counts those steps rather than declaring success.
Framework labels do not uniquely specify the state convention. PyTorch's SGD documentation describes a gradient-buffer implementation, its first-step buffer initialization, dampening, and a Nesterov update expressed at the stored parameters. Its stored parameter sequence should not be compared blindly with the explicit unshifted look-ahead sequence here. Relate the buffer and parameter variables first, and match learning-rate schedule, initialization, weight decay, and dampening. Restore optimizer buffers as well as parameters when resuming training.
Varying momentum and learning rates. A scheduled Nesterov method chooses coefficients to match a convergence theorem; a constant coefficient is a separate variant. Scheduling a learning rate in a gradient-buffer formulation changes how old gradients are scaled relative to storing historical displacements. Restarting can zero the velocity when a chosen diagnostic triggers, but it changes the recurrence and needs a stated rule and associated cost. Returning a best-so-far point can protect a reporting metric while requiring separate storage; it must not silently overwrite the actual optimizer state.
Stochastic gradients and regularization. Use one mini-batch at the chosen probe and average its gradients consistently. Noise can also accumulate through momentum, so a larger coefficient need not improve training. To evaluate an explicit look-ahead gradient with automatic differentiation, compute it at the temporary parameter values and restore or commit parameters carefully. Differentiating through the optimizer is another task. Coupled $L_2$ regularization adds a parameter-dependent term to the gradient; decoupled weight decay performs a distinct parameter update and is not interchangeable under momentum.
Conditioning and limits. Standardizing features or preconditioning a quadratic can reduce the curvature disparity before tuning momentum. A scalar preconditioned curvature analysis is then performed on the transformed system. The programs deliberately use the conservative $1/L$ guard; heavy ball has a larger stable interval on an individual quadratic mode, so a rejected rate is not a universal proof of divergence. Reusing a learning rate from plain gradient descent without checking the full momentum recurrence is also unsafe. Boundaries, nonsmooth terms, and constraints require suitable projection or proximal methods and their own analysis.
Use the old velocity to locate the Nesterov probe, then add the new velocity to the old parameters exactly once. Do not stop on one zero probe gradient or one unchanged point, and do not treat increasing loss as automatic implementation failure. Keep velocity units, learning-rate scaling, and checkpoint buffers consistent when translating formulas or comparing frameworks.
Practice Problems and Real-World Use
Use the sample trace as a small regression check before experimenting with a larger problem. Compare methods under the same objective, starting point, gradient-work budget, and reporting policy. Measure full-loss monitoring separately from training gradients when the dataset is large. For a convergence comparison, specify whether you report the final loss, the minimum observed loss, a current-point gradient norm, or elapsed time.
The following exercises build from arithmetic to optimizer engineering. The objectives and checks are explicit so that a passing trace tests a mathematical property, not just matching a particular implementation.
| Problem or system | Concrete task / check | Idea tested | Difficulty |
|---|---|---|---|
| Hand trace continuation | Derive both step-$5$ states; compare with the experiment card | Carry, probe, and velocity indexing | Beginner |
| Gradient-descent reduction | Set $\beta=0$ and verify $4,2,1,1/2$ for $f(x)=x^2/2$, $\eta=1/2$ | Both branches reduce to the same update | Beginner |
| Flat-coordinate quadratic | Start $(7,3)$ with center $(0,1)$ and curvatures $(0,2)$; verify the first coordinate stays $7$ | Nonunique minimizers and zero-velocity initialization | Intermediate |
| Rotated narrow valley | Use $f(\mathbf x)=\tfrac12\mathbf x^{\mathsf T}Q\mathbf x$ with $Q=\begin{pmatrix}5&4\\4&5\end{pmatrix}$; eigenvalues are $1,9$ | Coupled gradients, conditioning, simultaneous updates | Intermediate |
| Stability laboratory | Derive both characteristic polynomials and plot root magnitudes as $\eta$ varies | Stability versus loss monotonicity | Advanced |
| Mini-batch model training | Apply both probe rules to a squared-error model using the same batch stream; log training and monitoring costs | Noisy gradients and fair work accounting | Advanced |
| Optimizer checkpoint | Save parameters and velocity, resume, and match an uninterrupted trajectory | Persistent state and exact continuation | Intermediate |
| Framework convention audit | Match a library SGD buffer convention algebraically before comparing trajectories | Variable transformations, schedules, dampening | Advanced |