Lesson 53: Gaussian Mixture Models & the EM Algorithm

USAAIO Lesson 53, from Week 19 of Phase 3. It covers the GMM density model as K weighted Gaussians, then derives the E-step soft assignments and the M-step parameter updates from the expected complete-data log-likelihood, along with the convergence guarantees of EM. It covers the covariance types and model selection by BIC, compares a GMM with k-Means on non-spherical data, and ends with GMM-based anomaly detection. You build a GMM from scratch with NumPy and SciPy and validate it against sklearn. The lesson runs to 32 slides.

Subject: Machine Learning · 63 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Gaussian Mixture Models & the EM Algorithm

Title

USAAIO · Lesson 53 · Week 19 (Phase 3)

Soft assignments, covariance shapes, non-convex log-likelihood. Today: derive the E-step and M-step from first principles, implement EM from scratch, and detect anomalies with score_samples.

2. By the end of this lesson you can

Objectives

  1. Write the GMM density p(x) = sum_k pi_k * N(x; mu_k, Sigma_k) and explain each term
  2. Derive the E-step soft-assignment formula r_{ik} from Bayes' rule
  3. Derive the M-step parameter updates from the expected complete-data log-likelihood
  4. Implement GMM EM from scratch and match sklearn.mixture.GaussianMixture
  5. Use score_samples for anomaly detection and BIC for selecting K

3. What survived from k-Means Clustering?

Warm-up

Discussion prompt

Before we open Lesson 53: Gaussian Mixture Models & the EM Algorithm: without looking back, what was the main idea of k-Means Clustering, and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

k-Means assign-then-update, k-Means++ initialization, elbow method and WCSS, silhouette score, EM interpretation (E=assign / M=centroid update), soft k-Means as GMM, and k-Means failure on non-spherical clusters fixed by DBSCAN. Build k-Means from scratch with random and k++ init, implement elbow method and silhouette from scratch, and verify convergence on make_blobs.

4. The GMM density model

Section

Part 1 of 4

5. A mixture of K Gaussians

Concept

A GMM is a weighted sum of K Gaussian components. Each component k has weight pi_k, mean mu_k, and covariance Sigma_k.

\[ p(x) = \sum_{k=1}^{K} \pi_k \, \mathcal{N}(x;\, \mu_k,\, \Sigma_k) \]

Constraints: pi_k >= 0 and sum_k pi_k = 1. Compared to k-Means (Lesson 21), GMM is a probabilistic model: it assigns fractional membership and outputs a density, not just labels.

6. Break it if you can: A mixture of K Gaussians

Counterexample

Discussion prompt

A GMM is a weighted sum of K Gaussian components. Each component k has weight pi_k, mean mu_k, and covariance Sigma_k.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Constraints: pi_k >= 0 and sum_k pi_k = 1. Compared to k-Means (Lesson 21), GMM is a probabilistic model: it assigns fractional membership and outputs a density, not just labels.

7. Covariance types — four flavors

Concept

typeshapeparameters (D=2)
sphericalsigma^2 * I (same radius all dims)1 per component
diagonaldiag(sigma_1^2, ..., sigma_D^2)D per component
tiedone shared Sigma for all kD*(D+1)/2 total
fullseparate Sigma_k per componentD*(D+1)/2 per component

Full covariance lets each component be an arbitrary ellipse. The price is more parameters — use BIC to select type and K simultaneously (see Part 4).

8. Fill in: shape for Covariance types — four flavors

Comparison

Comparison matrix

From Covariance types — four flavors: refill the shape column from what you know. The rest of the table is as it appeared.

typeshapeparameters (D=2)
sphericalsigma^2 * I (same radius all dims)1 per component
diagonaldiag(sigma_1^2, ..., sigma_D^2)D per component
tiedone shared Sigma for all kD*(D+1)/2 total
fullseparate Sigma_k per componentD*(D+1)/2 per component

9. Why soft assignments beat hard ones

Intuition

K-Means (Lesson 21) commits: every point is 100% in one cluster. A point near the boundary gets assigned exactly as confidently as a point at the center.

GMM instead asks: given the current parameters, what fraction of this point's density comes from each component? A boundary point might be 51% component 1, 49% component 2.

xGMM r_leftGMM r_rightk-Means hard
-2.01.00000.00000
+0.00.51030.48970
+2.00.00001.00001

At x=0 (midpoint), k-Means flips a coin once and locks in; GMM gives near-equal probability to both. Those soft weights drive the M-step updates.

10. What each one costs: Why soft assignments beat hard ones

Trade off

Comparison matrix

From Why soft assignments beat hard ones: every row here is a choice with a cost. Fill the k-Means hard column, then say which row you would actually pick and what you give up for it.

