Mathematical Foundations

VI.7 · Optimization

Can a few examples give a useful but noisy gradient?

Retain the exact indices selected by each update, evaluate their residuals, and average their parameter gradients. Compare that sampled direction with the full objective while replaying the same seeded path.

Trace the selected examples and their averaged gradient

Parameter space: θ₁ and θ₂

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Stop: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path.

Loss slice: θ₂ = 0

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

Current parameter vector
(0, 0)
Current objective value
1.250000
Selected batch mean gradient
(−0.5, 0)
Learning rate
0.100000
Update vector
(0.05, 0)
Next parameter vector
(0.05, 0)
New objective value
1.164375
Selected zero-based indices (with repeats)
1, 0
Full-data gradient for comparison
(−1.75, −1)
Selected batch mean loss
0.500000
Explicit RNG state
42 → 378494188

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

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Stop: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path.

L=14∑i=1412(θ1xi+θ2−yi)2L=\frac14\sum_{i=1}^4\frac12(\theta_1 x_i+\theta_2-y_i)^2
gBk=1∣Bk∣∑i∈Bk∇ℓi(θk),θk+1=θk−ηgBkg_{B_k}=\frac1{|B_k|}\sum_{i\in B_k}\nabla\ell_i(\theta_k),\quad\theta_{k+1}=\theta_k-\eta g_{B_k}
θ1≈[00]+[0.050]≈[0.050]\theta_{1}\approx \begin{bmatrix}0\\0\end{bmatrix}+\begin{bmatrix}0.05\\0\end{bmatrix}\approx \begin{bmatrix}0.05\\0\end{bmatrix}
Evaluated binary64 history. Replay changes the selected row, never the stored mathematics.
kθ(k)L(k)gradientηupdateθ(k+1)L(k+1)indicesRNG beforeRNG after
0(0, 0)1.25(−0.5, 0)0.1(0.05, 0)(0.05, 0)1.1643751, 042378494188
1(0.05, 0)1.164375(−1.45, −0.5)0.1(0.145, 0.05)(0.195, 0.05)0.8933942, 0378494188955863294
2(0.195, 0.05)0.893394(−0.4275, −0.0475)0.1(0.04275, 0.00475)(0.23775, 0.05475)0.8295881, 0955863294110225632
3(0.23775, 0.05475)0.829588(−0.4085, −0.064125)0.1(0.04085, 0.006413)(0.2786, 0.061163)0.7698911, 0110225632508781842
4(0.2786, 0.061163)0.769891(−2.763275, −1.381638)0.1(0.276328, 0.138164)(0.554928, 0.199326)0.385683, 35087818424271921684
5(0.554928, 0.199326)0.38568(−0.690819, −0.745746)0.1(0.069082, 0.074575)(0.624009, 0.273901)0.2990933, 142719216842146095206
6(0.624009, 0.273901)0.299093(−1.029125, −0.790085)0.1(0.102913, 0.079009)(0.726922, 0.352909)0.211832, 321460952063699926152
7(0.726922, 0.352909)0.21183(−0.773078, −0.147091)0.1(0.077308, 0.014709)(0.80423, 0.367618)0.1754652, 03699926152389768954
8(0.80423, 0.367618)0.175465(−0.305617, 0.269733)0.1(0.030562, −0.026973)(0.834791, 0.340645)0.1713310, 33897689544080598076
9(0.834791, 0.340645)0.171331(0.020456, 0.010228)0.1(−0.002046, −0.001023)(0.832746, 0.339622)0.1722533, 340805980763820240078
10(0.832746, 0.339622)0.172253(−0.667254, −0.160378)0.1(0.066725, 0.016038)(0.899471, 0.35566)0.1502522, 03820240078775312688
11(0.899471, 0.35566)0.150252(−0.372434, −0.694604)0.1(0.037243, 0.06946)(0.936715, 0.425121)0.1331772, 17753126881112253922
Each selected example at the current parameters; duplicates are retained. The update gradient is their arithmetic mean.
zero-based indexexample lossexample gradient
10.5(0, −1)
00.5(−1, 1)

