Deep LearningAutoencoders & VAEs

Autoencoders & VAEs

Background
  • Autoencoders as dimensionality reduction: Rumelhart et al. (1986) used backpropagation for non-linear PCA
  • Variational Autoencoders (VAEs) by Kingma & Welling (2013) added a probabilistic latent space enabling generation
  • Foundation for latent diffusion models (Stable Diffusion) and representation learning
The Concept

Autoencoders compress input to a latent code (encoder) then reconstruct it (decoder), learning a compact representation. VAEs add a constraint that the latent space follows a Gaussian distribution, enabling sampling and interpolation - you can generate new examples by sampling from the latent space.

How it Links
  • Latent diffusion models (Stable Diffusion) use VAE encoders/decoders around a diffusion process
  • The VAE ELBO loss connects to variational inference and KL divergence
  • Representation learning connects to SSL (self-supervised learning), contrastive learning, and CLIP
Concrete Example
A VAE trained on face images: the latent space organizes by age, gender, hairstyle, glasses. Sampling a point between two face latents produces a face that blends their features. Moving along the "age" direction in latent space makes the face look older or younger continuously.
Learning outcomes
  • Define the vanilla, denoising, and sparse autoencoders and explain when each helps.
  • Derive the ELBO from the marginal log-likelihood and the KL divergence between the variational posterior and the true posterior.
  • Justify the reparameterization trick: why we sample z = mu + sigma * eps instead of z ~ q(z|x), and how it enables backpropagation through stochastic nodes.
  • Derive the closed-form KL divergence between two diagonal Gaussians and apply it as the regularizer in a VAE.
  • Explain the beta-VAE disentanglement trade-off and the KL-vanishing failure mode (Bowman 2015).

TL;DR

TL;DR

An autoencoder learns a bottlenecked encoder-decoder pair (gϕ,fθ)(g_\phi, f_\theta) that minimizes reconstruction error on its inputs [1]. Denoising autoencoders [2] corrupt the input and reconstruct the clean target, learning robust features. Sparse autoencoders add an L1 penalty on activations to learn sparse codes. The variational autoencoder (VAE) [3][4] places a probabilistic prior p(z)=N(0,I)p(z) = \mathcal{N}(0, I) on the latent and trains by maximizing the evidence lower bound (ELBO)

L(θ,ϕ;x)=Eqϕ(z∣x)[log⁡pθ(x∣z)]−KL(qϕ(z∣x) ∥ p(z)),\mathcal{L}(\theta, \phi; x) = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] - \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)),

with the reparameterization trick z=μϕ(x)+σϕ(x)⊙ϵz = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon for ϵ∼N(0,I)\epsilon \sim \mathcal{N}(0, I) enabling gradients through the sample. beta-VAE [5] upweights the KL term to encourage disentangled factors at the cost of reconstruction sharpness. VAE samples are blurry because the per-pixel Gaussian likelihood is the wrong noise model for natural images, a fact diffusion models partially fix.

Intuition

An autoencoder is a network that learns to copy its input to its output through a bottleneck: the encoder gϕ:x↦zg_\phi : x \mapsto z compresses the input into a low-dimensional latent code zz, and the decoder fθ:z↦x^f_\theta : z \mapsto \hat x reconstructs xx from zz. The bottleneck forces the network to discover a compressed representation; once trained, the latent code zz is a learned feature vector for downstream tasks.

The plain autoencoder is deterministic: one input maps to one latent, and reconstructing x^=fθ(gϕ(x))\hat x = f_\theta(g_\phi(x)) is just a non-linear PCA-like operation. The variational autoencoder makes everything stochastic: the encoder outputs a distribution qϕ(z∣x)q_\phi(z \mid x) over latents (typically a diagonal Gaussian), the decoder defines a distribution pθ(x∣z)p_\theta(x \mid z) (typically Gaussian or Bernoulli), and we train by maximizing a lower bound on log⁡pθ(x)\log p_\theta(x). The reward is a generative model: sample z∼p(z)=N(0,I)z \sim p(z) = \mathcal{N}(0, I), pass through the decoder, and you get a new “thought” from the same distribution as the training data.

What makes a VAE different from a plain autoencoder

A plain autoencoder produces an arbitrary code per example, with no constraint on what the code distribution looks like across the dataset. The codes can land anywhere in the latent space. To sample new data you would need to know which regions of latent space are plausible.

A VAE imposes a prior p(z)=N(0,I)p(z) = \mathcal{N}(0, I) on the latent distribution and pulls the encoder’s output toward it via a KL term. After training, the marginal posterior ∫qϕ(z∣x)pdata(x) dx\int q_\phi(z \mid x) p_{\text{data}}(x)\, dx is approximately N(0,I)\mathcal{N}(0, I), so to generate new examples you sample from N(0,I)\mathcal{N}(0, I) and decode. The KL term is what turns “compressor” into “generative model” [3].

Vanilla and denoising autoencoders

A vanilla autoencoder with squared loss is

Lvanilla=1N∑i∥xi−fθ(gϕ(xi))∥22.\mathcal{L}_{\text{vanilla}} = \frac{1}{N} \sum_i \lVert x_i - f_\theta(g_\phi(x_i)) \rVert_2^2.

If the encoder and decoder are linear and the loss is squared error, the optimum recovers PCA: gϕg_\phi projects onto the top-kk principal directions and fθf_\theta is the transpose. With non-linear encoders/decoders the autoencoder generalizes PCA to arbitrary manifolds [1].

