Course page PDF
CS 486/686
Training Models From Scratch

Lecture 17

Core loop: data → model → loss → gradient → update

Recorded lecture (asynchronous)

🎥 This lecture is pre-recorded - I'm away at ICML this week, so there's no in-person class today. Watch at your own pace.

📅 Due today (Tue Jul 7): Chat 8. The CS686 project proposal is now due Thu Jul 9 (submit on LEARN under "Project Proposal").

💬 Questions? Post on Piazza - the TAs are available, and I'll follow up when I'm back.

Today's target

By the end, this loop should feel readable:

for x, y in loader:
    pred = model(x)
    loss = loss_fn(pred, y)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

Everything later - neural nets, LLMs, diffusion - scales up this idea.

The learning recipe

data model prediction loss update each update makes the next prediction a little better

Parameters are the knobs

ŷ = wx + b input x target y

The data is fixed.

The model has tunable parameters: \(w\) and \(b\).

Training means choosing parameters that make predictions good.

Start with one knob

start best L(x) = (x−2)²

Forget ML for one slide.

We only need to make a loss small.

This is the smallest possible version of training.

Move downhill

The gradient points uphill, so step the other way.

\(x \leftarrow x - \eta \dfrac{dL}{dx}\)

\(-dL/dx\)

direction that decreases loss

\(\eta\)

learning rate: how big a step?

Playground: gradient descentlive

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: drag the learning rate \(\eta\) and press Step / Run to watch \(x\) roll down \(L(x)=(x-2)^2\). Small steps crawl; too large and it diverges. Runs instantly in your browser.

PyTorch computes gradients for us

Manual derivative

def loss(x):
    return (x - 2) ** 2

def manual_grad(x):
    return 2 * (x - 2)

Automatic differentiation

x = nn.Parameter(torch.tensor(-4.0))
L = (x - 2) ** 2
L.backward()
print(x.grad)  # -12

For millions of parameters, we rely on autograd. But how does it know the answer?

How can gradients be automatic?

Every operation records its own local derivative; the chain rule multiplies them backward.

Forward: \(u = x - 2\), then \(L = u^2\).

Chain rule: \(\tfrac{dL}{dx} = \tfrac{dL}{du}\cdot\tfrac{du}{dx}\)

Local: \(\tfrac{dL}{du} = 2u\)  (since \(L=u^2\))

Local: \(\tfrac{du}{dx} = 1\)  (since \(u=x-2\))

Multiply: \(\tfrac{dL}{dx} = 2u\cdot 1 = 2(x-2)\)

At \(x=-4\): \(\tfrac{dL}{dx} = 2(-6) = -12\) — exactly x.grad.

Autograd follows the computation graph

x x - 2 square L ×1×2(x−2)×1 backward pass: multiply local derivatives

Run it: autograd from scratchlive python

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: a ~20-line reverse-mode autograd (a tiny Value class) builds the graph \(x \to (x-2) \to \text{square} \to L\), runs L.backward(), and prints x.grad == -12. Editable real Python, executed in your browser via Pyodide.

From one knob to many

A model has many parameters, collected as \(\theta\). The loop does not change.

\(\theta \leftarrow \theta - \eta\,\nabla_\theta L(\theta)\)

optimizer = SGD(model.parameters(), lr=0.1) for x, y in loader:    pred = model(x)    loss = loss_fn(pred, y)    loss.backward()    optimizer.step()    optimizer.zero_grad()

model.parameters() → the parameters \(\theta\)

loss.backward() → the gradient \(\nabla_\theta L\)

optimizer.step() → update \(\theta \leftarrow \theta - \eta\nabla_\theta L\)

Example 1: regression

ŷ = wx + b input x target y

Prediction: \(\hat y = wx + b\)  (\(w,b\) are the parameters)

Error: \(L = \frac{1}{m}\sum_i(\hat y_i-y_i)^2\)

Start with a bad line — big errors (dashed).

Measure the error, nudge \(w,b\) to shrink it.

Repeat — the errors keep shrinking.

Good fit: the errors are small.

Regression in PyTorch

# the knob: a line y = wx + bmodel = nn.Linear(1, 1)
# how we score error: mean squared errorloss_fn = nn.MSELoss()
# how we update the parametersoptimizer = torch.optim.SGD(model.parameters(), lr=0.1)
# forward: predict from inputspred = model(x)
# measure how wrong we areloss = loss_fn(pred, y)
# autograd fills in the gradientsloss.backward()
# one step downhill: update w and boptimizer.step()
# clear gradients before the next batchoptimizer.zero_grad()

