← All workbooks

ID2223 Scalable Machine Learning and Deep Learning

How the grade works

How to use these notes: read one section, close the doc, and explain it out loud as if teaching a friend. Then take the quiz.


LECTURE 2: Regression and Classification

2.1 What linear regression is trying to do

You have m houses. For each one you know some features (living area x₁, number of bedrooms x₂) and the target (price y). You want a function that predicts the price of a house you haven't seen.

Linear regression assumes the price is roughly a weighted sum of the features:

ŷ = w₁x₁ + w₂x₂ + b

"Learning" means finding the w and b that make ŷ close to y on the training data.

Example. Suppose the model is price = 0.3·area + 10·bedrooms + 50. For 100 m² with 3 bedrooms, it predicts 30 + 30 + 50 = 110.

2.2 The bias trick (x₀ = 1)

The problem: b behaves differently from the weights, because it doesn't multiply any feature. So you'd need separate formulas for b everywhere: in the prediction, in the gradient, and in the update.

The trick: any constant can be written as b·1. So give every example an extra "feature" x₀ that always equals 1, and rename b to w₀:

ŷ = w₀·1 + w₁x₁ + w₂x₂ = wᵀx, with x = [1, x₁, x₂] and w = [w₀, w₁, w₂]

Check with numbers: x = [1, 100, 3] and w = [50, 0.3, 10] give 50 + 30 + 30 = 110. That's the same answer, because the model is unchanged. Only the notation is simpler.

What you gain: one formula for everything. The prediction is wᵀx, the gradient is ∇J(w), and the update is w ← w − η∇J(w). The bias is learned automatically as just another weight. For the whole dataset, put a column of 1s in front of the data matrix X, and every prediction at once is ŷ = Xw, which is how NumPy and GPUs compute it.

The bias neuron in neural networks (Lecture 3), which "always outputs 1", is the same trick.

Exam (re-exam Q1): "Why x₀ = 1?" → to include the bias term in the weight vector. It doesn't normalize anything (a constant 1 rescales nothing). It doesn't remove the need for gradient descent. And it doesn't reduce overfitting, because the model and its number of parameters are exactly the same.

2.3 Measuring "how wrong": residuals and MSE

The residual for example i is y⁽ⁱ⁾ − ŷ⁽ⁱ⁾, how far off one prediction is. You need one number for the whole dataset, so you can compare different choices of w.

Why not just add up the residuals? Positive and negative errors cancel. Errors of +50 and −50 sum to 0, which looks perfect when it isn't.

Mean Squared Error: J(w) = (1/m) Σ (ŷ⁽ⁱ⁾ − y⁽ⁱ⁾)²

Why squaring is a good choice:

  1. It's always positive, so nothing cancels.
  2. Large errors count much more. An error of 10 contributes 100, and an error of 1 contributes 1. The model is pushed hard to avoid big mistakes.
  3. It's differentiable everywhere (a smooth parabola), so gradient descent can follow its slope. |error| has a sharp corner at 0, which is less convenient.
  4. For linear regression it's convex, a single bowl with one bottom, so gradient descent can't get stuck in a wrong valley.

Example: three houses with errors 2, −1 and 5 give MSE = (4 + 1 + 25)/3 = 10. Notice how the single error of 5 dominates.

Exam (Jan Q3): "Why is MSE reasonable for linear regression?" → it penalizes large errors heavily and is differentiable. The wrong options: it guarantees nothing about fairness (see 2.6). It doesn't give sparse solutions (that's L1, in Lecture 4). "Prevents local minima" isn't a property of MSE in general. Convexity here comes from MSE combined with a linear model, and it fails with a sigmoid (see 2.8).

2.4 Gradient descent: how we actually find w

Intuition (from the slides): you're lost on a mountain in thick fog, and you can only feel the slope under your feet. To reach the valley floor, you step in the steepest downhill direction and repeat.

Here, the "mountain" is the cost surface J(w): each point is one choice of weights, and the height is how wrong that choice is.

The algorithm:

  1. Start with random w.
  2. Compute the gradient ∇J(w), a vector of slopes, one per weight. It points uphill, toward increasing error.
  3. Step the opposite way: w ← w − η ∇J(w)
  4. Repeat until the gradient is about 0 (flat ground, the minimum) or you run out of steps.

