DATASCI 447 Lecture 11: Bayesian Principles and CNNs - I promise I wasn’t wasting your time a few weeks ago!

Kevin McAlister

February 17, 2026

Administrative Stuff

Convolutional Neural Networks: Architecture as Prior

Last time: how to train deep networks — gradient flow, batch normalization, regularization.

Today: how to encode domain knowledge about images directly into the network architecture.

The central idea: architecture is a prior. When the prior matches the data generating process, generalization follows.

Where We Left Off

From L10, we have a complete toolkit for training deep networks:

  • He initialization, gradient clipping → prevent explosions
  • Batch normalization → keep activations healthy during training
  • Weight decay, dropout, early stopping → regularize and generalize

We applied all of this to CIFAR-10 (32×32 color images, 10 classes) using a 20-layer fully connected MLP and reached ~55% test accuracy.

That’s better than random (10%), but humans get ~94% on CIFAR-10.

Where’s the gap?

What Flattening Destroys

To feed an image into a fully connected network, we flatten it to a vector.

A 32×32×3 color image becomes a 3,072-dimensional vector.

Pixel (0,0) and pixel (31,31) are now equally “close” in the vector. Every spatial relationship — edges, shapes, neighborhoods — is destroyed.

The Parameter Cost of Ignorance

In a fully connected layer, every hidden unit connects to every pixel.

  • We are giving our network the ability (really the directive) to learn mappings of all pixels to every hidden unit!

  • The first FC layer of our L10 MLP: 3,072 inputs × 256 hidden units = 786,432 parameters.

Most of these connections link spatially distant pixels with no meaningful relationship. We’re estimating hundreds of thousands of parameters for relationships that don’t exist.

And it gets worse with resolution. For a 224×224 color image (standard for modern vision):

\[150{,}528 \times 256 = \textbf{38.5 million parameters}\]

in the first layer alone. For a dataset with a few thousand images, this is hopeless.

The Bayesian Framing

In regression, we choose priors that reflect our beliefs about the data:

Prior Belief Method
Uniform “I have no beliefs about which coefficients matter” OLS
L2 (Gaussian) “I believe most coefficients are small” Ridge
L1 (Laplace) “I believe most coefficients are zero” LASSO

A fully connected layer is a uniform prior over spatial structure: any pixel can influence any hidden unit, with no preference for local relationships.

  • For images, we have strong prior knowledge before seeing any data.

  • Can we encode it directly into the network architecture?

Three Beliefs About Images

Before seeing a single training image, we believe:

1. Locality

Useful visual features — edges, textures, corners — are computed from small spatial neighborhoods, not the entire image.

  • The color of a dog’s fur is informative for the other fur parts close by, but not for what’s going on in the background or other parts of the dog!

Three Beliefs About Images

Before seeing a single training image, we believe:

2. Translation Equivariance

The same feature can appear anywhere. A vertical edge in the top-left is the same feature as a vertical edge in the bottom-right.

  • The snout of a standing dog is the same as a dog snout of a silly dog rolling around on his back! It doesn’t matter where it is - a snout is a snout.

Three Beliefs About Images

Before seeing a single training image, we believe:

3. Hierarchy / Compositionality

Complex features are built from simpler features. Edges → textures → parts → objects.

  • A dog’s face is made up of its ears, eyes, and snout, which are themselves made up of simpler features like edges and colors.

These aren’t engineering convenience. They follow from how the physical world produces images: light interacts locally, objects move, and parts compose into wholes.

From Beliefs to Architecture

Each belief maps to a specific architectural constraint:

Belief Constraint Effect
Locality Local receptive fields Each unit sees a small patch, not all pixels
Translation equivariance Weight sharing Same filter applied at every position
Hierarchy Stacking layers Each layer builds on the previous one

A convolutional layer encodes all three.

But before we build one, let’s be precise about what “translation equivariance” actually means. This is the theoretical heart of why CNNs work.

Equivariance: The Formal Statement

Let \(T\) be a transformation (e.g., shifting an image 5 pixels to the right).

Equivariance: A function \(f\) is equivariant to \(T\) if:

\[f(T(\mathbf x)) = T(f(\mathbf x))\]

Transforming the input, then applying \(f\), gives the same result as applying \(f\), then transforming the output.

Example: If we detect a cat in an image then shift the cat image 5 pixels right, then we should still detect the cat using the exact same set of rules

  • The “what” is detected regardless of “where.”

Invariance: A Different Property