Evaluated iteration 0

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples.

θ1≈[00]+[0.050]≈[0.050]\theta_{1}\approx \begin{bmatrix}0\\0\end{bmatrix}+\begin{bmatrix}0.05\\0\end{bmatrix}\approx \begin{bmatrix}0.05\\0\end{bmatrix}

from −3 to 3, up to 2 decimal places

from −3 to 3, 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 4294967295, whole numbers only

from 1 to 4, whole numbers only

Inspect each selected zero-based data index, its residual and parameter gradient, the batch average, and the resulting update. Compare the full-data gradient and loss using the same current parameters.

The canonical start is (w,b)=(0,0)(w,b)=(0,0) with rate 0.10.1, seed 4242, and two selected slots. Predict whether its first sampled gradient equals the full gradient (−7/4,−1)(-7/4,-1). The actual selected indices are [1,0][1,0], so this batch gives (−1/2,0)(-1/2,0) instead. Both quantities are valid; they answer different questions.

Intuition: estimate the direction using actual examples

A full-data gradient combines every example’s contribution before an update. A stochastic gradient uses one selected example, and a mini-batch gradient averages several selected slots. This changes the direction supplied to the optimizer while preserving the same full objective we ultimately care about.

Different examples can pull parameters in different directions. Their complete average can vanish even though individual gradients do not. Sampling therefore introduces variation into the path, including motion near a full-data minimum. A larger batch can reduce gradient-estimation variance under a stated sampling model; it does not make every observed update identical to the full gradient.

Definition: one loss per example, one mean objective

Use the four fixed data pairs with zero-based indices:

(xi,yi)∈{(−1,−1),(0,1),(1,2),(2,2)},i=0,1,2,3.(x_i,y_i)\in\{(-1,-1),(0,1),(1,2),(2,2)\},\qquad i=0,1,2,3.

The model is y^i=wxi+b\widehat y_i=wx_i+b, with parameter vector θ=(w,b)\theta=(w,b). Data coordinates xi,yix_i,y_i are fixed observations, not the coordinates being optimized. Define

ri=wxi+b−yi,ℓi(θ)=12ri2,L(θ)=14∑i=03ℓi(θ).r_i=wx_i+b-y_i,\qquad \ell_i(\theta)=\frac12r_i^2,\qquad L(\theta)=\frac14\sum_{i=0}^{3}\ell_i(\theta).

Differentiating each residual gives its parameter gradient

gi(θ)=∇ℓi(θ)=(rixi,ri),∇L(θ)=14∑i=03gi(θ).g_i(\theta)=\nabla\ell_i(\theta)=(r_ix_i,r_i),\qquad \nabla L(\theta)=\frac14\sum_{i=0}^{3}g_i(\theta).

A selected batch of B slots has indices I1,…,IBI_1,\ldots,I_B, retains any repeats, and uses

g^B(θ)=1B∑s=1BgIs(θ),θk+1=θk−ηg^B(θk).\widehat g_B(\theta)=\frac1B\sum_{s=1}^{B}g_{I_s}(\theta),\qquad \theta_{k+1}=\theta_k-\eta\widehat g_B(\theta_k).

The deterministic full batch is [0,1,2,3][0,1,2,3] exactly once each. Four random draws with replacement are not automatically that full batch.

Mechanics: one generator transition per selected slot

The default mini-batch has B=2B=2, start (0,0)(0,0), rate 1/101/10, and uint32 seed 4242. Each sampled slot advances the explicit generator and maps its new state to one of four indices:

snext=(1664525s+1013904223) mod 232,I=⌊4snext232⌋.s_{\mathrm{next}}=(1664525s+1013904223)\bmod2^{32},\qquad I=\left\lfloor\frac{4s_{\mathrm{next}}}{2^{32}}\right\rfloor.

A slot can select an index already present in the same batch. The next slot continues from the updated state; it does not restart from the seed. One sampled example consumes one transition, and a two-slot mini-batch consumes two. Full-batch mode consumes no random draws and preserves the generator state.