xGMM r_leftGMM r_rightk-Means hard
-2.01.00000.00000
+0.00.51030.48970
+2.00.00001.00001

11. E-step — soft responsibilities

Section

Part 2 of 4

12. E-step: posterior responsibility

Concept

Given current parameters, the responsibility r_{ik} is the posterior probability that component k generated point i — just Bayes' rule.

\[ r_{ik} = \frac{\pi_k\,\mathcal{N}(x_i;\,\mu_k,\,\Sigma_k)}{\sum_{j=1}^{K}\pi_j\,\mathcal{N}(x_i;\,\mu_j,\,\Sigma_j)} \]

The denominator is exactly p(x_i) — marginalizing over all components. Computing this for all N points is the E (expectation) step.

13. By analogy: E-step: posterior responsibility

Analogy

Discussion prompt

Explain E-step: posterior responsibility by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Given current parameters, the responsibility r_{ik} is the posterior probability that component k generated point i — just Bayes' rule.

14. What has to happen first: E-step by hand (K=2, 1-D)

Ranking

Put in order

Put the moves of E-step by hand (K=2, 1-D) into the order they have to happen.

  1. Compute pi_1 * N(0.5; -2.0, 0.7) = 0.35 * 0.00115 = 0.000402
  2. Compute pi_2 * N(0.5; +2.0, 0.7) = 0.65 * 0.10917 = 0.070960
  3. p(x=0.5) = 0.000402 + 0.070960 = 0.071362; r1 = 0.0056, r2 = 0.9944

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The left component's PDF at x=0.5 is tiny: 2.5 standard deviations away.

15. E-step by hand (K=2, 1-D)

Worked example

Fix pi = [0.35, 0.65], mu = [-2.0, +2.0], sigma = 0.7 for both. Evaluate r at x = +0.5.

Compute pi_1 * N(0.5; -2.0, 0.7) = 0.35 * 0.00115 = 0.000402

Why: The left component's PDF at x=0.5 is tiny: 2.5 standard deviations away.

Compute pi_2 * N(0.5; +2.0, 0.7) = 0.65 * 0.10917 = 0.070960

Why: The right component's PDF at x=0.5 is much larger: only 2.14 std devs away and has higher weight.

p(x=0.5) = 0.000402 + 0.070960 = 0.071362; r1 = 0.0056, r2 = 0.9944

Why: The point is 99.4% attributed to the right component. This soft weight is what gets passed to the M-step.

xpi1*N (left)pi2*N (right)r_leftr_right
-2.00.19947~01.00000.0000
+0.00.003370.006250.35000.6500
+2.0~00.370450.00001.0000

16. Watch it run: E-step by hand (K=2, 1-D)

Pattern

Step through it

Step through E-step by hand (K=2, 1-D) one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: x is -2.0
  2. Step 2: x is +0.0
  3. Step 3: x is +2.0

17. M-step — update parameters

Section

Part 3 of 4

18. M-step: weighted MLE

Concept

Given responsibilities r_{ik}, we update each parameter to the weighted maximum-likelihood estimate, treating r_{ik} as soft counts.

\[ N_k = \sum_{i=1}^{N} r_{ik} \qquad \pi_k^\text{new} = \frac{N_k}{N} \qquad \mu_k^\text{new} = \frac{\sum_i r_{ik}\, x_i}{N_k} \]

\[ \Sigma_k^\text{new} = \frac{\sum_i r_{ik}\,(x_i - \mu_k^\text{new})(x_i - \mu_k^\text{new})^\top}{N_k} \]

Compare to k-Means M-step (Lesson 21): those updates are identical but with r_{ik} in {0,1}. EM generalizes k-Means by allowing fractional membership.

19. Teach it back: M-step: weighted MLE

Explain it

Discussion prompt

Explain M-step: weighted MLE to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

Given responsibilities r_{ik}, we update each parameter to the weighted maximum-likelihood estimate, treating r_{ik} as soft counts.

20. What has to happen first: M-step by hand (N=4 toy)

Ranking

Put in order

Put the moves of M-step by hand (N=4 toy) into the order they have to happen.

  1. N_k1 = 0.92 + 0.88 + 0.05 + 0.03 = 1.88; N_k2 = 4 - 1.88 = 2.12
  2. mu_k1_new = (0.92-2.5 + 0.88-1.8 + 0.051.5 + 0.032.3) / 1.88 = -1.9894
  3. pi_k1_new = 1.88 / 4 = 0.4700; pi_k2_new = 2.12 / 4 = 0.5300

Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. Effective count for each cluster — not an integer because assignments are soft.

21. M-step by hand (N=4 toy)

Worked example

Four points xs = [-2.5, -1.8, +1.5, +2.3]. After E-step, responsibilities for component 1 are r = [0.92, 0.88, 0.05, 0.03].

