Skip to content
ML Academy

Core day 34 of 60 · Week 7 · Part II · application depth

Training Deep Networks

A digit classifier's training loss is 0.003, but its validation loss bottomed out at epoch 120 and has crept up since. Another run's loss jumps to 24 on the first update. For each curve, which single change do you make first, and what do you check afterwards?

By the end: From per-epoch training and validation losses you label a run diverging, underfitting, overfitting or healthy, cite the epochs that support the label, and choose one intervention whose effect you can verify on the next run.

At a glance

Read training and validation loss together, name the symptom, change one knob that targets it, and rerun with the same seed and split.

  • Diverging

    Loss jumps or turns nan: lower the learning rate, check input scale, warm up.

  • Underfitting

    Both curves high and slow: raise the rate, train longer, add capacity.

  • Overfitting

    Training falls, validation turns up: stop at the minimum, then regularize.

  • Scale at step 0

    Initialization matched to the activation keeps signals near size 1 through depth.

  • One change per run

    Same seed and split; a bundle of changes cannot be attributed.

Recall · 5 min

Reviews due from Days 15, 32 and 33 appear first. If none are due: write one sentence separating backpropagation from the optimizer; say what the loss of a 10-class softmax network should be at initialization and why; and describe in one line how early stopping worked for gradient boosting on Day 15.

Open due reviews

Sign in to record this block.

Concept · 10 min

Deep networks fail in a few recognizable ways, and the two loss curves together say which: diverging, underfitting, overfitting or healthy. Each symptom has one first intervention. Change one thing per run, or you cannot tell what helped; practitioner checklists such as Karpathy's "recipe" say the same.

Each symptom has its own curve shape, and the shape tells you which single knob to turn

Predict first

Training loss is 0.01 and falling; validation loss fell until epoch 20 and has risen steadily since. What is the lowest-risk first change?

Text description of the figure

Training and validation loss curves for a 2-16-16-1 tanh network trained live in the browser on 40 noisy two-moons points (not the digits of the code block), redrawn as you change learning rate, weight decay, dropout and epochs. A high learning rate gives a jagged or exploding curve, a tiny one a slow flat curve, and a long run on little data shows the training loss falling while the validation loss turns upward.

Read the two curves together Map

Overfitting

Memorizing. Stop at the validation minimum, then add decay or dropout, or more data.

Suspicious

Both high and far apart: check the split, preprocessing parity and label quality.

Healthy

Both low and close. Confirm over seeds; consider more capacity only if it pays.

Underfitting

Both high and close: raise the rate, train longer, or add capacity.

Divergence is the fifth case: a training curve that jumps up or turns nan. Lower the learning rate first.

Measured curves from today's run: learning rate 0.5 versus 0.01 Chart
0123200400600lr 0.5 validation minimumlr 0.5, training — epoch: 1, cross-entropy loss: 2.069lr 0.5, training — epoch: 10, cross-entropy loss: 0.903lr 0.5, training — epoch: 20, cross-entropy loss: 0.349lr 0.5, training — epoch: 40, cross-entropy loss: 0.112lr 0.5, training — epoch: 60, cross-entropy loss: 0.059lr 0.5, training — epoch: 80, cross-entropy loss: 0.038lr 0.5, training — epoch: 100, cross-entropy loss: 0.027lr 0.5, training — epoch: 120, cross-entropy loss: 0.021lr 0.5, training — epoch: 150, cross-entropy loss: 0.015lr 0.5, training — epoch: 200, cross-entropy loss: 0.01lr 0.5, training — epoch: 300, cross-entropy loss: 0.006lr 0.5, training — epoch: 400, cross-entropy loss: 0.004lr 0.5, training — epoch: 500, cross-entropy loss: 0.003lr 0.5, training — epoch: 600, cross-entropy loss: 0.003lr 0.5, traininglr 0.5, validation — epoch: 1, cross-entropy loss: 2.179lr 0.5, validation — epoch: 10, cross-entropy loss: 1.28lr 0.5, validation — epoch: 20, cross-entropy loss: 0.773lr 0.5, validation — epoch: 40, cross-entropy loss: 0.546lr 0.5, validation — epoch: 60, cross-entropy loss: 0.496lr 0.5, validation — epoch: 80, cross-entropy loss: 0.481lr 0.5, validation — epoch: 100, cross-entropy loss: 0.476lr 0.5, validation — epoch: 120, cross-entropy loss: 0.475lr 0.5, validation — epoch: 150, cross-entropy loss: 0.476lr 0.5, validation — epoch: 200, cross-entropy loss: 0.482lr 0.5, validation — epoch: 300, cross-entropy loss: 0.493lr 0.5, validation — epoch: 400, cross-entropy loss: 0.504lr 0.5, validation — epoch: 500, cross-entropy loss: 0.513lr 0.5, validation — epoch: 600, cross-entropy loss: 0.52lr 0.5, validationlr 0.01, training — epoch: 1, cross-entropy loss: 2.401lr 0.01, training — epoch: 20, cross-entropy loss: 2.253lr 0.01, training — epoch: 60, cross-entropy loss: 2.056lr 0.01, training — epoch: 100, cross-entropy loss: 1.914lr 0.01, training — epoch: 150, cross-entropy loss: 1.759lr 0.01, training — epoch: 200, cross-entropy loss: 1.612lr 0.01, training — epoch: 300, cross-entropy loss: 1.334lr 0.01, training — epoch: 400, cross-entropy loss: 1.089lr 0.01, training — epoch: 500, cross-entropy loss: 0.883lr 0.01, training — epoch: 600, cross-entropy loss: 0.715lr 0.01, traininglr 0.01, validation — epoch: 1, cross-entropy loss: 2.361lr 0.01, validation — epoch: 20, cross-entropy loss: 2.267lr 0.01, validation — epoch: 60, cross-entropy loss: 2.148lr 0.01, validation — epoch: 100, cross-entropy loss: 2.059lr 0.01, validation — epoch: 150, cross-entropy loss: 1.955lr 0.01, validation — epoch: 200, cross-entropy loss: 1.85lr 0.01, validation — epoch: 300, cross-entropy loss: 1.638lr 0.01, validation — epoch: 400, cross-entropy loss: 1.437lr 0.01, validation — epoch: 500, cross-entropy loss: 1.26lr 0.01, validation — epoch: 600, cross-entropy loss: 1.112lr 0.01, validationepochcross-entropy loss
  • lr 0.5, training
  • lr 0.5, validation
  • lr 0.01, training
  • lr 0.01, validation