Inspect the selected-example table alongside the selected mean gradient, full gradient, full loss, batch loss, and generator states. The update uses the selected gradient, while the displayed objective history evaluates all four examples. Replaying a stored step reveals its actual batch; replay does not draw a replacement batch.

Derivation: unbiasedness is a conditional expectation claim

Suppose that, conditional on the current parameters θ\theta, fresh indices are independent and uniform on {0,1,2,3}\{0,1,2,3\}. Every slot then has

E[gIs(θ)∣θ]=14∑i=03gi(θ)=∇L(θ).\mathbb E[g_{I_s}(\theta)\mid\theta] =\frac14\sum_{i=0}^{3}g_i(\theta)=\nabla L(\theta).

Linearity gives E[g^B∣θ]=∇L(θ)\mathbb E[\widehat g_B\mid\theta]=\nabla L(\theta). Independent slots also give

Cov⁡(g^B∣θ)=1BCov⁡(gI(θ)∣θ).\operatorname{Cov}(\widehat g_B\mid\theta) =\frac1B\operatorname{Cov}(g_I(\theta)\mid\theta).

This identity concerns repeated ideal draws at the same current point. It does not require an observed batch to contain distinct examples. Sampling without replacement would need a different covariance calculation; the lesson deliberately uses replacement.

The fixed-seed LCG supplies reproducibility rather than literal iid randomness. Given its seed, the entire sequence is determined. A finite run cannot prove the ideal conditional independence assumptions or an optimizer convergence theorem. Keep the sampling theorem’s assumptions separate from the deterministic implementation used to illustrate it.

For this dataset the full gradient can also be expanded directly:

∇L(w,b)=(32w+12b−74,12w+b−1),H=[3/21/21/21].\nabla L(w,b)=\left(\frac32w+\frac12b-\frac74, \frac12w+b-1\right),\qquad H=\begin{bmatrix}3/2&1/2\\1/2&1\end{bmatrix}.

Solving ∇L=0\nabla L=0 gives (w,b)=(1,1/2)(w,b)=(1,1/2). The Hessian is positive definite because its leading diagonal entry is positive and determinant is 5/4>05/4>0, so this is the unique full-data global minimum.

Worked example: seed 42 selects a different direction

At (0,0)(0,0), compute every contribution before averaging:

IndexResidualHalf-squared lossGradient (w,b)
0111/21/2(−1,1)(-1,1)
1−1-11/21/2(0,−1)(0,-1)
2−2-222(−2,−2)(-2,-2)
3−2-222(−4,−2)(-4,-2)

Thus L=5/4L=5/4 and ∇L=(−7/4,−1)\nabla L=(-7/4,-1). Seed 4242 advances first to 10838142731083814273, selecting index 1, then to 378494188378494188, selecting index 0. The selected loss is 1/21/2 and the selected mean gradient is

g^2=12((0,−1)+(−1,1))=(−1/2,0).\widehat g_2=\frac12\bigl((0,-1)+(-1,1)\bigr)=(-1/2,0).

With η=1/10\eta=1/10 the update is (1/20,0)(1/20,0), giving next parameters (1/20,0)(1/20,0). Their four residuals are 19/20,−1,−39/20,−19/1019/20,-1,-39/20,-19/10, so the new full loss is

L(1/20,0)=18[(1920)2+1+(−3920)2+(−1910)2]=18631600.L(1/20,0)=\frac18\left[ \left(\frac{19}{20}\right)^2+1+ \left(-\frac{39}{20}\right)^2+ \left(-\frac{19}{10}\right)^2\right] =\frac{1863}{1600}.

Worked example: Trace the selected examples and their averaged gradient

Parameter space: θ₁ and θ₂

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Stop: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path.

Loss slice: θ₂ = 0

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