Invariance: A function \(f\) is invariant to \(T\) if:

\[f(T(\mathbf x)) = f(\mathbf x)\]

The output doesn’t change at all when the input is transformed.

  • For classification, we want invariance: a cat is a cat regardless of where it appears.

But we don’t want invariance everywhere — early layers should track where features are (equivariance), and only the final classification should discard location (invariance).

The CNN pipeline:

\[\underbrace{\text{Conv layers}}_{\text{equivariant}} \to \underbrace{\text{Pooling}}_{\text{adds local invariance}} \to \underbrace{\text{Global pool + FC}}_{\text{fully invariant}}\]

The network gradually trades spatial precision for semantic abstraction.

Why This Matters

Equivariance is the precise mathematical statement of the inductive bias/prior that CNNs provide:

  • It’s not just “CNNs are good at images.” It’s: “CNNs encode a specific symmetry — translation — that natural images possess.”

  • When the symmetry matches the data generating process, generalization follows. This is the same logic as encoding an L2 prior in Ridge regression — but structural rather than parametric.

An Important Caveat

Conv layers are equivariant to translation but NOT to rotation or scale.

  • A rotated cat produces a different representation in the hidden space, not a rotated representation.

  • The network must learn rotation/scale invariance from data — or we provide it via data augmentation (next lecture).

The Convolution Operation

Now let’s build the layer. Recall from L9: each module has a forward pass that computes an output from an input.

In a fully connected layer, the forward pass is:

\[z_k = \mathbf w_k^\top \mathbf x + b_k\]

An inner product with the entire input. This is where the “uniform prior” comes from.

In a convolutional layer, the forward pass is:

\[z_{i,j} = \sum_{m=0}^{k-1} \sum_{n=0}^{k-1} W_{m,n} \cdot x_{i+m,\, j+n} + b\]

An inner product with a small local patch of the input. Same operation, just restricted to a \(k \times k\) neighborhood.

Walking Through 1D Convolution

Before 2D, let’s see this in 1D:

\[[1,2,3,4,5,6] \circledast [1,2] = [5,8,11,14,17]\]

Walking Through 1D Convolution

Before 2D, let’s see this in 1D:

\[[\boxed{\color{red}1,2},3,4,5,6] \circledast [\boxed{\color{red}1,2}] = [\boxed{\color{red}5}, 8, 11,14,17]\] \[(1 \times 1) + (2 \times 2) = 5\]

Walking Through 1D Convolution

Before 2D, let’s see this in 1D:

\[[1,\boxed{\color{red}2,3},4,5,6] \circledast [\boxed{\color{red}1,2}] = [5, \boxed{\color{red}8}, 11,14,17]\] \[(2 \times 1) + (3 \times 2) = 8\]

Walking Through 1D Convolution

Before 2D, let’s see this in 1D:

\[[1,2,\boxed{\color{red}3,4},5,6] \circledast [\boxed{\color{red}1,2}] = [5, 8, \boxed{\color{red}11},14,17]\] \[(3 \times 1) + (4 \times 2) = 11\]

The filter slides across the input. Same weights at every position — that’s weight sharing.

Walking Through 2D Convolution

Now in 2D: a \(3 \times 3\) image with a \(2 \times 2\) filter.

\[ \begin{bmatrix} \boxed{a_{11}} & \boxed{a_{12}} & a_{13} \\ \boxed{a_{21}} & \boxed{a_{22}} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{bmatrix} \circledast \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} \]

\[c_{11} = a_{11}w_{11} + a_{12}w_{12} + a_{21}w_{21} + a_{22}w_{22}\]

Walking Through 2D Convolution

\[ \begin{bmatrix} a_{11} & \boxed{a_{12}} & \boxed{a_{13}} \\ a_{21} & \boxed{a_{22}} & \boxed{a_{23}} \\ a_{31} & a_{32} & a_{33} \end{bmatrix} \circledast \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} \]

\[c_{12} = a_{12}w_{11} + a_{13}w_{12} + a_{22}w_{21} + a_{23}w_{22}\]

Walking Through 2D Convolution

\[ \begin{bmatrix} a_{11} & a_{12} & a_{13} \\ \boxed{a_{21}} & \boxed{a_{22}} & a_{23} \\ \boxed{a_{31}} & \boxed{a_{32}} & a_{33} \end{bmatrix} \circledast \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} \]

