Loading MNIST dataset...
February 5, 2026
But: We didn’t fully understand what the hidden layer actually captures
Today: Use MNIST to see exactly what’s happening inside the network
The hidden layer transforms raw pixels into a space where classes are linearly separable
PCA: Optimizes for reconstruction (minimize \(\|x - \hat{x}\|^2\))
NN hidden layer: Optimizes for the task (minimize classification loss)
\[\phi_{\text{PCA}}(x) \neq \phi_{\text{NN}}(x)\]
The NN learns a representation that’s useful for prediction, not reconstruction
Today: Let’s visualize this directly with a tractable example
Before we dive into examples, let’s establish the matrix formulation that will be essential for understanding backpropagation - the main method which allows us to optimize the complicated neural network loss functions.
For a single observation \(x\):
\[\hat{y} = \underset{(K \times 1)}{\sigma_{\text{out}}}(\underset{(K \times M)}{W_{\text{out}}} \cdot \underset{(M \times 1)}{\varphi}(\underset{(M \times P)}{W_1} \underset{(P \times 1)}{x} + \underset{(M \times 1)}{b_1}) + \underset{(K \times 1)}{b_{\text{out}}})\]
Where:
\(K\) is the number of outputs per input (1 for regular regression, 1 or 2 for binary logistic regression, number of categories for multinomial, number of pixels if we’re generating an image)
\(M\) is the number of hidden units in the hidden layer
\(P\) is the number of input features
This equation is gnarly, but we can break it into pieces to show it as a chained equation
Step 1: Linear transformation \[\underset{(M \times 1)}{z_1} = \underset{(M \times P)}{W_1} \underset{(P \times 1)}{x} + \underset{(M \times 1)}{b_1}\]
Step 2: Nonlinear activation (ReLU) \[\underset{(M \times 1)}{h} = \underset{(M \times 1)}{\text{ReLU}}(\underset{(M \times 1)}{z_1}) = \underset{(M \times 1)}{\max}(0, \underset{(M \times 1)}{z_1})\]
Step 3: Output linear transformation \[\underset{(K \times 1)}{z_{\text{out}}} = \underset{(K \times M)}{W_{\text{out}}} \underset{(M \times 1)}{h} + \underset{(K \times 1)}{b_{\text{out}}}\]
Step 4: Output activation \[\underset{(K \times 1)}{\hat{y}} = \underset{(K \times 1)}{\sigma}(\underset{(K \times 1)}{z_{\text{out}}})\]
\(\sigma\) is the sigmoid function for binary classification
\(\sigma\) is the softmax function for multi-class classification
\(\sigma\) is the identity function for regression or any other method that wants predictions in \(\mathbb R\)
Step 5: Compute Loss
For a training set with \(N\) observations:
\[ \mathcal L = \frac{1}{N} \sum_{i=1}^N \mathcal L(\hat{y}_i, y_i) \]
Our goal is to minimize this function!
We can write this as function composition:
\[\hat{y} = f_{\text{out}}(f_{\text{hidden}}(x))\]
Where:
The network is a composition:
\[\hat{y} = (f_{\text{out}} \circ f_{\text{hidden}})(x)\]
This view will be essential for computing gradients (backpropagation)!
Loading MNIST dataset...
MNIST: 70,000 handwritten digits (28×28 grayscale images)
Why MNIST instead of faces?
Why 4 vs. 9? This is the hardest pair for linear classifiers!
Both digits have: - A vertical stroke on the right side - Structure in the upper portion
The key difference: - 9 has a closed loop at the top - 4 has intersecting lines and an open top or an angular top
Detecting “loop” vs. “intersection” requires understanding shape, not just “where are the pixels?”
A linear classifier asks: “Is pixel (i, j) bright?”
But we need: “Do the bright pixels form a closed curve?”
Training Logistic Regression...
Logistic Regression Results:
Training accuracy: 98.0%
Test accuracy: 96.6%
~96% accuracy — pretty good!
This leaves a little room for improvement. Can a neural network do better?
Some are genuinely ambiguous cases!
But some seem weird…
Logistic regression learns: “If pixel here is bright → probably a 9 (or 4)”
But it cannot learn: “If pixels form a closed curve → 9”
A simple 4 hidden unit network
\[ \underset{(1 \times 1)}{\hat{y}} = \underset{(1 \times 1)}{\sigma}\left(\underset{(1 \times 4)}{W_{\text{out}}} \cdot \underset{(4 \times 1)}{\text{ReLU}}\left(\underset{(4 \times 784)}{W_1} \underset{(784 \times 1)}{\mathbf x} + \underset{(4 \times 1)}{b_1}\right) + \underset{(1 \times 1)}{b_{\text{out}}}\right) \]
Dimensions:
Parameters to learn:
| Matrix/Vector | Shape | # Parameters |
|---|---|---|
| \(W_1\) | \(4 \times 784\) | 3,136 |
| \(b_1\) | \(4 \times 1\) | 4 |
| \(W_{\text{out}}\) | \(1 \times 4\) | 4 |
| \(b_{\text{out}}\) | \(1 \times 1\) | 1 |
| Total | 3,145 |
With 3,145 parameters, we can classify handwritten digits better?
Compare to:
Neural Network (4 hidden units) Results:
Training accuracy: 99.5%
Test accuracy: 98.3%
Improvement over Logistic Regression:
Test accuracy: 96.6% → 98.3% (+1.7%)
With only 4 hidden units, the neural network improves on logistic regression!
Key question: How does a 4-unit hidden layer outperform a linear model?
Answer: It learns better features. Let’s see what they are!
We trained a neural network with 4 hidden units that beats logistic regression.
The key question: What do those 4 units actually compute?
Recall the hidden layer: \[\underset{(4 \times 1)}{h} = \underset{(4 \times 1)}{\text{ReLU}}\left(\underset{(4 \times 784)}{W_1} \underset{(784 \times 1)}{x} + \underset{(4 \times 1)}{b_1}\right)\]
Each row of \(W_1\) is a vector of 784 weights — one per pixel.
\[W_1 = \begin{bmatrix} — w_1^T — \\ — w_2^T — \\ — w_3^T — \\ — w_4^T — \end{bmatrix} \in \mathbb{R}^{4 \times 784}\]
Each \(w_k \in \mathbb{R}^{784}\) can be reshaped to 28×28 — a “template”!
Blue regions: “I want bright pixels here” (positive weights)
Red regions: “I want dark pixels here” (negative weights)
Each unit has learned a different pattern detector!
For hidden unit \(k\), the activation for input \(x\) is:
\[h_k = \underset{\text{scalar}}{\text{ReLU}}(\underset{1 \times 784}{w_k^T} \underset{784 \times 1}{x} + \underset{\text{scalar}}{b_k}) = \text{ReLU}\left(\sum_{p=1}^{784} w_{kp} \cdot x_p + b_k\right)\]
This is a template match:
The unit “fires” when the input looks like its template!
Now we have 4 hidden activations: \(h = [h_1, h_2, h_3, h_4]^T\)
The output layer computes: \[\underset{(1 \times 1)}{z_{\text{out}}} = \underset{(1 \times 4)}{W_{\text{out}}} \underset{(4 \times 1)}{h} + \underset{(1 \times 1)}{b_{\text{out}}}\]
Positive weight: “When this unit is active, vote for 9”
Negative weight: “When this unit is active, vote against 9”
The network has learned:
Each image \(x\) is transformed into a 4D point \(h = [h_1, h_2, h_3, h_4]^T\)
In this 4D space, the two classes are more separable than in the original 784D pixel space!
The hidden layer’s job: create a space where a hyperplane can separate the classes!
The network learns a transformation \(\phi(x) = \text{ReLU}(W_1 x + b_1)\)
This representation is optimized for:
NOT optimized for:
Key insight: The features are learned from data, for the task
This is why neural networks can discover features that humans might not think of!
We got good results with 4 hidden units on 4 vs. 9.
But each unit is trying to do everything at once:
What if the pattern is more complex?
What makes a 9 a 9?
What makes a 4 a 4?
The key distinction: LOOP vs. INTERSECTION
A single layer must learn: “complete 9 pattern” vs “complete 4 pattern” directly from pixels
The same “curve detector” helps for many digits!
Real-world patterns are compositional:
Examples:
| Domain | Hierarchy |
|---|---|
| Digits | pixels → strokes → curves → loops → digits |
| Faces | pixels → edges → face parts → expressions |
| Language | characters → words → phrases → sentences |
| Audio | samples → frequencies → phonemes → words |
Depth exploits compositionality; width must memorize.
Let’s formalize the multi-layer idea.
Two hidden layers:
\[h_1 = \underset{M_1 \times 1}{\text{ReLU}}(\underset{M_1 \times P}{W_1} \underset{P \times 1}{\mathbf x} + \underset{M_1 \times 1}{b_1})\]
\[h_2 = \underset{M_2 \times 1}{\text{ReLU}}(\underset{M_2 \times M_1}{W_2} \underset{M_1 \times 1}{h_1} + \underset{M_2 \times 1}{b_2})\]
\[\hat{y} = \underset{K \times 1}{\sigma}(\underset{K \times M_2}{W_{\text{out}}} \underset{M_2 \times 1}{h_2} + \underset{K \times 1}{b_{\text{out}}})\]
Two layers: \[\hat{y} = f_{\text{out}}(f_2(f_1(x)))\]
General \(D\)-layer network: \[\hat{y} = f_{\text{out}} \circ f_D \circ f_{D-1} \circ \cdots \circ f_1(x)\]
Each \(f_l\) is: \[f_l(h_{l-1}) = \text{ReLU}(W_l h_{l-1} + b_l)\]
Why this matters: The chain rule lets us differentiate through this composition!
\[\frac{\partial \mathcal{L}}{\partial W_1} = \frac{\partial \mathcal{L}}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial h_2} \cdot \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial W_1}\]
Architecture: \(784 \to 4 \to 3 \to 2\)
\[h_1 = \underset{4 \times 1}{\text{ReLU}}(\underset{4 \times 784}{W_1} \underset{784 \times 1}{x} + \underset{4 \times 1}{b_1})\]
\[h_2 = \underset{3 \times 1}{\text{ReLU}}(\underset{3 \times 4}{W_2} \underset{4 \times 1}{h_1} + \underset{3 \times 1}{b_2})\]
\[\hat{y} = \underset{1 \times 1}{\text{softmax}}(\underset{1 \times 3}{W_{\text{out}}} \underset{3 \times 1}{h_2} + \underset{1 \times 1}{b_{\text{out}}})\]
Total parameters: \((4 \times 784 + 4) + (3 \times 4 + 3) + (1 \times 3 + 1) = 3,159\)
Shallow Network (784 → 4 → 2):
Train accuracy: 99.5%
Test accuracy: 98.3%
Deep Network (784 → 4 → 3 → 2):
Train accuracy: 100.0%
Test accuracy: 98.3%
Let’s look at what each layer learns!
Layer 1 detects low-level features: edges, curves, stroke patterns
These are similar to the shallow network — detecting basic visual elements.
Some separation, but not complete. That’s okay!
Layer 1’s job isn’t to classify — it’s to extract useful features for Layer 2.
\[h_2 = \underset{3 \times 1}{\text{ReLU}}(\underset{3 \times 4}{W_2} \underset{4 \times 1}{h_1} + \underset{3 \times 1}{b_2})\]
\[\underset{784}{x} \xrightarrow{W_1, b_1} \underset{4}{h_1} \xrightarrow{W_2, b_2} \underset{3}{h_2} \xrightarrow{W_{\text{out}}, b_{\text{out}}} \underset{2}{\hat{y}}\]
Each layer learns a representation of the previous representation!
We trained on digits 4 vs. 9 only.
Key question: What if we trained on ALL 10 digits?
Would Layer 1 learn completely different features?
Deep Network (784 → 10 → 10 → 2) on 4 vs 9:
Training accuracy: 100.0%
Test accuracy: 98.1%
Deep Network (784 → 10 → 10 → 10) on Full MNIST:
Training accuracy: 96.2%
Test accuracy: 93.4%
Observation: Similar features! Edges, curves, stroke detectors appear in both networks.
Early layers learn general features that apply to many classes:
Later layers learn task-specific combinations:
If early layers learn general features:
This is called transfer learning — we’ll revisit it later!
\[\underbrace{W_1, W_2, W_3}_{\text{general features (freeze)}} \quad \underbrace{W_4, W_{\text{out}}}_{\text{task-specific (retrain)}}\]
Why it works:
The hierarchical structure of deep networks makes this possible!
We’ve seen that deep networks learn hierarchical features.
But is there a computational advantage to depth?
Question: For the same number of hidden units, does depth help?
\[P(y=1|x) \propto \sin(x_1^2 - x_2^2)\]
A complex, nonlinear decision boundary — perfect for testing depth!
Same total hidden units \(M\), different architectures:
| Depth | Architecture | Example (M=12) |
|---|---|---|
| 1 layer | \(2 \to M \to 2\) | \(2 \to 12 \to 2\) |
| 2 layers | \(2 \to M/2 \to M/2 \to 2\) | \(2 \to 6 \to 6 \to 2\) |
| 3 layers | \(2 \to M/3 \to M/3 \to M/3 \to 2\) | \(2 \to 4 \to 4 \to 4 \to 2\) |
Question: For the same “budget” of hidden units, does depth help?
The wiggly boundary \(\sin(x_1^2 - x_2^2) = 0\) has compositional structure:
A deep network can learn this hierarchy:
A shallow network must learn the complete pattern directly!
Any intuition for the compute gains?
Each ReLU unit creates a fold in the input space (a hyperplane where the function “bends”).
More folds = more complex functions!
Width adds regions: \(M\) units → \(M+1\) regions (1D)
Depth multiplies regions: \(D\) layers → regions grow exponentially in \(D\)!
Adding width (M) creates more folds, but they’re all parallel hyperplanes.
Same 100 units, but deeper networks create more complex decision boundaries!
For a ReLU network with:
The maximum number of linear regions is:
\[ \text{total regions} = \mathcal{O}\left(\binom{M}{P}^{P(D-1)} M^P\right) \]
\[ \text{total regions} = \mathcal{O}\left(\binom{M}{P}^{P(D-1)} M^P\right) \]
Depth 1: Regions \(\sim M^P\) (polynomial in width)
Depth D: Regions \(\sim M^{PD}\) (exponential in depth!)
Deeper networks are more expressive, but:
Recall: For linear regression and logistic regression, the loss function is convex.
Neural networks are different. Even a single-layer network has a non-convex loss!
Consider a tiny problem:
Network equations:
\[h_k = \text{ReLU}(w_k x + b_k) = \max(0, w_k x + b_k), \quad k = 1, 2\]
\[\hat{y} = \beta_1 h_1 + \beta_2 h_2 + b_{\text{out}}\]
Two different parameter settings can achieve the same low loss:
Setting A: \(w_1 = 1, w_2 = -1, b_1 = b_2 = 0, \beta_1 = 1, \beta_2 = -1, b_{\text{out}} = 0\)
For observation 1 (\(x_1 = 1, y_1 = 1\)): \[h_1 = \text{ReLU}(1 \cdot 1 + 0) = 1, \quad h_2 = \text{ReLU}(-1 \cdot 1 + 0) = 0\] \[\hat{y}_1 = 1 \cdot 1 + (-1) \cdot 0 + 0 = 1 \quad \checkmark\]
For observation 2 (\(x_2 = -1, y_2 = -1\)): \[h_1 = \text{ReLU}(1 \cdot (-1) + 0) = 0, \quad h_2 = \text{ReLU}(-1 \cdot (-1) + 0) = 1\] \[\hat{y}_2 = 1 \cdot 0 + (-1) \cdot 1 + 0 = -1 \quad \checkmark\]
\[\mathcal{L}(A) = \frac{1}{2}[(1-1)^2 + (-1-(-1))^2] = 0\]
Setting B: \(w_1 = -1, w_2 = 1, b_1 = b_2 = 0, \beta_1 = -1, \beta_2 = 1, b_{\text{out}} = 0\)
For observation 1 (\(x_1 = 1, y_1 = 1\)): \[h_1 = \text{ReLU}(-1 \cdot 1 + 0) = 0, \quad h_2 = \text{ReLU}(1 \cdot 1 + 0) = 1\] \[\hat{y}_1 = (-1) \cdot 0 + 1 \cdot 1 + 0 = 1 \quad \checkmark\]
For observation 2 (\(x_2 = -1, y_2 = -1\)): \[h_1 = \text{ReLU}(-1 \cdot (-1) + 0) = 1, \quad h_2 = \text{ReLU}(1 \cdot (-1) + 0) = 0\] \[\hat{y}_2 = (-1) \cdot 1 + 1 \cdot 0 + 0 = -1 \quad \checkmark\]
\[\mathcal{L}(B) = \frac{1}{2}[(1-1)^2 + (-1-(-1))^2] = 0\]
Both achieve perfect fit! (Settings A and B are equivalent, just with units swapped)
Now consider the average: \(w_1 = 0, w_2 = 0, \beta_1 = 0, \beta_2 = 0\)
For observation 1 (\(x_1 = 1, y_1 = 1\)): \[h_1 = \text{ReLU}(0 \cdot 1 + 0) = 0, \quad h_2 = \text{ReLU}(0 \cdot 1 + 0) = 0\] \[\hat{y}_1 = 0 \cdot 0 + 0 \cdot 0 + 0 = 0\]
For observation 2 (\(x_2 = -1, y_2 = -1\)): \[h_1 = \text{ReLU}(0 \cdot (-1) + 0) = 0, \quad h_2 = \text{ReLU}(0 \cdot (-1) + 0) = 0\] \[\hat{y}_2 = 0 \cdot 0 + 0 \cdot 0 + 0 = 0\]
\[\mathcal{L}(\text{avg}) = \frac{1}{2}[(0-1)^2 + (0-(-1))^2] = \frac{1}{2}[1 + 1] = 1\]
Convexity would require: \(\mathcal{L}(\text{avg}) \leq \frac{1}{2}\mathcal{L}(A) + \frac{1}{2}\mathcal{L}(B)\)
But here: \(1 > 0\)! The loss function is non-convex.
This happens because ReLU creates a non-convex response surface.
A single hidden layer already has multiple minima.
Adding depth makes it exponentially worse:
Recall our deep network:
\[\hat{y} = f_{\text{out}}(f_D(f_{D-1}(\cdots f_1(x))))\]
To update \(W_1\) (the first layer), we need:
\[\frac{\partial \mathcal{L}}{\partial W_1} = \frac{\partial \mathcal{L}}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial h_D} \cdot \frac{\partial h_D}{\partial h_{D-1}} \cdots \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial W_1}\]
The gradient must flow backward through ALL \(D\) layers!
Simple example: Suppose each layer just multiplies by weight \(w\):
\[h^{(D)} = w \cdot w \cdot \ldots \cdot w \cdot x = w^D \cdot x\]
The gradient with respect to \(w\):
\[\frac{\partial h^{(D)}}{\partial w} \propto w^{D-1}\]
Why is this going to be a problem for SGD methods?
1980s-1990s: “Deep networks don’t work”
The breakthrough came in stages:
| Year | Innovation | Impact |
|---|---|---|
| 2006 | Layer-wise pretraining (Hinton) | First “deep” networks trained |
| 2010 | ReLU activation | Reduced vanishing gradients |
| 2012 | AlexNet (GPUs + dropout) | Deep CNNs work! |
| 2015 | ResNets (skip connections) | 100+ layer networks |
| 2017 | Transformers (attention) | Massively deep models |
What if instead of: \[h_{l+1} = f_l(h_l)\]
We used: \[h_{l+1} = f_l(h_l) + h_l\]
This is called a skip connection or residual connection.
\[h_{l+1} = f_l(h_l) + h_l\]
Gradient flow:
\[\frac{\partial h_{l+1}}{\partial h_l} = \frac{\partial f_l(h_l)}{\partial h_l} + I\]
Even if \(\frac{\partial f_l}{\partial h_l}\) vanishes, the gradient can still flow through the \(+I\) term!
The layer only needs to learn the “residual” — what to change about \(h_l\).
We’ll see this in detail with ResNets and Transformers.
To train deep networks successfully:
ReLU activations — Avoids vanishing gradients from saturating activations (unlike sigmoid/tanh)
Careful initialization — Start with reasonable gradient magnitudes (Xavier, He initialization)
Skip connections — Provide gradient highways through the network
Normalization layers — BatchNorm, LayerNorm stabilize activations
Adaptive optimizers — Adam, etc. adjust learning rates per-parameter
Details next lecture: Backpropagation!
We have: \(\hat{y} = f_{\text{out}}(f_2(f_1(x)))\)
We need: \(\frac{\partial \mathcal{L}}{\partial W_1}, \frac{\partial \mathcal{L}}{\partial W_2}, \frac{\partial \mathcal{L}}{\partial W_{\text{out}}}\)
Next lecture:
Come prepared: How do you differentiate \(f(g(x))\) with respect to parameters in \(g\)?