VI.5 · Optimization
When does a larger step help, oscillate, or run away?
Follow the same quadratic from the same starting point at several learning rates. Curvature turns the shared rate into a separate error multiplier for each coordinate, making convergence and failure predictable.
Inspect stability and the actual learning-rate history
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: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path. Quadratic stability: monotone contraction; eigenvalues (1, 4), mode factors ≈ (0.75, 0). All starts converge exactly in real arithmetic only for 0 < η < 0.5. An initially zero unstable component is an exceptional run.
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
- Quadratic error multipliers
- (0.75, 0)
- All-mode stability bound
- 0.500000
≈ 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: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path. Quadratic stability: monotone contraction; eigenvalues (1, 4), mode factors ≈ (0.75, 0). All starts converge exactly in real arithmetic only for 0 < η < 0.5. An initially zero unstable component is an exceptional run.
| k | θ(k) | L(k) | gradient | η | update | θ(k+1) | L(k+1) |
|---|---|---|---|---|---|---|---|
| 0 | (2, 1) | 4 | (2, 4) | 0.25 | (−0.5, −1) | (1.5, 0) | 1.125 |
| 1 | (1.5, 0) | 1.125 | (1.5, 0) | 0.25 | (−0.375, 0) | (1.125, 0) | 0.632813 |
| 2 | (1.125, 0) | 0.632813 | (1.125, 0) | 0.25 | (−0.28125, 0) | (0.84375, 0) | 0.355957 |
| 3 | (0.84375, 0) | 0.355957 | (0.84375, 0) | 0.25 | (−0.210938, 0) | (0.632813, 0) | 0.200226 |
| 4 | (0.632813, 0) | 0.200226 | (0.632813, 0) | 0.25 | (−0.158203, 0) | (0.474609, 0) | 0.112627 |
| 5 | (0.474609, 0) | 0.112627 | (0.474609, 0) | 0.25 | (−0.118652, 0) | (0.355957, 0) | 0.063353 |
| 6 | (0.355957, 0) | 0.063353 | (0.355957, 0) | 0.25 | (−0.088989, 0) | (0.266968, 0) | 0.035636 |
| 7 | (0.266968, 0) | 0.035636 | (0.266968, 0) | 0.25 | (−0.066742, 0) | (0.200226, 0) | 0.020045 |
| 8 | (0.200226, 0) | 0.020045 | (0.200226, 0) | 0.25 | (−0.050056, 0) | (0.150169, 0) | 0.011275 |
| 9 | (0.150169, 0) | 0.011275 | (0.150169, 0) | 0.25 | (−0.037542, 0) | (0.112627, 0) | 0.006342 |
| 10 | (0.112627, 0) | 0.006342 | (0.112627, 0) | 0.25 | (−0.028157, 0) | (0.08447, 0) | 0.003568 |
| 11 | (0.08447, 0) | 0.003568 | (0.08447, 0) | 0.25 | (−0.021118, 0) | (0.063353, 0) | 0.002007 |
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.
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
At the default , predict what happens separately to x and y. Now choose the no-decay preset . The first loss still drops, but does the entire parameter vector converge to the minimum? Read the y values over several iterations before answering.
Intuition: one distance scale, two sensitivities
A narrow valley has one direction in which small movements change the gradient sharply and another in which they change it slowly. A shared learning rate must handle both. Too small a rate makes progress slow in the gentle direction; too large a rate overshoots in the steep one.
Overshooting does not automatically mean failure. A coordinate can cross its target on every step and still get closer. The useful distinction is whether its error magnitude shrinks, remains constant, or grows. A path’s zigzag shape alone cannot decide that distinction, and a short initial loss decrease cannot prove long-run convergence.
Definition: error contraction and all-start stability
For with , the unique minimizer is . Define error coordinates and . Gradient descent uses with .
A scalar error mode with curvature obeys
Here the Hessian eigenvalues are and , so the two multipliers are and . In exact real arithmetic, convergence to the minimizer from every starting point requires
This is an all-start guarantee for this positive-curvature quadratic. An unstable or nondecaying mode whose initial error is exactly zero remains absent. Such a particular run can converge outside the all-start interval. Neither the formula nor a successful run supplies a universal rate bound for other objectives.
Mechanics: change the rate, keep the comparison controlled
Reset restores the default quadratic, , start , and rate . The selected iteration exposes its current point, loss, gradient, rate, displacement, and next point. The error-factor rows connect that evaluated step with the recurrence’s longer-term prediction.
Compare the presets at . Their x factors are , while their y factors are . The first pair is slow, the middle negative factor produces decaying oscillation, and the final two y factors separate nondecay from growth.
The history stores evaluated binary64 states. Replay selects a previous state rather than undoing arithmetic. When the path leaves the plotting window, use the retained table and stated stopping reason; leaving the visible rectangle does not change the mathematical recurrence.
Derivation: the curvature condition is a geometric-series condition
The gradient is , so direct substitution gives
Unrolling each recurrence yields
For a nonzero initial component, a power tends to zero exactly when . Solve : subtract 1 and divide by positive to obtain . Both modes must satisfy this, so the larger curvature sets the shared upper boundary.
| Mode factor r | Nonzero error behavior |
|---|---|
| Same-side contraction | |
| That component reaches its target in one step | |
| Alternating, shrinking error | |
| Alternating without decay | |
| Growing error magnitude |
The loss follows squared factors:
Within the strict all-start interval, each active contribution decreases. Negative factors can therefore produce decreasing loss even while coordinates alternate. At a boundary, an active contribution can remain fixed while another decreases.
Worked examples: a drop, a boundary, and an exception
With , the factors are . The canonical first step is
The second point is with loss . For every , the y component is zero and . This is the default worked replay.
Worked example: Inspect stability and the actual learning-rate history
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: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path. Quadratic stability: monotone contraction; eigenvalues (1, 4), mode factors ≈ (0.75, 0). All starts converge exactly in real arithmetic only for 0 < η < 0.5. An initially zero unstable component is an exceptional run.
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
- Quadratic error multipliers
- (0.75, 0)
- All-mode stability bound
- 0.500000
≈ 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: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path. Quadratic stability: monotone contraction; eigenvalues (1, 4), mode factors ≈ (0.75, 0). All starts converge exactly in real arithmetic only for 0 < η < 0.5. An initially zero unstable component is an exceptional run.
| k | θ(k) | L(k) | gradient | η | update | θ(k+1) | L(k+1) |
|---|---|---|---|---|---|---|---|
| 0 | (2, 1) | 4 | (2, 4) | 0.25 | (−0.5, −1) | (1.5, 0) | 1.125 |
| 1 | (1.5, 0) | 1.125 | (1.5, 0) | 0.25 | (−0.375, 0) | (1.125, 0) | 0.632813 |
| 2 | (1.125, 0) | 0.632813 | (1.125, 0) | 0.25 | (−0.28125, 0) | (0.84375, 0) | 0.355957 |
| 3 | (0.84375, 0) | 0.355957 | (0.84375, 0) | 0.25 | (−0.210938, 0) | (0.632813, 0) | 0.200226 |
| 4 | (0.632813, 0) | 0.200226 | (0.632813, 0) | 0.25 | (−0.158203, 0) | (0.474609, 0) | 0.112627 |
| 5 | (0.474609, 0) | 0.112627 | (0.474609, 0) | 0.25 | (−0.118652, 0) | (0.355957, 0) | 0.063353 |
| 6 | (0.355957, 0) | 0.063353 | (0.355957, 0) | 0.25 | (−0.088989, 0) | (0.266968, 0) | 0.035636 |
| 7 | (0.266968, 0) | 0.035636 | (0.266968, 0) | 0.25 | (−0.066742, 0) | (0.200226, 0) | 0.020045 |
| 8 | (0.200226, 0) | 0.020045 | (0.200226, 0) | 0.25 | (−0.050056, 0) | (0.150169, 0) | 0.011275 |
| 9 | (0.150169, 0) | 0.011275 | (0.150169, 0) | 0.25 | (−0.037542, 0) | (0.112627, 0) | 0.006342 |
| 10 | (0.112627, 0) | 0.006342 | (0.112627, 0) | 0.25 | (−0.028157, 0) | (0.08447, 0) | 0.003568 |
| 11 | (0.08447, 0) | 0.003568 | (0.08447, 0) | 0.25 | (−0.021118, 0) | (0.063353, 0) | 0.002007 |
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.
At , the factors become . The first two next points and losses are
Loss approaches while y alternates between and . The vector does not converge to ; decreasing loss has concealed a nondecaying mode.
At , a start has , so its y error grows. A start instead has and , so it converges. This zero-mode exception does not refute the all-start boundary .
For a balanced comparison inside the interval, gives factors . Its first point is and loss . Both squared contributions multiply by thereafter. This first loss is higher than the canonical first loss, yet its worst error factor is smaller: fastest initial improvement and fastest eventual contraction need not select the same rate.
Executable reference code
The standard-library program uses exact Fraction arithmetic for the gradient steps, then independently checks every stored point against the closed-form powers. Literal assertions cover slow contraction, canonical steps, the boundary, damped oscillation, a growing mode, and a zero-mode exception. It also supports positive curvature beyond the bounded explorer, including the hand-worked exercise.
The NumPy program advances multiple rates simultaneously with matrix operations, then compares the complete bounded path with a separately broadcasted power grid. Its documented tolerances apply to these binary64 calculations; the analytic recurrence establishes the convergence condition. No finite iteration budget proves an asymptotic theorem by itself.
"""Exact quadratic error factors and rate-dependent trajectories; no third-party imports."""
from fractions import Fraction as F
def factors(eta, a=1):
eta, a = F(eta), F(a)
if eta <= 0 or a <= 0:
raise ValueError("Require positive curvature a and learning rate eta")
return (1-eta*a, 1-4*eta)
def trajectory(start=(2, 1), eta=F(1, 4), iterations=8, a=1, c=0):
if type(iterations) is not int or not 0 <= iterations <= 1000:
raise ValueError("Require an integer iteration count in [0,1000]")
x, y = map(F, start)
eta, a, c = F(eta), F(a), F(c)
fx, fy = factors(eta, a)
path = [(x, y)]
for _ in range(iterations):
gradient = (a*(x-c), 4*y)
x, y = x-eta*gradient[0], y-eta*gradient[1]
path.append((x, y))
for k, point in enumerate(path):
assert point == (c+(F(start[0])-c)*fx**k, F(start[1])*fy**k)
losses = [a*(x-c)**2/2+2*y*y for x, y in path]
return path, losses
def main():
assert factors(F(1, 10)) == (F(9, 10), F(3, 5))
path, losses = trajectory(eta=F(1, 10), iterations=1)
assert path[1] == (F(9, 5), F(3, 5)) and losses[1] == F(117, 50)
assert trajectory(eta=F(1, 4), iterations=1)[1] == [4, F(9, 8)]
boundary, boundary_losses = trajectory(eta=F(1, 2))
assert boundary[1:3] == [(F(1), F(-1)), (F(1, 2), F(1))]
assert boundary_losses[:3] == [4, F(5, 2), F(17, 8)]
assert all(loss > 2 for loss in boundary_losses)
zero_mode, zero_mode_losses = trajectory((2, 0), F(3, 4))
assert zero_mode[1] == (F(1, 2), F(0))
assert all(b < a for a, b in zip(zero_mode_losses, zero_mode_losses[1:]))
divergent, _ = trajectory((2, 1), F(3, 4), 4)
assert divergent[-1][1] == 16
alternating, alternating_losses = trajectory(eta=F(3, 8))
assert alternating[1] == (F(5, 4), F(-1, 2))
assert all(b < a for a, b in zip(alternating_losses, alternating_losses[1:]))
balanced, balanced_losses = trajectory(eta=F(2, 5), iterations=2)
assert balanced[1] == (F(6, 5), F(-3, 5))
assert balanced_losses == [4, F(36, 25), F(324, 625)]
assert factors(F(1, 8), a=8) == (0, F(1, 2))
assert trajectory((2, 1), F(1, 8), 1, a=8, c=1)[0][1] == (1, F(1, 2))
assert F(2, max(8, 4)) == F(1, 4)
for invalid in (0, -1):
try:
factors(invalid)
except ValueError:
pass
else:
raise AssertionError("Nonpositive learning rate accepted")
print("Exact default factors at eta=1/4:", factors(F(1, 4)))
print("Exact eta=1/2 boundary path:", boundary[:4], "; y does not decay")
print("All-start stability: 0 < eta < 2/max(a,4), for a>0.")
print("A zero starting component can hide an unstable mode; finite runs are not proofs.")
if __name__ == "__main__":
main()"""Vectorized binary64 GD paths for many rates, checked by a closed-form power grid."""
import numpy as np
def trajectories(start, rates, iterations=12, a=1.0, c=0.0):
start, rates = np.asarray(start, dtype=np.float64), np.asarray(rates, dtype=np.float64)
if start.shape != (2,) or rates.ndim != 1 or not rates.size:
raise ValueError("Require one two-component start and a nonempty rate vector")
if not np.all(np.isfinite(start)) or not np.all(np.isfinite(rates)) or np.any(rates <= 0):
raise ValueError("Require finite start and positive finite rates")
if not np.isfinite(a) or a <= 0 or not np.isfinite(c):
raise ValueError("Require positive finite a and finite c")
if type(iterations) is not int or not 0 <= iterations <= 1000:
raise ValueError("Require an integer iteration count in [0,1000]")
center, hessian = np.array([c, 0.0]), np.diag([a, 4.0])
path = np.empty((iterations+1, rates.size, 2), dtype=np.float64)
path[0] = start
for k in range(iterations):
path[k+1] = path[k] - rates[:, None]*((path[k]-center) @ hessian)
if not np.all(np.isfinite(path[k+1])):
raise OverflowError(f"Non-finite binary64 iterate at step {k+1}")
shifted = path-center
losses = np.einsum("kni,ij,knj->kn", shifted, hessian, shifted)/2
if not np.all(np.isfinite(losses)):
raise OverflowError("Non-finite binary64 loss")
return path, losses
def main():
rates = np.array([0.03, 0.25, 0.4, 0.5, 0.75])
path, losses = trajectories([2, 1], rates)
factors = 1-rates[:, None]*np.array([1.0, 4.0])
powers = np.arange(13)[:, None, None]
direct = np.array([2.0, 1.0])*factors[None, :, :]**powers
np.testing.assert_allclose(path, direct, rtol=2e-14, atol=1e-14)
np.testing.assert_array_equal(path[1, 1], [1.5, 0])
np.testing.assert_array_equal(path[:3, 3, 1], [1, -1, 1])
assert np.all(np.diff(losses[:, :3], axis=0) < 0)
assert path[-1, 4, 1] == 4096
exception, _ = trajectories([2, 0], [0.75], 8)
np.testing.assert_array_equal(exception[:, 0, 1], np.zeros(9))
assert exception[-1, 0, 0] == 2*(0.25**8)
np.testing.assert_array_equal(np.linalg.eigvalsh(np.diag([1.0, 4.0])), [1, 4])
for invalid in ([0], [-1], [np.nan]):
try:
trajectories([2, 1], invalid)
except ValueError:
pass
else:
raise AssertionError("Invalid rate accepted")
print("Binary64 final losses by rate ≈", losses[-1])
print("The eta=0.5 y mode alternates without decay; eta=0.75 grows unless y starts at zero.")
print("Closed-form cross-check: rtol=2e-14, atol=1e-14 on this bounded grid.")
if __name__ == "__main__":
main()Explore: predict an active mode before running
Compare the slow preset with the canonical run. Identify the error factor closest to magnitude 1, then predict which coordinate will dominate after many steps. On the default quadratic the x curvature is smaller, so a small rate can erase y error sooner while leaving x progress slow.
Choose damped oscillation and count sign changes in y while comparing its magnitude. Then choose no decay and repeat the comparison. Finally choose the divergent preset with y set to zero, predict the exceptional behavior, and introduce a small nonzero y start to excite the unstable mode.
Move the center c while leaving a and the rate fixed. The x error remains ; the multiplier does not change. As a hand extension, use , rate , and start . The x error disappears immediately and y halves, producing . The all-start upper boundary is now , set by the x curvature rather than y.
Common mistakes and counterexamples
The rate is not the error multiplier. Different curvatures convert the same rate into different factors. Also apply the x factor to , not to x, when the center is shifted.
Oscillation does not imply divergence: a factor of contracts, while does not decay and grows. A short decreasing loss trace does not imply parameter convergence, as the example demonstrates. A zero starting mode can also make an unstable rate appear safe until that mode is perturbed.
Numerical considerations
The convergence derivation concerns exact real arithmetic. A binary64 trajectory can stagnate when a displacement rounds away, underflow to zero, overflow, or stop at a finite iteration budget. These are computational events, distinct from the behavior of as .
Near a boundary, a small rate or curvature perturbation can move a factor from slightly inside to slightly outside magnitude 1. Compare the unrounded state and a stated numerical error scale rather than a rounded displayed factor. An exactly absent mode is also a special case; small representation or input errors in that coordinate can matter greatly when its factor has magnitude above 1.
The references label numerical trajectories binary64 and reject non-finite states. Their finite tolerance checks verify implementation on a bounded set of runs; the tolerance is not part of the mathematical definition of stability.
Engineering and ML connections
The Hessian describes directional curvature, and gradient descent turns that curvature into repeated errors. For a positive-definite quadratic, a large ratio of largest to smallest curvature can force a shared stable rate that contracts the gentle direction slowly. Parameter scaling can therefore change how efficiently the same optimizer moves.
A neural-network objective usually lacks one constant Hessian valid across the full path, so this quadratic interval must not be presented as its global safety certificate. It is a local mechanism to inspect: changes in curvature, gradient scale, and parameter units can change a rate’s behavior. The next chapter adds momentum, whose extra memory changes the recurrence and requires its own analysis.
Exercises
Calculate factors before predicting a path. State whether a claim concerns every start, an active mode, a finite numerical run, or an exact limit.
Summary and checkpoints
A quadratic gradient step multiplies each error mode by . Check that you can derive the all-start interval, distinguish shrinking oscillation from nondecay, recover the boundary’s limiting loss , and explain why an absent unstable mode is an exception. A rate controls a complete trajectory, and its first few favorable updates do not establish the limiting outcome.