← All tutorials

Gaussian Mixture Models (GMM)

Soft clustering with overlapping bell curves.

On this page

1 Hard circles or soft ellipses 2 A mixture adds weighted bumps 3 Each point gets a share, by Bayes rule 4 Fitting the mixture with EM 5 k-means is the hard-label limit 6 Covariance shapes trade flexibility for parameters 7 How many components? Let BIC decide 8 Gaussian mixtures in practice 9 Where GMMs are used 10 Common traps 11 What if the hidden cluster changes over time? 12 References

k-Means Clustering gives every point one cluster and every cluster a circle. A Gaussian mixture gives each point a share in every cluster and lets clusters be tilted ellipses.

1 Hard circles or soft ellipses

A Gaussian mixture model (GMM) describes the data as a weighted sum of Gaussian bumps, gives each point a share in every bump, fits the bumps with the EM loop from Maximum Likelihood and EM, and picks the number of bumps with a score that charges for extra parameters.

k-means, hardmixture, softE stepM step
Figure 1. k-means gives hard colors and circles, a GMM soft colors and ellipses.

2 A mixture adds weighted bumps

Each Gaussian, a component, has a center, a covariance and a weight, and EM picks the values that maximize the likelihood of the data. EM runs once over the training set; after that, scoring a new point is one pass over the K components.

weight 0.37weight 0.63their sum
Figure 2. In one dimension, two weighted bumps add to one curve with two humps.
The mixture’s density at a point x is a weighted sum of Gaussians, one per component. K is the number of components.
p(x) = Σk wk N(x | μk, Σk)
k numbers the components, and the plain Σk adds over all K of them. The bold Σk is component k’s covariance, its width along each axis and its tilt (see Common Probability Distributions). N(x | μk, Σk) is a Gaussian centered at μk. wk is component k’s mixing weight, the fraction of the data component k explains. The weights add to 1, so the sum still has area 1. A larger wk makes its component taller without moving it, and where components overlap their heights add, which is how two humps form one curve.

3 Each point gets a share, by Bayes rule

Which component made a point? Bayes rule answers with shares: each component’s height at the point, times its weight, rescaled to add to 1.

point i30%70%
Figure 3. Higher on the orange bump, so orange gets 70% and blue 30%.
Bayes rule splits each point among the components, in proportion to how well each one explains the point and how common it is.
aik = wk N(xi | μk, Σk)
i numbers the points. aik is the height of component k at point xi, times how common k is: the likelihood times the prior, the top of Bayes rule.
rik = aikΣj aij
rik, the responsibility, is the share of point i given to component k. The bottom adds the same score over every component j, so each point’s shares add to 1. A point deep inside one component goes almost wholly to it; a point between two components is split, which lets the ellipses overlap.

4 Fitting the mixture with EM

The shares need the components and the components need the shares, the loop EM solves. The E step computes every rik; the M step refits each component from the points counted at their shares.

Figure 4. Toy data like the Old Faithful geyser, eruption length against waiting time, in minutes.

One EM round computes every point’s shares from the current ellipses, then refits each ellipse from the points counted at those shares.
rik = aikΣj aij
E step. The shares are recomputed from this round’s centers, ellipses and weights; the M step reads nothing else.
μk = 1Nk Σi rik xi
M step. Nk is the sum of every point’s share in k, the number of points component k holds. The new center is the share-weighted average of the points, the plain average of a single Gaussian fit with each point counted at its share, so a point that k barely claims barely pulls its center.
Σk = 1Nk Σi rik
(xi − μk)(xi − μk)T
The new ellipse averages each point’s gap from the center times that same gap, at the point’s share. The raised T is the transpose, which makes that product a 2 by 2 table of variances and tilt. Points that line up along a diagonal have gaps of the same sign on both axes, and that tilts the ellipse along them.
wk = NkN
The new weight is the average share over all N points, so a component that holds more of the data counts for more in the next E step.

This snippet runs one EM round in NumPy on two toy blobs and prints the new weights and means; after the starting guess, its lines follow the card from top to bottom.

import numpy as np
from scipy.stats import multivariate_normal as mvn
rng = np.random.default_rng(0)
blob_a = rng.normal([-1, -1], 0.5, (100, 2))
blob_b = rng.normal([1, 1], 0.5, (150, 2))
X = np.vstack([blob_a, blob_b])
w = np.array([0.5, 0.5])
mu = np.array([[-0.5, 0.0], [0.5, 0.0]])
Sigma = np.array([np.eye(2), np.eye(2)])
K = len(w)
components = [mvn(mu[k], Sigma[k]) for k in range(K)]
a = np.column_stack([w[k] * components[k].pdf(X) for k in range(K)])
r = a / a.sum(axis=1, keepdims=True)
N_k = r.sum(axis=0)
mu = r.T @ X / N_k[:, None]
covs = [np.cov(X.T, aweights=r[:, k], bias=True) for k in range(K)]
Sigma = np.stack(covs)
w = N_k / len(X)
print(w.round(2), mu.round(2))
  1. 1Draws two toy clumps of 100 and 150 points.
  2. 2Sets starting weights w, means μ and covariances Σ for K = 2.
  3. 3Computes the scores aik, eq card in Section 3, line 1, then the E-step shares rik, eq card in Section 4, line 1.
  4. 4Moves each mean μk, eq card in Section 4, line 2.
  5. 5Refits each Σk, eq card in Section 4, lines 3 and 4.
  6. 6Resets the weights wk, eq card in Section 4, line 5.