Current parameter vector
(0, 0)
Current objective value
1.250000
Selected batch mean gradient
(−0.5, 0)
Learning rate
0.100000
Update vector
(0.05, 0)
Next parameter vector
(0.05, 0)
New objective value
1.164375
Selected zero-based indices (with repeats)
1, 0
Full-data gradient for comparison
(−1.75, −1)
Selected batch mean loss
0.500000
Explicit RNG state
42 → 378494188

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

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples. Stop: iteration budget. Parameters outside the displayed window are retained in the table; the view never changes the path.

L=14∑i=1412(θ1xi+θ2−yi)2L=\frac14\sum_{i=1}^4\frac12(\theta_1 x_i+\theta_2-y_i)^2
gBk=1∣Bk∣∑i∈Bk∇ℓi(θk),θk+1=θk−ηgBkg_{B_k}=\frac1{|B_k|}\sum_{i\in B_k}\nabla\ell_i(\theta_k),\quad\theta_{k+1}=\theta_k-\eta g_{B_k}
θ1≈[00]+[0.050]≈[0.050]\theta_{1}\approx \begin{bmatrix}0\\0\end{bmatrix}+\begin{bmatrix}0.05\\0\end{bmatrix}\approx \begin{bmatrix}0.05\\0\end{bmatrix}
Evaluated binary64 history. Replay changes the selected row, never the stored mathematics.
kθ(k)L(k)gradientηupdateθ(k+1)L(k+1)indicesRNG beforeRNG after
0(0, 0)1.25(−0.5, 0)0.1(0.05, 0)(0.05, 0)1.1643751, 042378494188
1(0.05, 0)1.164375(−1.45, −0.5)0.1(0.145, 0.05)(0.195, 0.05)0.8933942, 0378494188955863294
2(0.195, 0.05)0.893394(−0.4275, −0.0475)0.1(0.04275, 0.00475)(0.23775, 0.05475)0.8295881, 0955863294110225632
3(0.23775, 0.05475)0.829588(−0.4085, −0.064125)0.1(0.04085, 0.006413)(0.2786, 0.061163)0.7698911, 0110225632508781842
4(0.2786, 0.061163)0.769891(−2.763275, −1.381638)0.1(0.276328, 0.138164)(0.554928, 0.199326)0.385683, 35087818424271921684
5(0.554928, 0.199326)0.38568(−0.690819, −0.745746)0.1(0.069082, 0.074575)(0.624009, 0.273901)0.2990933, 142719216842146095206
6(0.624009, 0.273901)0.299093(−1.029125, −0.790085)0.1(0.102913, 0.079009)(0.726922, 0.352909)0.211832, 321460952063699926152
7(0.726922, 0.352909)0.21183(−0.773078, −0.147091)0.1(0.077308, 0.014709)(0.80423, 0.367618)0.1754652, 03699926152389768954
8(0.80423, 0.367618)0.175465(−0.305617, 0.269733)0.1(0.030562, −0.026973)(0.834791, 0.340645)0.1713310, 33897689544080598076
9(0.834791, 0.340645)0.171331(0.020456, 0.010228)0.1(−0.002046, −0.001023)(0.832746, 0.339622)0.1722533, 340805980763820240078
10(0.832746, 0.339622)0.172253(−0.667254, −0.160378)0.1(0.066725, 0.016038)(0.899471, 0.35566)0.1502522, 03820240078775312688
11(0.899471, 0.35566)0.150252(−0.372434, −0.694604)0.1(0.037243, 0.06946)(0.936715, 0.425121)0.1331772, 17753126881112253922
Each selected example at the current parameters; duplicates are retained. The update gradient is their arithmetic mean.
zero-based indexexample lossexample gradient
10.5(0, −1)
00.5(−1, 1)

Evaluated iteration 0

Iteration 0: at θ ≈ (0, 0), L ≈ 1.25 and gradient ≈ (−0.5, 0). Rate η ≈ 0.1 gives update ≈ (0.05, 0). Adding that update gives θ next ≈ (0.05, 0) and L next ≈ 1.164375. Indices [1, 0] are averaged with repeats. RNG state 42 becomes 378494188. Full-data gradient is ≈ (−1.75, −1); displayed objective values use all four examples.