Run it: train the linelive python

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: a real numpy training loop (predict \(\to\) MSE \(\to\) gradients \(\to\) update) fits \(\hat y = wx+b\) to noisy data. Drag the learning rate and steps, press Train, and watch the line snap to the data as the loss drops. Runs in your browser via Pyodide.

Example 2: classification

Now the target is a category, not a number.

Regression

\(\hat y = 217.3\)

a number

Classification

\(\hat y = \text{``spam''}\)

a class

But gradient descent needs a smooth output — so we predict a probability instead.

Logistic regression: predict a probability

σ(z) score z = w·x + b

Compute a score:

\(z = w\cdot x + b\)

Squash it into a probability:

\(P(\text{spam}\mid x)=\sigma(z)=\frac{1}{1+e^{-z}}\)

Loss for classification

Cross-entropy punishes being confidently wrong.

Recall \(\hat y = P(y=1\mid x) = \sigma(z)\) — the probability the model predicts.

\(L = -\big[y\log \hat y + (1-y)\log(1-\hat y)\big]\)

True label spam \(\Rightarrow y=1\), so \(L = -\log \hat y\).

confident & right

\(\hat y=0.9\)

\(L=-\log 0.9 \approx 0.11\)

confident & wrong

\(\hat y=0.1\)

\(L=-\log 0.1 \approx 2.30\)

Playground: cross-entropy losslive

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: drag the score \(z\) to see the sigmoid probability \(\sigma(z)\) and the cross-entropy loss — being confident and wrong is punished hard. Flip the true label to compare. Runs instantly in your browser.

Many classes: softmax

One score per class; softmax turns scores into probabilities.

\(P(y=k\mid x)=\dfrac{e^{z_k}}{\sum_j e^{z_j}}\)

0.10
dog
0.70
cat
0.15
bird
0.05
fish

Next-token prediction is exactly this, over a vocabulary.

Playground: softmax & temperaturelive

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: adjust the class scores (logits) and a temperature slider and watch the softmax probabilities sharpen or flatten. Runs instantly in your browser.

The full loop, annotated

# loop over the training datafor x, y in loader:
# forward pass: predictpred = model(x)
# measure the errorloss = loss_fn(pred, y)
# backward pass: compute gradientsloss.backward()
# update the parametersoptimizer.step()
# reset gradients for the next stepoptimizer.zero_grad()

This is the pattern to recognize for the rest of the module.

Why mini-batches?

Full batch
entire dataset
1 update / epoch
Mini-batch
batchbatchbatchbatch
4 updates / epoch

Full batch: few, expensive, smooth steps. Mini-batch: many, cheap, noisy steps — usually faster progress, and GPU-friendly.

Split the data: train / validation / test

train

updates the weights

validation

chooses settings

test

final, one-time estimate

Never tune on the test set.

More power is not always better

Same data, increasingly flexible models.

input x target y
underfit good fit overfit

Watch train and validation loss

stop here train validation epochs → loss

Training loss keeps dropping.

Validation loss starts rising — that growing gap is overfitting. Stop where validation bottoms out.

Playground: overfittinglive python

Interactive demo — runs live in your browser (open the slides to try it).

Interactive on the live slides: raise the polynomial degree and watch training loss keep dropping while validation loss turns up - overfitting appears as a U-shaped validation curve. Fit with numpy in your browser via Pyodide.

Three ways to fight overfitting

More data

show the model more of the world

Regularize

prefer simpler weights

\(L(\theta) + \lambda\lVert\theta\rVert^2\)

Early stop

stop when validation turns up

What carries forward?

Only the model and the loss change. The loop stays the same.

TaskModel (architecture)Loss
Regression (today)Linear / MLPsquared error
Language modelTransformercross-entropy (next token)
Image generationDiffusion U-Netsquared error (predict noise)

forward → loss → backward → step, every time.

Recap - next: learned representations

  • ✓  A model learns by minimizing a loss.
  • ✓  Autograd computes gradients through a graph.
  • ✓  Regression uses squared error; classification uses cross-entropy.
  • ✓  Validation loss catches overfitting.

L18: if linear models use fixed features, how do neural nets learn their own?