Why the gradient tells you the direction: ∂J/∂wⱼ says how much the error changes if you increase wⱼ a little. If it's positive, increasing wⱼ makes things worse, so you decrease it. The minus sign does exactly that.

A tiny worked example. One weight, J(w) = (w − 3)², so the minimum is at w = 3. The gradient is 2(w − 3).

2.5 Learning rate and the three variants of gradient descent

Learning rate η is the step size.

Exam (re-exam Q2): "What happens if the learning rate is too large?" → the algorithm may diverge. Not "faster and more stable" (it's unstable). Not "underfitting" (that's about model capacity). Not "the gradient becomes zero" (it usually grows).

How much data do you use to compute each gradient?

Variant Each step uses Why you'd use it Downside

| Batch GD | all m examples | exact gradient, smooth path to the minimum | one step needs a full pass over the data, which is far too slow for millions of examples | | Stochastic GD (SGD) | 1 random example | very cheap steps, starts improving immediately; the noise can help it escape shallow local minima | noisy and zig-zagging; it keeps bouncing around the minimum instead of settling | | Mini-batch GD | e.g. 32–256 examples | the middle ground: steadier than SGD, much cheaper than batch, and matrix operations on a batch run efficiently on GPUs | — (this is the deep-learning default) |

Feature scaling: if area ranges from 0 to 300 and bedrooms from 1 to 5, the cost surface becomes a long, narrow valley. Gradient descent zig-zags across the steep direction and crawls along the flat one. Scaling all features to similar ranges (for example, standardizing to mean 0 and std 1) makes the bowl round, so the steps head straight to the minimum.

2.6 Critical view: MSE and fairness

The example from the slides: a model predicts student performance to decide where resources go. It has a very low MSE, but it was trained mostly on data from well-resourced schools. It systematically underpredicts students from under-resourced schools, who then get fewer resources, which reinforces the original inequality.

Why this happens: MSE averages the error over everyone. If one group is 90% of the data, the model can do great on them and badly on the other 10%, and still have a low average. MSE minimizes total error, not fairness.

What to ask: "Better for whom?" Check the error separately for each group, and consider group-weighted metrics that balance fairness and accuracy.

Exam: any option claiming MSE or a low loss "guarantees fairness across subgroups" is wrong.

2.7 Classification: why linear regression fails

The task: predict cancer (1) or no cancer (0) from tumor size. We want a probability of class 1.

Why not fit a straight line? (1) A line's outputs aren't limited to [0,1], so it can predict 1.7 or −0.4, which aren't probabilities. (2) One very large tumor in the data tilts the whole line and moves the decision boundary, misclassifying other examples. The slides call this a "bad model".

2.8 Logistic regression

Fix: keep the weighted sum t = wᵀx, but pass it through a function that squashes any number into (0,1):

Sigmoid: σ(t) = 1 / (1 + e⁻ᵗ)

So p̂ = σ(wᵀx) can be read as "the probability this is class 1". Decision rule: predict 1 if p̂ ≥ 0.5, which happens exactly when wᵀx ≥ 0. So the decision boundary is still a straight line (or plane) where wᵀx = 0. The sigmoid only turns the distance from that line into a confidence.

Example: with wᵀx = 2, p̂ = 1/(1 + e⁻²) ≈ 0.88, so predict cancer with 88% confidence.