N_k1 = 0.92 + 0.88 + 0.05 + 0.03 = 1.88; N_k2 = 4 - 1.88 = 2.12

Why: Effective count for each cluster — not an integer because assignments are soft.

mu_k1_new = (0.92-2.5 + 0.88-1.8 + 0.051.5 + 0.032.3) / 1.88 = -1.9894

Why: Points with high responsibility pull the mean strongly; low-responsibility points barely contribute.

pi_k1_new = 1.88 / 4 = 0.4700; pi_k2_new = 2.12 / 4 = 0.5300

Why: New mixing weights are just the fraction of total soft count each component holds.

parametercomponent 1component 2
N_k (soft count)1.882.12
mu_k (new mean)-1.9894+1.5283
pi_k (new weight)0.47000.5300

22. Work backwards from the answer: M-step by hand (N=4 toy)

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

pi_k1_new = 1.88 / 4 = 0.4700; pi_k2_new = 2.12 / 4 = 0.5300

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Four points xs = [-2.5, -1.8, +1.5, +2.3]. After E-step, responsibilities for component 1 are r = [0.92, 0.88, 0.05, 0.03].

23. Something is wrong here: EM maximizes a local optimum, not global

Anomaly

Predict first

A student writes this, and it looks reasonable:

The GMM log-likelihood is non-convex, but EM is guaranteed to converge — so it finds the global maximum.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: EM guarantees the log-likelihood never decreases, so it converges — but only to a local maximum.

EM guarantees monotone non-decreasing log-likelihood, not the global maximum.

Why: EM guarantees the log-likelihood never decreases, so it converges — but only to a local maximum. The non-convex landscape has many local optima, and a bad initialization can land in a poor one.

24. Trap: EM maximizes a local optimum, not global

Trap

The trap

The GMM log-likelihood is non-convex, but EM is guaranteed to converge — so it finds the global maximum.

Run EM once from a single random initialization and accept whatever it converges to

Why: EM guarantees the log-likelihood never decreases, so it converges — but only to a local maximum. The non-convex landscape has many local optima, and a bad initialization can land in a poor one.

The fix

EM guarantees monotone non-decreasing log-likelihood, not the global maximum.

Run EM with multiple restarts (n_init > 1) and keep the solution with the highest final log-likelihood

Why: sklearn defaults to n_init=1 — set n_init=10 for robustness. Initialization with k-Means (the sklearn default init='kmeans') also helps avoid degenerate starts.

25. Break it on purpose: EM maximizes a local optimum, not global

Break the constraint

Discussion prompt

The rule this trap just fixed:

EM guarantees monotone non-decreasing log-likelihood, not the global maximum.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

EM guarantees the log-likelihood never decreases, so it converges — but only to a local maximum. The non-convex landscape has many local optima, and a bad initialization can land in a poor one.

26. Guess the shape of the answer: EM convergence trace (sklearn)

Estimation

Predict first

Run GaussianMixture with max_iter capped at increasing values and read off the log-likelihood after each cap.

Commit before you compute: what does EM convergence trace (sklearn) come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Iter 1: -1120.32, iter 2: -1119.92, iter 3: -1119.77, iter 5: -1119.67, iter 10: -1119.67 (converged)

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The log-likelihood is strictly non-decreasing and flattens after ~5 iterations on this clean data.

27. EM convergence trace (sklearn)

Worked example

Run GaussianMixture with max_iter capped at increasing values and read off the log-likelihood after each cap.

from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_blobs
import numpy as np

X, _ = make_blobs(300, 2, centers=3, cluster_std=1.0, random_state=0)
for it in [1, 2, 3, 5, 10, 20]:
    gm = GaussianMixture(3, covariance_type='full', random_state=0, max_iter=it)
    gm.fit(X)
    print(f'iter={it:3d}: LL={gm.score(X)*len(X):.2f}')

Iter 1: -1120.32, iter 2: -1119.92, iter 3: -1119.77, iter 5: -1119.67, iter 10: -1119.67 (converged)

Why: The log-likelihood is strictly non-decreasing and flattens after ~5 iterations on this clean data. Real datasets may need 50-200 iterations.

max_iterlog-likelihooddelta
1-1120.32--
2-1119.92+0.40
3-1119.77+0.15
5-1119.67+0.10
10-1119.670.00 (converged)

28. Where does each piece belong: Lesson 53: Gaussian Mixture Models & the EM…

Sorting

Sort into buckets

These are the pieces of Lesson 53: Gaussian Mixture Models & the EM Algorithm, out of order. Put each one back under the part of the lesson it belongs to.