\[c_{21} = a_{21}w_{11} + a_{22}w_{12} + a_{31}w_{21} + a_{32}w_{22}\]

Walking Through 2D Convolution

\[ \begin{bmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & \boxed{a_{22}} & \boxed{a_{23}} \\ a_{31} & \boxed{a_{32}} & \boxed{a_{33}} \end{bmatrix} \circledast \begin{bmatrix} w_{11} & w_{12} \\ w_{21} & w_{22} \end{bmatrix} \]

\[c_{22} = a_{22}w_{11} + a_{23}w_{12} + a_{32}w_{21} + a_{33}w_{22}\]

The filter slides over every position. Each output value is a local inner product — template matching at that location.

The Inner Product as Template Matching

The inner product \(\mathbf w^\top \mathbf x\) measures similarity between a weight vector and an input.

  • High when they point in the same direction (positive correlation).
  • Low when they point in opposite directions (negative correlation).
  • Zero when uncorrelated.

For zero-centered values, this is the covariance — high when \(\mathbf w\) and \(\mathbf x\) point in the same direction.

The Inner Product as Template Matching

A convolutional filter is a template for a small visual pattern:

  • A vertical edge filter has large positive weights on one side, large negative weights on the other
  • When the filter slides over a vertical edge in the image, the inner product is large
  • When it slides over a flat region, the inner product is near zero

After ReLU: only positive similarity survives. The output tells us where the pattern is present.

What the Filter Finds

The vertical edge filter responds strongly along the vertical edge (bright) and is silent elsewhere. The horizontal edge filter responds along the horizontal edge. Each filter detects one local pattern everywhere in the image.

What the Filter Finds

What the Filter Finds

Weight Sharing Is Why Equivariance Holds

The same filter \(\mathbf W\) slides over every spatial position.

This is exactly what guarantees translation equivariance:

  • If the input shifts by \((dx, dy)\), the filter encounters the same local patches at shifted positions
  • The inner products are the same — just at shifted locations
  • The output feature map shifts by the same \((dx, dy)\)

A fully connected layer doesn’t have this property. Each position has its own weights, so moving the pattern to a new position produces a completely different hidden activation.

Multiple Filters = Multiple Features

One filter detects one pattern. We want to detect many patterns: vertical edges, horizontal edges, color gradients, corners, blobs…

Stack \(C_{\text{out}}\) different filters, each producing its own feature map:

\[\text{Input image} \xrightarrow{C_\text{out} \text{ filters}} C_{\text{out}} \text{ feature maps}\]

The output is a 3D tensor: height × width × \(C_{\text{out}}\).

Multiple Filters = Multiple Features

The layer has learned a vocabulary of local features — each filter is a “word” for a different spatial pattern.

Multi-Channel Input

A color image has 3 input channels (R, G, B).

Each filter must have the same depth as the input:

  • A \(3 \times 3\) filter on an RGB image is actually \(3 \times 3 \times 3 = 27\) weights
  • The filter computes one inner product across all channels at each position
  • When humans look at pictures, we don’t see 3 dims, we just see two. It rarely makes sense to think about convolving over the color channel!

Multi-Channel Input

In general, a conv layer with \(C_{\text{in}}\) input channels and \(C_{\text{out}}\) filters has:

\[C_{\text{out}} \times C_{\text{in}} \times k \times k \quad \text{parameters (+ } C_{\text{out}} \text{ biases)}\]

Each filter integrates information across all input channels to produce one feature map.

After the first layer, the “channels” are no longer R/G/B — they’re the feature maps from the previous layer. The next layer’s filters operate on combinations of features, not raw colors.

The Parameter Payoff

Let’s make this concrete. For Pets images resized to 224×224:

FC Layer Conv Layer (3×3, 64 filters)
Input 150,528 (flattened) 224×224×3 (spatial)
Output 256 scalar units 224×224×64 (64 feature maps)
Parameters 150,528 × 256 = 38.5 million 3×3×3×64 = 1,728

The conv layer produces a richer output (64 spatial feature maps vs. 256 scalars) with 22,000× fewer parameters.

This is the payoff of encoding locality + weight sharing as an architectural prior.

The network doesn’t have to learn that nearby pixels are related — the architecture assumes it.

Forward/Backward: Same Module Pattern

A conv layer fits the same module pattern from L9:

Forward: slide filters across input, compute local inner products, cache input and filter values.

Backward: receive upstream \(\delta\) (a 3D tensor with the same shape as the output), compute gradients for the filter weights, pass \(\delta\) downstream.

The backward pass through a convolution is itself a convolution (with the filter transposed). PyTorch handles this — nn.Conv2d has a forward/backward pair just like nn.Linear.

The Backprop Rule for Convolutions

Recall from L9 that every linear module produces three things in its backward pass:

FC Layer: \(\mathbf z = \mathbf W^T \mathbf h + \mathbf b\) Conv Layer: \(\mathbf Z = \mathbf W \circledast \mathbf H + b\)
Bias gradient \(\frac{\partial \mathcal{L}}{\partial \mathbf b} = \boldsymbol \delta\) \(\frac{\partial \mathcal{L}}{\partial b} = \sum_{i,j} \delta_{ij}\)
Weight gradient \(\frac{\partial \mathcal{L}}{\partial \mathbf W} = \mathbf h \cdot \boldsymbol \delta^T\) \(\frac{\partial \mathcal{L}}{\partial \mathbf W} = \mathbf H \circledast \boldsymbol \Delta\)
Pass-through \(\frac{\partial \mathcal{L}}{\partial \mathbf h} = \mathbf W \cdot \boldsymbol \delta\) \(\frac{\partial \mathcal{L}}{\partial \mathbf H} = \text{rot}_{180}(\mathbf W) \circledast_{\text{full}} \boldsymbol \Delta\)

The Backprop Rule for Convolutions

The structure is identical — the operation changes from matrix multiply to convolution.

  • Bias: sum the upstream gradient (over spatial positions instead of just passing through)
  • Weights (A 3 x 3 matrix for each output channel): the outer product becomes a convolution of the input with the upstream \(\boldsymbol \delta\)
  • Pass-through: multiply by \(\mathbf W\) becomes convolve with the rotated filter (full convolution to recover input size)

The Backprop Rule for Convolutions

Why this matters: The convolution layer is just more multiplies and adds

  • Just done in a structured way!

  • GPUs FLY through this kind of computation!

But there’s way more to do!

  • For a 32 x 32 x 3 flattened image flattened with 256 hidden units: 786,432 multiply-adds

  • For a 32 x 32 x 3 image with a 3 x 3 convolutional layer with 256 filters: 7,077,088 multiply-adds

Parameter count decreases but compute time increases!

  • Trade RAM for processing power

The Backprop Rule for Convolutions

The chain of equations from L9 is identical:

\[\delta_0 \xrightarrow{\text{conv} \;\times} \delta_1 \xrightarrow{\odot \; \mathbb{I}} \delta_2 \xrightarrow{\text{conv} \;\times} \delta_3 \cdots\]

Only the internal operation of each \(\times\) module has changed. Everything from L10 — BN, gradient flow, regularization — still applies.

A Conv Layer in PyTorch

nn.Conv2d(
    in_channels=3,      # RGB input
    out_channels=64,     # learn 64 different filters
    kernel_size=3,       # each filter is 3×3
    padding=1,           # preserve spatial dimensions
)

Same Sequential chain, same loss.backward(), same optimizer step:

model = nn.Sequential(
    nn.Conv2d(3, 64, 3, padding=1),
    nn.BatchNorm2d(64),   # BN for conv layers (per-channel)
    nn.ReLU(),
    nn.Conv2d(64, 128, 3, padding=1),
    nn.BatchNorm2d(128),
    nn.ReLU(),
    # ...
)

The API is almost identical to nn.Linear. What changed is the structure of the weight matrix — from fully connected to local + shared.

Practical Mechanics: Padding and Stride

The next few slides cover mechanical choices that control the shape of outputs.

These matter for getting the code to run, but they don’t fundamentally affect what the network learns. Think of these as plumbing, not architecture.

  • As long as the choices are reasonable, performance is largely unchanged. The important decisions are filter size, number of filters, and depth.

Padding

A \(3 \times 3\) filter on a \(32 \times 32\) input produces a \(30 \times 30\) output — we lose 1 pixel on each border.

In general, a \(k \times k\) filter on an \(n \times n\) input gives:

\[(n - k + 1) \times (n - k + 1)\]

Padding

To preserve spatial dimensions, pad the input with zeros:

  • Same padding is the norm. Most dimensionality reduction happens in the pooling layers (still to come).

Stride

By default, the filter moves one pixel at a time (stride = 1).

With stride = 2, the filter jumps two pixels → output spatial dimensions halve.

\[H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} + 2p - k}{s} \right\rfloor + 1\]

