24. Gaussian Mixture Models - A Friendly Guide
Gaussian Mixture Models
Clustering with probability, not hard boundaries
K-Means assigns every point to exactly one cluster. But real data is rarely so clear-cut. A customer might be a 70% “budget shopper” and 30% “premium shopper.” An email might be 80% likely spam and 20% likely a legitimate promotion.
Gaussian Mixture Models (GMMs) solve this with soft clustering — instead of assigning a hard label, they give each point a probability of belonging to each cluster.
This tutorial covers:
- The intuition — what is a mixture of Gaussians?
- Hard vs soft clustering.
- The EM algorithm — how GMMs learn.
- Full Python demonstration.
- GMM vs K-Means — when to use which.
1. The intuition — overlapping bell curves
A Gaussian is a bell curve — a normal distribution described by a mean (centre) and variance (spread).
A Gaussian Mixture Model assumes that the data was generated by several bell curves mixed together:
Cluster 1: Gaussian centred at (2, 3), spread = 0.5
Cluster 2: Gaussian centred at (7, 1), spread = 1.2
Cluster 3: Gaussian centred at (5, 6), spread = 0.8
Each data point was drawn from one of these Gaussians. The model’s job is to figure out which Gaussians exist and what each point’s probability is for each Gaussian.
Memory trick: Imagine two overlapping spotlights on a stage. Every person on stage is lit partly by spotlight 1 and partly by spotlight 2. GMM figures out where the spotlights are and how bright each one shines on each person.
2. Hard vs soft clustering
import numpy as np
# K-Means: hard assignment
# Point [4.5, 2.0] → Cluster 2 (one definitive answer)
# GMM: soft assignment
# Point [4.5, 2.0] → [0.72, 0.21, 0.07]
# cluster1 cluster2 cluster3
# The point is 72% cluster 1, 21% cluster 2, 7% cluster 3
Soft assignments are useful when:
- Points genuinely sit between clusters
- You want a confidence level, not just a label
- Data comes from overlapping populations
3. The EM algorithm — how GMMs learn
GMMs are fitted using the Expectation-Maximisation (EM) algorithm:
E-step (Expectation): Given current Gaussian parameters, compute the probability that each point belongs to each Gaussian.
M-step (Maximisation): Given those probabilities, update the Gaussian parameters (means, covariances, weights) to best explain the data.
Repeat until the parameters stop changing.
Initialise: random Gaussian parameters
↓
E-step: for each point, compute P(cluster k | point)
↓
M-step: update means, covariances, and weights using those probabilities
↓
Repeat until convergence
4. Full Python demonstration
import numpy as np
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
# Generate data from three overlapping clusters
np.random.seed(42)
cluster1 = np.random.multivariate_normal([2, 3], [[0.5, 0.2],[0.2, 0.5]], 100)
cluster2 = np.random.multivariate_normal([7, 1], [[1.0, 0.0],[0.0, 1.0]], 100)
cluster3 = np.random.multivariate_normal([5, 6], [[0.8, 0.3],[0.3, 0.8]], 100)
X = np.vstack([cluster1, cluster2, cluster3])
# Fit GMM with 3 components
gmm = GaussianMixture(n_components=3, covariance_type='full', random_state=42)
gmm.fit(X)
# Hard labels (most likely cluster)
labels = gmm.predict(X)
# Soft probabilities
probs = gmm.predict_proba(X)
print("Point 0 probabilities:", probs[0].round(3))
# e.g. [0.961, 0.032, 0.007] → 96% cluster 0, 3% cluster 1, 1% cluster 2
# Model parameters
print("\nLearned cluster means:")
for i, mean in enumerate(gmm.means_):
print(f" Cluster {i}: {mean.round(2)}")
print("\nMixture weights (how common each cluster is):")
for i, w in enumerate(gmm.weights_):
print(f" Cluster {i}: {w:.3f}")
# Visualise
plt.figure(figsize=(8, 5))
scatter = plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='tab10', alpha=0.6)
plt.scatter(gmm.means_[:, 0], gmm.means_[:, 1],
marker='X', s=200, c='black', label='Cluster centres')
plt.title('Gaussian Mixture Model — Soft Clustering')
plt.legend()
plt.tight_layout()
plt.show()
Choosing the number of components
Use BIC (Bayesian Information Criterion) — lower is better:
bic_scores = []
n_range = range(1, 8)
for n in n_range:
gmm = GaussianMixture(n_components=n, covariance_type='full', random_state=42)
gmm.fit(X)
bic_scores.append(gmm.bic(X))
best_n = n_range[np.argmin(bic_scores)]
print(f"Best number of components: {best_n}")
plt.plot(n_range, bic_scores, 'o-')
plt.xlabel('Number of components')
plt.ylabel('BIC score (lower = better)')
plt.title('Choosing number of GMM components')
plt.tight_layout()
plt.show()
Anomaly detection with GMM
Points with very low probability under the model are anomalies:
# Score each point (log-probability under the model)
log_probs = gmm.score_samples(X)
# Low log-probability = unusual = potential anomaly
threshold = np.percentile(log_probs, 5) # bottom 5% = anomalies
anomalies = X[log_probs < threshold]
print(f"Detected {len(anomalies)} anomalies out of {len(X)} points")
5. Covariance types
GMM has four options for how each cluster’s shape is described:
| Type | Meaning | Flexibility | Parameters |
|---|---|---|---|
spherical |
Each cluster is a circle | Lowest | 1 number per cluster |
diag |
Ellipse aligned with axes | Medium | d numbers per cluster |
tied |
All clusters same shape | Medium | One shared matrix |
full |
Any ellipse shape | Highest | Full matrix per cluster |
for cov_type in ['spherical', 'diag', 'tied', 'full']:
gmm = GaussianMixture(n_components=3, covariance_type=cov_type)
gmm.fit(X)
print(f"{cov_type:12s} BIC: {gmm.bic(X):.1f}")
6. GMM vs K-Means
| Aspect | K-Means | GMM |
|---|---|---|
| Assignment | Hard — one cluster per point | Soft — probability per cluster |
| Cluster shape | Spherical only | Any ellipse (with full covariance) |
| Speed | Fast | Slower (EM iterations) |
| Noise handling | Poor — all points assigned | Good — low-probability points = anomalies |
| Needs scaling | Yes | Yes |
| Choosing k | Elbow method | BIC / AIC |
Use GMM when:
- Points genuinely overlap between clusters
- You need probability of cluster membership, not just a label
- Clusters have different shapes or sizes
- You want built-in anomaly detection
Use K-Means when:
- Clusters are well-separated and roughly spherical
- Speed is critical
- You have very large datasets
Summary
- GMM assumes data comes from multiple overlapping Gaussian (bell curve) distributions.
- Instead of hard cluster assignments, GMM gives probabilities for each cluster.
- The EM algorithm alternates between computing membership probabilities and updating the Gaussian parameters.
- Use BIC to choose the number of components.
- The
covariance_typeparameter controls the shape of each cluster. - GMM doubles as an anomaly detector — low-probability points are unusual.