5 k-means is the hard-label limit

Give every component the same round covariance, with its standard deviation shrinking toward zero. Each share becomes 0 or 1 for the nearest center, and the center update becomes k-means’. k-means never estimates a covariance, hence its circles.

Figure 5. Narrow components give the dark point to the nearer center, like k-means.

6 Covariance shapes trade flexibility for parameters

A full covariance lets every component tilt and stretch freely, at three numbers per component in 2D. Cheaper options constrain it; scikit-learn names them with covariance_type.

fulldiagsphericaltied
Figure 6. Full tilts, diag follows the axes, spherical is round, tied shares one shape.

The long axes of each ellipse are the directions PCA (principal component analysis) would find inside that cluster.

7 How many components? Let BIC decide

More components always raise the likelihood, so the likelihood alone cannot pick K. The Bayesian information criterion (BIC) can.

BIC adds a penalty for every parameter to minus twice the log-likelihood, and the number of components with the lowest score wins.
BIC = −2 log L + m log N
L is the fit’s likelihood, so −2 log L falls as the fit improves. m is the number of free parameters (weights, centers and covariance entries). Each parameter costs log N, so the penalty grows slowly with the data. A new component always lowers −2 log L a little but raises m by a whole block, so past the real number of clumps the penalty wins and BIC climbs again.
123456lowestnumber of components KBIC, lower is better
Figure 7. BIC is lowest at two components (schematic bars).

This snippet fits 1 to 6 components to the same two blobs and prints each BIC; look for the smallest.

import numpy as np
from sklearn.mixture import GaussianMixture
rng = np.random.default_rng(0)
blob_a = rng.normal([-1, -1], 0.5, (100, 2))
blob_b = rng.normal([1, 1], 0.5, (150, 2))
X = np.vstack([blob_a, blob_b])
for K in range(1, 7):
    gm = GaussianMixture(n_components=K, random_state=0).fit(X)
    print(K, gm.bic(X))  # lowest wins

With two separate blobs, expect the smallest BIC at 2.

8 Gaussian mixtures in practice

In scikit-learn a mixture sits after a scaler; fit runs EM once on the training points, and predict_proba or score reads new points.

import numpy as np
from sklearn.mixture import GaussianMixture
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(0)
short_eruptions = rng.normal([2.0, 55], [0.3, 6], (100, 2))
long_eruptions = rng.normal([4.3, 80], [0.4, 6], (172, 2))
X = np.vstack([short_eruptions, long_eruptions])
gm = GaussianMixture(n_components=2, covariance_type="full", random_state=0)
model = make_pipeline(StandardScaler(), gm)
model.fit(X)
new = [[2.1, 54.0], [4.0, 79.0]]
print(model.predict_proba(new).round(2))  # shares r_ik
print(model.score(new), gm.n_iter_)  # mean log-likelihood, rounds
  1. 1Draws a geyser-like toy cloud, eruption length and waiting time both in minutes, 100 short and 172 long eruptions.
  2. 2Chains StandardScaler with a two-component GaussianMixture.
  3. 3Runs EM rounds until the average log-likelihood per point gains less than tol.
  4. 4Gives new points their shares rik, eq card in Section 3, line 2.
  5. 5Prints the mean log-likelihood and the rounds run.
stepwhen, on what, and its cost
fitOnce, in EM rounds over all N points; each round evaluates K component densities at every point (Bishop 2006, sec. 9.2).
predict_proba, scorePer new point, K component densities, no refit.
statewk, μk, Σk per component (weights_, means_, covariances_).
settingraise it, lower itdefault
n_components, K in ΣkUp always raises the likelihood and soon overfits; K = 1 is one component for the whole cloud. BIC picks it.1
covariance_type, the shape of Σk"full" fits any tilt at the most parameters; "diag", "spherical" and "tied" constrain it."full"
reg_covar, added to the diagonal of ΣkUp keeps components wide and stable; 0 lets a component collapse onto one point.1e-6
tol, max_iterEM stops when a round raises the average log-likelihood per point by less than tol, or after max_iter rounds. A smaller tol runs more rounds and lands closer to the peak; too large a tol stops early. Hitting max_iter stops with a convergence warning.1e-3, 100
n_init, init_paramsMore starts, best kept, fewer poor local peaks. The first guess comes from k-means; "k-means++", "random" and "random_from_data" are the other options, and a poor start lands on a poorer local peak.1, "kmeans"

Defaults are from the GaussianMixture docs. Gotchas are in Section 10.

9 Where GMMs are used

In speaker verification, the classic use, Reynolds et al. (2000) fit a large background GMM to speech from many people and adapt each speaker’s model from it. Each short frame becomes a vector of MFCCs, numbers describing its spectrum shape (see MFCC and Other Audio Features), and both models score the frames.

framesof soundspeaker GMMlog p(X | speaker)background GMMlog p(X | many)scorea score above a threshold accepts the claim
Figure 8. The score is log p(X | speaker) minus log p(X | many) for the frames X.

A fitted mixture scores every new point (score_samples); a very low score flags an anomaly. Classic speech recognizers used a mixture per hidden state to score each frame (Rabiner 1989, eq. 49), as in Hidden Markov Models.

10 Common traps

11 What if the hidden cluster changes over time?

When the hidden cluster follows a chain over time, as words in speech or a casino’s dice do, the model is a hidden Markov model.

12 References