Exam (re-exam Q3): "Correct statement about the sigmoid?" → it maps real numbers to probabilities. Its range is (0,1), not [−1,1] (that's tanh). It flattens for large inputs; it isn't linear there. It outputs 0.5 only at t = 0.

2.9 Why logistic regression uses log loss instead of MSE

Option 1, MSE: J = (1/m)Σ(σ(wᵀx) − y)². Combined with the sigmoid, this surface is non-convex: it has bumps and several valleys. The slides' test for non-convex is that the line joining two points on the curve crosses the curve. Gradient descent can then settle in a local minimum that isn't the best solution.

Option 2, log loss (cross-entropy):

Why it behaves well: look at y = 1.

2.10 Multi-class: softmax regression

With k classes (say digits 0–9), compute one score per class, sⱼ = wⱼᵀx, then turn the scores into probabilities with softmax:

p̂ⱼ = e^{sⱼ} / Σₗ e^{sₗ}

Example: scores [2, 1, 0] → e² ≈ 7.39, e¹ ≈ 2.72, e⁰ = 1, with a sum of ≈ 11.1 → probabilities ≈ [0.67, 0.24, 0.09]. Predict class 1 (the first one).

The loss is cross-entropy, −log(p̂ of the true class), the same idea as log loss.

Exam (Jan Q4): the best pairing for multi-class is softmax + cross-entropy. Sigmoid treats each output independently, so the outputs don't sum to 1 and it doesn't give a distribution over mutually exclusive classes. MSE with probability outputs gives weak gradients and the non-convexity problem described above.


LECTURE 3: Deep Feedforward Networks

3.1 From biological neurons to the perceptron

3.2 How a perceptron learns

Hebb's rule: "neurons that fire together wire together". If neuron A often helps trigger neuron B, strengthen the connection between them.

Perceptron rule: for each training example, predict, then update:

w_{i,j} ← w_{i,j} + η (y_j − ŷ_j) x_i

Read it case by case:

The perceptron convergence theorem says that if the classes are linearly separable, this rule finds a separating line.

3.3 The XOR problem: why one layer isn't enough

XOR truth table: (0,0)→0, (0,1)→1, (1,0)→1, (1,1)→0.

Plot those four points on a square. The 1s sit on one diagonal and the 0s on the other. No single straight line has both 1s on one side and both 0s on the other. XOR is not linearly separable.

A perceptron's decision boundary is always one straight line (wᵀx = 0), so it cannot learn XOR, however long you train it. The slides show that fitting a linear model to XOR with MSE gives w₁ = w₂ = 0 and b = 0.5, which predicts 0.5 for everything. The best a line can do is give up.

The fix: add a hidden layer. For example:

In the (h₁, h₂) space, the four points become (0,0)→0, (1,0)→1, (1,0)→1, (1,1)→0, and now one line separates them. The hidden layer re-represented the input with non-linear features, so the problem became linearly separable.

Exam (Jan Q5, re-exam Q4): the reason a hidden layer solves XOR / the key advantage of an MLP over a perceptron → it learns non-linear feature transformations / can model non-linear decision boundaries. The wrong options: depth doesn't reduce overfitting (it tends to increase it). Hidden layers don't replace bias neurons. They don't guarantee faster convergence or lower cost; MLPs are slower and costlier than a perceptron.

3.4 The Multi-Layer Perceptron (MLP)

3.5 Why the activation function matters (two exam traps)

Trap 1: the step function can't be used with backprop. Training needs ∂(error)/∂(weight). The step function is flat everywhere, so its derivative is 0, and at z = 0 it jumps, so the derivative is undefined. Every gradient that passes through a step neuron becomes 0, so no weight before it ever learns. That's why MLPs use smooth functions:

Exam (re-exam Q5): "Why not a step function in hidden layers with backprop?" → it's not differentiable (gradient zero or undefined). Being slow, producing non-binary outputs or causing overfitting are all false.

Trap 2: linear activations make depth useless. If every layer computes a linear function, then layer 2 applied to layer 1 is W₂(W₁x) = (W₂W₁)x = W′x, which is just one matrix. A 100-layer linear network is mathematically the same as a single-layer linear model, and it still can't solve XOR. Non-linearity is what makes depth add power.

Exam (re-exam Q6): "All activations linear → ?" → it behaves like a single linear model. It isn't more expressive. It can still be trained (it's just weak). It doesn't become non-convex (non-linearity is what causes non-convexity).

3.6 Choosing the output layer and the loss

The output layer depends on what you're predicting:

Task Output layer Why Loss

| Regression (a price) | linear, no activation | the output can be any real number | MSE | | Binary (spam or not) | 1 sigmoid neuron | the output is P(class 1) in (0,1) | binary cross-entropy | | Multi-class (digit 0–9) | k neurons + softmax | the outputs form a distribution that sums to 1 | cross-entropy |

3.7 Backpropagation: how an MLP learns

The problem: a deep network might have millions of weights. For gradient descent you need ∂E/∂w for every weight. Nudging each weight one at a time to see what happens would take millions of forward passes per step, which is impossible.

The tool: the chain rule. If y = g(x) and z = f(y), then dz/dx = (dz/dy)·(dy/dx). Effects multiply along a chain. If x affects z through two paths, add the two chains together.