The GMM density model
A mixture of K Gaussians; Covariance types — four flavors; Why soft assignments beat hard ones
E-step — soft responsibilities
E-step: posterior responsibility; E-step by hand (K=2, 1-D)
M-step — update parameters
M-step: weighted MLE; M-step by hand (N=4 toy); EM convergence trace (sklearn)
s1
The GMM density model is where Lesson 53: Gaussian Mixture Models & the EM Algorithm puts A mixture of K Gaussians, Covariance types — four flavors, Why soft assignments beat hard ones. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
E-step — soft responsibilities is where Lesson 53: Gaussian Mixture Models & the EM Algorithm puts E-step: posterior responsibility, E-step by hand (K=2, 1-D). Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
M-step — update parameters is where Lesson 53: Gaussian Mixture Models & the EM Algorithm puts M-step: weighted MLE, M-step by hand (N=4 toy), EM convergence trace (sklearn). Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

29. GMM from scratch + applications

Section

Part 4 of 4

30. From-scratch EM: the 20-line loop

Concept

The entire EM algorithm is: initialize, then alternate E-step (compute R) and M-step (update mus, covs, pis) until convergence.

  1. Init: choose starting means (e.g. random points); set Sigma_k = I, pi_k = 1/K
  2. E-step: for each k compute R[:,k] = pi_k * N(X; mu_k, Sigma_k); normalize rows
  3. M-step: N_k = R.sum(0); mu_k = (R[:,k] @ X) / N_k; Sigma_k = weighted_cov; pi_k = N_k/N
  4. Repeat until log-likelihood change < tol (or max_iter)

31. Teach it back: From-scratch EM: the 20-line loop

Explain it

Discussion prompt

Explain From-scratch EM: the 20-line loop to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.

Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.

Answer:

The entire EM algorithm is: initialize, then alternate E-step (compute R) and M-step (update mus, covs, pis) until convergence.

32. Guess the shape of the answer: EM from scratch — iteration trace

Estimation

Predict first

K=2, make_blobs(200, centers=2, std=0.8, seed=0). Initialize means at two random data points; covs = I; pis = [0.5, 0.5]. Track LL across iterations.

Commit before you compute: what does EM from scratch — iteration trace come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: After 20 iterations: means = [[0.964, 4.356], [1.954, 0.762]] — matches sklearn (differs by <0.01)

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The from-scratch loop converges to the same local optimum as sklearn's optimized C++ backend, confirming the math is correct.

33. EM from scratch — iteration trace

Worked example

K=2, make_blobs(200, centers=2, std=0.8, seed=0). Initialize means at two random data points; covs = I; pis = [0.5, 0.5]. Track LL across iterations.

import numpy as np
from scipy.stats import multivariate_normal as mvn
from sklearn.datasets import make_blobs

X, _ = make_blobs(200, 2, centers=2, cluster_std=0.8, random_state=0)
K, N, D = 2, *X.shape
rng = np.random.default_rng(0)
mus = X[rng.choice(N, K, replace=False)].astype(float)
covs = [np.eye(D)] * K
pis = np.ones(K) / K

for it in range(1, 21):
    R = np.column_stack([pis[k]*mvn.pdf(X, mus[k], covs[k]) for k in range(K)])
    R /= R.sum(1, keepdims=True)
    Nk = R.sum(0)
    mus = (R.T @ X) / Nk[:, None]
    covs = [(R[:, k:k+1] * (X-mus[k])).T @ (X-mus[k]) / Nk[k] for k in range(K)]
    pis = Nk / N

print(np.round(mus, 3))

After 20 iterations: means = [[0.964, 4.356], [1.954, 0.762]] — matches sklearn (differs by <0.01)

Why: The from-scratch loop converges to the same local optimum as sklearn's optimized C++ backend, confirming the math is correct.

iterLLnotes
1-605.86initial step, large jump
2-602.33+3.53
3-601.34+0.99
5-601.04+0.30
10-601.03~converged
20-601.03same as sklearn final

34. GMM vs k-Means: when does GMM win?

Concept

K-Means implicitly assumes spherical clusters of equal size. It assigns each point to the nearest centroid using Euclidean distance, so elongated or correlated clusters mislead it.

methodassignment typecovarianceaccuracy (oblique clusters)
k-Meanshard (0 or 1)spherical only0.920
GMM sphericalsoftspherical0.935
GMM full-covsoftarbitrary ellipse0.970

On data from two oblique correlated Gaussians (off-diagonal covariance), GMM full-cov gains 5 percentage points over k-Means. The gap widens as the correlation grows stronger (Lesson 21 comparison).

35. Fill in: assignment type for GMM vs k-Means: when does GMM win?

Comparison

Comparison matrix

From GMM vs k-Means: when does GMM win?: refill the assignment type column from what you know. The rest of the table is as it appeared.

