XP: 0
⚙️ Part 1 · Module 3

The Training Loop

Module 2 gave you one gradient step. Now we build the real, complete loop that repeats it — the for epoch in ... that actually trains a model. You'll meet loss functions, optimizers (Stochastic Gradient Descent, or SGD, and Adam), the learning rate, and how to catch overfitting before it ruins you.
The heartbeat

Every model trains with the same 5-step loop

Whether it's a tiny classifier or GPT-4, training is this loop, repeated. Five steps, over and over, each pass making the model slightly less wrong:

python · the canonical training loop
for epoch in range(num_epochs):
    pred = model(x)                 # 1. FORWARD: make a prediction
    loss = loss_fn(pred, y)         # 2. LOSS: how wrong are we?
    optimizer.zero_grad()          # 3. reset old gradients
    loss.backward()                # 4. BACKWARD: compute gradients (autograd!)
    optimizer.step()               # 5. UPDATE: nudge weights downhill
You already know steps 4 & 5

Step 4 (backward) is autograd from Module 2. Step 5 (step) is x = x − lr×grad, now handled by an optimizer object so you don't write it by hand. Steps 1–3 are new but simple: predict, measure error, clear the old slopes. Master these 5 lines and you can train anything.

🎮 Game 1

Step through the loop

Click each step to see what it does. This is the exact order every single training run follows.

The 5 steps — click each
One epoch = one trip around this loop.
👆 Click a step to see what it does.
🎮 Game 2

Run a real training loop (watch loss drop)

This trains a tiny model to fit a line. Press "Train" and watch the loss curve fall as the model learns — the payoff of the whole loop. (Simulated to show the shape; the real code is below.)

Fitting y = 2x + 1
Loss should drop and flatten as it learns.
epoch0
loss
learned: y = ? · x + ?
python · full example
import torch, torch.nn as nn

torch.manual_seed(1)                            # same starting weight each run

x = torch.tensor([[1.0], [2.0], [3.0], [4.0]])  # inputs: shape (4, 1)
y = torch.tensor([[3.0], [5.0], [7.0], [9.0]])  # targets: y = 2x + 1

model = nn.Linear(1, 1)                         # y = w*x + b
loss_fn = nn.MSELoss()                          # mean squared error
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

for epoch in range(10):
    pred = model(x)
    loss = loss_fn(pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    print(epoch, loss.item())
🎮 Game 3

Pick the right loss function

A loss function measures "how wrong." Different jobs need different rulers. The two you'll use 90% of the time:

Match the loss to the task
Click a task; see which loss fits.
👆 Click a task above.
🎮 Game 4

Stochastic Gradient Descent (SGD) vs Adam — the optimizer race

The optimizer decides how to use the gradient to update weights. Stochastic Gradient Descent (SGD) takes plain steps. Adam is smarter — it adapts the step size per-weight and adds "momentum," so it usually converges faster. Watch them race down the same loss.

Same hill, two optimizers
Adam usually reaches the bottom in fewer steps.
Press "Race" to compare.
python
# swapping optimizers is one line:
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)     # Stochastic Gradient Descent
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)   # adaptive, popular default
🎮 Game 5

Tune the learning rate (the #1 knob)

The learning rate is the single most important setting. Slide it and run — see the loss curve go smooth, sluggish, or explode. This is the intuition you'll rely on constantly.

lr = 0.03
Find the "just right" zone.
The safety net

Train / Validation / Test — and overfitting

Here's the trap: a model can get a perfect score on its training data by memorizing it — then fail completely on anything new. That's overfitting. To catch it, you split your data:

The three splits

Train set (~80%) — the model learns from this. Validation set (~10%) — checked during training to see if it generalizes (and tune settings). Test set (~10%) — touched once at the very end, for an honest final grade. The rule: never let the model train on your test set.

Analogy — studying for an exam

Train = the practice problems you study. Validation = a mock exam to check you actually understand (not just memorized answers). Test = the real exam, seen once. A student who memorizes the practice answers aces practice but bombs the real test — that's overfitting.

🎮 Game 6 — spot the overfitting

Train loss keeps dropping — but watch the validation loss. When val starts rising while train keeps falling, the model has stopped learning and started memorizing. Run it and find that moment.

Train loss ↓ but val loss ↑ = overfitting
The gap between the two lines is the tell.
▬ train loss▬ validation loss
Press "Train" and watch for the split.
The fix: early stopping

When validation loss stops improving (or rises), stop training — that's called early stopping. It's the simplest overfitting cure. Two others come up constantly: dropout and train/eval mode — let's cover them now.

Two things everyone trips on

Dropout & the train() / eval() switch

Dropout — a simple overfitting cure

Dropout randomly "switches off" a fraction of neurons on each training step. It sounds destructive, but it forces the network to not over-rely on any single neuron — so it generalizes better instead of memorizing. It's one of the most common regularization tools.

Analogy — a study group where random members skip

If the same one person always answers, the group learns to lean on them. Randomly benching members each session forces everyone to actually learn the material — so the group is robust even when someone's missing. Dropout does that to neurons.

The critical catch: model.train() vs model.eval()

Here's a bug that bites almost every beginner. Dropout should be ON while training (to regularize) but OFF when evaluating or deploying (you want the full, stable network — no random neurons missing). PyTorch controls this with a mode switch you must set yourself:

python · the mode switch
model = nn.Sequential(
    nn.Linear(10, 64), nn.ReLU(),
    nn.Dropout(0.2),          # randomly drop 20% of neurons
    nn.Linear(64, 1),
)

# --- during TRAINING ---
model.train()                # dropout ON (regularizes)
for batch in train_loader:
    ...                      # the 5-step loop

# --- during VALIDATION / TEST / INFERENCE ---
model.eval()                 # dropout OFF (full, deterministic network)
with torch.no_grad():        # also skip gradient tracking → faster, less memory
    preds = model(val_x)
The #1 silent bug — forgetting to switch modes

If you evaluate while still in train() mode, dropout stays on and your validation scores come out randomly worse (and different every run). If you train while in eval() mode, you lose your regularization. The habit: model.train() before the training loop, model.eval() before you measure or predict. Pair eval() with torch.no_grad() so you're not wasting memory tracking gradients you won't use.

Why it's a mode, not a parameter

Some layers behave differently in training vs. inference — Dropout (random off vs. all on) and BatchNorm (uses batch stats vs. running averages) are the big two. Rather than configure each layer, you flip the whole model's mode with one call, and every layer adjusts itself. That's what train()/eval() do.

🎯 Check your understanding

Quick challenge

1. What are the 5 steps of the training loop, in order?
2. What does an optimizer do?
3. Which loss for classification (predicting a category)?
4. You see train loss falling but validation loss rising. What's happening?
5. What is the test set for?
Takeaway

What you learned

The training loop is 5 steps repeated: forward → loss → zero_grad → backward → step. A loss function measures wrongness (MSE for numbers, Cross-Entropy for categories). An optimizer (Stochastic Gradient Descent, or SGD, or the popular Adam) turns gradients into weight updates. The learning rate is the most important knob — too small crawls, too big explodes. Split data into train / validation / test; when validation loss rises while train falls, you're overfitting — stop early.

Next up → Module 4: Datasets & Tokenizers — where the x and y in this loop actually come from. Loading real data with Hugging Face, batching, and turning text into tokens the model can eat.