A weight deep in the network affects the error through a chain: weight → that neuron's input → its output → the next layer → … → output → error. So its gradient is the product of the local derivatives along that chain.

The algorithm (for each mini-batch):

  1. Forward pass: compute and store every neuron's output, layer by layer, and the final error.
  2. Backward pass: start at the output, where you know how the error depends on each output. Move back one layer at a time. Each layer's gradients are computed from the gradients of the layer after it, plus its local derivatives. Intermediate results are reused instead of recomputed, so all gradients cost about as much as one extra forward pass.
  3. Update: a gradient-descent step on every weight, w ← w − η ∂E/∂w.

The worked example from the slides. 2 inputs, 2 hidden neurons (h₁, h₂), 2 outputs (o₁, o₂), sigmoid everywhere, squared error E = Σ ½(target − out)². We want ∂E/∂w₅, where w₅ connects h₁ to o₁.

How w₅ affects E: w₅ → net_o1 (o₁'s weighted input) → out_o1 (after the sigmoid) → E. So:

∂E/∂w₅ = ∂E/∂out_o1 × ∂out_o1/∂net_o1 × ∂net_o1/∂w₅

  1. ∂E/∂out_o1 = (out_o1 − target_o1): how wrong the output is. If o₁ is too high, this is positive.
  2. ∂out_o1/∂net_o1 = out_o1(1 − out_o1): the sigmoid's derivative. It shows how sensitive the output is to its input. Near 0 or 1 this is tiny, which is the root of vanishing gradients (4.3).
  3. ∂net_o1/∂w₅ = out_h1: since net_o1 = w₅·out_h1 + w₆·out_h2 + b, the effect of w₅ is the size of the signal it carries.

Multiply the three factors, then update w₅ ← w₅ − 0.5 × (that product), with η = 0.5.

In words: a weight changes in proportion to how wrong the output is × how responsive the neuron is × how active its input was. Make sure you can say that sentence.

For a hidden-layer weight (say w₁, from input x₁ to h₁), the chain is longer. h₁ affects both outputs, so you add the contributions through o₁ and through o₂. That's the "two paths" chain rule.


LECTURE 4: Training Deep Feedforward Networks

Deep networks are powerful but hard to train. The slides name three challenges, each with its own set of fixes. Learn them as problem → cause → fixes.

4.1 Challenge 1: Overfitting

The problem: a network with millions of parameters has a huge degree of freedom. It can fit almost any dataset, including the noise. It can memorize the training set, getting training error near 0 but high error on new data.

The general fix: reduce the effective freedom of the model. There are four techniques.

Early stopping

Plot the error over training. Training error keeps falling, because the model keeps fitting the training data more closely. Validation error falls at first (the model is learning real patterns), then reaches a minimum, then rises (it's now learning noise specific to the training set). Stop at that validation minimum and keep those weights.

Exam (re-exam Q7): the signal to stop → validation loss starts increasing. "Training loss stops decreasing" is wrong, because training loss usually keeps decreasing while you overfit. Accuracy rarely reaches 100%, and gradients vanishing is a separate issue.

L1 and L2 regularization

Add a penalty for large weights: J_total = J(w) + λ·R(w).

Why large weights mean overfitting: to pass exactly through every noisy training point, a model needs extreme weights that make the function swing sharply. Penalizing large weights forces a smoother, simpler function. λ sets how strong the penalty is.

Dropout

At each training step, each neuron is temporarily removed with probability p (e.g. 0.5). Its output is set to 0 for that step, and different neurons are dropped on every step.

The analogy from the slides: would a company work better if each employee tossed a coin every morning to decide whether to come in? Surprisingly, yes in one sense. No task can depend on one specific person, so knowledge gets spread across the team and nobody is a single point of failure.

Why it reduces overfitting:

  1. A neuron can't rely on particular other neurons always being present, so it must learn features that are useful on their own and redundant across neurons. Memorizing through fragile co-adaptations stops working.
  2. With N neurons, there are 2ᴺ possible thinned networks. Each step trains a different one, all sharing weights. The final network acts like an average of a huge ensemble, and ensembles generalize better.

At inference, dropout is turned off. All neurons are used, and outputs are scaled so the expected activation matches what the network saw during training.

Exam (Jan Q6): why dropout reduces overfitting → it forces neurons to learn redundant, more robust representations. It doesn't increase the dataset (that's data augmentation). It doesn't remove layers at inference (all neurons are used). It can't make optimization convex.

Data augmentation

More data is the best cure for overfitting, but real data is expensive. So create realistic variations of the existing examples, such as images that are shifted, rotated, flipped or have brightness changed. A cat rotated by 10° is still a cat. The model learns that these changes don't matter instead of memorizing exact pixels.

4.2 Challenge 2: Vanishing and exploding gradients

The problem: backprop sends the error from the output back toward the input. In deep networks, the gradients often shrink as they travel down the layers. The lower layers get almost no update, so their weights stay near their random initial values and never learn useful features, even though the top layers train fine. That's vanishing gradients. The opposite case, where gradients grow layer by layer until the updates are huge and training blows up, is exploding gradients (common in RNNs).

Why it happens (the slides' derivation). Take a network with just one neuron per layer, with activations aⱼ = σ(zⱼ). By the chain rule, the gradient for the first layer is a product with one factor per layer:

∂C/∂b₁ = σ′(z₁) · w₂ · σ′(z₂) · w₃ · σ′(z₃) · w₄ · σ′(z₄) · ∂C/∂a₄

The sigmoid's derivative is at most 0.25, and with typical small weights each wⱼ·σ′(zⱼ) is below 1. Multiply many numbers below 1 and the product shrinks exponentially. For example, 0.25¹⁰ ≈ 0.000001. That's vanishing. If each factor is above 1 (large weights), the product grows exponentially instead. That's exploding.

There are four fixes, each addressing a different part of that product.

Fix 1: Weight initialization

Why initialization matters at all: the cost function of a neural net is non-convex, and where gradient descent starts strongly affects where it ends up. Two requirements:

  1. Break symmetry. Suppose every weight starts at the same value. Then every neuron in a layer computes the same output, gets the same gradient, receives the same update, and stays identical forever. A layer of 100 neurons acts like 1. Random initialization makes the neurons different from the start, so they can learn different features.
  2. Keep the signal flowing in both directions. If the weights are too small, activations shrink layer by layer on the way forward and gradients shrink on the way back (vanishing). If they're too large, both explode. You want the variance of each layer's outputs to roughly equal the variance of its inputs, and the same for gradients going backward.

That's done by scaling the random weights based on how many inputs and outputs each layer has:

Exam (re-exam Q8): "Why is proper initialization critical?" → it avoids symmetry and ensures signal flow. It doesn't affect inference speed or dataset size, and it doesn't eliminate overfitting.

Fix 2: Non-saturating activation functions

The sigmoid saturates: for large |z| it's flat, so σ′ ≈ 0. That's the tiny factor in the product above.

ReLU(z) = max(0, z) has derivative exactly 1 for z > 0, so it doesn't shrink the gradient at all on that side. It's also very cheap to compute.

ReLU's problem, dying ReLUs: if a neuron's weighted input becomes negative for every example (for instance, after a big update), its output is always 0 and its gradient is always 0. It never updates again, so it's permanently dead. Sometimes half a network ends up dead.

Fixes for dying ReLUs:

Fix 3: Batch Normalization

The problem: during training, every layer's weights keep changing, so the distribution of inputs to the next layer keeps shifting. Each layer is chasing a moving target, and if those inputs drift into the sigmoid's flat region, gradients vanish.

BN's fix: inside the network, before or after each layer's activation, normalize the values to a consistent range. For each mini-batch B, with m_B examples:

  1. Compute the batch mean μ_B and standard deviation σ_B of each feature.
  2. Normalize: x̂ = (x − μ_B) / √(σ_B² + ε). Now the values have mean ≈ 0 and variance ≈ 1. The tiny ε avoids division by zero.
  3. Scale and shift: z = γ ⊗ x̂ + β, where γ and β are learned parameters (⊗ is element-wise multiplication).

Why step 3? Forcing mean 0 and variance 1 might be wrong for that layer. For example, it would keep a sigmoid always in its middle linear region. With learnable γ and β, the network can choose the best scale and offset, and it can even undo the normalization completely if that helps. BN guarantees a stable distribution, not necessarily a standard one.

Benefits: much less vanishing, larger learning rates are possible, it's less sensitive to initialization, and it has a slight regularizing effect.

Exam (re-exam Q9): what BN primarily does → keeps the inputs to each layer in a stable distribution. It doesn't remove layers, reduce parameters (it adds γ and β), or "remove noise".

Fix 4: Gradient clipping

This one is for exploding gradients: just cap them. If any gradient exceeds a threshold, clip it during backprop before the update. There are two ways, with different effects:

clipnorm is usually the safer choice, because the step still points in the true downhill direction.

4.3 Challenge 3: Training speed (better optimizers)

Plain gradient descent, w ← w − η∇J, only looks at the slope right now. That causes two problems:

  1. On a gentle slope, the gradient is small, so it crawls.
  2. In a narrow valley, the gradient points mostly across the valley, so it zig-zags from wall to wall and makes slow progress along the valley floor.

Momentum

Idea: think of a ball rolling downhill. It doesn't just follow the local slope. It builds up speed and keeps moving in the direction it's been going.

Why it's faster: on a consistent slope, the gradients point the same way every step, so m keeps growing. With β = 0.9, the effective step approaches 10× the plain step. In a zig-zag valley, the across-valley components flip sign each step and cancel out, while the along-valley components add up. It also has enough "speed" to roll over small bumps and out of shallow local minima.

Exam (Jan Q7): the problem momentum primarily addresses → slow convergence. Not vanishing gradients (that's initialization, activations and BN). Not overfitting (that's regularization). Not feature scaling.

Nesterov momentum

This is a small but useful improvement. Compute the gradient not at the current position but slightly ahead, in the direction momentum is already carrying you. It's like looking where you're about to land before deciding how to steer. It corrects overshooting earlier and usually converges a bit faster.

RMSProp

A different problem: one global learning rate is a bad fit when parameters see very different gradient sizes. A parameter with consistently large gradients needs small steps or it overshoots. One with tiny gradients needs big steps or it never moves.

Idea: give each parameter its own adaptive learning rate.

Dividing by √s means that if a parameter's gradients have been large, its step shrinks, and if they've been small, its step grows. "Recent" matters: because it's an exponentially decaying average, old gradients are forgotten. (An older method, AdaGrad, summed all past gradients, so its steps shrank toward zero and training stalled.)

Adam = Momentum + RMSProp

Adam keeps both running averages:

The update moves in the momentum direction, scaled per parameter by the RMSProp factor. It works well with little tuning, so it's the default choice in practice.

4.4 Summary: problem → fixes

Problem Root cause Fixes

| Overfitting | too much freedom, so the model memorizes noise | early stopping, L1/L2, dropout, data augmentation | | Vanishing/exploding gradients | a product of many factors < 1 or > 1 in backprop | Glorot/He initialization, ReLU family, batch norm, gradient clipping (exploding) | | Slow training | plain GD only sees the current slope and uses one global learning rate | momentum, Nesterov, RMSProp, Adam |

Exam strategy: for any "what does X fix" question, find X's row in this table. Options naming problems from a different row are the distractors.


LECTURE 5: Convolutional Neural Networks

5.1 Why plain (dense) networks struggle with images

The lecture starts by building an MNIST digit classifier with the tools from Lectures 2–4, then shows where it breaks.

Step 1: a one-layer network. Each MNIST image is 28×28 grey pixels. Flatten it into a vector of 784 numbers and connect every pixel to each of 10 output neurons (one per digit). For a batch of 100 images, X is 100×784, W is 784×10, and the scores are XW + b. Apply softmax to each row, use cross-entropy as the cost, and update with gradient descent, W ← W − η∇J(W). This is just softmax regression from 2.10.

Step 2: make it better with everything from Lecture 4. Add hidden layers (sigmoid at first), keep softmax at the output, then upgrade: He initialization, ReLU, Adam, dropout against overfitting, and a learning rate that starts fast and decays exponentially. Accuracy climbs to about 98%, then stalls.

Why it stalls: flattening throws away shape. Once the image is a 784-long list, the network no longer knows that pixel 29 sits directly below pixel 1. Digits are made of strokes and curves, which are spatial patterns, and that information is discarded before learning even starts. A dense network would have to relearn "a vertical stroke" separately at every position where it might appear.

And it doesn't scale. A 200×200 colour image has 120,000 inputs. A single dense layer of 1,000 neurons on top of it needs 120 million weights. That's slow, memory-hungry, and easy to overfit.

What makes vision hard in general: the same object can appear with different rotation, lighting, deformation (non-rigid bending), scale and viewpoint. A good image model needs to recognize a pattern wherever and however it appears.

Exam (Jan Q9): why vanilla fully connected networks do poorly on MNIST → they discard spatial structure by flattening images. They can be trained with gradient descent and can use non-linear activations. "Too many convolution filters" doesn't apply, because they have none.

5.2 The idea: local receptive fields (inspired by the visual cortex)

Hubel & Wiesel (1959) recorded neurons in the visual cortex and found that many react only to stimuli in a small region of the visual field, their local receptive field. Some respond to horizontal lines, others to lines at other angles. Higher-level neurons combine the outputs of lower-level ones to detect more complex patterns.

The CNN version: a flashlight sliding over the image.

A filter is a feature detector. If the filter's weights look like a vertical edge (positive on one side, negative on the other), the feature map lights up wherever the image has a vertical edge. The network doesn't hand-design these filters: it learns their weights with backpropagation. First-layer filters typically end up detecting edges, simple colours and curves.

5.3 Weight sharing: the key idea

Every neuron in a feature map uses the same filter weights. They're all the same flashlight, just at different positions.

Why this is so powerful:

  1. Far fewer parameters. A 5×5 filter on one channel has 25 weights + 1 bias = 26 parameters, whether the image is 28×28 or 1,000×1,000. A dense layer connecting a 28×28 image to a 28×28 output needs 784 × 784 ≈ 615,000 weights.
  2. Translation equivariance. A pattern learned at one position is automatically detected anywhere in the image. A "7" in the corner and a "7" in the middle activate the same filter. A dense network would have to learn both separately.
  3. Local connectivity. Each neuron only looks at its small receptive field, which matches the fact that nearby pixels are strongly related and distant pixels much less so.

Exam (Jan Q8, re-exam Q10): why do all neurons in a conv layer share the same weights → to reduce the number of parameters / to detect the same feature anywhere in the image. The wrong options:

5.4 Output size: padding and stride

Without padding, the image shrinks. A 5×5 filter can only be placed where it fits completely inside the image. On a 32×32 image, its top-left corner can sit at positions 1 to 28 in each direction, so the output is 28×28. In general, with input size n and filter size f, the output is n − f + 1.

Zero padding adds a border of zeros around the input so the filter can also be centred on edge pixels. With padding p = (f − 1)/2, the output keeps the same size as the input. In TensorFlow/Keras:

Stride is the step between two consecutive filter positions. Stride 1 visits every position. Stride 2 skips every other one, which roughly halves the width and height and cuts computation.

The general formula (memorize this):

output size = ⌊(n + 2p − f) / s⌋ + 1

where n is the input size, f the filter size, p the padding on each side, and s the stride.

Worked examples:

Exam traps for re-exam Q11: 32×32 would need padding. 30×30 is what a 3×3 filter gives. 27×27 is an off-by-one (forgetting the +1).

5.5 Many feature maps, and colour images

One filter detects one kind of pattern. To detect edges at different angles, corners, colour blobs and so on, a convolutional layer applies many filters in parallel. Each produces its own feature map, and the maps are stacked into a 3-D output volume: height × width × number of filters.

Input channels. A colour image has 3 channels (R, G, B). A filter therefore has a depth equal to the number of input channels: a "5×5" filter on an RGB image really has 5×5×3 = 75 weights (+1 bias). At each position, it sums over all channels at once. The next layer's input has as many channels as the previous layer had filters, so a 5×5 filter there has 5×5×32 weights if the previous layer produced 32 maps.

Parameter count of a conv layer:

params = (f × f × C_in + 1) × C_out

Example: 5×5 filters, 1 input channel, 32 filters → (25 + 1) × 32 = 832. The next layer, 5×5 with 32 → 64 channels → (5·5·32 + 1) × 64 = 51,264. Compare that with the millions a dense layer would need.

5.6 Pooling layers

After a convolution and its activation, a pooling layer shrinks (subsamples) each feature map.

Why pool?

  1. Less computation and memory, and fewer parameters later. A 2×2/stride-2 pool cuts each map to 1/4 of its size.
  2. Some invariance to small shifts. If a feature moves by one pixel but stays inside the same 2×2 window, the max doesn't change.
  3. Larger receptive fields. After pooling, a 5×5 filter in the next layer covers a larger area of the original image, so deeper layers can see bigger patterns such as parts and whole objects.

The cost is that you lose some precise position information, which is fine for classification but can matter for tasks like segmentation.

5.7 Flatten and dense layers: classification

The convolution and pooling stages turn the image into a stack of small feature maps (the what is where summary). To classify, we need one score per class:

  1. Flatten the final stack of maps into a single 1-D vector. Flattening here is fine, because the spatial patterns have already been extracted into features.
  2. Feed that vector into one or more dense (fully connected) layers, often with dropout between them.
  3. The final dense layer has one neuron per class (10 for digits) with softmax, so the output is a probability for each class.

The whole pipeline in one line: one image becomes a stack of filtered images (convolution) → a stack with no negative values (ReLU) → a smaller stack (pooling) → repeat, with each module's output feeding the next → flatten → dense → softmax.

5.8 A complete CNN for MNIST (from the slides)

Layer Operation Output shape

| Input | 28×28 grey image | [batch, 28, 28, 1] | | Conv 1 | 32 filters, 5×5, padding same, ReLU | [batch, 28, 28, 32] | | Pool 1 | max 2×2, stride 2 | [batch, 14, 14, 32] | | Conv 2 | 64 filters, 5×5, padding same, ReLU | [batch, 14, 14, 64] | | Pool 2 | max 2×2, stride 2 | [batch, 7, 7, 64] | | Flatten | — | [batch, 3136] (= 7·7·64) | | Dense | 1,024 neurons, ReLU (+ dropout) | [batch, 1024] | | Output | 10 neurons, softmax | [batch, 10] |

How to read it:

5.9 Training a CNN: backprop through a convolution

Convolutional layers are trained with ordinary backpropagation. The only new part is weight sharing. The slides' example: input X is 3×3, one filter W is 2×2, no padding, stride 1. The output H is (3 − 2)/1 + 1 = 2×2.

Forward pass. Each output is the filter placed on one 2×2 patch:

Backward pass. The error is the sum of the error contributions of all outputs: E = E_h11 + E_h12 + E_h21 + E_h22. Because the same W₁₁ was used in all four outputs, its gradient is the sum over every place it was used (the "several paths" chain rule from 3.7):

∂E/∂W₁₁ = (∂E/∂h₁₁)·X₁₁ + (∂E/∂h₁₂)·X₁₂ + (∂E/∂h₂₁)·X₂₁ + (∂E/∂h₂₂)·X₂₂

since ∂h₁₁/∂W₁₁ = X₁₁, ∂h₁₂/∂W₁₁ = X₁₂, and so on. In words: each shared weight collects gradient from every position the filter visited, each time multiplied by the input pixel it touched there. That sum is itself a convolution of the input with the output gradients.

Update as usual: W ← W − η·∂E/∂W, for all four weights.

5.10 Summary

Concept One-line meaning Why it exists

| Receptive field | the small input region one neuron sees | nearby pixels matter most | | Filter / kernel | small learned weight grid = feature detector | learn edges, curves, then parts | | Weight sharing | all positions use the same filter | far fewer parameters; detect a feature anywhere | | Padding | zeros around the border | keep the size ("same"), use edge pixels | | Stride | step between filter positions | downsample, save computation | | Feature maps | one output map per filter | detect many kinds of features | | Pooling | max/mean over small windows, no weights | shrink maps, shift tolerance, bigger receptive fields | | Flatten + dense | features → class scores | final classification with softmax | | Output size | ⌊(n + 2p − f)/s⌋ + 1 | exam calculation | | Params per conv layer | (f·f·C_in + 1)·C_out | independent of image size |


Past exams

Self-check quiz

Later topics on the exams (these get detailed notes once you upload those lectures; CNNs are now Lecture 5 above)

Study routine

  1. Read one lecture section, then explain it out loud without notes. Then take the L1–L4 quiz closed-book and aim for 15 or more out of 16.
  2. For each new lecture, upload the slides and I'll add detailed notes in this format.
  3. Every week, take both past exams closed-book and aim for 19 or more out of 20.
  4. In the last 2 weeks, only do mixed practice, with extra time on the weakest block (MLOps is the biggest).