θ1≈[00]+[0.050]≈[0.050]\theta_{1}\approx \begin{bmatrix}0\\0\end{bmatrix}+\begin{bmatrix}0.05\\0\end{bmatrix}\approx \begin{bmatrix}0.05\\0\end{bmatrix}

Replay the canonical seed-42 mini-batch: indices [1,0], averaged gradient (−1/2,0), next parameters (1/20,0), and full loss 1863/1600.

Continue from state 378494188378494188. The second batch is [2,0][2,0], ending at state 955863294955863294. At the current point its mean gradient is (−29/20,−1/2)(-29/20,-1/2), giving next parameters (39/200,1/20)(39/200,1/20). Resetting the generator at each iteration would produce a different path.

A full-batch first step instead uses (−7/4,−1)(-7/4,-1), moves to (7/40,1/10)(7/40,1/10), and leaves seed state 4242 unchanged. The optimizer rate is the same, but the supplied gradient and generator transition differ.

Executable reference code

The standard-library program evaluates fixed-data residuals, losses, and gradients exactly with Fraction. Its integer generator computes the specified updated states and indices without floating-point conversion. Assertions verify both seed-42 batches, the first two parameter updates, full-batch non-advancement, repeated indices, and literal full-objective values.

It also enumerates all 16 ordered pairs under the ideal uniform two-draw model and checks that their mean gradient equals the full gradient. That is a finite expectation calculation for this dataset, separate from any claim about a seeded PRNG’s independence.

The NumPy program uses gathered design-matrix rows and vectorized residual and gradient calculations across several seeds. Slots advance sequentially, while the seed axis is vectorized. It uses the same explicit LCG rather than substituting NumPy’s random generator, then independently checks full gradients, ordered-pair expectations, and noisy movement at the optimum.

code/python/optimization_stochastic.py
"""Exact fixed-data gradients with a reproducible uint32 LCG; sampling is with replacement."""
from fractions import Fraction as F
from itertools import product

DATA = ((-1, -1), (0, 1), (1, 2), (2, 2))
MODULUS = 2**32


