Variational Autoencoders & ELBO Derivation

Derive the Evidence Lower Bound from Jensen's inequality, calculate the closed-form Gaussian KL divergence, master the reparameterization trick, and inspect zero-dependency implementations in Python and Rust.

04. Generative AI, Large Language Models & Diffusion9 min read

Standard deterministic autoencoders compress high-dimensional inputs xx into discrete latent codes z=f(x)z = f(x). However, because deterministic autoencoders do not enforce continuity across latent space, the resulting manifold contains arbitrary unmapped regions (holes) where decoding novel vectors generates degraded artifacts.

Variational Autoencoders (VAEs) (Kingma and Welling, 2013) reformulate representation learning into probabilistic Bayesian inference, mapping each sample to a continuous probability distribution and optimizing the Evidence Lower Bound (ELBO).

ReparameterizationBayesian Deep Learning
z = mu + sigma * epsilon

Isolates stochasticity into parameter-free noise epsilon ~ N(0, I) to enable end-to-end backpropagation.

1. Mathematical Derivation of the Evidence Lower Bound (ELBO)#

Let x∈RDx \in \mathbb{R}^D be observed data and z∈Rdz \in \mathbb{R}^d be unobserved latent factors with prior distribution p(z)=N(0,I)p(z) = \mathcal{N}(0, I).

The marginal likelihood of observation xx integrates over all possible latent states:

p(x)=∫p(x,z) dz=∫pθ(x∣z)p(z) dzp(x) = \int p(x, z) \, dz = \int p_\theta(x | z) p(z) \, dz
Architecture Diagram
Rendering diagram...

Because true posterior p(z∣x)=p(x∣z)p(z)p(x)p(z | x) = \frac{p(x|z)p(z)}{p(x)} is computationally intractable to evaluate in high dimensions, we introduce a parameterized variational approximation qϕ(z∣x)≈p(z∣x)q_\phi(z | x) \approx p(z | x).

1

Step 1: Formulate the Log-Marginal Likelihood

Multiply and divide the integrand by qϕ(z∣x)q_\phi(z | x):

ln⁡p(x)=ln⁡∫p(x,z) dz=ln⁡∫qϕ(z∣x)p(x,z)qϕ(z∣x) dz\ln p(x) = \ln \int p(x, z) \, dz = \ln \int q_\phi(z | x) \frac{p(x, z)}{q_\phi(z | x)} \, dz

Express the integral as an expectation with respect to variational distribution qϕ(z∣x)q_\phi(z | x):

ln⁡p(x)=ln⁡Eqϕ(z∣x)[p(x,z)qϕ(z∣x)]\ln p(x) = \ln \mathbb{E}_{q_\phi(z | x)}\left[ \frac{p(x, z)}{q_\phi(z | x)} \right]
2

Step 2: Apply Jensen's Inequality

Because the natural logarithm f(u)=ln⁡(u)f(u) = \ln(u) is a strictly concave function, Jensen's Inequality states that ln⁡E[Y]≥E[ln⁡Y]\ln \mathbb{E}[Y] \ge \mathbb{E}[\ln Y]. Applying this yields the lower bound:

ln⁡p(x)≥Eqϕ(z∣x)[ln⁡(p(x,z)qϕ(z∣x))]≡ELBO(ϕ,θ)\ln p(x) \ge \mathbb{E}_{q_\phi(z | x)}\left[ \ln\left( \frac{p(x, z)}{q_\phi(z | x)} \right) \right] \equiv \text{ELBO}(\phi, \theta)
3

Step 3: Exact Decomposition via KL Divergence

We can confirm the exact tightness gap by expanding the joint distribution p(x,z)=p(z∣x)p(x)p(x, z) = p(z | x) p(x):

Eqϕ(z∣x)[ln⁡(p(x,z)qϕ(z∣x))]=Eqϕ[ln⁡p(x)+ln⁡p(z∣x)−ln⁡qϕ(z∣x)]\mathbb{E}_{q_\phi(z | x)}\left[ \ln\left( \frac{p(x, z)}{q_\phi(z | x)} \right) \right] = \mathbb{E}_{q_\phi}\left[ \ln p(x) + \ln p(z | x) - \ln q_\phi(z | x) \right]
=ln⁡p(x)∫qϕ(z∣x) dz−Eqϕ[ln⁡(qϕ(z∣x)p(z∣x))]=ln⁡p(x)−DKL(qϕ(z∣x) ∥ p(z∣x))= \ln p(x) \int q_\phi(z | x) \, dz - \mathbb{E}_{q_\phi}\left[ \ln\left( \frac{q_\phi(z | x)}{p(z | x)} \right) \right] = \ln p(x) - D_{KL}\Big( q_\phi(z | x) \,\|\, p(z | x) \Big)

Rearranging terms:

ln⁡p(x)=ELBO(ϕ,θ)+DKL(qϕ(z∣x) ∥ p(z∣x))\ln p(x) = \text{ELBO}(\phi, \theta) + D_{KL}\Big( q_\phi(z | x) \,\|\, p(z | x) \Big)