Values recorded from the application code. lr 0.5: validation bottoms at 0.475 and creeps to 0.520 (overfitting). lr 0.01: both still falling at epoch 600 (underfitting).

One symptom, one intervention, one check Table
SymptomLikely causeFirst interventionCheck on the next run
Loss explodes or nanRate too high, unscaled inputsLower the rate 3-10x; warmup or clippingNo spikes in training loss
Both curves flat and highRate too low, too few epochsRaise the rate or train longerA tiny batch can be fit exactly
Train low, validation risingMemorizationEarly stopping at the validation minimumFinal validation equals the minimum
Deep network does not startActivations vanish or explodeInitialization matched to the activationPer-layer activation scale near 1
The debugging loop: one change per lap Loop
1Plot both curvestraining and validation,same axes2Name the symptomdiverging, under, over orhealthy3Change one knobthe one the symptompoints to4Rerun and compareagainst the seed spreadread the pairpick its knobrerunnext lap
  1. Plot both curvestraining and validation, same axesread the pair ↓
  2. Name the symptomdiverging, under, over or healthypick its knob ↓
  3. Change one knobthe one the symptom points torerun ↓
  4. Rerun and compareagainst the seed spreadnext lap ↓
  5. ↺ back to 1 · Plot both curves

Keep and log a change only if it moves the curve beyond seed-to-seed variation; otherwise revert it. Reverting means the symptom was probably misread: go back to the pair of curves, not to a new knob.

The knobs and what each one fixes Overview
  • Initialization

    Sets activation and gradient scale at step 0; match it to the activation.

  • Normalization

    BatchNorm or LayerNorm keeps each layer's inputs near zero mean, unit scale.

  • Dropout

    Zeroes random units in training only; off in evaluation.

  • Weight decay

    Shrinks weights each step; with Adam use the decoupled AdamW form.

  • Early stopping

    Keeps the weights from the validation minimum.

  • Schedule

    Warmup starts gently, decay ends with small steps.

  • Scale at step 0: Weight initialization matched to the activation (Glorot and Bengio, 2010; He et al., 2015 for ReLU).
  • Normalization: Batch normalization (per feature, over the batch) and Layer normalization (per example, over its features).
  • Regularization: Dropout (training only), Weight decay (with Adam use AdamW; Loshchilov and Hutter, 2019) and Early stopping.
  • Step size over time: a Learning-rate schedule, warmup then decay.
  • The pictures map each symptom to its knob and the check that confirms it.

Assumptions

  • Training and validation rows come from the same process, and validation only monitors and picks the stopping point; once it guides many decisions its loss is optimistic (Days 9, 15).

  • The curves come from one seed; seed-to-seed variation can match a small intervention's effect.

  • Loss is the monitored quantity; accuracy can stay flat while validation loss rises as the model grows overconfident (Day 32).

  • Regularizers trade variance for bias; too much decay or dropout underfits.

Sign in to record this block.

Application and workplace decision · 10 min