methodassignment typecovarianceaccuracy (oblique clusters)
k-Meanshard (0 or 1)spherical only0.920
GMM sphericalsoftspherical0.935
GMM full-covsoftarbitrary ellipse0.970

36. Something is wrong here: using GMM with diagonal covariance on correlated data

Anomaly

Predict first

A student writes this, and it looks reasonable:

diagonal covariance is cheaper than full and still models different variances per feature — it should work fine on any blob-like clusters.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Diagonal covariance forces the fitted ellipses to be axis-aligned — it cannot represent off-diagonal correlation.

Use covariance_type='full' when the data may have correlations between features.

Why: Diagonal covariance forces the fitted ellipses to be axis-aligned — it cannot represent off-diagonal correlation. On data generated by a full covariance matrix, it makes the same error as spherical, just with different axis scales.

37. Trap: using GMM with diagonal covariance on correlated data

Trap

The trap

diagonal covariance is cheaper than full and still models different variances per feature — it should work fine on any blob-like clusters.

Set covariance_type='diag' and trust it to fit correlated clusters

Why: Diagonal covariance forces the fitted ellipses to be axis-aligned — it cannot represent off-diagonal correlation. On data generated by a full covariance matrix, it makes the same error as spherical, just with different axis scales.

The fix

Use covariance_type='full' when the data may have correlations between features.

Select covariance type by BIC: lower BIC = better model after penalizing parameter count

Why: BIC automatically trades fit quality against parameter count. On the oblique-cluster data, full wins on BIC even though it has more parameters — the fit improvement outweighs the penalty.

38. Which of these survive contact with Lesson 53: Gaussian Mixture Models & the EM…?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
A GMM is a weighted sum of K Gaussian components. Each component k has weight pi_k, mean mu_k, and covariance Sigma_k.; K-Means (Lesson 21) commits: every point is 100% in one cluster. A point near the boundary gets assigned exactly as confidently as a point at the center.; Given current parameters, the responsibility r_{ik} is the posterior probability that component k generated point i — just Bayes' rule.
Breaks
The GMM log-likelihood is non-convex, but EM is guaranteed to converge — so it finds the global maximum.; diagonal covariance is cheaper than full and still models different variances per feature — it should work fine on any blob-like clusters.
sound
These are stated as this lesson states them — each one survives the edge cases Lesson 53: Gaussian Mixture Models & the EM Algorithm puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

39. Guess the shape of the answer: BIC for selecting K and covariance type

Estimation

Predict first

Fit GMM with K = 1 through 6 (full covariance) on make_blobs(300, centers=3, std=0.8, seed=42). The true K=3 should win on BIC.

Commit before you compute: what does BIC for selecting K and covariance type come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: K=1: 3753.0, K=2: 2600.0, K=3: 2150.3 (minimum), K=4: 2171.7, K=5: 2202.3

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. BIC drops steeply from K=1 to K=3 (large fit gain) then rises (penalty exceeds marginal fit gain).

40. BIC for selecting K and covariance type

Worked example

Fit GMM with K = 1 through 6 (full covariance) on make_blobs(300, centers=3, std=0.8, seed=42). The true K=3 should win on BIC.

from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_blobs

X, _ = make_blobs(300, 2, centers=3, cluster_std=0.8, random_state=42)
for k in range(1, 7):
    gm = GaussianMixture(k, covariance_type='full', random_state=0)
    gm.fit(X)
    print(f'K={k}: BIC={gm.bic(X):.1f}')

K=1: 3753.0, K=2: 2600.0, K=3: 2150.3 (minimum), K=4: 2171.7, K=5: 2202.3

Why: BIC drops steeply from K=1 to K=3 (large fit gain) then rises (penalty exceeds marginal fit gain). The elbow at K=3 correctly identifies the true number of components.

KBICverdict
13753.0underfit
22600.0still underfit
32150.3MINIMUM — best K
42171.7overfit
52202.3overfit

41. Work backwards from the answer: BIC for selecting K and covariance type

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

K=1: 3753.0, K=2: 2600.0, K=3: 2150.3 (minimum), K=4: 2171.7, K=5: 2202.3

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Fit GMM with K = 1 through 6 (full covariance) on make_blobs(300, centers=3, std=0.8, seed=42). The true K=3 should win on BIC.

42. Anomaly detection with score_samples

Concept

Once fitted, gm.score_samples(X) returns log p(x_i) for each point. Anomalies are points with very low density — far from all component means.

A simple threshold: flag any point below the 5th percentile of training log-probabilities. On iris (150 samples), this flags 8 points whose log p(x) < -4.398.

samplelog p(x)class
idx 0 (typical setosa)1.571normal
idx 118 (worst-case)-7.062anomaly
5th pct threshold-4.398boundary

43. By analogy: Anomaly detection with score_samples