You won’t memorize this — PyTorch computes it for you.

  • Stride is a computational choice about when to reduce spatial resolution.

Stride is useful because the convolution operator applied at every pixel will produce very similar values in the same neighborhood!

Pooling: Building the Hierarchy

With padding = ⌊k/2⌋, conv layers preserve spatial dimensions.

But we want to reduce spatial resolution as we go deeper. Two reasons:

1. Computational efficiency — processing 224×224×64 tensors at every layer is expensive.

2. Hierarchy — deeper layers should capture larger-scale patterns. Reducing resolution forces each unit to cover a larger region of the original image.

This is Belief #3 (hierarchy): complex features are compositions of simpler ones. To compose, each layer must “see” a progressively larger region.

Max Pooling

The simplest downsampling: take non-overlapping \(2 \times 2\) windows, keep only the maximum (Max Pool 2, Stride 2):

\[ \begin{bmatrix} \boxed{1} & \boxed{2} & 3 & 4 \\ \boxed{5} & \boxed{6} & 7 & 8 \\ 9 & 10 & 11 & 12 \\ 13 & 14 & 15 & 16 \end{bmatrix} \xrightarrow{\text{MaxPool}(2)} \begin{bmatrix} \boxed{6} & 8 \\ 14 & 16 \end{bmatrix} \]