Same 64-64-10 ReLU network, 60 training digits, 400 validation digits (scikit-learn's bundled digits, rescaled by a fixed 1/16), full-batch gradient descent for 600 epochs, four settings:

  • learning rate 0.5;
  • learning rate 0.5 with weight decay 0.001;
  • learning rate 0.01;
  • learning rate 40.

Write diverging, underfitting, overfitting or healthy for each before running. The block prints the training loss after the first update and at the end, the best validation loss with its epoch, and the last validation loss.

Python

import numpy as np
from sklearn.datasets import load_digits

X, y = load_digits(return_X_y=True)
X, Y = X / 16.0, np.eye(10)[y]                    # pixels are 0-16: a fixed rescale, nothing fitted
perm = np.random.default_rng(0).permutation(len(X))
tr, va = perm[:60], perm[1000:1400]               # tiny training set on purpose; other rows stay unused

def train(lr, wd=0.0, epochs=600, h=64):
    r = np.random.default_rng(0)
    W1, b1 = r.normal(0, np.sqrt(2 / 64), (64, h)), np.zeros(h)
    W2, b2, hist = r.normal(0, np.sqrt(1 / h), (h, 10)), np.zeros(10), []
    def fwd(A):
        Z = np.maximum(0, A @ W1 + b1) @ W2 + b2
        E = np.exp(Z - Z.max(1, keepdims=True))
        return E / E.sum(1, keepdims=True)
    ce = lambda rows: -np.mean(np.sum(Y[rows] * np.log(fwd(X[rows]) + 1e-12), axis=1))
    for _ in range(epochs):
        A = X[tr]
        H = np.maximum(0, A @ W1 + b1)
        dZ = (fwd(A) - Y[tr]) / len(A)                         # softmax + cross-entropy gradient
        dH = (dZ @ W2.T) * (H > 0)
        W2, b2 = W2 - lr * (H.T @ dZ + wd * W2), b2 - lr * dZ.sum(0)
        W1, b1 = W1 - lr * (A.T @ dH + wd * W1), b1 - lr * dH.sum(0)
        hist.append((ce(tr), ce(va)))
    return np.array(hist)

for name, kw in [("lr 0.5", dict(lr=0.5)), ("lr 0.5 + weight decay", dict(lr=0.5, wd=0.001)),
                 ("lr 0.01", dict(lr=0.01)), ("lr 40", dict(lr=40.0))]:
    h = train(**kw)
    b = h[:, 1].argmin()
    print(f"{name:<22} train {h[0,0]:.2f} -> {h[-1,0]:.3f} | val best {h[b,1]:.3f} at epoch {b+1}, last {h[-1,1]:.3f}")

This code is executed in the repository's checks (pnpm lesson:py / lesson:sql). It is not run on this site; copy it into your notebook.

Sign in to record this block.

Mini-assessment · 8 min

Your first answer is your evidence. You can re-answer later in practice mode; that is labelled practice.

Question 1 · easy

A network contains dropout (p = 0.5) and BatchNorm layers. What must you do before computing validation loss or serving predictions?
Options

How confident are you? (optional, used for calibration)

Question 2 · medium

A run logs validation loss for epochs 1 to 8: 0.92, 0.61, 0.48, 0.44, 0.45, 0.47, 0.46, 0.49. Early stopping uses patience 3: training stops once three consecutive epochs have failed to beat the best validation loss so far, and the weights from the best epoch are restored. Which epoch's weights does the model end up with?
epoch number

How confident are you? (optional, used for calibration)

Question 3 · hard

A retail team trains a segment classifier on 2,000 rows with Adam (learning rate 0.001), no dropout and no weight decay. Train / validation loss by epoch: epoch 1, 1.38 / 1.37; epoch 5, 0.62 / 0.66; epoch 8, 0.38 / 0.57; epoch 10, 0.31 / 0.58; epoch 20, 0.08 / 0.71; epoch 40, 0.01 / 1.04. A colleague proposes a new run. Which proposal is best supported by the curves?
Options

How confident are you? (optional, used for calibration)

Question 4 · medium

A hidden layer takes 256 inputs and applies a ReLU. Its weights are drawn from a zero-mean normal distribution, with the variance set by the rule of He et al. (2015) so that the scale of the activations stays steady from layer to layer. What standard deviation should the weights have? (Three decimals.)
standard deviation

How confident are you? (optional, used for calibration)

Question 5 · hard

A team runs the Day 34 network (64-64-10 ReLU, 60 training digits, 400 validation digits, full-batch gradient descent) with learning rate 0.001. Training loss goes from 2.41 at epoch 1 to 2.32 at epoch 100, and validation loss from 2.37 to 2.31; a uniform guess over ten classes scores $\ln 10 = 2.30$. A colleague notes that "validation is not improving" and proposes adding dropout 0.3 and weight decay. What is the best-supported next step?
Options

How confident are you? (optional, used for calibration)

Sign in to complete lessons and keep your progress.

Technical depth · optional · 26 min in total

Open only what you need. Nothing here is required to complete the day; the assessment covers the core above.

Why there is a cliff: gradient descent on a quadratic4 minThe exact update factor, the 2/lambda threshold derived, four rates worked by hand, and the same cliff on the real network.
Gradient descent on a quadratic: the whole dynamics in one factor Formula
wt+1=wt−η λwt=(1−ηλ) wt,wt=(1−ηλ)t w0w_{t+1} = w_t - \eta\, \lambda w_t = (1 - \eta\lambda)\, w_t, \qquad w_t = (1 - \eta\lambda)^t\, w_0
f(w)=12λw2f(w) = \tfrac{1}{2}\lambda w^2
the model problem; lambda is the curvature and the gradient is lambda w
1−ηλ1 - \eta\lambda
each step multiplies w by this fixed factor, so the iterates are a geometric sequence
∣1−ηλ∣<1|1 - \eta\lambda| < 1
the convergence condition; solving it gives 0 < eta < 2 / lambda
2/λmax2 / \lambda_{max}
with several directions, the steepest curvature sets the ceiling for the shared rate

Between 1/lambda and 2/lambda the factor is negative: the step overshoots the minimum yet still converges. Past 2/lambda every overshoot grows. Near a minimum a real loss is approximately quadratic, with lambda its largest curvature: that is why every learning-rate sweep shows a gentle slope, then a cliff.

Worked by hand: lambda = 25, w0 = 1, so the threshold is 2/25 = 0.08 Table
RateFactor 1 - 25 etaw over steps 0-4Behaviour
0.01+0.751, 0.75, 0.563, 0.422, 0.316Smooth decay
0.07-0.751, -0.75, 0.563, -0.422, 0.316Overshoots, still converges
0.08-1.001, -1, 1, -1, 1The edge: oscillates forever
0.09-1.251, -1.25, 1.563, -1.953, 2.441Diverges

Each row is the geometric sequence (1 - eta lambda)^t, verified in numpy. The loss at rate 0.09 grows by 1.25 squared = 1.5625 per step.

The derivation, drawn: three rates around the threshold Illustration
-1-0.500.5104812η = 0.01: |1 − ηλ| = 0.75w ← (+0.75) · wStep tw at step t

Well below the threshold 2/λ = 0.08: each step keeps 75% of the distance, so w decays smoothly (1, 0.75, 0.5625, …) and the loss falls every step.

-1-0.500.5104812η = 0.07: factor −0.75w ← (-0.75) · wStep tw at step t

Past η = 1/λ the factor turns negative: the step overshoots the minimum each time (1, −0.75, 0.5625, …), but |factor| < 1, so the oscillation still dies out.

-4-202404812η = 0.09: factor −1.25, divergesw ← (-1.25) · wStep tw at step t

Above 2/λ = 0.08 the factor's size exceeds 1: every overshoot lands farther out (1, −1.25, 1.5625, −1.9531, …) and the loss grows without bound.

Computed exactly: gradient descent w ← w − η·λ·w on f(w) = ½·λ·w² with λ = 25 and w₀ = 1, for the three printed rates. The iterates are the geometric sequence (1 − ηλ)ᵗ; no simulation noise.

Below the threshold the iterates decay; past 1/lambda they overshoot but still shrink; above 2/lambda every overshoot lands farther out. The lr 40 run on the digits network is the third panel's behaviour in the wild.

Initialization, derived and measured4 minThe variance argument behind Glorot and He, why ReLU needs twice the variance, and what a badly scaled start does to 20 layers.
The He and Glorot variances, derived Sequence
  1. One pre-activation

    z = sum of w_i x_i over n_in independent zero-mean terms, so Var(z) = n_in x Var(w) x Var(x)

  2. Keep the scale

    Asking Var(z) = Var(x) forces Var(w) = 1 / n_in: the Glorot-style forward condition for tanh-like units

  3. ReLU halves it

    ReLU zeroes the negative half of a symmetric z, so the second moment of its output is Var(z) / 2; compensating needs Var(w) = 2 / n_in (He)

  4. Backward pass

    The same argument on the gradients gives Var(w) = 1 / n_out; Glorot's published compromise is 2 / (n_in + n_out)

  5. Measured

    The table below shows what 20 ReLU layers actually do to the activation scale under each rule

The derivation assumes independent zero-mean weights and inputs and holds at initialization only; training then moves the weights.

Initialization variance by activation Formula
Var⁡(W)=1nin  (tanh-like, Glorot-style),Var⁡(W)=2nin  (ReLU, He)\operatorname{Var}(W) = \frac{1}{n_{in}} \;\text{(tanh-like, Glorot-style)}, \qquad \operatorname{Var}(W) = \frac{2}{n_{in}} \;\text{(ReLU, He)}
ninn_{in}
number of inputs to the layer (fan-in)
22
ReLU zeroes about half its inputs, so it needs twice the variance to keep the scale

Glorot and Bengio also give 2 / (n_in + n_out), which balances the forward and backward passes.

The initial weight scale, before any training Illustration
ReLU: activation size per layer1e-301e-201e-101e01e1015101520layer

Size after 20 ReLU layers: 8e-29 (sd 0.01), 7e-4 (Var 1/n), 0.76 (He), 8e5 (sd 0.5). Dashed: the prediction (n·Var(w)/2)^(l/2), which the measured lines follow.

Tanh: signal kept on the way back1e-141e-121e-101e-81e-61e-41e-21e015101520layer

Tanh, product of the mean slope over the layers so far (what a backward signal keeps): 1e-1 with Var 1/n, 3e-4 with Var 2/n, 2e-10 with sd 0.5. Too large a scale saturates tanh units.

  • N(0, 0.01) (ReLU panel only)
  • Var 1/n (Glorot-style)
  • Var 2/n (He)
  • N(0, 0.5)

Computed: one seeded batch of 16 standard-normal rows through 20 freshly drawn 32 x 32 weight matrices, no biases, no training. n = 32 inputs per layer here, so He means sd √(2/32) = 0.25.

In a ReLU stack each layer multiplies the activation size by about the square root of n Var(w) / 2, so only Var = 2/n stays level. In a tanh stack a large scale saturates the units and the backward signal collapses.

Std of the activations after 20 ReLU layers (width 100), by initialization Table
InitializationWeight stdStd at layer 20Verdict
N(0, 0.01)0.014.6e-24Signal vanished
Glorot-style, Var 1/n0.14.4e-04Shrinking: too small for ReLU
He, Var 2/n0.1412.0Stable
N(0, 0.3)0.32.8e+06Exploded

Values printed by this lesson's hands-on lab (forward pass only, one draw of weights). Library defaults differ: scikit-learn's MLP uses a Glorot-style uniform range even for ReLU units (Day 31), and normalization layers soften a poor start.

The learning rate, the batch and the schedule4 minA log-scale sweep, how batch size moves the best rate, and warmup then cosine decay.
Two ways to find the cliff before you tune anything else Comparison
CriterionSweep of separate runsRange test (Smith, 2017)
How it runsOne short run per rate, rates spaced on a log scaleOne short run, the rate raised a little every step
Costk runs for k rates1 run
What you read offBest final loss per rate; the edge where runs start to failThe rate where the loss stops falling and turns up

Both answer the same question: where the gentle slope ends. The range test is cheaper; separate runs also show how each rate ends, which the sweep below draws.

Too small, workable, too large Illustration
Training loss by epoch00.40.81.21.6coin flip, ln 2epoch04080

rate 0.001: 0.49, rate 0.3: 0.34, rate 3: 1.43 training loss after 80 epochs.

Loss after 80 epochs, by rate00.40.81.21.6coin flip, ln 2learning rate (log scale)0.0010.010.10.31310×

Lowest training loss at rate 1, lowest validation loss at rate 0.3. The loss falls slowly as the rate grows, then jumps off a cliff: rate 10 diverges (×). Sweep rates on a log scale (or run a range test that raises the rate within one run) before tuning anything else.

  • rate 0.001: too small
  • rate 0.3: workable
  • rate 3: too large
  • right: final training loss
  • right: best validation loss (dashed)

Computed: the training-curves widget's data and 2-16-16-1 tanh network, minibatch SGD with batches of 8, seed 1, 80 epochs per rate; loss clipped at 1.6. One seed: the exact best rate moves between seeds, the U shape does not.

The loss improves slowly as the rate rises, then falls off a cliff. Sweep on a log scale; the best rate sits just below the edge. Momentum and Adam move the stable range and the speed inside it; they do not remove the cliff (Day 33's lab measures all three on the quadratic).

Batch size and learning rate move together Overview
  • Linear scaling rule

    Multiply the batch by k, multiply the rate by k, with a warmup (Goyal et al., 2017, for large-batch SGD).

  • Gradient noise

    Small batches give noisier gradients: more updates per epoch, each less precise.

  • BatchNorm statistics

    Batch size also changes the statistics BatchNorm normalizes with; very small batches make them noisy.

  • Re-tune after a change

    A new batch size invalidates the old best rate; sweep again before comparing.

Warmup for 5 steps, then cosine decay to 0 over 100 steps (peak 0.1) Chart
00.0250.050.0750.1020406080end of warmupLearning rate — step: 0, learning rate: 0.02Learning rate — step: 1, learning rate: 0.04Learning rate — step: 2, learning rate: 0.06Learning rate — step: 3, learning rate: 0.08Learning rate — step: 4, learning rate: 0.1Learning rate — step: 5, learning rate: 0.1Learning rate — step: 10, learning rate: 0.0993Learning rate — step: 20, learning rate: 0.094Learning rate — step: 30, learning rate: 0.0839Learning rate — step: 40, learning rate: 0.0701Learning rate — step: 50, learning rate: 0.0541Learning rate — step: 60, learning rate: 0.0377Learning rate — step: 70, learning rate: 0.0227Learning rate — step: 80, learning rate: 0.0105Learning rate — step: 90, learning rate: 0.0027Learning rate — step: 99, learning rate: 0steplearning rate

Exact values of the lab's schedule function. Warmup avoids large early steps while Adam's moments and normalization statistics settle. The plain cosine is the single-cycle case of cosine decay with restarts (SGDR, Loshchilov and Hutter, 2017).

BatchNorm and LayerNorm4 minOne formula on two axes, both computed by hand on three rows, and why the axis matters at batch size 1 and in evaluation.
BatchNorm and LayerNorm are the same formula on different axes Formula
x^=x−μσ2+ϵ,y=γ x^+β\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}, \qquad y = \gamma\, \hat{x} + \beta
μ,σ2\mu, \sigma^2
BatchNorm: per feature, across the batch rows. LayerNorm: per example, across its own features
ϵ\epsilon
a small floor (about 1e-5) so the division never blows up on a constant feature
γ,β\gamma, \beta
learned scale and shift per feature, so the layer can undo the normalization where that helps

Only the axis of the statistics differs, and that one choice drives everything in the comparison below, from batch-size sensitivity to the evaluation-mode switch.

Worked by hand: BatchNorm and LayerNorm on three sensor rows (manufacturing) Table
RowInput (temp, vibration, pressure)BatchNorm: per columnLayerNorm: per row
1(1, 2, 3)(-1.225, -1.225, -1.389)(-1.225, 0, 1.225)
2(3, 4, 8)(0, 0, 0.926)(-0.926, -0.463, 1.389)
3(5, 6, 7)(1.225, 1.225, 0.463)(-1.225, 0, 1.225)
Statistics-column means (3, 4, 6), variances (2.667, 2.667, 4.667)row means (2, 5, 6), variances (0.667, 4.667, 0.667)
Row 1 alone (batch of 1)(1, 2, 3)(0, 0, 0): each value minus itself(-1.225, 0, 1.225): unchanged

Illustrative production-line readings. Variances divide by n, the biased form both layers normalize with; epsilon = 1e-5 is negligible here, gamma = 1 and beta = 0. Rows 1 and 3 differ only by a shift of 4, so LayerNorm gives them identical outputs: it discards each row's level. Verified in numpy.

BatchNorm versus LayerNorm Comparison
CriterionBatchNormLayerNorm
Normalizes overEach feature, across the rows of the mini-batchEach example, across its own features
Depends on batch sizeYes; small batches give noisy statisticsNo
Training versus evaluationBatch statistics in training, running averages in evaluationSame computation in both modes
Batch of 1No statistics: zeros in training mode; PyTorch BatchNorm1d raises an error on (1, C)Works normally
Typical homeConvolutional networks (Day 36)Transformers and recurrent networks (Days 37, 39)
BatchNorm keeps two sets of statistics, and the mode picks one Diagram
Training batchmodel.train()Batch mean and varnormalize this batchwith themRunningestimatesone mean and varper featureEvaluation inputmodel.eval(), any batchsizeNormalize, fixedstatsindependent of the otherrowsWeights averagedSWA or EMA: stats nowstaleupdate_bn passone pass over trainingdatamomentum 0.1normalizereadrecompute
  1. Training batchmodel.train()
    • to Batch mean and var
  2. Evaluation inputmodel.eval(), any batch size
    • normalize: to Normalize, fixed stats
  3. Weights averagedSWA or EMA: stats now stale
    • to update_bn pass
  4. Batch mean and varnormalize this batch with them
    • momentum 0.1: to Running estimates
  5. update_bn passone pass over training data
  6. Running estimatesone mean and var per feature
  7. Normalize, fixed statsindependent of the other rows

PyTorch's BatchNorm updates its running estimates with momentum 0.1 during training and reads them in evaluation (BatchNorm2d docs). Averaged weights (torch.optim.swa_utils) never produced those statistics, so recompute them before trusting evaluation.

Dropout and weight decay, done right3 minInverted dropout's rescaling, and why Adam needs the decoupled decay of AdamW.
Inverted dropout, in training and evaluation Sequence
  1. Training

    Zero each unit with probability p, for example 0.5

  2. Rescale

    Divide the survivors by 1 - p so the expected activation is unchanged

  3. Evaluation

    Turn dropout off; no rescaling needed (call model.eval() in PyTorch)

  4. Check

    Lab: training mean 0.999 versus evaluation mean 1.000

L2 penalty inside Adam versus decoupled weight decay (AdamW) Comparison
CriterionL2 inside the gradientAdamW
Where the decay enterslambda times theta is added to g before Adam's momentsSubtracted from the weights directly, outside the moments
Effect of the adaptive stepDivided by sqrt(v-hat): weights with large gradients are decayed lessEvery weight shrinks by the same fraction lr times lambda
ResultUneven, hard-to-tune regularizationDecay behaves like plain weight decay in SGD

Loshchilov and Hutter (2019). For plain SGD the two forms coincide up to a rescaling of lambda.

Early stopping as a regularizer2 minThe knobs, their scikit-learn defaults, and what to write yourself in PyTorch.
Early-stopping knobs, and scikit-learn's defaults (1.9.1, checked) Table
KnobMLPClassifier defaultWhat it controls
early_stoppingFalseOff unless switched on: training runs to max_iter or the tol rule
validation_fraction0.1Share of training rows held out to monitor
n_iter_no_change (patience)10Epochs without improvement before stopping
tol (minimum improvement)0.0001Smaller changes count as no improvement
Best weightsrestored when early_stopping is onThe coefficients with the best validation score are kept

In plain PyTorch you write this loop yourself: track the best validation loss, checkpoint it, stop when patience runs out, reload the checkpoint. The rows that pick the stopping epoch are now tuning rows, so the final estimate needs a separate test part (Days 9 and 15).

A checklist before tuning2 minFive checks that find bugs before you spend runs on knobs.
Before tuning anything Sequence
  1. Check the loss at step 0

    About ln K for K balanced classes: ln 10 = 2.30 for digits

  2. Overfit one small batch

    If it cannot reach near-zero loss, look for a bug, not a knob

  3. Simplest model first

    No dropout, no decay, a known-good learning rate

  4. Add one thing at a time

    Each with its own run, seed and logged curves

  5. Then regularize

    Only after the training curve shows enough capacity

A practitioner checklist in the spirit of Karpathy's recipe (2019).

Run hygiene and reproducibilityadvanced3 minWhat a seed does and does not guarantee, what a checkpoint must hold, and two ordering bugs that bend curves.
Run hygiene that makes curves comparable Overview
  • Seeds are not enough

    PyTorch does not promise identical results across releases, platforms or CPU versus GPU.

  • Checkpoint everything

    Model and optimizer state (Adam's moments) plus epoch, to resume a run honestly.

  • Scheduler order

    Call scheduler.step() after optimizer.step(); the reverse skips the first rate.

  • Mode test

    Same input twice in eval mode must give the same output; catches dropout or BatchNorm left in training mode.

  • Seed spread

    Report the spread over three or more seeds next to any claimed improvement; one seed is one sample (Day 35).

From the PyTorch reproducibility, saving-and-loading and optim documentation (2.14 pages).

Memory anchor

Read both curves, name the symptom, change one thing, and rerun with the same seed and split.

Useful for

  • Triage of a failed or unstable training run - a demand forecaster's, a defect classifier's, a churn model's - from the logged loss curves before touching hyperparameters.

  • Writing a run log that states the symptom, the single change made, and the result against a stated expectation.

  • Explaining to a stakeholder why a model was stopped early or why a larger network was not the answer.

Not suitable for

  • Deciding whether the model is good enough to ship - that needs the sealed test evaluation and the cost of errors (Days 16 and 35).

  • Fixing data problems such as label noise, leakage or a shifted validation set; regularization can only hide them.

Traps

Reading overfitting as divergence, or the reverse

A rising validation loss is called "divergence" and the learning rate is lowered, although the training loss is still falling. Or an exploding run is called overfitting and regularizers are added.

How to avoid it

Always read training and validation loss together; divergence shows in the training curve, overfitting in the gap.

Fixing everything at once

Learning rate, weight decay, dropout, schedule and batch size change in one run. The curve improves, nobody knows why, and the next model starts from scratch.

How to avoid it

Change one thing per run, pre-register what you expect, and use several seeds when the difference is small.

Evaluating in training mode

Dropout stays active and BatchNorm uses batch statistics, so validation loss fluctuates between runs and depends on batch composition.

How to avoid it

Switch to evaluation mode before validation and serving, and add a test that the same input gives the same output twice.

Senior reasoning

A senior treats the training log as evidence and the intervention as a hypothesis. They first confirm the basics (loss at initialization near ln⁡K\ln K, a tiny batch can be overfit, input scale is sane), then name the symptom, change one thing, and state beforehand what the curves should do. They know a single seed is a sample, so small differences are repeated before they are believed, and they remember that regularization trades variance for bias: if the validation minimum itself is poor, the next step is data or capacity, not more penalty.

Interview question

"Training loss keeps falling but validation loss has started rising. What do you do?" How would you answer?

Recall cards scheduled after completion

  • Name the four curve patterns and the single first intervention for each.
  • How do dropout and BatchNorm behave differently in training and evaluation mode?

Reviewed on a 1 · 3 · 7 · 14 · 30-day schedule; a miss returns the card to 1 day.

References

Optional sections

Deep dive · 10 min

Why initialization needs a scale. For y=∑i=1nwixiy = \sum_{i=1}^{n} w_i x_i with independent zero-mean terms, Var(y)=n Var(w) Var(x)\text{Var}(y) = n\, \text{Var}(w)\, \text{Var}(x). Choosing Var(w)=1/n\text{Var}(w) = 1/n keeps the variance constant from layer to layer for linear or tanh-like units. ReLU sets about half of its inputs to zero, halving the second moment, so Var(w)=2/n\text{Var}(w) = 2/n compensates (He et al.). The lab prints the result: after 20 ReLU layers, 2/n2/n keeps the activation scale near 2, while 1/n1/n shrinks it to about 4×10−44 \times 10^{-4} and a standard deviation of 0.3 inflates it to millions.

BatchNorm versus LayerNorm. BatchNorm computes mean and variance per feature across the batch, so a batch of one has no information and small batches give noisy statistics; at inference it substitutes running averages. LayerNorm computes them per example across features, which is why Transformer blocks use it (Day 39): same computation in training and evaluation, any batch size.

Weight decay versus L2. For plain SGD, adding λ∥w∥2/2\lambda \|w\|^2 / 2 to the loss is the same as shrinking each weight by a factor (1−ηλ)(1 - \eta \lambda) per step. For Adam, an L2 term enters the gradient and is then divided by the adaptive denominator, so weights with large gradient history are penalized less. AdamW applies the shrinkage directly, outside the adaptive step. Exclude biases and normalization scales from decay as a common practice.

Schedules. Warmup ramps the rate up over the first steps (a common explanation is that Adam's second-moment estimates are unreliable at the start); cosine decay lowers it smoothly to near zero. A learning-rate range test (raise the rate exponentially for a few hundred steps and watch where the loss turns up) gives a starting value for the peak.

Hands-on lab: Initialization, normalization, dropout and a schedule, one at a time · 15 min

Run the block. Part 1 pushes a batch through 20 ReLU layers with four weight scales and prints the activation scale at the end. Part 2 contrasts batch and layer normalization on a batch and on a single row. Part 3 checks inverted dropout. Part 4 prints a warmup-plus-cosine schedule.

Predict first: which of the four initializations keeps the final activation scale within a factor of ten of the input's? Then answer: (1) Why does the Glorot-style variance shrink the signal in a ReLU network, and what would you use for tanh? (2) What does BatchNorm output for a batch of one, and why does that matter at serving time? (3) Why is no correction needed at evaluation time with inverted dropout? (4) Where in a training loop would you call the schedule?

import numpy as np

rng = np.random.default_rng(0)
width, depth = 100, 20
X0 = rng.normal(size=(256, width))

# 1) Activation scale after 20 ReLU layers under different initializations (forward pass only)
def last_layer_std(scale):
    a = X0
    for _ in range(depth):
        a = np.maximum(0, a @ rng.normal(0, scale, (width, width)))
    return a.std()
for name, s in [("N(0, 0.01)", 0.01), ("Glorot-style, Var 1/n", np.sqrt(1 / width)),
                ("He, Var 2/n", np.sqrt(2 / width)), ("N(0, 0.3)", 0.3)]:
    print(f"init {name:<24} std of layer-{depth} activations: {last_layer_std(s):.3e}")

# 2) BatchNorm normalizes each feature across the batch; LayerNorm normalizes each example across its features
def batchnorm(a, eps=1e-5):
    return (a - a.mean(0)) / np.sqrt(a.var(0) + eps)
def layernorm(a, eps=1e-5):
    return (a - a.mean(1, keepdims=True)) / np.sqrt(a.var(1, keepdims=True) + eps)
A = rng.normal(5.0, 3.0, size=(8, 4))
print("BN column means", batchnorm(A).mean(0).round(6), "| LN row means", layernorm(A).mean(1).round(6))
one = A[:1]
print("batch of 1: BN output", batchnorm(one).round(3), "(all zeros, no information) | LN output", layernorm(one).round(2))

# 3) Inverted dropout: scale by 1/(1-p) in training so evaluation needs no change
p = 0.5
a = np.ones((10000, 10))
mask = rng.random(a.shape) >= p
print(f"train mean {(a * mask / (1 - p)).mean():.3f} (expected 1.000) | eval mean {a.mean():.3f} (identity)")

# 4) Learning-rate schedule: 5 warmup steps, then cosine decay to zero over 100 steps
def lr_at(t, peak=0.1, warm=5, total=100):
    if t < warm:
        return peak * (t + 1) / warm
    return 0.5 * peak * (1 + np.cos(np.pi * (t - warm) / (total - warm)))
print("lr at steps 0, 4, 5, 50, 99:", [round(float(lr_at(t)), 4) for t in (0, 4, 5, 50, 99)])
Interview practice · 10 min

On an insurance claims-fraud classifier, your validation loss is flat and high from the first epoch, while the training loss is also high and barely moving. A teammate wants to add dropout. What do you ask and say?

Content version 6 · last verified 2026-10-18 · depends on numpy, scikit-learn