A denoising autoencoder [2] introduces a corruption distribution x~∼q(x~∣x)\tilde x \sim q(\tilde x \mid x) (Gaussian noise, masking, salt-and-pepper) and trains the model to reconstruct the clean xx from the corrupted x~\tilde x:

Ldenoising=1N∑iEx~i∼q(⋅∣xi) ⁣[ ∥xi−fθ(gϕ(x~i))∥22].\mathcal{L}_{\text{denoising}} = \frac{1}{N} \sum_i \mathbb{E}_{\tilde x_i \sim q(\cdot \mid x_i)}\!\bigl[\, \lVert x_i - f_\theta(g_\phi(\tilde x_i)) \rVert_2^2 \bigr].

The model cannot just memorize the input; it has to learn structure that survives noise. Empirically denoising autoencoders learn features that transfer better to downstream tasks than vanilla autoencoders, and BERT’s masked-language-modelling objective is exactly denoising at the level of tokens.

A sparse autoencoder keeps the vanilla reconstruction loss but adds an L1 penalty on the latent activations:

Lsparse=1N∑i∥xi−fθ(gϕ(xi))∥22+λ∥gϕ(xi)∥1.\mathcal{L}_{\text{sparse}} = \frac{1}{N} \sum_i \lVert x_i - f_\theta(g_\phi(x_i)) \rVert_2^2 + \lambda \lVert g_\phi(x_i) \rVert_1.

The L1 pulls most latent units to zero, leaving only a few active per example. The result is a sparse, often more interpretable code; this is the textbook setup that motivated dictionary-learning and the mainstream sparse-coding literature.

The variational autoencoder

The full ELBO derivation (interview gold)

Setup

We have a generative model pθ(x,z)=p(z) pθ(x∣z)p_\theta(x, z) = p(z)\, p_\theta(x \mid z) with prior p(z)=N(0,I)p(z) = \mathcal{N}(0, I) and decoder pθ(x∣z)p_\theta(x \mid z) (e.g. Gaussian or Bernoulli). We want maximum likelihood: maximize log⁡pθ(x)\log p_\theta(x) over θ\theta. The trouble is that

pθ(x)=∫pθ(x,z) dz=∫p(z) pθ(x∣z) dzp_\theta(x) = \int p_\theta(x, z)\, dz = \int p(z)\, p_\theta(x \mid z)\, dz

is intractable, and the true posterior pθ(z∣x)p_\theta(z \mid x) is intractable for the same reason.

Step 1: introduce a variational posterior

Introduce a tractable approximation qϕ(z∣x)q_\phi(z \mid x) to the true posterior. The encoder network produces the parameters of qϕq_\phi (e.g. mean and log-variance of a diagonal Gaussian).

Step 2: identity for log⁡pθ(x)\log p_\theta(x)

For any distribution qq that has the same support as pp,

log⁡pθ(x)=Eqϕ(z∣x)[log⁡pθ(x)],\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x)],

because the expectation of a constant (in zz) is the constant. Now use pθ(x)=pθ(x,z)/pθ(z∣x)p_\theta(x) = p_\theta(x, z) / p_\theta(z \mid x):

log⁡pθ(x)=Eqϕ(z∣x) ⁣[log⁡pθ(x,z)pθ(z∣x)].\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{p_\theta(z \mid x)} \right].

Step 3: insert and split with qϕq_\phi

Multiply and divide by qϕ(z∣x)q_\phi(z \mid x) inside the log:

log⁡pθ(x)=Eqϕ(z∣x) ⁣[log⁡pθ(x,z)qϕ(z∣x)⋅qϕ(z∣x)pθ(z∣x)].\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{q_\phi(z \mid x)} \cdot \frac{q_\phi(z \mid x)}{p_\theta(z \mid x)} \right].

Split the log into two terms:

log⁡pθ(x)=Eqϕ(z∣x) ⁣[log⁡pθ(x,z)qϕ(z∣x)]⏟L(θ,ϕ;x) = ELBO + Eqϕ(z∣x) ⁣[log⁡qϕ(z∣x)pθ(z∣x)]⏟KL(qϕ(z∣x) ∥ pθ(z∣x)).\log p_\theta(x) = \underbrace{\mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{q_\phi(z \mid x)} \right]}_{\mathcal{L}(\theta, \phi; x)\, =\, \text{ELBO}} \,+\, \underbrace{\mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{q_\phi(z \mid x)}{p_\theta(z \mid x)} \right]}_{\mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p_\theta(z \mid x))}.

Both pieces have a name. The first is the evidence lower bound, L(θ,ϕ;x)\mathcal{L}(\theta, \phi; x); the second is the KL divergence from the variational posterior to the true posterior.

Step 4: KL is non-negative, so ELBO ≤log⁡pθ(x)\le \log p_\theta(x)

By Gibbs’ inequality KL(q ∥ p)≥0\mathrm{KL}(q \,\Vert\, p) \ge 0, with equality iff q=pq = p almost everywhere. Therefore

  log⁡pθ(x) ≥ L(θ,ϕ;x)  =  Eqϕ(z∣x) ⁣[log⁡pθ(x,z)qϕ(z∣x)].  \boxed{\; \log p_\theta(x) \,\ge\, \mathcal{L}(\theta, \phi; x) \;=\; \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{q_\phi(z \mid x)} \right]. \;}

This is the evidence lower bound (ELBO). Maximizing the ELBO over (θ,ϕ)(\theta, \phi) does two things at once: it pushes log⁡pθ(x)\log p_\theta(x) up (the gap closes from below) and it tightens the bound by minimizing KL(qϕ ∥ pθ(z∣x))\mathrm{KL}(q_\phi \,\Vert\, p_\theta(z \mid x)) (the variational posterior approaches the true posterior).