Remember that each value in the post-convolution + ReLU feature map says “Centered on this pixel, was the feature present?”

Max Pooling

Spatial dimensions halve: \(H \times W \to \frac{H}{2} \times \frac{W}{2}\). No learnable parameters.

What pooling says: “I care that this feature was detected in this \(2 \times 2\) region, but not exactly where within the region.”

This is local spatial invariance — robustness to small shifts.

The Funnel: Trading Space for Semantics

The Effective Receptive Field

Stacking \(3 \times 3\) convolutions with pooling gives each unit a progressively larger “view” of the original image:

Depth Effective Receptive Field
1 conv layer 3×3
2 conv layers 5×5
2 conv + pool + 2 conv ~14×14
5 blocks with pooling covers most of 224×224

Deeper networks capture longer-range spatial dependencies — not by having larger filters, but by composing many small ones.

Each \(3 \times 3\) filter is cheap (9 parameters). The expressive power comes from depth.

Reading the Funnel

Each pooling step halves the spatial dimensions while the number of channels (features) grows:

After Block 1 (112×112, 64 channels): Simple local features — edges at various orientations, color gradients. Each feature is detected in a small 3×3 patch.

After Block 3 (28×28, 256 channels): Combinations of features — textures, object parts. Each unit’s effective receptive field covers a much larger region of the original image.

After Block 5 (7×7, 512 channels): Abstract, semantic features. Each position “sees” roughly a quarter of the entire image. These features respond to high-level concepts: “cat face,” “dog nose,” “striped fur.”

The network starts with many simple features over small patches and progressively combines them into few complex features that span the whole image.

Flatten and Classify

After the conv/pool stages: a small spatial grid of rich feature maps.

For example: \(7 \times 7 \times 512 = 25{,}088\) values.

Flatten to a vector → feed to one or two FC layers → softmax over classes.

Flatten and Classify

The conv layers did the representation learning — extracting meaningful hierarchical features from raw pixels.

The FC layers do the classification — mapping features to class probabilities.

This is where the ConvNet reconnects to everything from L9–L10: the FC “head” is exactly the MLP we already understand, just operating on much better input features.

The Bottleneck Problem

After the final conv block, we have a rich feature tensor — say \(7 \times 7 \times 512 = 25{,}088\) values.

If we flatten this directly and connect to a FC classifier with 512 hidden units:

\[25{,}088 \times 512 = \textbf{12.8 million parameters}\]

just in that one layer. This is more parameters than the entire conv backbone that built the features!

  • Incredibly wasteful

Global Average Pooling

Key idea: Each of those 512 feature maps already represents “how much of feature \(k\) is present in this image.”

We don’t need 25,088 numbers to summarize that — we need one number per feature map.

Global Average Pooling (GAP): Average each \(H \times W\) feature map down to a single scalar.

\[\text{GAP}: \quad \underset{H \times W \times C}{\mathbf{F}} \;\longrightarrow\; \underset{C \times 1}{\bar{\mathbf{f}}} \qquad \text{where} \quad \bar{f}_k = \frac{1}{HW} \sum_{i=1}^{H} \sum_{j=1}^{W} F_{ijk}\]

