Suppose we observe the 600 points in the left panel and assume they come from three Gaussian clusters. Fitting this Gaussian mixture means estimating a weight, a mean and a covariance for each cluster.
If we knew which cluster produced each point
(right panel), each cluster could be fitted on its own, which is easy. But
without these labels, we are stuck in a loop:
To fit a cluster, we need to know its points.
To know which points belong to a cluster, we need its fitted parameters.
The
expectation–maximization (EM) algorithm
breaks the loop by alternating, starting from a guess of the parameters:
E-step:
Using the current parameters, give each point a probability of belonging to each cluster, its soft label.
M-step:
Refit the parameters with these soft labels.
Repeat.
No round ever decreases the likelihood.
The same recipe applies to any model with missing or unobserved variables
[1].
This post derives EM from a lower bound on the log-likelihood, works it out for the Gaussian mixture in Python, and then covers its pitfalls and its link to variational inference and the VAE.
Notation
Observed data are
x=(x_1,\ldots,x_N), and latent variables are
z=(z_1,\ldots,z_N), one
z_n
per
x_n.
\theta
collects the model parameters, and
\ell(\theta)=\log p(x\mid\theta)
is the log-likelihood.
q(z)
is any distribution over the latent variables, and
\mathbb{E}_q
is the expectation under it.
Latent variables are written as discrete, with sums. For continuous
z, replace each sum by an integral.
Latent Variable Models
The cluster labels above are one example of a
latent variable: part of the model that we do not observe. This section sets up such models in general and shows why their likelihood is hard to maximize directly.
Complete and Incomplete Data
A
latent variable model
specifies a joint distribution
p(x,z\mid\theta)
over observed and latent variables. Only
x
is observed, so the likelihood is the marginal:
p(x\mid\theta)=\sum_{z}p(x,z\mid\theta)
Here
x
and
z
are the whole dataset, so the sum runs over every joint labeling
(z_1,\ldots,z_N). For i.i.d. data the pairs
(x_n,z_n)
are independent across points, so the likelihood splits over points.
Each
z_n
is unobserved and could be any of its values, so each point's likelihood sums over all of them:
Dempster, Laird and Rubin (DLR)
[1], who named the method, call the two sides:
Complete data(x,z), with the
complete-data log-likelihood\log p(x,z\mid\theta).
Incomplete datax, with the
incomplete-data log-likelihood\ell(\theta)=\log p(x\mid\theta).
Latent variables appear for two reasons:
Variables that were never observed.
The cluster label of a point, the hidden state of a hidden Markov model, or the topic of a word.
Data that went missing.
Censored measurements or skipped survey answers. Here the complete data exist, and we only see part of them.
DLR's Original Formulation
DLR state EM without latent variables, as a many-to-one map from complete to observed data
[1]. In this toggle only,
x
is the complete data and
y
the observation:
The complete data
x
live in a sample space
\mathcal{X}
with density
f(x\mid\theta), but are not observed directly.
We observe only
y=y(x), a many-to-one map: many complete
x
give the same
y.
Let
\mathcal{X}(y)
be the set of complete data consistent with the observation
y. Then the observed-data density is
A latent variable model is the special case where the complete data are the pair
(x,z)
of the main text and the map drops
z. Then
\mathcal{X}(y)
ranges over all values of
z, and the integral is the marginal
\sum_z p(x,z\mid\theta).
Why Direct Maximization is Hard
Take the Gaussian mixture of Why EM?, where
p(x_n,z_n=k\mid\theta)=\pi_k\,\mathcal{N}(x_n\mid\mu_k,\Sigma_k), and compare the log-likelihood with and without the labels.
With labels, we maximize the complete-data log-likelihood:
The equation looks like a weighted mean, but the weights
\gamma_{nk}
depend on
\mu_k
itself, so it is not a closed form.
It suggests an iteration:
fix the weights, solve for the means, recompute the weights, and repeat.
EM is exactly this iteration.
The EM Algorithm
The previous section ended with an iteration suggested by the Gaussian mixture's gradient. This section derives that iteration for any latent variable model: a lower bound on the log-likelihood, two steps that climb it, and a proof that the likelihood never decreases.
The Evidence Lower Bound
Take any distribution
q(z)
over the latent variables. By Bayes' rule,
p(x\mid\theta)=p(x,z\mid\theta)/p(z\mid x,\theta)
for every
z, so
The second term is the Kullback–Leibler (KL) divergence from
q
to the posterior, which is never negative. So:
For everyq,
\mathcal{F}(q,\theta)\le\ell(\theta).
The bound is tight
exactly when
q(z)=p(z\mid x,\theta), because the KL divergence is zero only when the two distributions are equal.
\mathcal{F}
is called the
evidence lower bound (ELBO). Neal and Hinton
[3]
note that, apart from its sign, it is analogous to the variational free energy of statistical physics. It splits into two parts:
Jensen's inequality is tight when the ratio
p(x,z\mid\theta)/q(z)
is constant in
z, that is, when
q
is proportional to
p(x,z\mid\theta). That is again the posterior.
The KL decomposition above says more: it gives the exact size of the gap.
💡
For everyq,\mathcal{F}(q, θ)is a lower bound on\log p(x | θ). It touches the log-likelihood whenqis the posteriorp(z | x, θ).
E-step and M-step
What each step takes in and puts out:
E-step:\theta_t
in, the posterior
p(z\mid x,\theta_t)
out.
So
the posterior must be tractable for every\theta.
e.g. For a Gaussian mixture, it is an
N\times K
table of label probabilities.
M-step:
that posterior in,
\theta_{t+1}
out.
So the
complete-data log-likelihood, averaged over the posterior, must be maximizable in\theta.
e.g. For a Gaussian mixture, this gives weighted means and covariances.
EM maximizes
\mathcal{F}(q,\theta)
by alternating between its two arguments
[3]. Start from an initial
\theta_0
and, at step
t:
E-step: maximize overqwith\theta_tfixed.
\ell(\theta_t)
does not depend on
q, so maximizing
\mathcal{F}
means minimizing the KL term, which reaches zero at the posterior:
q_{t+1}(z)=p(z\mid x,\theta_t)
No optimizer runs in the E-step:
it only evaluates the posterior at
\theta_t. So
the posterior must be computationally tractable.
e.g. For a Gaussian mixture, it follows from Bayes' rule:
Each entry is the joint at
\theta_t, normalized over labels:
q_{t+1}(z_n=k)=p(x_n,z_n=k\mid\theta_t)/\sum_{j}p(x_n,z_n=j\mid\theta_t).
M-step: maximize over\thetawithq_{t+1}fixed.
The entropy
H(q_{t+1})
does not depend on
\theta, so only the first part of
\mathcal{F}
matters:
In
\ell, the log of a sum keeps the problem from splitting into one piece per component, and the weights in its gradient move with
\theta
(see Why Direct Maximization is Hard).
In
Q, the log acts on the likelihood of one labeling at a time, and the weights are fixed at
\theta_t. So the M-step is the labeled problem with weights, and it usually has a closed form whenever the labeled problem does.
Monotone Improvement
Each step of EM moves up the bound, and the E-step makes the bound touch
\ell. Together:
The E-step (
e_step
) computes
r[n, 0]
=\gamma_{n1}=p(z_n=1\mid x_n,\mu_1)
at the current
\mu_1.
The M-step (
m_step
) plugs these fixed weights into the equation:
\mu_1=\sum_n\gamma_{n1}x_n/\sum_n\gamma_{n1}.
With
q
fixed, the only term of
\mathcal{F}(q,\mu_1)
that depends on
\mu_1
is
\sum_n\gamma_{n1}\log\mathcal{N}(x_n\mid\mu_1,1), a quadratic in
\mu_1. This weighted mean is its exact maximizer.
Run.
Starting from
\mu_1=-5, two EM steps, drawing the bound after each E-step.
Code Snippet
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import logsumexp, xlogy
from scipy.stats import norm
# 1D mixture of two unit-variance Gaussians with equal weights.
# Only the first mean mu1 is unknown; mu2 = 2 is held fixed.
rng = np.random.default_rng(0)
x = np.concatenate([rng.normal(-2, 1, 150), rng.normal(2, 1, 150)])
log_pi, mu2 = np.log([0.5, 0.5]), 2.0
def log_joint(mu1):
"""log p(x_n, z_n = k | mu1), shape (N, 2)."""
return log_pi + norm.logpdf(x[:, None], np.array([mu1, mu2]), 1.0)
def loglik(mu1):
"""log p(x | mu1) = sum_n log sum_k p(x_n, z_n = k | mu1)."""
return logsumexp(log_joint(mu1), axis=1).sum()
def e_step(mu1):
"""E-step at mu1. Returns r, shape (N, 2): r[n, k] = p(z_n = k | x_n, mu1),
the posterior probability that point n came from component k."""
lj = log_joint(mu1)
return np.exp(lj - logsumexp(lj, axis=1, keepdims=True))
def elbo(mu1, r):
"""Lower bound F(q, mu1). r is q written as a table: r[n, k] = q(z_n = k),
shape (N, 2). Passing the output of e_step makes q the posterior."""
return (r * log_joint(mu1)).sum() - xlogy(r, r).sum()
def m_step(r):
"""M-step. r: the (N, 2) table from e_step. Returns the mu1 that maximizes
F(q, mu1): the mean of x, each point weighted by r[n, 0], its probability of
coming from component 1."""
return (r[:, 0] * x).sum() / r[:, 0].sum()
grid = np.linspace(-6, 1, 400)
mu_t = -5.0
fig, ax = plt.subplots(figsize=(7, 3.8))
ax.plot(grid, [loglik(m) for m in grid], "k", lw=2, label="log p(x | μ₁)")
for step, color in zip(range(2), ["C0", "C1"], strict=True):
r = e_step(mu_t) # E-step: the bound now touches log p(x | μ₁) at mu_t
mu_next = m_step(r) # M-step: move to the bound's maximizer
ax.plot(grid, [elbo(m, r) for m in grid], color=color, lw=1.2,
label=f"F(q_{step + 1}, μ₁), tight at μ₁ = {mu_t:.2f}")
ax.plot(mu_t, loglik(mu_t), "o", color=color)
ax.annotate("", xy=(mu_next, loglik(mu_next)), xytext=(mu_t, loglik(mu_t)),
arrowprops=dict(arrowstyle="->", color=color))
mu_t = mu_next
ax.set_ylim(loglik(-6) - 50, loglik(-2) + 40)
ax.set_xlabel("μ₁")
ax.legend(loc="lower right", fontsize=8)
fig.tight_layout()
fig.savefig("em_lower_bound.png", dpi=150)
Result
Black: the log-likelihood. Each colored curve is the bound
\mathcal{F}(q_{t+1},\mu_1)
from the E-step at the current estimate
\mu_1^{(t)}. It touches the black curve at the current estimate, and the M-step moves to its maximum.
Two steps already bring the estimate close to the peak of the black curve, the maximum-likelihood estimate.
EM for Gaussian Mixtures
With the general algorithm in hand, we return to the Gaussian mixture from Why EM?. Both of its steps have closed forms, which makes it the standard first example of EM.
Deriving the Updates
The model.
A
Gaussian mixture model (GMM)
with
K
components has the density
M-step: weighted maximum likelihood.Q
is the labeled log-likelihood with point
n
counted
r_{nk}
times in component
k. With the effective count
N_k=\sum_n r_{nk}, its maximizer is
[2]:
These are the weight, mean and covariance of each cluster, computed with soft counts. The mean update is the fixed-point equation from Why Direct Maximization is Hard, now with the weights frozen at
\theta_t.
Derivation
Q
splits into a term in
\pi
and one term per component in
(\mu_k,\Sigma_k), so
each part is maximized separately.
Weights.
Maximize
\sum_k N_k\log\pi_k
subject to
\sum_k\pi_k=1.
With a Lagrange multiplier
\lambda, setting the derivative of
\sum_k N_k\log\pi_k+\lambda(1-\sum_k\pi_k)
to zero gives
\pi_k=N_k/\lambda.
Summing over
k
gives
\lambda=N.
Means.
The gradient of
\sum_n r_{nk}\log\mathcal{N}(x_n\mid\mu_k,\Sigma_k)
with respect to
\mu_k
is
\Sigma_k^{-1}\sum_n r_{nk}(x_n-\mu_k).
Setting it to zero gives the weighted mean.
Covariances.
Write
\Lambda_k=\Sigma_k^{-1}. The terms of
Q
in
\Lambda_k
are:
Fix every covariance to
\Sigma_k=\varepsilon I
and let
\varepsilon\to0. The responsibility of the nearest mean tends to one and every other to zero, so the E-step becomes the hard assignment of k-means, and the mean update becomes the k-means centroid update
[2].
EM for a GMM is a soft version of k-means that also learns the shape and the weight of each cluster.
Fitting a Mixture in Python
The following code fits a three-component GMM by EM:
Data.
The 600 points of Why EM?, drawn from a known three-component mixture.
Start.
Means at three random data points, every covariance at the covariance of all data, and uniform weights.
Stop.
When the log-likelihood gains less than
10^{-8}
in one step.
Stability.
The E-step works in log space with
logsumexp
, which avoids underflow when a point is far from every component.
Code Snippet
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import logsumexp
from scipy.stats import multivariate_normal
# Data: 600 points from a 3-component Gaussian mixture in 2D
rng = np.random.default_rng(0)
true_pi = np.array([0.5, 0.3, 0.2])
true_mu = np.array([[0.0, 0.0], [4.0, 4.0], [-3.0, 4.0]])
true_Sigma = np.array([[[1.5, 0.6], [0.6, 1.0]],
[[1.0, -0.4], [-0.4, 1.2]],
[[0.6, 0.0], [0.0, 0.6]]])
z = rng.choice(3, size=600, p=true_pi)
X = np.array([rng.multivariate_normal(true_mu[k], true_Sigma[k]) for k in z])
def e_step(X, pi, mu, Sigma):
"""E-step. X: data, shape (N, D). pi, mu, Sigma: current weights (K,), means (K, D)
and covariances (K, D, D).
Returns the responsibilities r[n, k] = p(z_n = k | x_n, θ),
shape (N, K), and log p(X | θ)."""
log_p = np.stack([np.log(pi[k]) + multivariate_normal.logpdf(X, mu[k], Sigma[k])
for k in range(len(pi))], axis=1) # log p(x_n, z_n = k | θ)
log_px = logsumexp(log_p, axis=1) # log p(x_n | θ)
return np.exp(log_p - log_px[:, None]), log_px.sum()
def m_step(X, r):
"""M-step. X: data (N, D). r: responsibilities (N, K) from e_step.
Returns the weighted MLE of (π, μ, Σ), each point counted r[n, k] times in component k."""
Nk = r.sum(axis=0) # effective number of points per component
pi = Nk / len(X)
mu = (r.T @ X) / Nk[:, None]
Sigma = np.array([((r[:, k, None] * (X - mu[k])).T @ (X - mu[k])) / Nk[k]
for k in range(len(Nk))])
return pi, mu, Sigma
def fit_gmm(X, K, max_iter=500, tol=1e-8, seed=0):
"""EM for a K-component GMM on data X (N, D), from a random start set by seed.
Stops when log p(X | θ) gains less than tol, or after max_iter iterations.
Returns (pi, mu, Sigma), the final responsibilities r (N, K), and the log-likelihood trace."""
rng = np.random.default_rng(seed)
pi = np.full(K, 1.0 / K)
mu = X[rng.choice(len(X), K, replace=False)] # K random data points as means
Sigma = np.array([np.cov(X.T)] * K)
lls = []
for _ in range(max_iter):
r, ll = e_step(X, pi, mu, Sigma)
lls.append(ll)
if len(lls) > 1 and lls[-1] - lls[-2] < tol:
break
pi, mu, Sigma = m_step(X, r)
return (pi, mu, Sigma), r, np.array(lls)
if __name__ == "__main__":
(pi, mu, Sigma), r, lls = fit_gmm(X, K=3)
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
axes[0].scatter(*X.T, c=r @ np.array([[0.12, 0.47, 0.71], [1.0, 0.5, 0.05],
[0.17, 0.63, 0.17]]), s=8)
t = np.linspace(0, 2 * np.pi, 100)
for k in range(3): # 2-std ellipse of each fitted component
vals, vecs = np.linalg.eigh(Sigma[k])
ellipse = mu[k][:, None] + 2 * vecs @ (np.sqrt(vals)[:, None] * [np.cos(t), np.sin(t)])
axes[0].plot(*ellipse, "k", lw=1)
axes[0].set_title("points colored by responsibilities")
axes[1].plot(lls, "C0.-")
axes[1].set_xlabel("iteration")
axes[1].set_ylabel("log p(X | θ)")
axes[1].set_title("log-likelihood per EM iteration")
fig.tight_layout()
fig.savefig("em_gmm.png", dpi=150)
Result
Left: the fitted 2-std ellipses. Each point is colored by mixing the three component colors with its responsibilities, so points between clusters take a blended color.
Right: the log-likelihood rises at every iteration, as the chain in Monotone Improvement guarantees.
Pitfalls in Practice
Monotone improvement is a strong guarantee, but it does not say which maximum EM reaches, or how fast it gets there. This section covers three common problems in practice.
Local Maxima
💡
EM guarantees that\ell(\theta_t)never decreases, not that it reaches the global maximum.
Under regularity conditions, the limit points of EM are stationary points of
\ell
[4].
The likelihood of a mixture usually has many such points, so the result depends on where EM starts.
The common remedies:
Restarts.
Run EM from several initializations and keep the run with the highest final log-likelihood.
Better starts.
Initialize the means with k-means, or spread the initial means apart.
Python Implementation and Result
The following code reuses
fit_gmm
from the previous snippet, saved as
em_gmm.py
:
Data.
Five well-separated clusters in 2D, 100 points each.
Fit.
A five-component GMM from 20 random starts.
Code Snippet
import matplotlib.pyplot as plt
import numpy as np
from em_gmm import fit_gmm # the e_step, m_step and fit_gmm defined above
# Five well-separated clusters in 2D, fitted with K = 5 from 20 random starts
rng = np.random.default_rng(1)
centers = np.array([[0, 0], [6, 0], [0, 6], [6, 6], [3, 3]])
X = np.concatenate([rng.normal(c, 0.7, size=(100, 2)) for c in centers])
runs = [fit_gmm(X, K=5, seed=s) for s in range(20)]
finals = np.array([lls[-1] for _, _, lls in runs])
def plot_fit(ax, run, title):
"""Points colored by their most likely component, with each component's 2-std ellipse."""
(pi, mu, Sigma), r, _ = run
ax.scatter(*X.T, c=r.argmax(axis=1), cmap="tab10", vmin=0, vmax=9, s=6)
t = np.linspace(0, 2 * np.pi, 100)
for k in range(len(pi)):
vals, vecs = np.linalg.eigh(Sigma[k])
ellipse = mu[k][:, None] + 2 * vecs @ (np.sqrt(vals)[:, None] * [np.cos(t), np.sin(t)])
ax.plot(*ellipse, "k", lw=1)
ax.set_title(title)
ax.set_aspect("equal")
i_best, i_worst = np.argmax(finals), np.argmin(finals)
fig, axes = plt.subplots(1, 3, figsize=(14, 4.2))
plot_fit(axes[0], runs[i_best], f"best start: log p = {finals[i_best]:.1f}")
plot_fit(axes[1], runs[i_worst], f"worst start: log p = {finals[i_worst]:.1f}")
at_best = np.isclose(finals, finals.max(), atol=0.1)
axes[2].scatter(np.flatnonzero(at_best), finals[at_best], color="C0", label="best maximum found")
axes[2].scatter(np.flatnonzero(~at_best), finals[~at_best], color="C3", label="worse local maximum")
axes[2].legend(fontsize=8, loc="lower right")
axes[2].set_xticks(range(0, 20, 2))
axes[2].set_xlabel("random start (seed)")
axes[2].set_ylabel("final log p(X | θ)")
axes[2].set_title("where each of the 20 starts ends")
fig.tight_layout()
fig.savefig("em_restarts.png", dpi=150)
Result
Left:
the best run, one component per cluster. Most starts end here (blue dots, right).
Middle:
the worst run. One component spans two clusters, while two components share one cluster.
Right:
the red starts stop at such worse solutions. EM still increased the likelihood at every step: it climbed a different hill.
Degenerate Solutions
EM maximizes the likelihood because a higher likelihood is supposed to mean a better fit.
For a GMM, a useless fit can make the likelihood arbitrarily large:
Put one component's mean on a single data point and shrink its covariance toward zero.
That point's density grows without bound, while the other components keep every other point's density from vanishing
[2].
So the log-likelihood goes to
+\infty. Ever higher scores go to a component that memorizes one point, so a global maximum does not exist.
EM can drift there: if a component ends up with only a few points, its covariance can shrink step after step, and the run ends with a near-singular
\Sigma_k
and a meaningless fit.
Python Implementation and Result
The following code shrinks one component onto a single data point:
Model.
A two-component 1D mixture on 100 points. Component 1 sits on the data point
x_1
with standard deviation
s, and component 2 covers all the data.
Run.
Shrink
s
and plot the log-likelihood.
Code Snippet
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import logsumexp
from scipy.stats import norm
# Two-component 1D GMM. Put component 1 exactly on the data point x[0]
# and shrink its standard deviation s. Component 2 covers all the data.
rng = np.random.default_rng(0)
x = rng.normal(0, 1, 100)
def loglik(s):
log_p = np.stack([np.log(0.5) + norm.logpdf(x, x[0], s),
np.log(0.5) + norm.logpdf(x, x.mean(), x.std())], axis=1)
return logsumexp(log_p, axis=1).sum()
fig, axes = plt.subplots(1, 2, figsize=(11, 3.8))
grid = np.linspace(-3, 3, 2000)
for s, color in [(1.0, "C0"), (0.1, "C1"), (0.02, "C3")]:
density = 0.5 * norm.pdf(grid, x[0], s) + 0.5 * norm.pdf(grid, x.mean(), x.std())
axes[0].plot(grid, density, color=color, lw=1.2, label=f"s = {s}")
axes[0].plot(x, np.zeros_like(x), "|", color="k", ms=10)
axes[0].axvline(x[0], color="gray", ls=":", lw=1)
axes[0].set_ylim(0, 3)
axes[0].set_xlabel("x")
axes[0].set_ylabel("p(x | θ)")
axes[0].set_title("component 1 collapses onto x[0] (dotted)")
axes[0].legend(fontsize=8)
s_grid = np.logspace(0, -128, 400)
axes[1].plot(s_grid, [loglik(s) for s in s_grid], "k", lw=1.5)
axes[1].set_xscale("log")
axes[1].set_xlim(1, 1e-128) # reversed: s shrinks to the right
axes[1].set_xticks([1, 1e-32, 1e-64, 1e-96, 1e-128])
axes[1].set_xlabel("s (shrinking →)")
axes[1].set_ylabel("log p(x | θ)")
axes[1].set_title("log-likelihood grows without bound")
fig.tight_layout()
fig.savefig("em_singularity.png", dpi=150)
Result
Left:
as
s
shrinks, component 1 becomes a spike on
x_1
(the dotted line,
x[0]
in the code) and stops covering the other points.
Right:
at first the cheat costs more than it gains: the log-likelihood falls, because component 1 stops covering the other points.
But the spike's own term,
-\log s, has no ceiling. It grows by about
\log 10\approx2.3
per tenfold shrink, passes the
s=1
fit near
s\approx10^{-30}, and keeps growing. This is why the axis runs so far.
The usual fixes are a floor on the variances, a small
\epsilon I
added to each
\Sigma_k, or a prior on
\Sigma_k, which turns the problem into MAP estimation (see Relaxing the Two Steps).
Slow Convergence
Near a maximum, EM converges linearly: the error shrinks by a roughly constant factor per step. DLR show that this factor is set by the
fraction of missing information, how much of the complete-data information about
\theta
the latent variables carry
[1].
Well-separated clusters leave little missing information.
The responsibilities are close to 0 or 1, and EM converges quickly near the maximum.
Heavily overlapping clusters leave much of it missing, and EM can crawl for many iterations.
Beyond Exact EM
Everything so far assumed that both steps can be solved exactly. This section relaxes that assumption, ending with the variational methods that connect EM to the VAE.
Relaxing the Two Steps
The E-step and the M-step need not be solved exactly. Each variant below changes one of them, and each still climbs a lower bound:
\mathcal{F}(q,\theta), or
\mathcal{F}(q,\theta)+\log p(\theta)
for MAP-EM.
variant
E-step
M-step
guarantee
EM
exact posterior
maximize
Q(\theta,\theta_t)
\ell(\theta)
never decreases
MAP-EM
exact posterior
maximize
Q(\theta,\theta_t)+\log p(\theta)
\log p(\theta\mid x)
never decreases
generalized EM (GEM)
exact posterior
any
\theta
with
Q(\theta,\theta_t)\ge Q(\theta_t,\theta_t)
\ell(\theta)
never decreases
partial E-step (incremental, sparse)
posterior updated for only part of
z
maximize
\mathcal{F}(q,\theta)
\mathcal{F}(q,\theta)
never decreases
variational EM
best
q
in a restricted family
maximize
\mathcal{F}(q,\theta)
\mathcal{F}(q,\theta)
never decreases
MAP-EM
adds a prior
p(\theta). The same chain holds for the log posterior, since the prior term does not involve
z
[1].
A prior on the covariances is one fix for the degenerate solutions above.
GEM
is useful when the M-step has no closed form: any step that increases
Q, such as a small enough gradient step, keeps the guarantee
[1].
Partial E-steps
keep
\mathcal{F}
non-decreasing, though not
\ell
at every step. Neal and Hinton
[3]
use the bound to justify two such variants:
Incremental:
for large datasets, to converge in fewer passes over the data.
The E-step updates one point at a time. After each point, the M-step refits
\theta
to all points, using that point's new posterior and the stored posteriors of the others.
So
\theta
is updated after every point instead of once per pass, as in SGD versus full-batch gradient descent.
Sparse:
for latent variables with many possible values, to cut the cost of each E-step.
Usually only a few values have non-negligible probability. The E-step recomputes only how probability is split among those few, and keeps the rest frozen.
All values are recomputed now and then, since the few that matter change as
\theta
moves.
Variational EM and the VAE
The E-step needs the tractable posterior
p(z\mid x,\theta).
In many models, such as those with many interacting latent variables or with neural-network likelihoods,
the posterior has no tractable form.
Variational EM
restricts
q
to a tractable family and runs the same coordinate ascent:
As in exact EM, we take
q
to split over points for i.i.d. data:
q(z)=\prod_n q(z_n;\lambda_n).
So each point
x_n
gets its own member of the family, set by its own
variational parameters\lambda_n, e.g. a mean and a variance for a Gaussian
q.
The E-step maximizes
\mathcal{F}(q,\theta_t)
over
\lambda_1,\ldots,\lambda_N.
Each
q(z_n;\lambda_n)
becomes the member closest to
p(z_n\mid x_n,\theta_t)
in KL.
The M-step is unchanged.
What is lost:
the posterior is usually outside the family, so the KL gap stays positive.
\mathcal{F}
still increases at every step, but
\ell(\theta)
is no longer guaranteed to.
Amortized inference
Variational EM refits every
\lambda_n
at every E-step, and a new point
x_*
needs its own optimization for
\lambda_*.
Amortized inference replaces the per-point parameters by one networkq_\phi(z\mid x), the
encoder, that maps any
x
to its approximate posterior:
The cost of inference is paid once, in training
\phi, and a new point needs only one forward pass.
The bound becomes a function of two sets of network weights:
The
variational autoencoder (VAE)
[5]
builds on this. With a neural-network likelihood
p_\theta(x\mid z), the
decoder, it maximizes
\mathcal{F}(\phi,\theta)
jointly by stochastic gradient ascent instead of alternating E- and M-steps:
The gradient step in
\phi
plays the role of the E-step.
The gradient step in
\theta
plays the role of the M-step.
💡
EM, variational EM and the VAE all maximize the same lower bound\mathcal{F}(q, θ). They differ in how closely they solve forq: exactly, within a family, or through a shared encoder.
Reference
[1] Dempster, A. P., Laird, N. M., & Rubin, D. B. (1977). Maximum Likelihood from Incomplete Data via the EM Algorithm. Journal of the Royal Statistical Society: Series B, 39(1), 1–22.
[3] Neal, R. M., & Hinton, G. E. (1998). A View of the EM Algorithm that Justifies Incremental, Sparse, and Other Variants. In Learning in Graphical Models, 355–368. Springer.