Step 5: split the ELBO into reconstruction and prior-matching

Use pθ(x,z)=pθ(x∣z) p(z)p_\theta(x, z) = p_\theta(x \mid z)\, p(z):

L(θ,ϕ;x)=Eqϕ(z∣x) ⁣[log⁡pθ(x∣z)p(z)qϕ(z∣x)]=Eqϕ(z∣x)[log⁡pθ(x∣z)]−KL(qϕ(z∣x) ∥ p(z)).\mathcal{L}(\theta, \phi; x) = \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x \mid z) p(z)}{q_\phi(z \mid x)} \right] = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] - \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)).

This is the standard form of the ELBO that the VAE optimizes:

  • Eqϕ(z∣x)[log⁡pθ(x∣z)]\mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] is the reconstruction term: how well the decoder explains xx given a code drawn from the encoder.
  • KL(qϕ(z∣x) ∥ p(z))\mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)) is the prior-matching term: how close the encoder’s output distribution is to the prior N(0,I)\mathcal{N}(0, I).

The VAE training objective for a minibatch is

L^=1∣B∣∑x∈B(Eqϕ(z∣x)[log⁡pθ(x∣z)]−KL(qϕ(z∣x) ∥ p(z))).\hat{\mathcal{L}} = \frac{1}{|B|}\sum_{x \in B} \Bigl( \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] - \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)) \Bigr).

The reconstruction expectation is estimated with a single Monte-Carlo sample z=μϕ(x)+σϕ(x)⊙ϵz = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon; the KL has a closed form for diagonal Gaussians (next DeepDive).

Reparameterization trick: gradients through a stochastic node

The problem

The ELBO contains Eqϕ(z∣x)[log⁡pθ(x∣z)]\mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)]. To optimize over ϕ\phi we need

∇ϕEqϕ(z∣x)[log⁡pθ(x∣z)].\nabla_\phi \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)].

If we sample z∼qϕ(z∣x)z \sim q_\phi(z \mid x) directly, the sampling step has no gradient (it is just an index into a random number stream), so the standard chain rule does not apply through zz.

REINFORCE-style estimator (high variance)

The score-function (REINFORCE) estimator handles this with the identity ∇ϕq=q ∇ϕlog⁡q\nabla_\phi q = q\, \nabla_\phi \log q:

∇ϕEq[f(z)]=Eq[ f(z)∇ϕlog⁡qϕ(z∣x) ].\nabla_\phi \mathbb{E}_q[f(z)] = \mathbb{E}_q[\, f(z) \nabla_\phi \log q_\phi(z \mid x) \,].

This works for any qq, including discrete distributions, but its variance is large: the gradient signal is whatever the log-likelihood ratio happens to be at the sample, scaled by ff. Plain REINFORCE without baselines is too noisy to train a VAE in reasonable time.

Reparameterization

For continuous qϕq_\phi that can be written as a deterministic transformation of a parameter-free noise distribution, do the change of variables. For a diagonal Gaussian:

qϕ(z∣x)=N(z;μϕ(x),diag(σϕ(x)2))  ⟺  z=μϕ(x)+σϕ(x)⊙ϵ,ϵ∼N(0,I).q_\phi(z \mid x) = \mathcal{N}(z; \mu_\phi(x), \mathrm{diag}(\sigma_\phi(x)^2)) \;\Longleftrightarrow\; z = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon, \quad \epsilon \sim \mathcal{N}(0, I).

The noise ϵ\epsilon does not depend on ϕ\phi; the transformation z=μϕ(x)+σϕ(x)⊙ϵz = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon does. Now

Eqϕ(z∣x)[f(z)]=Eϵ∼N(0,I)[f(μϕ(x)+σϕ(x)⊙ϵ)],\mathbb{E}_{q_\phi(z \mid x)}[f(z)] = \mathbb{E}_{\epsilon \sim \mathcal{N}(0, I)}[f(\mu_\phi(x) + \sigma_\phi(x) \odot \epsilon)],

and the gradient passes through ff and the transformation by ordinary chain rule:

∇ϕEq[f(z)]=Eϵ ⁣[∇zf(z)⋅∇ϕ(μϕ(x)+σϕ(x)⊙ϵ)].\nabla_\phi \mathbb{E}_q[f(z)] = \mathbb{E}_\epsilon\!\bigl[ \nabla_z f(z) \cdot \nabla_\phi (\mu_\phi(x) + \sigma_\phi(x) \odot \epsilon) \bigr].

This is the reparameterization trick [3][4]. The variance is much lower than REINFORCE because the gradient uses the shape of ff (its derivative), not just its value at the sample.

Why σ\sigma is parameterized as log⁡σ2\log \sigma^2 in practice

To keep σ\sigma positive, the encoder outputs log⁡σϕ2(x)\log \sigma_\phi^2(x) (a real-valued network output) and we recover σϕ(x)=exp⁡(12log⁡σ2)\sigma_\phi(x) = \exp(\tfrac{1}{2} \log \sigma^2). This avoids softplus or absolute-value tricks and gives a well-behaved gradient near σ→0\sigma \to 0.

Beyond Gaussian

The same trick works for any distribution that is the pushforward of a parameter-free base distribution under a smooth map: Gumbel-Softmax for categorical, planar/radial flows, normalizing flows in general. Discrete distributions need REINFORCE or relaxations.

Closed-form KL between two diagonal Gaussians

Statement

