Gaussian Mixture Models
a single gaussian is a committed statement: one bump, symmetric, thin tails. real data is usually several stories overlaid — different regimes, different subpopulations — and a gaussian mixture says so explicitly: each point was generated by one of \(k\) gaussians, we just don’t get told which. 𐃏 fitting one is the canonical latent-variable problem, and the algorithm that fits it — expectation-maximisation — is one of the great workhorses of statistics.
the latent-variable formulation
generative story for each data point \(x_i \in \mathbb{R}^d\):
- draw a component label \(z_i \sim \operatorname{Cat}(\pi_1, \dots, \pi_k)\), with \(\pi_j \ge 0\), \(\sum_j \pi_j = 1\);
- draw \(x_i \mid z_i = j \;\sim\; \mathcal{N}(\mu_j, \Sigma_j)\).
marginalising the label gives the mixture density
\begin{equation} p(x \mid \theta) = \sum_{j=1}^{k} \pi_j \, \mathcal{N}(x \mid \mu_j, \Sigma_j), \qquad \theta = \{\pi_j, \mu_j, \Sigma_j\}_{j=1}^{k}, \end{equation}
which can approximate any continuous density arbitrarily well with enough components. the log-likelihood of the data is
\begin{equation} \log p(X \mid \theta) = \sum_{i=1}^{n} \log \sum_{j=1}^{k} \pi_j \, \mathcal{N}(x_i \mid \mu_j, \Sigma_j), \end{equation}
and there is the problem: a log of a sum. differentiate it and the components refuse to decouple; no closed-form maximiser exists (Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon, 2020). contrast the complete-data log-likelihood — if we knew every \(z_i\), the sum inside the log would collapse to a single term and the mle would be per-component gaussian fits, a homework exercise. em manufactures exactly this simplification, in expectation.
responsibilities
the natural bridge between “labels known” and “labels unknown” is the posterior over labels. by bayes’ rule, the responsibility component \(j\) takes for point \(x_i\) is
\begin{equation} r_{ij} = p(z_i = j \mid x_i, \theta) = \frac{\pi_j \, \mathcal{N}(x_i \mid \mu_j, \Sigma_j)}{\sum_{l=1}^{k} \pi_l \, \mathcal{N}(x_i \mid \mu_l, \Sigma_l)}, \end{equation}
a soft assignment: \(r_{ij} \in (0, 1)\), \(\sum_j r_{ij} = 1\). where k-means forces each point to pick a side, a mixture lets boundary points hedge.
em, derived via the elbo
the bound
for any distributions \(q_i(j)\) over the labels, multiply and divide inside the log-sum, then apply jensen’s inequality (\(\log\) is concave, so \(\log \mathbb{E}[\cdot] \ge \mathbb{E}[\log(\cdot)]\)):
\begin{align*} \log p(X \mid \theta) &= \sum_{i=1}^{n} \log \sum_{j=1}^{k} q_i(j)\, \frac{\pi_j \, \mathcal{N}(x_i \mid \mu_j, \Sigma_j)}{q_i(j)} \\ &\ge \sum_{i=1}^{n} \sum_{j=1}^{k} q_i(j) \log \frac{\pi_j \, \mathcal{N}(x_i \mid \mu_j, \Sigma_j)}{q_i(j)} \;=:\; \mathcal{L}(q, \theta), \end{align*}
the evidence lower bound (elbo). a direct calculation shows the gap is exactly a kl divergence:
\begin{equation} \log p(X \mid \theta) - \mathcal{L}(q, \theta) = \sum_{i=1}^{n} \operatorname{KL}\!\big(q_i \,\Vert\, p(z_i \mid x_i, \theta)\big) \;\ge\; 0. \end{equation}
em is coordinate ascent on \(\mathcal{L}\):
- e-step: maximise \(\mathcal{L}\) over \(q\) with \(\theta\) fixed. the kl gap vanishes iff \(q_i = p(z_i \mid x_i, \theta)\) — set \(q_i(j) = r_{ij}\), the responsibilities, making the bound tight at the current \(\theta\).
- m-step: maximise \(\mathcal{L}\) over \(\theta\) with \(q\) fixed. dropping the \(\theta\)-free entropy term, this is maximising the expected complete-data log-likelihood \(Q(\theta) = \sum_{i,j} r_{ij} \log \big[\pi_j\, \mathcal{N}(x_i \mid \mu_j, \Sigma_j)\big]\).
m-step closed forms
write \(N_j = \sum_i r_{ij}\) (the effective count claimed by component \(j\)). expand the gaussian log-density in \(Q\):
\begin{equation} Q(\theta) = \sum_{i,j} r_{ij} \left[ \log \pi_j - \tfrac{1}{2}\log\lvert 2\pi\Sigma_j\rvert - \tfrac{1}{2}(x_i - \mu_j)^\top \Sigma_j^{-1} (x_i - \mu_j) \right]. \end{equation}
means. \(\nabla_{\mu_j} Q = \Sigma_j^{-1} \sum_i r_{ij}(x_i - \mu_j) = 0\) gives a responsibility-weighted average:
\begin{equation} \mu_j^{\text{new}} = \frac{1}{N_j} \sum_{i=1}^{n} r_{ij}\, x_i . \end{equation}
covariances. setting \(\nabla_{\Sigma_j^{-1}} Q = 0\) (differentiate with respect to the precision; the two useful identities are \(\partial \log\lvert A \rvert / \partial A = A^{-\top}\) and \(\partial\, a^\top A a / \partial A = a a^\top\)) gives a weighted scatter:
\begin{equation} \Sigma_j^{\text{new}} = \frac{1}{N_j} \sum_{i=1}^{n} r_{ij}\, (x_i - \mu_j^{\text{new}})(x_i - \mu_j^{\text{new}})^\top . \end{equation}
weights. maximise \(\sum_{i,j} r_{ij} \log \pi_j\) subject to \(\sum_j \pi_j = 1\) with a lagrange multiplier: \(\frac{N_j}{\pi_j} + \lambda = 0\), and enforcing the constraint fixes \(\lambda = -n\), so
\begin{equation} \pi_j^{\text{new}} = \frac{N_j}{n}. \end{equation}
every formula is the labelled-data mle with hard counts replaced by responsibility-weighted counts — the complete-data problem, solved in expectation (Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon, 2020). 𐃏
monotonicity
the two steps sandwich the log-likelihood upwards:
\begin{equation} \log p(X \mid \theta^{t+1}) \;\ge\; \mathcal{L}(q^t, \theta^{t+1}) \;\ge\; \mathcal{L}(q^t, \theta^t) \;=\; \log p(X \mid \theta^t), \end{equation}
where the first inequality is “elbo is a lower bound”, the second is “the m-step maximises over \(\theta\)”, and the equality is “the e-step made the bound tight”. so the observed log-likelihood never decreases — printing it each iteration is both a convergence criterion and a unit test (any decrease is a bug, full stop). under mild regularity the iterates converge to a stationary point of the likelihood — typically a local maximum, with no guarantee about the global one; em inherits k-means’ dependence on initialisation, and in fact k-means is routinely used as the initialiser. the general em recipe — bound, tighten, maximise — works for any latent-variable model whose complete-data mle is tractable (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome, 2009).1
singularities and regularisation
the gmm likelihood has a dirty secret: its supremum is \(+\infty\). park one component exactly on a single data point, \(\mu_j = x_i\), and shrink its covariance: the density \(\mathcal{N}(x_i \mid x_i, \sigma^2 I) \propto \sigma^{-d} \to \infty\) as \(\sigma \to 0\), while every other point stays covered by the remaining components. the “maximum likelihood solution” is a spike of zero width and infinite height — a component that has memorised one observation. 𐃏 defences, in ascending order of principle:
- variance floor: clamp \(\Sigma_j \succeq \varepsilon I\) (the
+ epsin the code below). - map estimation: put an inverse-wishart prior on each \(\Sigma_j\); the m-step gains an additive prior scatter and can no longer collapse.
- detect and restart: if a component’s determinant crashes, re-seed it and continue.
a proper bayesian treatment integrates \(\theta\) out and has no singularity at all — the spike has vanishing posterior mass.
how many components?
more components never lower the maximised likelihood, so \(k\) must be chosen by penalised fit. the bayesian information criterion,
\begin{equation} \mathrm{bic}(k) = -2 \log \hat{L}_k + p_k \log n, \qquad p_k = \underbrace{(k-1)}_{\text{weights}} + \underbrace{kd}_{\text{means}} + \underbrace{k\,\tfrac{d(d+1)}{2}}_{\text{covariances}}, \end{equation}
trades likelihood against parameter count with a sample-size-aware penalty; pick the \(k\) minimising it (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome, 2009). the covariance term dominates \(p_k\) in even modest dimension — full-covariance mixtures are parameter-hungry, which is why constrained families (diagonal, tied, spherical) exist. cross-validated held-out log-likelihood is the honest alternative when you can afford it. and remember the quiet assumption underneath: bic selects the best mixture-of-gaussians description, not the “true number of clusters” — a skewed unimodal blob is happily described by two overlapping gaussians.
relation to k-means
fix all weights equal and all covariances to \(\epsilon I\), and let \(\epsilon \to 0\): responsibilities harden into nearest-centroid indicators, the e-step becomes lloyd’s assignment step, and the weighted-mean m-step becomes the plain centroid update — k-means is degenerate em. the practical reading: a gmm buys you soft boundary points, per-cluster shape (anisotropic covariances), per-cluster mass, and an actual density you can evaluate — at the price of \(O(kd^2)\) parameters, slower iterations, and livelier failure modes.
from scratch
em on 1d data drawn from \(0.65\,\mathcal{N}(0, 1) + 0.35\,\mathcal{N}(4, 0.6^2)\) — the mixture in the figure — from a deliberately bad initialisation (both means near zero):
import numpy as np
rng = np.random.default_rng(1)
# 1d data: two components, unequal weights and spreads
n = 400
z_true = rng.random(n) < 0.35 # p(component 2) = 0.35
x = np.where(z_true, rng.normal(4.0, 0.6, n), rng.normal(0.0, 1.0, n))
def normal_pdf(x, mu, var):
return np.exp(-(x - mu)**2 / (2 * var)) / np.sqrt(2 * np.pi * var)
def em(x, iters=60, eps=1e-6):
# deliberately poor initialisation
pi, mu, var = 0.5, np.array([-1.0, 1.0]), np.array([1.0, 1.0])
ll_old = -np.inf
for t in range(iters):
# e-step: responsibilities
p1 = (1 - pi) * normal_pdf(x, mu[0], var[0])
p2 = pi * normal_pdf(x, mu[1], var[1])
r = p2 / (p1 + p2) # resp. of component 2
# m-step: closed forms
n2 = r.sum()
pi = n2 / len(x)
mu = np.array([((1 - r) * x).sum() / (len(x) - n2),
(r * x).sum() / n2])
var = np.array([((1 - r) * (x - mu[0])**2).sum() / (len(x) - n2) + eps,
(r * (x - mu[1])**2).sum() / n2 + eps])
ll = np.log(p1 + p2).sum() # log-lik at *previous* params
if t % 10 == 0 or t == iters - 1:
print(f"iter {t:>2}: log-lik {ll:>9.3f} pi2={pi:.3f} "
f"mu=({mu[0]: .3f},{mu[1]: .3f}) "
f"sd=({np.sqrt(var[0]):.3f},{np.sqrt(var[1]):.3f})")
if abs(ll - ll_old) < 1e-8:
print(f"converged at iter {t}")
break
ll_old = ll
return pi, mu, var
pi, mu, var = em(x)
print(f"\nrecovered: pi2={pi:.3f}, mu=({mu[0]:.3f}, {mu[1]:.3f}), "
f"sd=({np.sqrt(var[0]):.3f}, {np.sqrt(var[1]):.3f})")
print(f"truth: pi2=0.350, mu=(0.000, 4.000), sd=(1.000, 0.600)")
print(f"empirical: pi2={z_true.mean():.3f}, "
f"mu=({x[~z_true].mean():.3f}, {x[z_true].mean():.3f}), "
f"sd=({x[~z_true].std():.3f}, {x[z_true].std():.3f})")
iter 0: log-lik -1312.407 pi2=0.674 mu=(-0.623, 2.424) sd=(0.800,1.850)
iter 10: log-lik -790.622 pi2=0.424 mu=(-0.196, 3.646) sd=(0.883,1.088)
iter 20: log-lik -752.064 pi2=0.356 mu=(-0.008, 4.042) sd=(1.021,0.594)
converged at iter 27
recovered: pi2=0.355, mu=(-0.008, 4.042), sd=(1.021, 0.594)
truth: pi2=0.350, mu=(0.000, 4.000), sd=(1.000, 0.600)
empirical: pi2=0.357, mu=(-0.019, 4.039), sd=(1.005, 0.592)
the log-likelihood climbs monotonically (\(-1312 \to -791 \to -752\)) exactly as the sandwich argument promised, and from an initialisation where both means sat inside the left blob, em still pries the components apart and lands within a hair of the empirical per-component statistics — which, not the generating parameters, are the best any estimator could do on this sample. note the eps variance floor riding along in the m-step: on this dataset it never binds, but it is the difference between a demo and a fit that survives an unlucky seed.
see also
- k-means clustering — this model with the soft parts frozen hard
- principal component analysis — linear latent structure; gmms give multimodal latent structure
- naive bayes — class-conditional gaussians with the labels observed: the complete-data problem em emulates
- bias-variance decomposition — the trade bic is arbitrating, in its model-selection costume
em is due to dempster, laird & rubin (1977), maximum likelihood from incomplete data via the em algorithm, jrss-b 39(1) — one of the most-cited statistics papers ever written. the elbo view presented here is the modern one; it is also the launchpad for variational inference, where the e-step’s exact posterior is itself replaced by an optimised approximation.
References
Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon (2020). Mathematics for Machine Learning, Cambridge University Press.
Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome (2009). The Elements of Statistical Learning, Springer. ↩︎
Backlinks (5)
1. Digits /tags/digits/
The scikit-learn digits dataset: 1,797 tiny 8x8 greyscale images of handwritten digits, and the workhorse of every sklearn tutorial that needs a multiclass problem which loads instantly and fits in L2 cache.
Provenance
The data is the Optical Recognition of Handwritten Digits (optdigits) set from the UCI machine learning repository, created by E. Alpaydin and C. Kaynak at Bogazici University and donated in July 1998. It originates in Kaynak’s 1995 MSc thesis on combining multiple classifiers, and the companion paper is Alpaydin and Kaynak, Cascading Classifiers, Kybernetika 34(4), 1998.
2. K-means Clustering /wiki/ml/unsupervised/k-means-clustering/
k-means is unsupervised learning’s hello world: pick \(k\) prototype points, assign every datum to its nearest prototype, move each prototype to the centre of its flock, repeat. 𐃏 it is fast, it always terminates, and it is wrong in ways that are so instructive that every clustering course starts here anyway.
3. Principal Component Analysis (PCA) /wiki/ml/unsupervised/pca/
pca is the linear algebra exam question that escaped into industry. given a cloud of points in \(\mathbb{R}^d\), it finds the orthogonal directions along which the cloud spreads the most, and lets you throw away the rest. 𐃏 two apparently different questions — “which directions carry the most variance?” and “which subspace loses the least when i project onto it?” — turn out to have the same answer, and that answer is an eigendecomposition.
4. Wiki /wiki/
Knowledge is a paradox. The more one understand, the more one realises the vastness of his ignorance.