Generative AI
Autoencoder (Bottleneck Compression)
A bottleneck of k units forces every image to be rebuilt as a weighted sum of the same k shared templates. The code is only the weights — and how much detail survives is set by how many templates you allow.
Stage 1 of 5: The Data
Image 1 of 16, 7 by 7 pixels, squeezed through 4 latent units. The code keeps 92.3 percent of the dataset's variance, and the rebuild is off by 0.0029 per pixel on average. The rebuild also undershoots to -0.083, below black, which the brightness scale clips.
- Original image
- Reconstruction
- Absolute error
- Latent unit carrying variance
- Latent unit with none left to carry
Which of the sixteen images to push through the bottleneck.
Sixteen 7x7 images, each a soft blob sitting somewhere on a ring. Step through them with Input pattern. Forty-nine pixels per image, but only one thing really varies between them: the angle.
- 49 pixels
- project onto 4
- code of 4
- add 4 templates
- 92.3% recovered
Check your understanding
Forty-nine pixels in, a handful of numbers in the middle, forty-nine back out. Watch what survives the squeeze, watch where the loss lands, and find the point past which extra capacity buys nothing at all.
What you are looking at
Sixteen 7×7 images. Each is a soft blob sitting somewhere on a ring, so 49 pixels per image but really only one thing varies between them: the angle.
An autoencoder squeezes each image through a narrow layer and tries to rebuild it. The encoder turns 49 numbers into a few; the decoder turns those few back into 49. Nothing else about the network matters here, because the bottleneck is the only place information can be lost.
What the bottleneck actually is
The useful way to think about the middle layer is not "a compressed copy of the image". It is a set of mixing weights.
The decoder holds a small collection of template images, shared across the whole dataset — every input gets rebuilt from the same templates. What the code says is only how much of each. A bottleneck of 4 means every one of the sixteen images has to be expressible as four numbers weighting four fixed templates.
That is why the code panel is a bar chart of signed values rather than a picture. A negative weight means subtract that template, which is as meaningful as adding it, and a magnitude-only view would throw that away.
Why this lab computes PCA
There is no gradient descent here, and that is deliberate rather than a shortcut.
A linear autoencoder trained to convergence on squared error provably spans the same subspace as principal component analysis — this is a theorem, from Baldi and Hornik in 1989, not an approximation. So the lab computes the optimum directly. Every number you see is the best a linear autoencoder could do, which means any loss on screen is a property of the bottleneck rather than of an undertrained network.
The trade is that you cannot watch it learn. What you get instead is that nothing here is random at all: no seed, no sampling, no initialisation. Every figure below is exact and will be identical on your machine.
The exchange rate
Drag Bottleneck size at the What Was Lost stage and watch Variance kept:
| Units | Variance kept |
|---|---|
| 1 | 33.9% |
| 2 | 67.7% |
| 3 | 80.1% |
| 4 | 92.3% |
| 5 | 95.5% |
| 6 | 98.6% |
The returns fall off sharply, and they do so by construction: PCA sorts its components by how much variance they carry, so each one you add explains less than the last. A steep early curve like this is exactly the condition under which compression is worth doing — five numbers standing in for forty-nine, at 95% fidelity.
Two other things are worth noticing in that panel. The error map is per-pixel, and at a small bottleneck it shows a bright spot where the blob should be and a bright ring where the reconstruction put light that was never there — the decoder is not merely missing the blob, it is hedging across the whole ring. And the code's values arrive in pairs of similar size, as do the eigenvalues (0.62, 0.62, then 0.23, 0.23). That is the ring showing up in the maths: it takes two numbers, a sine and a cosine, to say where on a circle you are, so the templates come two at a time.
Break it, twice
Squeeze too hard. At 2 units the code keeps 67.7% and the rebuild stops being the image you picked — it smears along the ring, because two numbers cannot say where on the ring the blob sat. Nothing is broken and no number is invalid. This is what "lossy" means when you look at it directly.
Or give it capacity it cannot use. Push the bottleneck past 15 and Wasted units starts counting. Sixteen images, once centred, span at most fifteen directions — so there is no sixteenth direction of variance to project onto. The encoder writes exact zeros into those slots, the reconstruction stops changing entirely, and the code panel hatches the dead bars so they are distinguishable from ones that merely happen to be small.
That second one is worth sitting with. Capacity beyond the data's effective rank is not slightly wasteful, it is provably unusable — and the marker on the slider is computed from the dataset's own spectrum rather than written down.
Reference
- Encoder
- z = U_k^T (x - mu). Project the centred image onto the top k components. Forty-nine numbers become k.
- Decoder
- x_hat = mu + U_k z. Add the k templates back up, weighted by the code. The templates are shared by every image; only z changes.
- Why PCA is the honest model here
- A linear autoencoder trained to convergence on squared error spans exactly the same subspace as PCA (Baldi and Hornik, 1989). So this lab computes the optimum directly rather than watching a toy network approach it.
- Explained variance
- The share of the dataset's variance the first k components carry. Measured here: 33.9% at k=1, 67.7% at 2, 92.3% at 4, 95.5% at 5, 98.6% at 6.
- Effective rank
- How many components carry any variance at all — 15 for this dataset of 16 images. Beyond it the encoder can only write zeros.
- Reconstruction MSE
- Mean squared difference between the image and its rebuild, over all 49 pixels. Falls from 0.037 at one unit to under 1e-8 at full rank.
Break it on purpose
Take the bottleneck down to 1 or 2 units. Variance kept falls to 33.9% and 67.7%, and the reconstruction stops being the image you chose: the blob smears out along the ring, because two numbers cannot say where on the ring it sat. The error map shows exactly where the loss went — a bright spot where the blob should be, and a bright ring where the reconstruction put light that was never there. This is what "lossy" means concretely. Nothing is broken and no number is invalid; the code simply has fewer degrees of freedom than the data does, and the decoder is doing the best any linear decoder could.