Analogy

Discussion prompt

Explain Anomaly detection with score_samples by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Once fitted, gm.score_samples(X) returns log p(x_i) for each point. Anomalies are points with very low density — far from all component means.

44. Rebuild the recipe: The GMM / EM recipe

Ranking

Put in order

These are the steps of The GMM / EM recipe, scrambled. Put them back in order before the next slide shows you.

  1. Model: p(x) = sum_k pi_k * N(x; mu_k, Sigma_k), one row in the GMM density
  2. E-step: r_{ik} = pi_k N(x_i; mu_k, Sig_k) / p(x_i) — Bayes posterior over components
  3. M-step: weighted MLE — mu_k = sum_i(r_{ik} x_i) / N_k, Sigma_k similarly
  4. Convergence: LL never decreases; run multiple restarts (n_init >= 5) for robustness
  5. Model selection: BIC over K in {1..10} and covariance type; pick the minimum
  6. Anomaly detection: fit GMM on clean data; flag score_samples(X) < threshold

Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.

45. The GMM / EM recipe

Pattern

  1. Model: p(x) = sum_k pi_k * N(x; mu_k, Sigma_k), one row in the GMM density
  2. E-step: r_{ik} = pi_k N(x_i; mu_k, Sig_k) / p(x_i) — Bayes posterior over components
  3. M-step: weighted MLE — mu_k = sum_i(r_{ik} x_i) / N_k, Sigma_k similarly
  4. Convergence: LL never decreases; run multiple restarts (n_init >= 5) for robustness
  5. Model selection: BIC over K in {1..10} and covariance type; pick the minimum
  6. Anomaly detection: fit GMM on clean data; flag score_samples(X) < threshold

46. Where does it stop working: The GMM / EM recipe

Edge cases

Discussion prompt

The GMM / EM recipe works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

  1. Model: p(x) = sum_k pi_k * N(x; mu_k, Sigma_k), one row in the GMM density
  2. E-step: r_{ik} = pi_k N(x_i; mu_k, Sig_k) / p(x_i) — Bayes posterior over components
  3. M-step: weighted MLE — mu_k = sum_i(r_{ik} x_i) / N_k, Sigma_k similarly
  4. Convergence: LL never decreases; run multiple restarts (n_init >= 5) for robustness
  5. Model selection: BIC over K in {1..10} and covariance type; pick the minimum
  6. Anomaly detection: fit GMM on clean data; flag score_samples(X) < threshold

47. Rule out three: Check yourself — E-step formula

Elimination

Eliminate the wrong options

In the E-step, r_{ik} is the responsibility of component k for point i. What quantity does the denominator sum_j pi_j N(x_i; mu_j, Sigma_j) equal?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. The marginal density p(x_i) under the GMM
  • B. The joint probability p(x_i, k)
  • C. The prior pi_k for component k
  • D. The log-likelihood of the full dataset

Survives elimination: A