def sample(state, size=2):
    if type(state) is not int or not 0 <= state < MODULUS:
        raise ValueError("Require a uint32 seed/state")
    if type(size) is not int or not 1 <= size <= 64:
        raise ValueError("Require an integer batch size in [1,64]")
    indices = []
    for _ in range(size):
        state = (1664525*state+1013904223) % MODULUS
        indices.append((4*state)//MODULUS)
    return tuple(indices), state


def batch(theta, indices):
    w, b = map(F, theta)
    indices = tuple(indices)
    if not indices or any(type(i) is not int or not 0 <= i < 4 for i in indices):
        raise ValueError("Require a nonempty sequence of data indices 0..3")
    examples = []
    for index in indices:
        x, y = DATA[index]
        residual = w*x+b-y
        examples.append({"index": index, "residual": residual,
                         "loss": residual*residual/2, "gradient": (residual*x, residual)})
    count = len(examples)
    return {
        "loss": sum(item["loss"] for item in examples)/count,
        "gradient": tuple(sum(item["gradient"][j] for item in examples)/count for j in (0, 1)),
        "examples": examples,
    }


def step(theta=(0, 0), state=42, eta=F(1, 10), batch_size=2, full_batch=False):
    theta, eta = tuple(map(F, theta)), F(eta)
    if eta <= 0 or type(state) is not int or not 0 <= state < MODULUS:
        raise ValueError("Require a positive learning rate and uint32 state")
    if type(batch_size) is not int or not 1 <= batch_size <= 64:
        raise ValueError("Require an integer batch size in [1,64]")
    if type(full_batch) is not bool:
        raise ValueError("full_batch must be a boolean")
    if full_batch:
        indices, next_state = (0, 1, 2, 3), state
    else:
        indices, next_state = sample(state, batch_size)
    selected, full = batch(theta, indices), batch(theta, (0, 1, 2, 3))
    update = tuple(-eta*g for g in selected["gradient"])
    next_theta = tuple(theta[j]+update[j] for j in (0, 1))
    return {
        "theta": theta, "indices": indices, "state_before": state, "state_after": next_state,
        "batch_loss": selected["loss"], "gradient": selected["gradient"],
        "full_loss": full["loss"], "full_gradient": full["gradient"],
        "update": update, "next_theta": next_theta,
        "next_full_loss": batch(next_theta, (0, 1, 2, 3))["loss"],
    }


def main():
    assert sample(42, 1) == ((1,), 1083814273)
    assert sample(42, 2) == ((1, 0), 378494188)
    assert sample(42, 4) == ((1, 0, 2, 0), 955863294)
    full = batch((0, 0), (0, 1, 2, 3))
    assert full["loss"] == F(5, 4) and full["gradient"] == (F(-7, 4), -1)
    first = step()
    assert first["gradient"] == (F(-1, 2), 0) and first["batch_loss"] == F(1, 2)
    assert first["next_theta"] == (F(1, 20), 0) and first["next_full_loss"] == F(1863, 1600)
    second = step(first["next_theta"], first["state_after"])
    assert second["indices"] == (2, 0) and second["state_after"] == 955863294
    assert second["gradient"] == (F(-29, 20), F(-1, 2))
    assert second["next_theta"] == (F(39, 200), F(1, 20))
    deterministic = step(full_batch=True)
    assert deterministic["indices"] == (0, 1, 2, 3) and deterministic["state_after"] == 42
    assert deterministic["next_theta"] == (F(7, 40), F(1, 10))
    assert step() == first  # Replaying the same seed and start repeats the actual batch.
    optimum = batch((1, F(1, 2)), (0, 1, 2, 3))
    assert optimum["gradient"] == (0, 0) and optimum["loss"] == F(1, 8)
    duplicate = batch((1, F(1, 2)), (0, 0))
    assert duplicate["gradient"] == (F(-1, 2), F(1, 2))
    assert batch((F(21, 20), F(9, 20)), (0, 1, 2, 3))["loss"] == F(203, 1600)
    ordered_pairs = list(product(range(4), repeat=2))
    average = tuple(sum(batch((0, 0), pair)["gradient"][j] for pair in ordered_pairs)/16 for j in (0, 1))
    assert average == full["gradient"]  # Enumerates the ideal uniform two-draw model, not a PRNG theorem.
    for invalid in (-1, MODULUS, True, 1.5):
        try:
            sample(invalid)
        except ValueError:
            pass
        else:
            raise AssertionError("Invalid uint32 seed accepted")
    print("Exact first seed-42 mini-batch frame:", first)
    print("Exact second frame:", second)
    print("Full batch uses every index once and does not advance the generator.")
    print("A fixed PRNG seed is a reproducible illustration, not proof of iid sampling.")


if __name__ == "__main__":
    main()
code/python/optimization_stochastic_numpy.py
"""Vectorized fixed-data residuals and parallel LCG streams; no NumPy RNG substitution."""
from itertools import product
import numpy as np

DESIGN = np.array([[-1, 1], [0, 1], [1, 1], [2, 1]], dtype=np.float64)
TARGET = np.array([-1, 1, 2, 2], dtype=np.float64)


def sample(states, size=2):
    states = np.asarray(states)
    if states.ndim != 1 or not np.issubdtype(states.dtype, np.integer) or np.any(states < 0) or np.any(states >= 2**32):
        raise ValueError("Require a vector of uint32 seeds/states")
    if type(size) is not int or not 1 <= size <= 64:
        raise ValueError("Require an integer batch size in [1,64]")
    states = states.astype(np.uint64, copy=True)
    indices = np.empty((len(states), size), dtype=np.int64)
    for slot in range(size):
        states = (np.uint64(1664525)*states+np.uint64(1013904223)) % np.uint64(2**32)
        indices[:, slot] = (np.uint64(4)*states)//np.uint64(2**32)
    return indices, states


def batch(points, indices):
    points, indices = np.asarray(points, dtype=np.float64), np.asarray(indices)
    if points.ndim != 2 or points.shape[1] != 2 or not np.all(np.isfinite(points)):
        raise ValueError("Require finite parameter points of shape (n,2)")
    if indices.ndim != 2 or indices.shape[0] != len(points) or indices.shape[1] == 0:
        raise ValueError("Require a nonempty batch per point")
    if not np.issubdtype(indices.dtype, np.integer) or np.any(indices < 0) or np.any(indices >= 4):
        raise ValueError("Require data indices 0..3, retaining any repeats")
    selected = DESIGN[indices]
    residuals = np.einsum("nbi,ni->nb", selected, points)-TARGET[indices]
    losses = np.mean(residuals**2/2, axis=1)
    gradients = np.mean(residuals[..., None]*selected, axis=1)
    if not np.all(np.isfinite(losses)) or not np.all(np.isfinite(gradients)):
        raise OverflowError("Non-finite binary64 loss or gradient")
    return losses, gradients


def main():
    initial_states = np.array([42, 43, 0], dtype=np.uint64)
    indices, states = sample(initial_states)
    np.testing.assert_array_equal(indices[0], [1, 0])
    assert states[0] == 378494188 and initial_states[0] == 42
    np.testing.assert_array_equal(sample([42], 4)[0][0], [1, 0, 2, 0])
    points = np.zeros((3, 2))
    losses, gradients = batch(points, indices)
    np.testing.assert_array_equal(gradients[0], [-0.5, 0])
    points -= 0.1*gradients
    np.testing.assert_allclose(points[0], [0.05, 0], rtol=0, atol=1e-14)
    full_indices = np.broadcast_to(np.arange(4), (3, 4))
    full_losses, _ = batch(points, full_indices)
    np.testing.assert_allclose(full_losses[0], 1.164375, rtol=0, atol=1e-14)
    next_indices, states = sample(states)
    np.testing.assert_array_equal(next_indices[0], [2, 0])
    _, gradients = batch(points, next_indices)
    points -= 0.1*gradients
    np.testing.assert_allclose(points[0], [0.195, 0.05], rtol=0, atol=1e-14)
    full_losses, full_gradients = batch(np.zeros((3, 2)), full_indices)
    np.testing.assert_array_equal(full_losses, [1.25]*3)
    np.testing.assert_array_equal(full_gradients, [[-1.75, -1]]*3)
    full_next = -0.1*full_gradients  # This full-batch computation draws nothing.
    np.testing.assert_allclose(full_next[0], [0.175, 0.1], rtol=0, atol=1e-14)
    assert initial_states[0] == 42
    pairs = np.array(list(product(range(4), repeat=2)), dtype=np.int64)
    _, pair_gradients = batch(np.zeros((16, 2)), pairs)
    np.testing.assert_array_equal(pair_gradients.mean(axis=0), [-1.75, -1])
    duplicate_loss, duplicate_gradient = batch([[1, 0.5]], [[0, 0]])
    np.testing.assert_array_equal(duplicate_gradient, [[-0.5, 0.5]])
    noisy_next = np.array([[1, 0.5]])-0.1*duplicate_gradient
    np.testing.assert_allclose(batch(noisy_next, [[0, 1, 2, 3]])[0], [0.126875], rtol=0, atol=1e-14)
    print("Selected seed-42 batches:", indices[0], next_indices[0])
    print("Binary64 mini-batch iterates after two steps ≈", points)
    print("Binary64 full-batch next point ≈", full_next[0], "; RNG state unchanged.")
    print("Ideal uniform pair enumeration checks unbiasedness; fixed seeds do not prove iid behavior.")


if __name__ == "__main__":
    main()

Explore: reproduce a path, then change its sampling

Replay the canonical path and verify its first two batches before changing anything. Switch to full batch and confirm that the selected indices are always [0,1,2,3][0,1,2,3] and the generator state does not advance. Switch to one sampled example: the first index uses the same stream, but subsequent iteration boundaries occur after a different number of draws.

Keep the start and rate fixed while changing the seed. The full gradient at that initial point stays fixed; the selected gradient and path can change. Increase mini-batch size to four and inspect repeats. Seed 42 begins [1,0,2,0][1,0,2,0], omitting index 3, so this sampled four-slot batch is still not the full batch.

At the full-data optimum (1,1/2)(1,1/2), individual residuals are 1/2,−1/2,−1/2,1/21/2,-1/2,-1/2,1/2. As a repeat example, two slots selecting [0,0][0,0] have mean gradient (−1/2,1/2)(-1/2,1/2). A rate 1/101/10 moves to (21/20,9/20)(21/20,9/20) and raises full loss from 1/8=200/16001/8=200/1600 to 203/1600203/1600. A fixed-rate sampled update need not stop at the exact full minimum.

Common mistakes and counterexamples

Unbiased does not mean every observed estimate equals its target. The first canonical selected gradient already differs from the full one. More draws can reduce estimation variance under the ideal assumptions, while a particular observed batch can still point away from a full-data minimum.

Do not remove duplicate indices or divide by the number of distinct examples. Both would change the requested with-replacement average. Do not call a sampled size-four batch a full batch. Do not reseed each slot or iteration unless deliberately teaching a different deterministic process.

Numerical considerations

The uint32 transitions and their four index bins are exact integer operations. The standard-library reference uses a floor-equivalent integer quotient, preserving the updated-state convention. Residual, loss, and gradient trajectories in the stage and NumPy reference use binary64 approximations and are labeled accordingly.

Summation and division can introduce rounding even with a tiny fixed dataset. On larger data, residual magnitude and feature scale can amplify it. Separate genuine batch variation from numerical error and display rounding; a jagged path is not evidence that every source of variation is stochastic.

A seed is only part of a reproducibility record. Retain current parameters, generator state, draw order, batch size, sampling mode, objective normalization, and optimizer settings. Switching from mini to full changes how many draws occur; reverse replay must select saved states rather than advance the stream again.

Engineering and ML connections

Least squares supplies the fixed-data objective, and expectation explains why a uniformly selected contribution has the full average as its mean. Covariance describes how independent averaging reduces variability. Those links depend on the stated sampling model, not on the appearance of a particular path.

A small batch uses fewer example-gradient evaluations per update, while a larger batch carries more information about the full mean. Comparing algorithms therefore requires counting work as well as steps. Momentum can filter a sampled gradient history, and Adam retains both signed and squared-gradient averages. Neither memory mechanism makes conditional sampling assumptions automatic or supplies a global convergence guarantee.

Exercises

Use zero-based indices, preserve repeated slots, and distinguish batch values from full-data values. State the ideal probability assumptions separately from the seeded demonstration.

calculation

Average the actual example gradients

Data are (−1,−1),(0,1),(1,2),(2,2), model ŷ=wx+b, and each loss is (wx+b−y)²/2. At (w,b)=(0,0), give the full mean loss and full gradient w,b.

construction

Advance the seed before selecting a slot

Starting from uint32 state 42, use state_next=(1664525×state+1013904223) mod 2³² and index=floor(4×state_next/2³²). Give the first updated state and index, then the second updated state and index.

calculation

Use the selected batch, inspect the full loss

At (w,b)=(0,0), seed 42 selects [1,0]. With η=1/10, give the mean selected gradient w,b, next w,b, and the full-data loss at that new point.

prediction

Retain repeats at a full-data optimum

At (w,b)=(1,1/2), the full gradient is zero. A with-replacement batch contains [0,0]. With η=1/10, give its mean gradient w,b, next w,b, and next full loss.

explanation

State the conditional sampling assumption

Which statement correctly connects mini-batch unbiasedness with the fixed-seed demonstration?

Choose one answer

Summary and checkpoints

A sampled update averages the actual selected example gradients, with repeats, while the full objective remains the mean of all four losses. Check that you can reconstruct seed 42’s batches, recover full gradient (−7/4,−1)(-7/4,-1) and selected gradient (−1/2,0)(-1/2,0) at the origin, and explain movement at a full-data optimum. Reproducible finite paths illustrate sampling; unbiasedness and variance reduction require their ideal conditional assumptions.

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