Part I, Optimisation, generalisation, adaptation
What survives the bottleneck
An autoencoder learns by copying its input through a code. Make the code narrow, make it a distribution, or make it sparse, and you get three different models that answer three different questions.
Assumes principal component analysis; the KL divergence; the singular value decomposition, as in the piece on low-rank updates.
An autoencoder is a network trained to give back its input. An encoder maps to a code , a decoder maps the code back, and training minimises
Left alone, the task is trivial: with both maps can be the identity. Everything interesting comes from the constraint placed on the code, and the constraint decides what the model is for. A narrow code compresses. A code that is a distribution can be sampled, so the decoder becomes a generator. A sparse code, wider than the input, can be read: each coordinate comes to stand for one thing. This piece takes the three in turn, and trains each one live in front of you.
A narrow code
Start with the linear case, and with . Their product has rank at most , so the autoencoder is searching for the best rank- map from the data to itself. That is the Eckart–Young problem from the piece on low-rank updates, and its answer is the same: projection onto the top principal directions. For centred data with covariance eigenvalues ,
Baldi and Hornik showed more: this loss has no spurious local minima. Every local minimum is global, and every other critical point is a saddle, so gradient descent finds PCA.Baldi and Hornik, Neural networks and principal component analysis, 1989. It finds the principal subspace, though, not the principal directions.For any invertible matrix , the pair reconstructs exactly as well. The code’s coordinates are fixed only up to , which is why nothing forces them to mean anything. The sparse autoencoder at the end of this piece is one answer to that.
A nonlinear autoencoder can do better when the data lie near a curved surface instead of a flat one. The figure trains one with a single number as its code, on points near a curve in the plane. It trains while it is on the page; change any setting and it starts again.
Turn off the nonlinear layers first. The blue line straightens onto PCA’s dashed line, and the error trace settles exactly on PCA’s level: a linear autoencoder is PCA, found slowly. Turn them back on and the decoder’s curve bends to follow the data, and the error falls by close to two orders of magnitude.
The strip of codes is the more telling panel. On the arc, the shading runs light to dark from one end to the other. The code has recovered position along the curve, up to direction and scale, though nothing in the loss asked for it. The readout measures this as rank agreement between code and true position.
Now choose the spiral. The error can still get low while the order is lost: the decoder’s curve visits the arms in a different sequence, jumping between them. A low reconstruction error says the code is sufficient. It does not say the code is the coordinate you had in mind. Raise the noise to 0.05 and beyond, and once the arms blur into each other, no single number can tell them apart.
What a denoising autoencoder learns
A different way to stop an autoencoder copying is to corrupt its input and ask for the original. Train to output where .Vincent et al., Extracting and composing robust features with denoising autoencoders, 2008. The optimal reconstruction is the posterior mean , and for Gaussian noise that mean has a closed form, Tweedie’s formula:
where is the density of the noisy data. The reconstruction error is therefore a scaled estimate of the score, the gradient of the log density.Alain and Bengio, What regularized auto-encoders learn from the data-generating distribution, 2014. A denoising autoencoder trained at many noise levels is, in effect, the network inside a diffusion model.
A code you can sample from
A plain autoencoder’s code space has no reason to be filled. Decode a random and you may land anywhere. To generate, we need a code with a known distribution.
The variational autoencoder starts from a latent variable model: a prior and a decoder .Kingma and Welling, Auto-encoding variational Bayes, 2014; Rezende, Mohamed and Wierstra, 2014, independently. The likelihood is intractable, so an encoder approximates the posterior. For any , the log likelihood splits exactly:
The last term is non-negative, so the first two form a lower bound, the ELBO, and maximising it both raises the likelihood and pulls towards the true posterior.
Two details make it trainable. The sample is written as with , the reparameterisation, so gradients flow through and . And for a diagonal Gaussian , the KL term has a closed form:
With a Gaussian decoder of variance , plus a constant. Multiply the negative ELBO by and it becomes
So the β of the β-VAE is not a hack added to the objective.Higgins et al., β-VAE, 2017. The rate–distortion reading is from Alemi et al., Fixing a broken ELBO, 2018. It is the decoder’s assumed noise level, and it sets the price of information. The rate is how many nats the code spends describing each point, and it splits in two:
The first part is information the code genuinely carries about . The second is waste: the mismatch between the prior and the aggregate posterior , the distribution of codes the encoder actually uses. Where leaves holes that the prior covers, samples from the prior decode to places the data never were.
At β = 0.010 the posteriors are narrow bars laid out in order, the aggregate posterior roughly covers the prior, and the generated rings sit on the data. Drag β down towards . The bars shrink to slivers and the rate climbs past five nats: the code becomes nearly deterministic, an ordinary autoencoder with a little noise. A rough rule is that a 1-d code pinning a point to within of a range of width costs about nats.
Now drag β up. The posteriors widen and drift towards zero, the rate falls, and the distortion in the readout rises. Near β = 1 the rate drops to zero. Every posterior has become the prior, the decoder ignores , and every generated ring lands on the same point, the mean of the data. This is posterior collapse. Here we forced it by pricing information too high. With a decoder powerful enough to model on its own, such as an autoregressive one, it happens even at β = 1, because the decoder can do without the code.Bowman et al., Generating sentences from a continuous space, 2016, which also introduced KL annealing as a remedy. “Free bits” (Kingma et al., 2016) is another. Watch the training path in the bottom panel: at high β it runs left, towards zero rate, and stays there.
Derive the KL divergence between and given above. Then show that the rate splits as .
Solution
With and : . Under , and , giving , which is the formula.
For the split, write and take the expectation over and . The first term averages to the mutual information under the joint . In the second, the pair is distributed with marginal , so it averages to .
A code you can read
The last constraint goes the other way: make the code wider than the input, but insist that only a few of its coordinates are active at once.
The motivation comes from looking inside trained networks. A layer with neurons can represent far more than features, provided the features are rarely active together, by giving each feature a direction and letting the directions overlap. This is superposition.Elhage et al., Toy models of superposition, 2022. Its cost is that single neurons respond to several unrelated things, so reading the network neuron by neuron fails. If an activation is a sparse sum over directions, recovering the and is sparse dictionary learning, an old problem from vision.Olshausen and Field, Emergence of simple-cell receptive field properties by learning a sparse code for natural images, 1996.
A sparse autoencoder learns the dictionary and the encoding together:Bricken et al., Towards monosemanticity, 2023; Cunningham et al., Sparse autoencoders find highly interpretable features in language models, 2023.
The columns of are the dictionary, one atom per latent. The unit-norm constraint matters: without it, the model could shrink by any factor and grow by , and the penalty would fall to nothing at no cost.
The figure works in two dimensions so that everything can be seen. A handful of true features point in different directions in the plane, and each is on, with a random strength, only some of the time. Two dimensions hold five features only because they are rarely on together.
With the starting settings, a dictionary of 8 latents finds most of the features, each with an atom lying along its dashed line, and leaves a couple of latents dead. Lower λ. Reconstruction stays perfect, but the atoms stop tracking the features and more latents fire per point: in two dimensions, any two atoms can rebuild any point, so without a price on activity nothing pulls the dictionary towards the truth. Raise λ and the codes get sparser but the reconstruction suffers, because the L1 penalty charges for the size of an activation as well as its presence. The encoder learns to under-report.
That shrinkage motivated replacing the penalty with a hard constraint: keep only the largest activations and zero the rest, a TopK autoencoder.Gao et al., Scaling and evaluating sparse autoencoders, 2024. JumpReLU (Rajamanoharan et al., 2024) is a related fix. Set keep only the top k to 1 and every feature is found, nearly exactly. Set it to 2 and recovery breaks down even though the variance left unexplained drops to zero. Two active atoms span the plane, so any dictionary reconstructs perfectly and the objective no longer cares where the atoms point. The general lesson: a dictionary is identifiable only when the code is much sparser than the dimension it lives in. Real sparse autoencoders work with in the thousands, tens of thousands to millions of latents, and a few dozen active at a time.
The dictionary size has its own failure modes. With fewer latents than features, atoms settle between features and each latent stands for a blend. With many more, latents die, or two atoms split one feature between them. Note also what the readout can do here that a real study cannot: count the features found. Inside a trained model there is no ground truth, and judging whether a learned feature is real is the hardest part of the method.
A minimal sparse autoencoder in PyTorch:
class SAE(nn.Module):
def __init__(self, d, n_latents, lam=1e-3):
super().__init__()
self.W_e = nn.Parameter(torch.randn(n_latents, d) / d**0.5)
self.b_e = nn.Parameter(torch.zeros(n_latents))
self.W_d = nn.Parameter(self.W_e.detach().T.clone()) # start tied
self.b_d = nn.Parameter(torch.zeros(d))
self.lam = lam
def forward(self, x):
f = torch.relu((x - self.b_d) @ self.W_e.T + self.b_e)
x_hat = f @ self.W_d.T + self.b_d
loss = ((x - x_hat) ** 2).sum(-1).mean() + self.lam * f.abs().sum(-1).mean()
return x_hat, f, loss
@torch.no_grad()
def renorm(self): # after every optimiser step
self.W_d /= self.W_d.norm(dim=0, keepdim=True)
One feature of strength lies along a unit atom , and the encoder reads it exactly as before the penalty. Minimise over . What does the sparse autoencoder report for strong features, and for weak ones? How does TopK avoid this?
Solution
The derivative vanishes at , so . Strong features are under-reported by , and features weaker than vanish entirely. TopK puts no price on magnitude, so the activations it keeps are unbiased; it controls sparsity by count alone.
In the figure, set three true features, a dictionary of two, and λ at its starting value. Where do the atoms settle, and why? Then set the dictionary to three and the chance a feature is on to 50%. What goes wrong, and which assumption of the method has failed?
Solution
With two atoms for three features, each atom settles between features or on one feature and a blend of the others, trading reconstruction against sparsity. At 50%, features are routinely on together, so data points fill the plane instead of lying along rays, and a sparse code in a fixed dictionary stops being a good description. The method assumes sparsity, and that is what has failed.