Why: Summing pi_j * N(x_i; mu_j, Sigma_j) over all j marginalizes out the latent component assignment — this is exactly the GMM marginal density p(x_i). Dividing by p(x_i) turns the joint pi_k*N into a posterior (Bayes' theorem).

48. Check yourself — E-step formula

Check

Work through it before clicking.

Check your understanding

In the E-step, r_{ik} is the responsibility of component k for point i. What quantity does the denominator sum_j pi_j N(x_i; mu_j, Sigma_j) equal?

  • A. The marginal density p(x_i) under the GMM (correct)
  • B. The joint probability p(x_i, k)
  • C. The prior pi_k for component k
  • D. The log-likelihood of the full dataset

Answer: A

Why: Summing pi_j * N(x_i; mu_j, Sigma_j) over all j marginalizes out the latent component assignment — this is exactly the GMM marginal density p(x_i). Dividing by p(x_i) turns the joint pi_k*N into a posterior (Bayes' theorem).

Why B tempts people
The joint p(x_i, k) = pi_k * N(x_i; mu_k, Sigma_k) — that's the numerator, not the denominator.
Why C tempts people
pi_k is the prior for component k and appears only in the numerator as the weight for component k specifically.
Why D tempts people
The dataset log-likelihood is a sum over all N points; the denominator here is just the marginal for one point x_i.

49. Answer it before you see the options: Check yourself — M-step soft counts

Prediction

Predict first

After the E-step on N=200 points with K=3, the soft counts are N_1=72, N_2=83, N_3=45. What is the new mixing weight pi_3?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: 0.225

Why: pi_k_new = N_k / N = 45 / 200 = 0.225. The M-step weight update is just the fraction of total soft count that component k holds.

50. Check yourself — M-step soft counts

Check

Trace the update rule.

Check your understanding

After the E-step on N=200 points with K=3, the soft counts are N_1=72, N_2=83, N_3=45. What is the new mixing weight pi_3?

  • A. 0.225 (correct)
  • B. 0.415
  • C. 0.333
  • D. 0.045

Answer: A

Why: pi_k_new = N_k / N = 45 / 200 = 0.225. The M-step weight update is just the fraction of total soft count that component k holds.

Why B tempts people
0.415 = N_2/N = 83/200 — that is the update for component 2, not component 3.
Why C tempts people
0.333 would be correct if all components had equal soft count (N=200/3). That's only true at a fully symmetric solution.
Why D tempts people
0.045 would equal N_3 divided by N*10 — a factor-of-10 error, perhaps confusing N_k=45 with 45/1000.

51. Rule out three: Check yourself — EM convergence

Elimination

Eliminate the wrong options

Which statement about EM for GMM is TRUE?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. EM monotonically increases the log-likelihood but may converge to a local maximum
  • B. EM converges to the global maximum because the GMM log-likelihood is convex
  • C. EM can decrease the log-likelihood on some steps if sigma_k becomes very small
  • D. EM is guaranteed to find the same solution regardless of initialization

Survives elimination: A

Why: EM's convergence theorem (Dempster, Laird, Rubin 1977) guarantees log-likelihood is non-decreasing at every step. However, the GMM log-likelihood is non-convex, so EM can stop at a local maximum. Multiple restarts are the standard mitigation.

52. Check yourself — EM convergence

Check

Key property of EM.

Check your understanding

Which statement about EM for GMM is TRUE?

  • A. EM monotonically increases the log-likelihood but may converge to a local maximum (correct)
  • B. EM converges to the global maximum because the GMM log-likelihood is convex
  • C. EM can decrease the log-likelihood on some steps if sigma_k becomes very small
  • D. EM is guaranteed to find the same solution regardless of initialization

Answer: A

Why: EM's convergence theorem (Dempster, Laird, Rubin 1977) guarantees log-likelihood is non-decreasing at every step. However, the GMM log-likelihood is non-convex, so EM can stop at a local maximum. Multiple restarts are the standard mitigation.

Why B tempts people
The GMM log-likelihood is explicitly non-convex in (pi, mu, Sigma) — there is no global-maximum guarantee from EM alone.
Why C tempts people
A degenerate sigma (near zero) can cause numerical overflow of the Gaussian PDF, but the algorithm does NOT intentionally decrease the LL — this is a numerical failure mode, not an intended step.
Why D tempts people
Different initializations converge to different local optima; EM is highly initialization-sensitive, which is why n_init > 1 is recommended.

53. Your turn: build GMM from scratch

Section

Project

54. Project: GMM EM from scratch

Concept

Implement GMM EM from scratch in NumPy/SciPy, validate it against sklearn, then use it for anomaly detection on the iris dataset.

#milestonetool
1E-step: compute soft responsibility matrix Rscipy.stats.multivariate_normal
2M-step: update mus, covs, pis from Rnumpy weighted averages
3Iterate 20 steps; print final means + compare to sklearnGaussianMixture
4Anomaly detection on iris: flag bottom-5% log-probsgm.score_samples

Build rules: write E-step and M-step as separate functions; never divide before checking R.sum(1) is nonzero; add 1e-300 inside log to prevent -inf.

55. Break it if you can: Project: GMM EM from scratch

Counterexample

Discussion prompt

Implement GMM EM from scratch in NumPy/SciPy, validate it against sklearn, then use it for anomaly detection on the iris dataset.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Build rules: write E-step and M-step as separate functions; never divide before checking R.sum(1) is nonzero; add 1e-300 inside log to prevent -inf.

56. Milestone 1 & 2 — E-step and M-step functions

Worked example

Your turn: write e_step(X, mus, covs, pis) returning R, then m_step(X, R) returning new mus, covs, pis.

Hint: E-step — stack component densities column-wise, then divide each row by its sum. M-step — use Nk = R.sum(0); new mu_k = R[:,k] @ X / Nk[k].

import numpy as np
from scipy.stats import multivariate_normal as mvn

def e_step(X, mus, covs, pis):
    R = np.column_stack([pis[k]*mvn.pdf(X, mus[k], covs[k]) for k in range(len(mus))])
    R /= R.sum(1, keepdims=True)
    return R

def m_step(X, R):
    Nk = R.sum(0)
    mus = (R.T @ X) / Nk[:, None]
    covs = [(R[:, k:k+1]*(X-mus[k])).T@(X-mus[k])/Nk[k] for k in range(R.shape[1])]
    return mus, covs, Nk / len(X)

print('functions defined')
functioninputoutput
e_stepX (N,D), mus, covs, pisR (N,K) soft assignments
m_stepX (N,D), R (N,K)updated mus, covs, pis

57. Milestone 3 — 20-iteration loop & validation

Worked example

Your turn: initialize randomly, run 20 EM iterations, print final means. Predict whether they match sklearn.

Hint: seed np.random.default_rng(0); pick 2 random rows of X as starting means; set covs = [np.eye(D)]*K, pis = np.ones(K)/K.

from sklearn.datasets import make_blobs
from sklearn.mixture import GaussianMixture

X, _ = make_blobs(200, 2, centers=2, cluster_std=0.8, random_state=0)
K, N, D = 2, *X.shape
rng = np.random.default_rng(0)
mus = X[rng.choice(N, K, replace=False)].astype(float)
covs = [np.eye(D)] * K; pis = np.ones(K) / K

for _ in range(20):
    R = e_step(X, mus, covs, pis)
    mus, covs, pis = m_step(X, R)

print('scratch:', np.round(mus, 3))
gm = GaussianMixture(K, random_state=0).fit(X)
print('sklearn:', gm.means_.round(3))
sourcemean 0mean 1
scratch (20 iter)[0.964, 4.356][1.954, 0.762]
sklearn[0.963, 4.361][1.952, 0.767]
match (<0.01)yesyes

58. What each one costs: Milestone 3 — 20-iteration loop & validation

Trade off

Comparison matrix

From Milestone 3 — 20-iteration loop & validation: every row here is a choice with a cost. Fill the mean 0 column, then say which row you would actually pick and what you give up for it.

sourcemean 0mean 1
scratch (20 iter)[0.964, 4.356][1.954, 0.762]
sklearn[0.963, 4.361][1.952, 0.767]
match (<0.01)yesyes

59. Milestone 4 — anomaly detection on iris

Worked example

Your turn: fit a 3-component full-cov GMM on iris, call score_samples, flag the bottom-5% as anomalies. Predict how many flags you get.

Hint: np.percentile(log_probs, 5) gives the threshold; np.where(log_probs < threshold) gives anomaly indices.

from sklearn.datasets import load_iris
from sklearn.mixture import GaussianMixture
import numpy as np

X_ir = load_iris().data
gm = GaussianMixture(3, covariance_type='full', random_state=0).fit(X_ir)
lp = gm.score_samples(X_ir)
thresh = np.percentile(lp, 5)
anomalies = np.where(lp < thresh)[0]
print(f'threshold: {thresh:.3f}')
print(f'n_anomalies: {len(anomalies)}')
print(f'worst idx={lp.argmin()}, log p(x)={lp.min():.3f}')
metricvalue
threshold (5th pct)-4.398
anomalies flagged8 of 150 (5.3%)
min log p(x)-7.062 (idx 118)
max log p(x)+1.624 (idx 0, normal)

60. Fill in: value for Milestone 4 — anomaly detection on iris

Comparison

Comparison matrix

From Milestone 4 — anomaly detection on iris: refill the value column from what you know. The rest of the table is as it appeared.

metricvalue
threshold (5th pct)-4.398
anomalies flagged8 of 150 (5.3%)
min log p(x)-7.062 (idx 118)
max log p(x)+1.624 (idx 0, normal)

61. Show it off

Concept

Slides closed, out loud: (1) state the GMM density formula and explain the role of pi_k; (2) describe the E-step and M-step in words without equations; (3) explain why EM can get stuck and what you do about it.

Stretch (homework): (a) derive the E-step and M-step from the expected complete-data log-likelihood E_q[log p(X,Z|theta)] and verify both update equations match what you coded; (b) compare GMM full vs k-Means on make_moons — does GMM win? (c) use BIC to select K on the iris dataset.

62. Connect it up: Lesson 53: Gaussian Mixture Models & the EM Algorithm

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — The GMM density model · E-step — soft responsibilities · M-step — update parameters · GMM from scratch + applications · Your turn: build GMM from scratch. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

63. What you can do now

Recap

ideathe one thing to remember
GMM densityp(x) = sum_k pi_k N(x; mu_k, Sig_k)
E-stepr_ik = pi_k N(x_i) / p(x_i) — Bayes posterior
M-stepweighted MLE: mu_k = sum(r_ik x_i) / N_k
convergenceLL non-decreasing; local max only — use n_init
anomaly detectionscore_samples(X) < low-percentile threshold

Sources

  1. USAAIO Year-Long Master Lesson Plan, Lesson 53 (Week 19 — GMM & EM Algorithm) — Barron · USAAIO Round 2 Preparation, 2026
  2. E-step/M-step hand-traces, EM convergence, anomaly detection — all values verified — numpy 2.2.6 + sklearn + scipy, real execution, June 2026

Want this taught 1-on-1? Alexander tutors Machine Learning — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108