Because the Kullback-Leibler divergence is non-negative (DKL≥0D_{KL} \ge 0), maximizing the ELBO simultaneously maximizes marginal log-likelihood ln⁡p(x)\ln p(x) while driving the variational posterior qϕ(z∣x)q_\phi(z | x) toward the true posterior p(z∣x)p(z | x).

4

Step 4: Decompose ELBO into Reconstruction and Regularization

Expanding the numerator p(x,z)=pθ(x∣z)p(z)p(x, z) = p_\theta(x | z) p(z):

ELBO=Eqϕ[ln⁡(pθ(x∣z)p(z)qϕ(z∣x))]=Eqϕ[ln⁡pθ(x∣z)]+Eqϕ[ln⁡(p(z)qϕ(z∣x))]\text{ELBO} = \mathbb{E}_{q_\phi}\left[ \ln\left( \frac{p_\theta(x | z) p(z)}{q_\phi(z | x)} \right) \right] = \mathbb{E}_{q_\phi}[\ln p_\theta(x | z)] + \mathbb{E}_{q_\phi}\left[ \ln\left( \frac{p(z)}{q_\phi(z | x)} \right) \right]
ELBO=Eqϕ(z∣x)[ln⁡pθ(x∣z)]⏟Reconstruction Fidelity−DKL(qϕ(z∣x) ∥ p(z))⏟Prior Regularization Penalty\text{ELBO} = \underbrace{\mathbb{E}_{q_\phi(z | x)}[\ln p_\theta(x | z)]}_{\text{Reconstruction Fidelity}} - \underbrace{D_{KL}\Big( q_\phi(z | x) \,\|\, p(z) \Big)}_{\text{Prior Regularization Penalty}}

2. Closed-Form Analytical Derivation of Gaussian KL Divergence#

Assume the variational posterior is a diagonal multivariate Gaussian qϕ(z∣x)=N(μ,diag(σ2))q_\phi(z | x) = \mathcal{N}(\mu, \text{diag}(\sigma^2)) and the prior is a standard normal distribution p(z)=N(0,I)p(z) = \mathcal{N}(0, I) in Rd\mathbb{R}^d.

We compute the integral:

DKL(qϕ(z∣x) ∥ p(z))=∫q(z)ln⁡(q(z)p(z)) dz=∫q(z)ln⁡q(z) dz−∫q(z)ln⁡p(z) dzD_{KL}\Big( q_\phi(z | x) \,\|\, p(z) \Big) = \int q(z) \ln\left( \frac{q(z)}{p(z)} \right) \, dz = \int q(z) \ln q(z) \, dz - \int q(z) \ln p(z) \, dz

Evaluating the one-dimensional case for coordinate jj:

  1. Entropy of q(zj)q(z_j):
∫q(zj)ln⁡q(zj) dzj=−12ln⁡(2πσj2)−12\int q(z_j) \ln q(z_j) \, dz_j = -\frac{1}{2} \ln(2\pi \sigma_j^2) - \frac{1}{2}
  1. Cross-entropy under prior p(zj)=N(0,1)p(z_j) = \mathcal{N}(0, 1):
∫q(zj)ln⁡p(zj) dzj=∫q(zj)[−12ln⁡(2π)−zj22]dzj=−12ln⁡(2π)−12Eq[zj2]\int q(z_j) \ln p(z_j) \, dz_j = \int q(z_j) \left[ -\frac{1}{2} \ln(2\pi) - \frac{z_j^2}{2} \right] dz_j = -\frac{1}{2}\ln(2\pi) - \frac{1}{2}\mathbb{E}_{q}[z_j^2]

Since Eq[zj2]=μj2+σj2\mathbb{E}_{q}[z_j^2] = \mu_j^2 + \sigma_j^2:

=−12ln⁡(2π)−12(μj2+σj2)= -\frac{1}{2}\ln(2\pi) - \frac{1}{2}(\mu_j^2 + \sigma_j^2)

Subtracting the two terms:

DKL(j)=[−12ln⁡(2π)−12ln⁡(σj2)−12]−[−12ln⁡(2π)−12(μj2+σj2)]D_{KL}(j) = \left[ -\frac{1}{2}\ln(2\pi) - \frac{1}{2}\ln(\sigma_j^2) - \frac{1}{2} \right] - \left[ -\frac{1}{2}\ln(2\pi) - \frac{1}{2}(\mu_j^2 + \sigma_j^2) \right]
=−12(1+ln⁡(σj2)−μj2−σj2)= -\frac{1}{2} \Big( 1 + \ln(\sigma_j^2) - \mu_j^2 - \sigma_j^2 \Big)

Summing across all dd latent dimensions yields the exact analytical formula:

DKL(qϕ(z∣x) ∥ N(0,I))=−12∑j=1d(1+ln⁡(σj2)−μj2−σj2)D_{KL}\Big( q_\phi(z | x) \,\|\, \mathcal{N}(0, I) \Big) = -\frac{1}{2} \sum_{j=1}^d \Big( 1 + \ln(\sigma_j^2) - \mu_j^2 - \sigma_j^2 \Big)

3. The Reparameterization Trick#

To compute gradients of the reconstruction loss with respect to encoder parameters ϕ\phi:

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

If we sample zz directly from stochastic distribution N(μ(x),Σ(x))\mathcal{N}(\mu(x), \Sigma(x)), the sampling step creates a non-differentiable node in the computation graph. Gradients cannot backpropagate through random number generators.

Architecture Diagram
Rendering diagram...

The Reparameterization Trick reformulates random variable zz as a deterministic function of inputs, parameters, and an external auxiliary noise variable ϵ\epsilon:

z=μ(x)+σ(x)⊙ϵ,where ϵ∼N(0,I)z = \mu(x) + \sigma(x) \odot \epsilon, \quad \text{where } \epsilon \sim \mathcal{N}(0, I)

Now, the expectation is taken over the parameter-free noise distribution p(ϵ)p(\epsilon):

∇ϕEqϕ(z∣x)[f(z)]=∇ϕEp(ϵ)[f(μ(x)+σ(x)⊙ϵ)]=Ep(ϵ)[∇ϕf(μ(x)+σ(x)⊙ϵ)]\nabla_\phi \mathbb{E}_{q_\phi(z | x)}[f(z)] = \nabla_\phi \mathbb{E}_{p(\epsilon)}[f\big(\mu(x) + \sigma(x) \odot \epsilon\big)] = \mathbb{E}_{p(\epsilon)}\left[ \nabla_\phi f\big(\mu(x) + \sigma(x) \odot \epsilon\big) \right]

Gradients flow seamlessly into μ\mu and σ\sigma via standard backpropagation:

∂z∂μ=1,∂z∂σ=ϵ\frac{\partial z}{\partial \mu} = 1, \qquad \frac{\partial z}{\partial \sigma} = \epsilon

4. From-Scratch Implementation: Zero Dependencies#

The following implementations compute the complete VAE Loss Function (Reconstruction Mean Squared Error + Closed-Form Gaussian KL Divergence) and reparameterization sampling from scratch in pure Python and pure Rust.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import math import random from typing import List, Tuple class VAEPrimitives: @staticmethod def reparameterize(mu: List[float], log_var: List[float]) -> List[float]: # z = mu + exp(0.5 * log_var) * epsilon, where epsilon ~ N(0, 1) z = [] for m, lv in zip(mu, log_var): std = math.exp(0.5 * lv) eps = random.gauss(0.0, 1.0) z.append(m + std * eps) return z @staticmethod def kl_divergence_loss(mu: List[float], log_var: List[float]) -> float: # D_KL = -0.5 * sum(1 + log_var - mu^2 - exp(log_var)) d_kl = 0.0 for m, lv in zip(mu, log_var): d_kl += -0.5 * (1.0 + lv - (m ** 2) - math.exp(lv)) return d_kl @staticmethod def reconstruction_loss(x_true: List[float], x_recon: List[float]) -> float: # Mean Squared Error as negative Gaussian log-likelihood surrogate return sum((t - r) ** 2 for t, r in zip(x_true, x_recon)) / len(x_true) @classmethod def total_vae_loss( cls, x_true: List[float], x_recon: List[float], mu: List[float], log_var: List[float], beta_kl: float = 1.0 ) -> Tuple[float, float, float]: recon = cls.reconstruction_loss(x_true, x_recon) kl = cls.kl_divergence_loss(mu, log_var) total = recon + beta_kl * kl return total, recon, kl if __name__ == "__main__": random.seed(42) # Latent vector dimension 3 mu = [0.1, -0.2, 0.05] log_var = [-0.1, 0.0, 0.2] # Sample latent code z z_sample = VAEPrimitives.reparameterize(mu, log_var) print("Reparameterized Latent Vector z:") print(" ", [round(v, 4) for v in z_sample]) # Compute loss on synthetic reconstruction x_target = [1.0, 0.0, 0.5, 0.2] x_pred = [0.95, 0.05, 0.48, 0.22] total_loss, recon_l, kl_l = VAEPrimitives.total_vae_loss(x_target, x_pred, mu, log_var) print("\nLoss Components:") print(f" Reconstruction Loss (MSE): {recon_l:.6f}") print(f" KL Divergence Penalty: {kl_l:.6f}") print(f" Total VAE Loss: {total_loss:.6f}")

5. Active Knowledge Checks#

Concept check

In the Variational Autoencoder ELBO equation, why does maximizing the lower bound force the approximate posterior q_phi(z|x) to match true posterior p(z|x)?

Concept check

How does the reparameterization trick z = mu(x) + sigma(x) * epsilon enable backpropagation through stochastic latent variables?