For q=N(μ1,diag(σ12))q = \mathcal{N}(\mu_1, \mathrm{diag}(\sigma_1^2)) and p=N(μ2,diag(σ22))p = \mathcal{N}(\mu_2, \mathrm{diag}(\sigma_2^2)) in Rd\mathbb{R}^d:

KL(q ∥ p)=12∑j=1d(log⁡σ2,j2σ1,j2+σ1,j2+(μ1,j−μ2,j)2σ2,j2−1).\mathrm{KL}(q \,\Vert\, p) = \frac{1}{2} \sum_{j=1}^{d} \left( \log \frac{\sigma_{2,j}^2}{\sigma_{1,j}^2} + \frac{\sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2}{\sigma_{2,j}^2} - 1 \right).

For the VAE special case p=N(0,I)p = \mathcal{N}(0, I), so μ2=0\mu_2 = 0 and σ2,j2=1\sigma_{2,j}^2 = 1:

  KL(qϕ(z∣x) ∥ N(0,I))=12∑j=1d(μ1,j2+σ1,j2−1−log⁡σ1,j2).  \boxed{\; \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, \mathcal{N}(0, I)) = \frac{1}{2} \sum_{j=1}^{d} \bigl( \mu_{1,j}^2 + \sigma_{1,j}^2 - 1 - \log \sigma_{1,j}^2 \bigr). \;}

This is what the VAE actually computes for the KL term; no Monte Carlo needed.

Derivation

Start from the definition of KL and substitute the Gaussian density:

KL(q ∥ p)=∫q(z)log⁡q(z)p(z) dz=Eq[log⁡q(z)]−Eq[log⁡p(z)].\mathrm{KL}(q \,\Vert\, p) = \int q(z) \log \frac{q(z)}{p(z)}\, dz = \mathbb{E}_q[\log q(z)] - \mathbb{E}_q[\log p(z)].

For a diagonal Gaussian, log⁡q(z)=−12∑j[log⁡(2πσ1,j2)+(zj−μ1,j)2/σ1,j2]\log q(z) = -\tfrac{1}{2} \sum_j [\log(2\pi \sigma_{1,j}^2) + (z_j - \mu_{1,j})^2 / \sigma_{1,j}^2]. Take the expectation under qq, using Eq[(zj−μ1,j)2]=σ1,j2\mathbb{E}_q[(z_j - \mu_{1,j})^2] = \sigma_{1,j}^2:

Eq[log⁡q(z)]=−12∑j(log⁡(2πσ1,j2)+1)=−d2log⁡(2π)−12∑j(log⁡σ1,j2+1).\mathbb{E}_q[\log q(z)] = -\frac{1}{2} \sum_j (\log(2\pi \sigma_{1,j}^2) + 1) = -\frac{d}{2}\log(2\pi) - \frac{1}{2} \sum_j (\log \sigma_{1,j}^2 + 1).

For log⁡p(z)\log p(z), expand the squared term: Eq[(zj−μ2,j)2]=σ1,j2+(μ1,j−μ2,j)2\mathbb{E}_q[(z_j - \mu_{2,j})^2] = \sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2 (variance plus mean shift). So

Eq[log⁡p(z)]=−12∑jlog⁡(2πσ2,j2)−12∑jσ1,j2+(μ1,j−μ2,j)2σ2,j2.\mathbb{E}_q[\log p(z)] = -\frac{1}{2} \sum_j \log(2\pi \sigma_{2,j}^2) - \frac{1}{2} \sum_j \frac{\sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2}{\sigma_{2,j}^2}.

Subtract:

KL(q ∥ p)=12∑jlog⁡σ2,j2σ1,j2+12∑jσ1,j2+(μ1,j−μ2,j)2σ2,j2−d2.\mathrm{KL}(q \,\Vert\, p) = \frac{1}{2} \sum_j \log \frac{\sigma_{2,j}^2}{\sigma_{1,j}^2} + \frac{1}{2} \sum_j \frac{\sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2}{\sigma_{2,j}^2} - \frac{d}{2}.

Plug in p=N(0,I)p = \mathcal{N}(0, I) to recover the boxed formula [9][6].

beta-VAE and the disentanglement trade-off

The standard VAE objective is the ELBO. Higgins et al. [5] proposed multiplying the KL term by a tunable coefficient β\beta:

Lβ=Eqϕ(z∣x)[log⁡pθ(x∣z)]−β KL(qϕ(z∣x) ∥ p(z)).\mathcal{L}_\beta = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] - \beta\, \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)).
  • β=1\beta = 1 is the standard VAE.
  • β>1\beta > 1 strengthens the prior-matching pressure. Each latent dimension is pushed to be more independent from the others (because p(z)=N(0,I)p(z) = \mathcal{N}(0, I) is itself factorized), which encourages the latent code to align with disentangled factors of variation in the data: rotation, scale, lighting.
  • β<1\beta < 1 relaxes the prior; the code carries more information about xx, reconstructions sharpen, but sampling quality suffers.

The trade-off is sharp: at β=4\beta = 4 on dSprites, β\beta-VAE produces a code where each latent corresponds to one interpretable factor, but reconstructions are blurrier than at β=1\beta = 1. In practice, β\beta is tuned on the downstream task; for representation learning β∈[2,8]\beta \in [2, 8] is typical, for image generation β=1\beta = 1.

Connection to the information bottleneck

The β\beta-VAE objective is mathematically equivalent to a Lagrangian on the information bottleneck principle [7]:

max⁡ϕI(z;x)−β I(z;nuisance),\max_\phi I(z; x) - \beta\, I(z; \text{nuisance}),

