Autoencoders
Encoder, bottleneck and decoder, reconstruction error for anomalies, denoising, and the latent space image generators use.
Create a free account Try part of it here
Why it matters
An autoencoder is a network trained to copy its input to its output through a narrow middle layer. That sounds pointless until you see what the narrow layer forces: to pass the copy through a few numbers, the network has to learn what matters about the data and drop the rest. Those few numbers are a learned compression, with no labels needed. Autoencoders power anomaly detectors that learn what "normal" looks like, denoisers, and, scaled up, the latent space that image generators like Stable Diffusion paint in.
What you'll be able to do
- Explain how an autoencoder learns a compressed code by reconstructing its own input
- Build and train a small autoencoder, and read its reconstruction error
- Use reconstruction error to detect anomalies, and a noisy input to learn robust features
- Connect autoencoders to PCA, to embeddings and to the latent space image generators work in
What it covers
- Encoder, bottleneck, decoder
- Training one
- Choosing the bottleneck size
- Anomaly detection by reconstruction
- Reconstruction error per row
- Denoising, and other ways to stop copying
- From autoencoders to generating images
Show the answer
A 2-number summary of what the readings share
With only 2 numbers in the middle, it can't copy 8 independent values. It succeeds only if the readings move together in a pattern that 2 numbers can describe, and it learns that pattern.
Encoder, bottleneck, decoder
An autoencoder has two halves. The encoder maps an input to a short code , and the decoder maps the code back to a reconstruction . Training minimizes the reconstruction error, usually the mean squared difference between and .
In plain words: squeeze the input down, rebuild it, and score how far the rebuild is from the original.
The input is its own target, so no labels are needed: this is self-supervised learning.
The narrow middle, the bottleneck, is the whole point. If the code had as many numbers as the input, the network could learn to copy and learn nothing. Squeezed to a few numbers, it has to find the directions along which the data really varies. With linear layers and squared error, an autoencoder finds the same subspace as PCA; with nonlinear layers, it can follow curved structure that PCA can't.
Here's the data for this lesson: 8 readings from a gear-testing rig, driven by 2 hidden factors, temperature and load, plus a little noise.
Training one
The model below is 8 → 4 → 2 → 4 → 8, trained with Adam on the mean squared reconstruction error, using the course's PyTorch-style library:
import numpy as np import torch from torch import nn rng = np.random.default_rng(0) factors = rng.normal(size=(300, 2)) # temperature and load, hidden mixing = rng.normal(size=(2, 8)) readings = factors @ mixing + rng.normal(scale=0.1, size=(300, 8)) X = torch.tensor(readings, dtype=torch.float32) torch.manual_seed(0) encoder = nn.Sequential(nn.Linear(8, 4), nn.ReLU(), nn.Linear(4, 2)) decoder = nn.Sequential(nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 8)) optimizer = torch.optim.Adam(list(encoder.parameters()) + list(decoder.parameters()), lr=0.02) loss_fn = nn.MSELoss() for epoch in range(301): loss = loss_fn(decoder(encoder(X)), X) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 100 == 0: print(f"epoch {epoch}: reconstruction error {loss.item():.3f}") print("code for the first reading:", [round(v, 2) for v in encoder(X[:1]).tolist()[0]])
The error falls far below the data's own spread, because two learned numbers per reading capture almost everything. The code isn't temperature and load themselves; it's some mixture of them that's equally good for reconstruction, which is typical: the bottleneck finds the information, not the names.
import torch from torch import nn encoder = nn.Sequential(nn.Linear(8, 4), nn.ReLU(), nn.Linear(4, 2)) print(tuple(encoder(torch.zeros(5, 8)).shape))
Show the answer
(5, 2)
Each of the 5 rows is squeezed from 8 numbers to 4 and then to 2: one 2-number code per reading.
Choosing the bottleneck size
Too small a code and the reconstructions blur away real detail; too large and the network drifts back toward copying. The practical way to choose is the same as for the number of principal components: train a few sizes, plot the reconstruction error on held-out data against the code size, and pick the point where adding numbers stops helping much. Here, with two hidden factors behind the readings, the error drops sharply from 1 to 2 numbers and barely moves after that.
What the code is for matters too. For anomaly detection, a smaller code is often better, because a tight model of normal data reconstructs unusual data worse. For compression you want the smallest code that keeps the detail you care about, and for features you'll feed to another model, test the downstream model with each size.
Show the answer
It can just copy, so the code means nothing
With as many numbers in the middle as in the input, copying is a perfect solution, and the code is just the input in disguise. The bottleneck (or another constraint) is what forces learning.
Show the answer
Principal component analysis
Both find the few directions that keep as much of the data's variation as possible. Nonlinear layers let an autoencoder go further, along curved structure.
Write a sentence or two, then compare it with a good answer in the lesson.
Anomaly detection by reconstruction
An autoencoder trained on normal data learns to reconstruct normal data. Show it something unlike anything it trained on, and it reconstructs badly, because its few code numbers have no way to describe the new pattern. So the reconstruction error of each item is an anomaly score:
import numpy as np import torch from torch import nn rng = np.random.default_rng(0) mixing = rng.normal(size=(2, 8)) normal = rng.normal(size=(300, 2)) @ mixing + rng.normal(scale=0.1, size=(300, 8)) X = torch.tensor(normal, dtype=torch.float32) torch.manual_seed(0) model = nn.Sequential(nn.Linear(8, 4), nn.ReLU(), nn.Linear(4, 2), nn.Linear(2, 4), nn.ReLU(), nn.Linear(4, 8)) optimizer = torch.optim.Adam(model.parameters(), lr=0.02) for _ in range(300): loss = nn.MSELoss()(model(X), X) optimizer.zero_grad(); loss.backward(); optimizer.step() typical = rng.normal(size=(3, 2)) @ mixing broken = typical.copy(); broken[:, 5] += 4 # one sensor stuck high for name, rows in [("typical", typical), ("broken sensor", broken)]: t = torch.tensor(rows, dtype=torch.float32) errors = ((model(t) - t) ** 2).mean(dim=1) print(name, [round(e, 2) for e in errors.tolist()])
The broken readings break the pattern the network learned, so their errors are many times larger, about 2.3 against nearly 0. This works with no labeled faults at all, which is why it's common for machines, transactions and network traffic. The threshold comes from the same review-budget reasoning as any anomaly detector.
errors = [0.02, 0.01, 2.31, 0.04] print(max(range(len(errors)), key=lambda i: errors[i]))
Show the answer
2
Reading 2 reconstructs worst by far, so it's least like the normal data the model learned. The code prints its index, not its score.
Reconstruction error per row
Write row_errors(original, reconstructed) for two lists of equal-length rows: the mean squared difference of each row, as a list. This is the anomaly score.
def row_errors(original, reconstructed): return [] print(row_errors([[1, 2], [3, 4]], [[1, 2], [3, 6]]))
Write a sentence or two, then compare it with a good answer in the lesson.
Denoising, and other ways to stop copying
A bottleneck isn't the only way to force learning. A denoising autoencoder gets a corrupted input, with noise added or values blanked out, and must reconstruct the clean original. Copying no longer works, since the input is wrong, so the network has to learn the structure that tells signal from noise. Trained this way, it cleans up noisy sensor data and scanned documents, and the masked-input versions of the idea gave us BERT-style pretraining for text.
Other constraints work too: penalize the code for being large or for having many non-zero values (a sparse autoencoder). Researchers now use sparse autoencoders on the inside of large language models, to pull apart the many concepts mixed into each layer's activations and see which ones a model is using.
Show the answer
Copying a corrupted input gives the wrong answer
The target is the clean original, but the input is noisy, so a perfect copy is wrong. The network has to learn what clean data looks like.
Each use leans on a different part: the code, the reconstruction, or the error.
From autoencoders to generating images
A plain autoencoder's codes can be scattered: pick a random code and the decoder may produce garbage, because nothing made the space between training codes meaningful. A variational autoencoder (VAE) fixes this by training the encoder to produce a small cloud of codes for each input, all close to a standard shape, so the whole code space fills in and any point in it decodes to something plausible. Sampling a code and decoding it then generates new data.
That idea sits inside today's image generators. Stable Diffusion doesn't paint pixels directly: an autoencoder compresses each 512 by 512 image into a much smaller grid of codes, the diffusion model works in that latent space, where it's far cheaper, and the decoder turns the final codes back into pixels. The same compress-then-reconstruct pattern shows up in audio and video models too.
That also explains a familiar quirk of generated images: fine details such as small text and fingers depend on what the autoencoder's small grid can hold. Detail the decoder can't rebuild from the codes never appears in the final image, however good the diffusion model is.
Show the answer
The latent grid is far smaller, so each step is much cheaper
The autoencoder keeps what matters in a much smaller representation, so the expensive diffusion steps run on far fewer numbers; the decoder restores the pixels at the end.
Write a sentence or two, then compare it with a good answer in the lesson.
Practice what you just read
- 2 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 6 review cards for spaced repetition
Try a quiz question
What is an autoencoder trained to output?