The variational lower bound on that makes latent-variable models trainable, optimized by sampling the encoder through the reparameterization trick.
Equation
Maximizing the ELBO = reconstruction minus a KL regularizer pulling the posterior toward .
Reparameterization: , , moving the randomness off the gradient path so is a low-variance pathwise estimator.
Closed-form KL for diagonal Gaussian vs standard normal:
From scratch
import torch, torch.nn as nn, torch.nn.functional as F
from einops import rearrange
class VAE(nn.Module):
def __init__(self, d_in, d_h, d_z):
super().__init__()
self.enc = nn.Sequential(nn.Linear(d_in, d_h), nn.SiLU(),
nn.Linear(d_h, 2 * d_z)) # -> mu, logvar
self.dec = nn.Sequential(nn.Linear(d_z, d_h), nn.SiLU(),
nn.Linear(d_h, d_in))
def forward(self, x, beta=1.0): # x: (b, d_in)
mu, logvar = rearrange(self.enc(x), "b (p d) -> p b d", p = 2) # each (b, d_z)
std = (0.5 * logvar).exp()
z = mu + std * torch.randn_like(std) # reparameterized
recon = F.binary_cross_entropy_with_logits(
self.dec(z), x, reduction='none').sum(-1) # (b,)
kl = -0.5 * (1 + logvar - mu.pow(2) - logvar.exp()).sum(-1) # (b,)
return (recon + beta * kl).mean(), recon.mean(), kl.mean()Notes
- Predict
logvarrather thanstd: it is unconstrained, andexpkeeps variance positive without clamping. - Posterior collapse: with a powerful decoder the KL term drives and the latent is ignored. Mitigations are KL warmup (anneal from 0), free bits (floor the per-dimension KL), or weakening the decoder.
- trades reconstruction for disentangled, more Gaussian latents; latent-diffusion autoencoders use a very small precisely because they want reconstruction fidelity, not a usable prior.
- The KL gap means the ELBO is loose by exactly the posterior approximation error; importance-weighted bounds (IWAE) tighten it with samples.
Related: Variational Autoencoders, VQ-VAE, Diffusion Models