where II denotes mutual information. The KL term in the VAE is an upper bound on I(z;x)−constI(z; x) - \mathrm{const} measured against the prior, and pushing the KL down corresponds to compressing zz to remove variance unrelated to the reconstruction objective. The ”β\beta as compression dial” intuition is the practical takeaway.

Implementations

A canonical PyTorch VAE on MNIST and the equivalent in TensorFlow Keras.

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class VAE(nn.Module):
  """Diagonal-Gaussian VAE on flattened MNIST."""

  def __init__(self, in_dim: int = 784, h_dim: int = 400, z_dim: int = 20) -> None:
      super().__init__()
      # Encoder.
      self.fc1 = nn.Linear(in_dim, h_dim)
      self.fc_mu = nn.Linear(h_dim, z_dim)
      self.fc_logvar = nn.Linear(h_dim, z_dim)
      # Decoder.
      self.fc2 = nn.Linear(z_dim, h_dim)
      self.fc3 = nn.Linear(h_dim, in_dim)

  def encode(self, x):
      h = F.relu(self.fc1(x))
      return self.fc_mu(h), self.fc_logvar(h)

  def reparameterize(self, mu, logvar):
      # z = mu + sigma * eps, where sigma = exp(0.5 * logvar).
      std = torch.exp(0.5 * logvar)
      eps = torch.randn_like(std)
      return mu + std * eps

  def decode(self, z):
      h = F.relu(self.fc2(z))
      return torch.sigmoid(self.fc3(h))   # Bernoulli mean over pixels.

  def forward(self, x):
      mu, logvar = self.encode(x)
      z = self.reparameterize(mu, logvar)
      x_recon = self.decode(z)
      return x_recon, mu, logvar


def vae_loss(x, x_recon, mu, logvar, beta: float = 1.0):
  # Bernoulli reconstruction; sum over pixels, mean over batch.
  bce = F.binary_cross_entropy(x_recon, x, reduction='sum')
  # Closed-form KL between q and N(0, I), summed over latent dims.
  kl = -0.5 * torch.sum(1.0 + logvar - mu.pow(2) - logvar.exp())
  return (bce + beta * kl) / x.shape[0]


# Training step (assuming x is a flattened MNIST minibatch in [0, 1]):
# vae = VAE().to(device); opt = torch.optim.Adam(vae.parameters(), lr=1e-3)
# for x, _ in loader:
#     x = x.to(device).view(x.size(0), -1)
#     x_recon, mu, logvar = vae(x)
#     loss = vae_loss(x, x_recon, mu, logvar, beta=1.0)
#     opt.zero_grad(); loss.backward(); opt.step()
Two heads on the encoder for mu and log-variance; reparameterize via z = mu + std * eps; closed-form KL against N(0, I).

Run a tiny denoising autoencoder in NumPy on a flat random vector. The model has to remove additive Gaussian noise from a fixed clean target:

Idle (packages: numpy)
(no output yet)

Comparison

ModelLossSupports sampling?DisentanglementOutput sharpnessNotes
Vanilla AEreconstruction MSEno (no prior on z)nosharp on training dataReduces to PCA when linear; non-linear AEs generalize PCA.
Denoising AEreconstruction MSE on (noisy -> clean) pairsno (deterministic)nosharpRobust features; basis of masked-LM (BERT-style) pretraining.
Sparse AEreconstruction + L1 on znopartial (active units)sharpLearns sparse codes; classical motivation for dictionary learning.
VAEELBO = recon - KLyes (sample from prior)no (entangled latents)blurry (Gaussian likelihood)Standard generative AE; mode-covering due to KL(q || p) form.
beta-VAE (beta > 1)recon - beta * KLyesyes (compresses + factorizes)blurrier than VAEHiggins 2017; trade reconstruction for disentanglement.

Real-world uses

DomainUseWhy an autoencoder works
Denoisingimage, audio, signalBottleneck enforces a clean reconstruction; the network learns the data manifold and projects noise off it.
Anomaly detectionfraud, manufacturing defects, log analyticsTrain on "normal" only; high reconstruction error at inference flags an outlier.
Representation learningpretraining for downstream classifiersLatent code captures a compressed, often more linearly separable, view of the input.
Image generation (pre-2020)sampling from a learned latentVAEs were one of the two main generative models pre-diffusion; now mostly displaced by diffusion + Latent Diffusion (which uses a VAE encoder under the hood).
Recommender / embedding compressioncompress sparse user/item vectorsDense bottleneck makes downstream similarity search and ranking cheap.

Pitfalls

VAE samples are blurry

The standard VAE decoder pθ(x∣z)p_\theta(x \mid z) is a diagonal Gaussian, which is equivalent to per-pixel MSE between the decoder mean and the target. MSE assumes Gaussian noise, which is wrong for natural images: the actual residuals are heavy-tailed and structured. The MSE-optimal decoder under uncertainty is the conditional mean, which averages over plausible images and produces a blurry result.

Two fixes beyond plain VAE:

  1. Stronger likelihoods: pixelCNN/PixelSNAIL decoders (autoregressive over pixels), or a VAE-GAN hybrid where a discriminator provides perceptual loss.
  2. Use the VAE as a feature compressor: train a VAE to land in a clean latent and put a separate generative model (diffusion, autoregressive) on the latent. This is exactly what Latent Diffusion / Stable Diffusion does.
KL-vanishing (posterior collapse)

