Skip to main content

A Latent Space Is Not Automatically a Place

An autoencoder only needs codes that reconstruct its inputs. A VAE learns a probabilistic model whose prior gives generation somewhere to begin.

· 4 min read
Series parts
  1. Part 1 A One-Line Sampler Can Quietly Double the Odds
  2. Part 2 When Randomness Should Take Sides
  3. Part 3 The Raffle Changes After Every Winner
  4. Part 4 A Fair Sample in 100 Slots, Forever
  5. Part 5 The Hidden Full-Table Scan in `ORDER BY random()`
  6. Part 6 How Wrong Can a Random Sample Be?
  7. Part 7 Small Data Structures That Lie Just Enough
  8. Part 8 When Luck Is Part of the Proof
  9. Part 9 I Found a Probability Bug in My Old TSP Solver
  10. Part 10 Softmax Is Not Confidence
  11. Part 11 The Useful Fiction of Hidden Causes
  12. Part 12 A Latent Space Is Not Automatically a Place
  13. Part 13 Before a Token Is Chosen, Context Has to Move
  14. Part 14 Who Actually Chooses the Next Token?
  15. Part 15 One Token, Zero Materialized Logits
On this page

An autoencoder’s latent space looks like a place only because we draw it as one.

The training objective asks the encoder for codes that let the decoder reconstruct its inputs. It does not ask what should happen at an arbitrary coordinate nobody encoded. For reconstruction, that contract can be enough. For generation, it leaves no principled answer to the first question: where should a new latent point come from?

A variational autoencoder answers by specifying a probabilistic generative model and learning approximate inference for it.1

What an ordinary autoencoder gives you

A basic autoencoder learns a hidden code h=f(x)h = f(x) that is sufficient to reconstruct the input through a decoder x^=g(h)\hat{x} = g(h).

That is a perfectly good objective. It does not force the hidden codes to occupy the latent space in a known distribution. The encoder can place training examples on separated regions and leave large areas unused.

An arbitrary point can therefore decode badly without the autoencoder failing. We asked for reconstruction and silently hoped for geography.

What a VAE changes

A variational autoencoder keeps the encoder-decoder flavor but adds a probabilistic latent-variable model underneath.

The generative picture becomes:

  • Assume a latent variable zz.
  • Assume a prior p(z)p(z), often N(0,I)\mathcal{N}(0, I).
  • Generate xx from p(xz)p(x \mid z).
  • Learn an approximate posterior q(zx)q(z \mid x).

A VAE is not just “compress and reconstruct.” It is “learn a generative model with hidden variables, and learn an approximate posterior over those hidden variables.”

The practical intuition

A plain autoencoder learns codes. A VAE chooses a prior, commonly p(z)=N(0,I)p(z)=\mathcal{N}(0,I), and trains a decoder as part of a model that should assign probability to observations generated from prior samples.

People often summarize the KL term as “making the latent space smooth.” That picture is useful and incomplete. Optimizing the ELBO does not guarantee that every interpolation is meaningful, that the aggregated posterior exactly matches the prior, or that the decoder uses the latent variable at all.

The trick that makes training work

The encoder in a VAE does not output one deterministic code. It outputs the parameters of a distribution, often a mean and variance for a Gaussian approximate posterior.

That leads to the famous move: the reparameterization trick.

Instead of sampling zz directly in a way that blocks gradients, you write the sample as a deterministic function of encoder outputs and external noise:

z=μ(x)+σ(x)ε,εN(0,I).z = \mu(x) + \sigma(x)\odot \varepsilon, \qquad \varepsilon \sim \mathcal{N}(0, I).

This produces a low-variance pathwise gradient estimator: randomness stays in ε\varepsilon, while the sample remains a differentiable function of μ\mu and σ\sigma. Other gradient estimators exist, so reparameterization is not the only conceivable way to train a stochastic model. It is the move that made this continuous-latent setup practical with ordinary backpropagation.

Here, a small PyTorch example is the clearest option. This is training code, and Python keeps the mechanics visible.

import torch
import torch.nn as nn
class Encoder(nn.Module):
def __init__(self, d_in=784, d_hidden=256, d_latent=2):
super().__init__()
self.net = nn.Sequential(
nn.Linear(d_in, d_hidden),
nn.ReLU(),
)
self.mu = nn.Linear(d_hidden, d_latent)
self.logvar = nn.Linear(d_hidden, d_latent)
def forward(self, x):
h = self.net(x)
return self.mu(h), self.logvar(h)
class Decoder(nn.Module):
def __init__(self, d_latent=2, d_hidden=256, d_out=784):
super().__init__()
self.net = nn.Sequential(
nn.Linear(d_latent, d_hidden),
nn.ReLU(),
nn.Linear(d_hidden, d_out),
nn.Sigmoid(),
)
def forward(self, z):
return self.net(z)
def reparameterize(mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + std * eps

That reparameterize function contains the key step.

The objective

The training objective is often written as an evidence lower bound, or ELBO:

L(x)=Eq(zx)[logp(xz)]DKL(q(zx)p(z)).\mathcal{L}(x)= E_{q(z\mid x)}[\log p(x\mid z)] -D_{\mathrm{KL}}(q(z\mid x)\,\|\,p(z)).

It is a lower bound because L(x)logp(x)\mathcal{L}(x) \le \log p(x). Maximizing it both raises a tractable objective for the generative model and improves the approximate posterior used to compute that objective.

The formula is doing two jobs:

  • Reconstruct the input well.
  • Keep the approximate posterior close to the chosen prior.

If the reconstruction term dominates, the approximate posterior can drift into regions that are hard to reach from the prior. If the KL term dominates, the decoder may ignore zz and the approximate posterior may collapse toward the prior. The objective exposes the tension; it does not balance the two automatically.

A VAE gives generation an explicit starting distribution, but its guarantees are probabilistic and objective-dependent, not cartographic. The map metaphor helps only while I remember that the loss, not the diagram, defines the territory.