A network learns by measuring how wrong it is — then flowing that error backward to every weight.
CS231n covers a lot — k-NN, linear classifiers, CNNs, detection, segmentation, Transformers. But strip it down and one idea carries all of it: a model produces scores, a loss measures how wrong those scores are, and backpropagation computes how a small nudge to each weight would change that loss — so every weight knows which way to move.
The course's signature move is refusing to let you call a framework until you've computed those gradients by hand. That's the bet: if you can run backprop through a tiny circuit with a pencil, you understand every deep model — convnet, Transformer, or otherwise — because they are all just bigger versions of the same circuit. This page walks that exact path: pixels → scores → loss → gradient → a better network.
“Backpropagation is a beautifully local process. Every gate gets some inputs and can right away compute its output value and the local gradient.” — CS231n notes
The promise: a number you can push downhill
From pixels to a score, from a score to a loss you can minimize
Step 1 · Build it, don't call it
Most courses give you .fit(). CS231n makes you write the gradient first.
Here's the whole course in one contrast. A typical deep-learning tutorial hands you a finished model and a magic button. CS231n hides the button until you've built the machinery behind it — and that machinery is the same in every modern model:
Computer vision is the vehicle, not the destination. Image classification keeps the math concrete and the results visible, but what you actually learn — score functions, loss functions, and gradients — transfers to any deep model. This page picks the spine of the course: how a convolutional network sees, and how backprop teaches it to see better.
From the notes: “our approach will be to start with random W and then iteratively refine it, making it slightly better each time.” That loop — score, loss, gradient, update — is the whole game.
Step 2 · The score function
Flatten the image to a vector, multiply by a weight matrix, get one number per class.
You've seen the four stages. Stage one is the score: turning a picture into a number for every possible label. We start here because everything downstream — loss, gradient — is computed from these scores.
Take an image, unroll its pixels into one long column x. The score function is a single matrix multiply: f(x) = Wx + b. Each row of the weight matrix W is one class's template; the dot product of that row with the pixels is that class's score. A high score means “this image looks like my class.”
For CIFAR-10 the shapes are concrete: a 32×32×3 image is 3,072 numbers, there are 10 classes, so W is 10×3072 and one matrix multiply evaluates all ten class templates at once. No loops — just Wx + b.
A linear classifier is template matching: each class learns one template, and scoring is just a dot product — “how much does this image line up with my template?” A convnet, later, learns many small templates instead of one giant one — but the scoring idea never changes.
Step 3 · The loss, and the hill
One number says how wrong the scores are — and the gradient says which way is downhill.
Scores alone can't train anything — the network needs a single number grading them, so it knows whether a change helped. That number is the loss, and minimizing it is the entire job.
The loss is one number measuring how unhappy we are with the scores. CS231n's worked example uses the multiclass SVM loss (also called hinge loss): every wrong class should score at least a margin Δ below the correct class, or it costs us. With scores [13, −7, 11], correct class first, and margin Δ = 10:
Wrong class “−7”: is it a margin below 13?
Penalty is max(0, wrong − correct + Δ).
Wrong class “11”: is it a margin below 13?
Only 2 below the correct score — that violates the margin of 10.
Add the penalties
The loss for this one example is the sum over wrong classes.
Now picture L as the height of a landscape over all possible weights. Training is a blindfolded hiker walking downhill. The direction of steepest descent is the negative gradient — the vector of how L changes if you nudge each weight a hair. The update is one line:
“weights += − step_size · gradient” — step size η is the learning rate
Everything now hinges on one question: how do we get ∂L/∂W — the gradient with respect to millions of weights — efficiently? That single question is what backpropagation answers, and it's where Part B begins.
Aside: real training uses mini-batch SGD — estimate the gradient from a small batch of examples (say 256) instead of the whole dataset, because a batch is already a good approximation and far cheaper.
Backpropagation: the gradient, by hand
A circuit · the chain rule · three gate patterns you can read off by eye
Step 4 · A function is a circuit
Draw the computation as a graph of gates — then every gate only has to know its own little job.
Part A left us needing the gradient of the loss with respect to every weight. Backprop gets it by turning the function into a circuit and letting each piece do something tiny and local.
CS231n's running toy is deliberately small so you can do it with a pencil: f(x, y, z) = (x + y) · z. Draw it as a computational graph — a wiring diagram of gates. Name the intermediate wire q = x + y, so f = q · z. Two gates, three inputs, one output.
The whole trick: each gate, the moment it computes its output, can also compute its local gradient — how its output changes when each of its inputs wiggles — knowing nothing about the rest of the circuit. The add gate knows ∂q/∂x = 1 and ∂q/∂y = 1. The multiply gate knows ∂f/∂q = z and ∂f/∂z = q. That locality is what makes backprop work at any scale.
A real network is this same picture with millions of gates. Nothing new is needed at scale — just a lot more gates, each still doing the same two tiny jobs: compute an output, and compute its local gradient.
Step 5 · Run backprop yourself
Forward fills in the values; backward chains local gradients from the end to every input.
You have the circuit and you know each gate's local gradient. Now do the actual computation — forward, then backward — one step at a time. This is the moment CS231n is built around, so do it by hand.
We plug in CS231n's exact numbers: x = −2, y = 5, z = −4. Step the demo forward and the green values appear; keep stepping and the amber gradients flow back from the output. The rule at every gate is the chain rule: gradient on an input = the gradient arriving from downstream × this gate's local gradient.
Press Next to advance · forward fills values, backward flows gradients
Read the final gradients: ∂f/∂x = ∂f/∂y = −4 and ∂f/∂z = 3. So raising z by a tiny ε raises f by 3ε; raising x or y by ε lowers f by 4ε. Those four numbers are exactly what an optimizer needs to nudge each input the right way.
The chain rule, line by line
forward: q = x + y = (−2) + 5 = 3
forward: f = q · z = 3 · (−4) = −12
start: ∂f/∂f = 1
mul gate: ∂f/∂z = q = 3 · ∂f/∂q = z = −4
add gate: ∂f/∂x = ∂f/∂q · 1 = −4
add gate: ∂f/∂y = ∂f/∂q · 1 = −4
Notice the add gate just copied the −4 to both inputs, and the multiply gate handed each input the other input's value. Those are patterns you can learn to read off by eye — the next step.
Step 6 · The three gate patterns
Add distributes, max routes, multiply switches — read gradients off the page without algebra.
You just watched the add gate copy a gradient and the multiply gate swap inputs. Those aren't coincidences — three gates cover almost everything, and each has an intuitive backward behaviour.
The multiply pattern has a sharp consequence the notes call out: if one input is tiny and the other huge, the tiny input gets a huge gradient and the huge input gets a tiny one. That's why scaling the data matters — multiply gradient size is tangled up with input size. Intuitions like this are exactly what survives long after a specific architecture goes out of fashion.
Step 7 · Now the formula reads as relief
Everything you just did by hand is one line: multiply local gradients along every path.
You've run a circuit forward and backward and seen the gate patterns. Only now does the textbook equation arrive — and it should feel like a summary of what you already did, not a new hurdle.
The chain rule says: to get the gradient of the output with respect to some input, walk the path from output to input and multiply the local gradients along the way. For our circuit, the path from f to x goes through q:
downstream gradient × local gradient — the same −4 you computed by hand
A real network just has more factors in the product — one per layer the signal passes through. Forward, you cache each gate's output. Backward, you start with ∂L/∂L = 1 at the loss and multiply local gradients back to every weight, reusing the cached values. That's the whole algorithm, and it costs about the same as one forward pass.
Backprop is not a special trick for neural nets — it's just the chain rule applied systematically over a graph, computing all gradients in a single backward sweep instead of one at a time. Cheap, exact, and identical whether the graph is two gates or two billion.
Into the convnet — the same idea, applied to images
Local connectivity · parameter sharing · a convolution you slide by hand
Step 8 · Why not just a giant matrix?
Connect each neuron to a small patch and share weights — 105 million connections collapse to 35 thousand.
Part B gave you the engine — backprop computes the gradient for any graph. Now we change the graph to suit images, and watch two design choices shrink the problem by orders of magnitude.
A fully-connected layer wires every output to every input pixel. For images that explodes. Convnets fix it with two ideas, both from the notes:
Local connectivity. Each neuron looks only at a small spatial patch (its receptive field), but across the full depth of channels. A 5×5 filter on a 3-channel image needs just 5·5·3 = 75 weights, not one per pixel in the whole image.
Parameter sharing. If a vertical-edge detector is useful at one spot, it's useful everywhere — so the same filter slides across the whole image. The notes' headline example: AlexNet's first layer has 290,400 output neurons. Wired independently that's 105,705,600 parameters; sharing weights across positions drops it to 34,944.
Step 9 · Slide a filter yourself
A 3×3 grid of weights, slid across the image, lights up exactly where it finds its pattern.
You know convolution shares one small filter across the image. Now do the sliding — see how a single edge detector turns raw pixels into a map of where the edges are.
A convolution takes the small grid of weights — the kernel (or filter) — and slides it over the image. At each stop it multiplies the kernel against the patch beneath it, sums the products, and writes that one number into an output grid called a feature map. The stride is how far it jumps between stops.
Our toy: a 5×5 grayscale patch with a vertical bright→dark edge, and a 3×3 vertical-edge kernel (left column +1, right column −1). Slide it with stride 1 and watch where it fires.
Move the window — or press ← ↑ → ↓ — and watch the output
5×5 patch · brighter = higher
3×3 kernel K
3×3 feature map (output)
After ReLU
The kernel “lights up” exactly where the image goes bright → dark. At the edge the output is +27; deep in the dark region it's 0. A dark→bright edge would give −27 — which ReLU clips to 0. That's a learned edge detector, evaluated by hand.
The dot product for the top-left placement, and the output size
window covers cols 0–2 → pixels [9, 9, 0] in every row.
= 1·9 + 0·9 + (−1)·0 (row 0)
+ 1·9 + 0·9 + (−1)·0 (row 1)
+ 1·9 + 0·9 + (−1)·0 (row 2)
= 9 + 9 + 9 = 27.
output size = (W − F + 2P)/S + 1
here W=5, F=3, P=0, S=1 → (5 − 3 + 0)/1 + 1 = 3.
a 7×7 input with the same 3×3 filter at stride 2 → (7 − 3)/2 + 1 = 3.
This feature map is just more numbers — feed it to the next conv layer, eventually to a score function, a loss, and the very same backprop you ran in Step 5. Pixels → scores → loss → gradient → update. A convnet is that loop, stacked.
Step 10 · The stack you can now build
Bottom-up: each architecture answers a pain you already felt one rung below.
You can now score, measure loss, and flow the gradient back — through a linear layer or a conv layer. That's the rung CS231n stands you on so it can build the rest, never as a black box.
The course's arc is deliberately bottom-up. Each step exists because the previous one fell short on a problem you've already met:
| Rung | What it adds | The pain it answers |
|---|---|---|
| k-NN | memorize the training set | no learning at all; slow at test time |
| Linear classifier | one template per class (Step 2) | one template can't capture a cat in all poses |
| 2-layer net | a hidden layer + a nonlinearity | linear models can't bend decision boundaries |
| CNN | local filters + weight sharing (Step 8–9) | fully-connected nets ignore image structure and explode in size |
| ResNet · ViT | depth that trains; attention | very deep / global context, beyond this page |
By the end of CS231n's core, you can —
The course goes much further — pooling, batch norm, learning-rate schedules, detection, segmentation, Transformers, and a self-chosen final project where teams train multi-million-parameter nets. This page taught the spine; the notes teach the rest.
Step 11 · Can you re-explain it?
If you can answer these from memory, you've learned the one thing CS231n exists to teach.
One running thread carried the whole page — pixels become scores, a loss grades them, backprop flows the gradient to every weight. Test it: answers are folded; open each only after you've tried.
Why does CS231n make you build backprop before using a framework?
Because every deep model is the same circuit — score, loss, gradient, update. Once you've run the gradient by hand through a tiny graph, no network is magic, and you can debug or extend any of them.
What does the score function f(x) = Wx + b compute?
One number per class. Each row of W is a class template; its dot product with the flattened pixels x is that class's score. For CIFAR-10, one 10×3072 multiply scores all ten classes at once.
In the circuit f = (x+y)·z with x=−2, y=5, z=−4, what is ∂f/∂x and why?
It's −4. Backward, the multiply gate gives the q-wire its other input z = −4; the add gate copies that −4 unchanged to both x and y. So ∂f/∂x = ∂f/∂y = −4, and ∂f/∂z = q = 3.
How do the add, max, and multiply gates send gradient backward?
Add distributes the incoming gradient unchanged to all inputs. Max routes all of it to the largest input and zero to the rest. Multiply scales each input's gradient by the other input's value.
What two ideas make a conv layer cheap, and by how much?
Local connectivity (each neuron sees only a small patch) and parameter sharing (one filter reused everywhere). In AlexNet's first layer they cut 105,705,600 independent parameters down to 34,944.
For a 5×5 input, a 3×3 filter, stride 1, pad 0 — what's the output size?
(W − F + 2P)/S + 1 = (5 − 3 + 0)/1 + 1 = 3, so a 3×3 feature map. Switch to stride 2 on a 7×7 input and it's (7 − 3)/2 + 1 = 3 as well.
“Backpropagation can be thought of as gates communicating to each other whether they want their outputs to increase or decrease, and how strongly, so as to make the final output value higher.” — CS231n notes, Backpropagation
The course that turned a generation into people who build, not just call.
CS231n's lecture notes became an unofficial textbook precisely because they refused to hide the gradient. Its way of teaching — concrete numbers, build-it-yourself, computer vision as the vehicle — shaped how deep learning is taught everywhere.
The unofficial textbook
cs231n.github.io is cited and assigned worldwide; for many researchers it was their first real encounter with backprop, convnets, and optimization.
Gradients you can trust
The score → loss → gradient → update loop is identical in Transformers and diffusion models. The intuition outlives every specific architecture.
Build, then call
Write the layer by hand first; reach for the framework second. That order — now standard in serious courses — is CS231n's lasting pedagogy.
One course — and a generation learned to open the box instead of trusting it.