If the decoder pθ(x∣z)p_\theta(x \mid z) is too powerful, the model can learn to ignore zz entirely: it sets qϕ(z∣x)≈p(z)q_\phi(z \mid x) \approx p(z) (so the KL term goes to zero) and uses the decoder’s autoregressive capacity to fit pθ(x)p_\theta(x) directly. The latent code carries no information; the VAE is degenerate. Bowman et al. [8] first documented this on text VAEs with LSTM decoders.

Standard mitigations: KL annealing (start training with the KL term scaled down and ramp it up), free-bits (clip the per-dimension KL to a minimum so the model cannot collapse all dimensions to the prior), or weaker decoders. KL-vanishing is a known recurring failure mode that requires explicit attention.

beta-VAE trade-off is sharp

Tuning β>1\beta > 1 buys disentanglement but spends reconstruction sharpness; β<1\beta < 1 buys reconstruction sharpness but spends sample quality and disentanglement. Always evaluate both objectives (reconstruction error AND a disentanglement metric like MIG) when choosing β\beta, not one alone [5].

Use log-variance, not variance

Parameterize the encoder to output log⁡σ2\log \sigma^2, not σ2\sigma^2 directly: it keeps σ\sigma positive without softplus tricks, the gradient is well-behaved near σ→0\sigma \to 0, and the closed-form KL formula is in log⁡σ2\log \sigma^2 already.

Pop quiz

Check yourself
Score: 0 / 5(0 answered)
  1. 1. Starting from log p(x), the ELBO is derived by:
  2. 2. Why do we need the reparameterization trick for VAEs?
  3. 3. Why are VAE samples blurry?
  4. 4. In beta-VAE, increasing beta:
  5. 5. The closed-form KL between q = N(mu, diag(sigma^2)) and p = N(0, I) is:

Interview Q&A

Derive the ELBO from the marginal log-likelihood and KL divergence.research

We want to maximize log⁡pθ(x)\log p_\theta(x) but the marginal pθ(x)=∫p(z) pθ(x∣z) dzp_\theta(x) = \int p(z)\, p_\theta(x \mid z)\, dz is intractable. Introduce a variational posterior qϕ(z∣x)q_\phi(z \mid x) and use that the expectation of a constant-in-zz quantity under any distribution is the constant:

log⁡pθ(x)=Eqϕ(z∣x)[log⁡pθ(x)].\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x)].

Substitute pθ(x)=pθ(x,z)/pθ(z∣x)p_\theta(x) = p_\theta(x, z) / p_\theta(z \mid x):

log⁡pθ(x)=Eqϕ(z∣x) ⁣[log⁡pθ(x,z)pθ(z∣x)].\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{p_\theta(z \mid x)} \right].

Insert and divide by qϕ(z∣x)q_\phi(z \mid x):

log⁡pθ(x)=Eqϕ(z∣x) ⁣[log⁡pθ(x,z)qϕ(z∣x)]+Eqϕ(z∣x) ⁣[log⁡qϕ(z∣x)pθ(z∣x)].\log p_\theta(x) = \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{p_\theta(x, z)}{q_\phi(z \mid x)} \right] + \mathbb{E}_{q_\phi(z \mid x)}\!\left[ \log \frac{q_\phi(z \mid x)}{p_\theta(z \mid x)} \right].

The first term is the ELBO L(θ,ϕ;x)\mathcal{L}(\theta, \phi; x); the second is KL(qϕ(z∣x) ∥ pθ(z∣x))≥0\mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p_\theta(z \mid x)) \ge 0. Therefore log⁡pθ(x)≥L(θ,ϕ;x)\log p_\theta(x) \ge \mathcal{L}(\theta, \phi; x), with equality iff qϕq_\phi matches the true posterior.

Splitting the joint pθ(x,z)=pθ(x∣z)p(z)p_\theta(x, z) = p_\theta(x \mid z) p(z) rewrites the ELBO as

L(θ,ϕ;x)=Eqϕ(z∣x)[log⁡pθ(x∣z)]⏟reconstruction−KL(qϕ(z∣x) ∥ p(z))⏟prior matching.\mathcal{L}(\theta, \phi; x) = \underbrace{\mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)]}_{\text{reconstruction}} - \underbrace{\mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z))}_{\text{prior matching}}.

The reconstruction term is estimated by a single Monte Carlo sample via the reparameterization trick; the KL term has a closed form when qq and pp are both diagonal Gaussian [3][6].

Why do we need the reparameterization trick instead of just sampling z?onsite

The ELBO contains Eqϕ(z∣x)[log⁡pθ(x∣z)]\mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)]. To optimize over the encoder parameters ϕ\phi we need

∇ϕEqϕ(z∣x)[log⁡pθ(x∣z)].\nabla_\phi \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)].

If we sample z∼qϕz \sim q_\phi directly, the sample is a black-box random number with no gradient path back through ϕ\phi.

REINFORCE would fix this with ∇ϕEq[f(z)]=Eq[f(z)∇ϕlog⁡q]\nabla_\phi \mathbb{E}_q[f(z)] = \mathbb{E}_q[f(z) \nabla_\phi \log q], but the variance is enormous because the gradient depends on the value of ff at the sample, not on its shape. Training a VAE with REINFORCE without baselines is impractical.

Reparameterization rewrites zz as a deterministic function of an external noise variable: z=μϕ(x)+σϕ(x)⊙ϵz = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon, ϵ∼N(0,I)\epsilon \sim \mathcal{N}(0, I). The randomness lives in ϵ\epsilon, which is independent of ϕ\phi. The expectation becomes