Global Average Pooling

The classifier is now just \(\text{nn.Linear}(512, K)\)512 \(\times\) \(K\) parameters. For 10 classes, that’s 5,120 vs. 12.8 million.

  • Good evidence that this drastically reduced training time and minimally impacts accuracy!

In PyTorch:

nn.AdaptiveAvgPool2d(1)  # output is 1×1 regardless of input spatial size

The “adaptive” means it works for any input resolution — the kernel size adjusts automatically so the output is always \(1 \times 1 \times C\).

VGG: The Standard Template

VGG (Visual Geometry Group - Simonyan & Zisserman, 2014) is the cleanest instantiation of the three beliefs.

One design rule: always use \(3 \times 3\) filters, double the channels each time you halve the spatial dimensions.

The architecture is just this pattern repeated:

\[[\text{Conv}(3 \times 3) \to \text{BN} \to \text{ReLU}] \times B \to \text{MaxPool}(2 \times 2)\]

(Note: the original VGG predates BN — modern implementations always include it.)

VGG Architecture

Block Conv Layers Output Size Channels
Input 224 × 224 3
Block 1 Conv×2, Pool (2,2) 112 × 112 64
Block 2 Conv×2, Pool (2,2) 56 × 56 128
Block 3 Conv×3, Pool (2,2) 28 × 28 256
Block 4 Conv×3, Pool (2,2) 14 × 14 512
Block 5 Conv×3, Pool (2,2) 7 × 7 512
Classifier Global Avg Pool → FC 1 × 1 → \(K\) 512 → \(K\)

The conv backbone — the representation learning part — has ~15M parameters.

In the original VGG, the FC head had ~120M parameters. Modern implementations use global average pooling instead: average each \(7 \times 7\) feature map to a single number, giving a 512-dim vector. The classifier becomes a single nn.Linear(512, K).

Why Always 3×3?

Two stacked \(3 \times 3\) conv layers have the same effective receptive field as one \(5 \times 5\) layer.

Three \(3 \times 3\) layers match one \(7 \times 7\).

But stacked \(3 \times 3\) is strictly better:

Fewer parameters: \(2 \times (3^2) = 18\) vs. \(5^2 = 25\) for equivalent receptive field (28% fewer).

More nonlinearities: a ReLU between each layer → more expressive per parameter.

Better gradient flow: more BN layers to stabilize training.

  • This is why nobody uses \(5 \times 5\) or \(7 \times 7\) filters in modern networks — stacking small filters dominates.

VGG Block in PyTorch

def vgg_block(in_ch, out_ch, n_convs):
    """VGG-style block: N × (Conv3×3 → BN → ReLU) → MaxPool"""
    layers = []
    for i in range(n_convs):
        layers.append(nn.Conv2d(
            in_ch if i == 0 else out_ch, out_ch,
            kernel_size=3, padding=1, bias=False
        ))
        layers.append(nn.BatchNorm2d(out_ch))
        layers.append(nn.ReLU(inplace=True))
    layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
    return nn.Sequential(*layers)

Same module pattern. Same loss.backward(). Same optimizer.

Drop-in replacement for the FC layers from L10.

Why the Improvement?

The CNN doesn’t have more capacity than the MLP — the parameter counts are comparable.

The improvement comes entirely from the prior knowledge:

  • The conv structure eliminates a vast space of functions that violate locality and translation equivariance. What’s left is a much smaller, better-matched function class.

Why the Improvement?

This is bias-variance in action:

  • More bias (structural constraints → can’t represent functions with long-range non-local interactions)
  • Dramatically less variance (fewer effectively free parameters → less overfitting with limited data)

When the prior is correct (images have local, translation-equivariant structure), the tradeoff is overwhelmingly favorable.

When the prior is wrong (tabular data, no spatial structure), convolutions hurt. Nobody uses CNNs on Kaggle tabular competitions.

What’s Still Missing

VGG with 13 conv layers works well. But:

Depth ceiling: Past ~20 layers, adding more layers makes VGG worse. Not just test accuracy — training accuracy degrades. This is an optimization failure, not overfitting. And BatchNorm can’t save this one…

Next lecture:

  • Skip connections (ResNets) solve the depth problem — enabling 50, 100, 150+ layer networks
  • Data augmentation encodes additional invariances the architecture doesn’t provide
  • Representation learning: reframing CNNs not as classifiers but as feature transforms — which sets up transfer learning in L13