Eqϕ(z∣x)[f(z)]=Eϵ[f(μϕ(x)+σϕ(x)⊙ϵ)],\mathbb{E}_{q_\phi(z \mid x)}[f(z)] = \mathbb{E}_\epsilon[f(\mu_\phi(x) + \sigma_\phi(x) \odot \epsilon)],

and ordinary backprop flows through ff, then through the linear transformation, then into ϕ\phi. The gradient uses the derivative of ff, not its raw value, which gives much lower variance.

The trick generalizes to any distribution that can be written as the pushforward of a parameter-free base distribution under a smooth map: planar/radial flows, Gumbel-Softmax for categorical, normalizing flows in general. Discrete or heavy-tailed distributions still need REINFORCE or relaxations [4].

Why are VAE samples blurry, and what fixes the problem?onsite

The standard VAE decoder is pθ(x∣z)=N(μθ(z),σ2I)p_\theta(x \mid z) = \mathcal{N}(\mu_\theta(z), \sigma^2 I), equivalent to per-pixel MSE between the decoder mean μθ(z)\mu_\theta(z) and the target xx. Per-pixel MSE assumes pixel residuals are independent Gaussian with constant variance. That is wrong for natural images: residuals are heavy-tailed, spatially correlated, and structured (edges, textures).

Under the wrong noise model, the loss-optimal decoder at any point in latent space is the conditional mean of the data conditional on zz. The conditional mean averages over plausible images consistent with the latent code (e.g. a digit at slightly different positions/styles), and the average of plausible images is blurry. There is no “blurry photograph” in the data; blur is an artifact of MSE optimality.

Three classes of fix:

  1. Stronger likelihoods. Replace the diagonal Gaussian decoder with an autoregressive PixelCNN/PixelSNAIL or a discretized logistic mixture. The model now puts probability mass on actual sharp images, not their average.
  2. Adversarial / perceptual loss (VAE-GAN). Train a discriminator that distinguishes real from reconstructed images, and replace pixel MSE with a feature-space loss from a fixed perceptual network. This pushes reconstructions toward the true image manifold rather than its mean.
  3. Use the VAE as a compressor, put a generative model on the latent. This is the Latent Diffusion / Stable Diffusion architecture: a VAE compresses pixels to a small latent, a diffusion model is trained on the latent, and decoding back to pixels uses the (sharp) VAE decoder. The diffusion handles the mode-seeking; the VAE handles the compression.

The blurriness is a known property of the standard VAE objective, not a bug to debug.

What is the beta-VAE trade-off and how do you tune beta in practice?onsite

The beta-VAE objective [5] is

Lβ=Eqϕ(z∣x)[log⁡pθ(x∣z)]−β KL(qϕ(z∣x) ∥ p(z)).\mathcal{L}_\beta = \mathbb{E}_{q_\phi(z \mid x)}[\log p_\theta(x \mid z)] - \beta\, \mathrm{KL}(q_\phi(z \mid x) \,\Vert\, p(z)).

The KL term is upper-bounded by mutual information I(z;x)I(z; x) measured against the prior. Multiplying by β\beta changes the trade-off:

  • β=1\beta = 1: standard VAE, optimizes the ELBO exactly.
  • β>1\beta > 1: stronger compression of zz. Forces the encoder’s output distribution toward the factorized prior N(0,I)\mathcal{N}(0, I), encouraging each latent dimension to be independent and align with one disentangled factor of variation in the data. Reconstruction sharpness drops because the code carries less information.
  • β<1\beta < 1: weaker compression. Code carries more information, reconstructions sharpen, but sampling from the prior becomes less faithful and disentanglement disappears.

Tuning in practice:

  1. Pick the metric that matters: reconstruction error for compression, sample FID/IS for generation, mutual information gap (MIG) or modularity for disentanglement.
  2. Sweep β\beta across {0.5,1,2,4,8}\{0.5, 1, 2, 4, 8\} on the train+val split. Plot the trade-off curve.
  3. Beware KL-vanishing at very small β\beta: even β=0\beta = 0 does not guarantee informative latents because the decoder may simply ignore zz (Bowman 2015 [8]). Always check the per-dim KL after training; if many dims are at zero, the latent is collapsing.
  4. Production recipe for representation learning: β∈[2,8]\beta \in [2, 8] with KL annealing (start near 0, ramp to target).
  5. Production recipe for generation: β=1\beta = 1 with a stronger decoder (PixelCNN, GAN, diffusion on the latent).

The tradeoff is sharp: there is no β\beta that gives both perfect reconstruction and full disentanglement on natural data, only a Pareto curve.

Derive the closed-form KL divergence between two diagonal Gaussians.onsite

For q=N(μ1,diag(σ12))q = \mathcal{N}(\mu_1, \mathrm{diag}(\sigma_1^2)) and p=N(μ2,diag(σ22))p = \mathcal{N}(\mu_2, \mathrm{diag}(\sigma_2^2)) in Rd\mathbb{R}^d, the KL is

KL(q ∥ p)=Eq[log⁡q(z)]−Eq[log⁡p(z)].\mathrm{KL}(q \,\Vert\, p) = \mathbb{E}_q[\log q(z)] - \mathbb{E}_q[\log p(z)].

For a diagonal Gaussian,

log⁡q(z)=−d2log⁡(2π)−12∑jlog⁡σ1,j2−12∑j(zj−μ1,j)2σ1,j2.\log q(z) = -\frac{d}{2} \log(2\pi) - \frac{1}{2} \sum_j \log \sigma_{1,j}^2 - \frac{1}{2} \sum_j \frac{(z_j - \mu_{1,j})^2}{\sigma_{1,j}^2}.

Take the expectation under qq, using Eq[(zj−μ1,j)2]=σ1,j2\mathbb{E}_q[(z_j - \mu_{1,j})^2] = \sigma_{1,j}^2:

Eq[log⁡q(z)]=−d2log⁡(2π)−12∑jlog⁡σ1,j2−d2.\mathbb{E}_q[\log q(z)] = -\frac{d}{2} \log(2\pi) - \frac{1}{2} \sum_j \log \sigma_{1,j}^2 - \frac{d}{2}.

For log⁡p(z)\log p(z), use Eq[(zj−μ2,j)2]=σ1,j2+(μ1,j−μ2,j)2\mathbb{E}_q[(z_j - \mu_{2,j})^2] = \sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2 (variance plus squared mean shift):

Eq[log⁡p(z)]=−d2log⁡(2π)−12∑jlog⁡σ2,j2−12∑jσ1,j2+(μ1,j−μ2,j)2σ2,j2.\mathbb{E}_q[\log p(z)] = -\frac{d}{2} \log(2\pi) - \frac{1}{2} \sum_j \log \sigma_{2,j}^2 - \frac{1}{2} \sum_j \frac{\sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2}{\sigma_{2,j}^2}.

Subtract:

KL(q ∥ p)=12∑j=1d(log⁡σ2,j2σ1,j2+σ1,j2+(μ1,j−μ2,j)2σ2,j2−1).\mathrm{KL}(q \,\Vert\, p) = \frac{1}{2} \sum_{j=1}^{d} \left( \log \frac{\sigma_{2,j}^2}{\sigma_{1,j}^2} + \frac{\sigma_{1,j}^2 + (\mu_{1,j} - \mu_{2,j})^2}{\sigma_{2,j}^2} - 1 \right).

For the VAE special case p=N(0,I)p = \mathcal{N}(0, I) (μ2=0\mu_2 = 0, σ2,j2=1\sigma_{2,j}^2 = 1):

KL(q ∥ N(0,I))=12∑j=1d(μ1,j2+σ1,j2−1−log⁡σ1,j2).\mathrm{KL}(q \,\Vert\, \mathcal{N}(0, I)) = \frac{1}{2} \sum_{j=1}^{d} \bigl( \mu_{1,j}^2 + \sigma_{1,j}^2 - 1 - \log \sigma_{1,j}^2 \bigr).

This is what the VAE actually computes; no Monte Carlo needed. The corresponding code is -0.5 * (1 + logvar - mu**2 - logvar.exp()).sum() [9][3].

Build it yourself

A weekend project that takes a VAE from textbook to working:

  1. Implement the PyTorch VAE above on MNIST. Train for 20 epochs at β=1\beta = 1. Check that the per-dim KL after training is non-zero on most dimensions (no collapse).
  2. Plot a 2D latent slice: encode a few training images, then decode points along a line in latent space. The decoder should produce a smooth interpolation (e.g. 3 morphing into 8). This is the qualitative test that the latent is well-organized.
  3. Sweep β∈{0.5,1,2,4,8}\beta \in \{0.5, 1, 2, 4, 8\}. Record reconstruction error and sample quality at each. You should see the Pareto curve appear.
  4. Trigger KL-vanishing on purpose: replace the decoder with a powerful autoregressive PixelCNN (or a deep ResNet) and train at β=1\beta = 1. Watch the per-dim KL collapse to ~0 and the latent become uninformative. Add KL annealing as the fix.
  5. Stretch: train a denoising autoencoder on MNIST with masked input (set 50% of pixels to 0). Compare features extracted from the bottleneck against the VAE’s mean encoding for downstream classification. Denoising features often transfer better.

References

  1. Hinton, G. E. and Salakhutdinov, R. R. (2006). Reducing the Dimensionality of Data with Neural Networks. Science 313(5786), 504-507. doi:10.1126/science.1127647
  2. Vincent, P., Larochelle, H., Bengio, Y., and Manzagol, P.-A. (2008). Extracting and composing robust features with denoising autoencoders. ICML 2008. doi:10.1145/1390156.1390294
  3. Kingma, D. P. and Welling, M. (2013). Auto-Encoding Variational Bayes. ICLR 2014 (arXiv preprint 2013). arXiv:1312.6114
  4. Rezende, D. J., Mohamed, S., and Wierstra, D. (2014). Stochastic Backpropagation and Approximate Inference in Deep Generative Models. ICML 2014. arXiv:1401.4082
  5. Higgins, I., Matthey, L., Pal, A., Burgess, C., Glorot, X., Botvinick, M., Mohamed, S., and Lerchner, A. (2017). beta-VAE: Learning Basic Visual Concepts with a Constrained Variational Framework. ICLR 2017. https://openreview.net/forum?id=Sy2fzU9gl
  6. Doersch, C. (2016). Tutorial on Variational Autoencoders. arXiv preprint. arXiv:1606.05908
  7. Tishby, N. and Zaslavsky, N. (2015). Deep Learning and the Information Bottleneck Principle. IEEE Information Theory Workshop. arXiv:1503.02406
  8. Bowman, S. R., Vilnis, L., Vinyals, O., Dai, A. M., Jozefowicz, R., and Bengio, S. (2015). Generating Sentences from a Continuous Space. CoNLL 2016 (arXiv preprint 2015). arXiv:1511.06349
  9. Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. ISBN 978-0-387-31073-2

Interactive Demo

Input
Latent Space (2D)
Output (reconstructed)
Circle

Tip: Use Interpolate mode and slide between two shapes to see a smooth latent space transition