Classical ML Phase B · Lecture 9 of 12
Part IV — Unsupervised Learning · Lecture 9

k-means, Gaussian Mixtures, and EM

Clustering without labels. k-means is alternating minimisation of a squared-error objective; Gaussian mixtures are its probabilistic upgrade; and EM — the algorithm that fits them — is one of the most reused ideas in the subject.

By the end you can

  • State the k-means objective and prove Lloyd's algorithm never increases it
  • Explain why k-means finds local minima, and what k-means++ does about it
  • Write down a Gaussian mixture and see why direct likelihood maximisation is hard
  • Derive the EM updates from responsibilities, and state why the likelihood increases
  • Show that k-means is the zero-variance limit of a spherical GMM

Assumed

  • Phase A, Lecture 14: likelihood, Gaussians, the multivariate density
  • Phase A, Lecture 7: convexity, alternating optimisation, local minima
  • Phase B, Lecture 8: covariance matrices and their eigen-structure

1What clustering is, and what it is not

We have \(\vx_1,\dots,\vx_n\) and no labels. We want to partition them into \(K\) groups such that points within a group are similar. Before any algorithm, three honest warnings.

2k-means: the objective first

The k-means objective

Choose centroids \(\vm_1,\dots,\vm_K\) and an assignment \(c:\{1..n\}\to\{1..K\}\) to minimise the within-cluster sum of squares (also called inertia):

\[ J(\{\vm_k\},c) = \sum_{i=1}^{n}\big\|\vx_i-\vm_{c(i)}\big\|^2. \]

This is a joint optimisation over a discrete variable (the assignment) and a continuous one (the centroids). It is NP-hard in general. Lloyd's algorithm attacks it by alternating minimisation: fix one, optimise the other exactly, repeat.

Lloyd's algorithm, and why J never increases0 / 5
\[\textbf{E-step: } \quad c(i) \leftarrow \argmin_{k} \|\vx_i-\vm_k\|^2\]
Fix the centroids, optimise the assignment. The objective is a sum of independent terms, one per point, so we can minimise each separately: assign each point to its nearest centroid. This is exactly optimal given the centroids, not an approximation.
\[\textbf{M-step: } \quad \vm_k \leftarrow \frac{1}{|C_k|}\sum_{i\in C_k}\vx_i\]
Fix the assignment, optimise the centroids. Now \(J\) splits into \(K\) independent terms, and each is \(\sum_{i\in C_k}\|\vx_i-\vm_k\|^2\) — minimised by the mean of the cluster, by Phase A Lecture 13's result that the mean minimises expected squared deviation. Again exactly optimal, not approximate.
\[J(\vm^{(t)},c^{(t)}) \;\ge\; J(\vm^{(t)},c^{(t+1)}) \;\ge\; J(\vm^{(t+1)},c^{(t+1)})\]
Monotone decrease. Each step minimises \(J\) over one argument holding the other fixed, so neither can increase it. The first inequality is the E-step, the second the M-step. This is the general pattern of block coordinate descent.
\[\text{finitely many assignments} \;+\; \text{monotone decrease} \;\Longrightarrow\; \text{convergence in finite steps}\]
Termination. There are at most \(K^n\) possible assignments — an enormous number, but finite. Since \(J\) never increases and each distinct assignment determines its centroids uniquely, no assignment can recur; so the algorithm must stop. ∎
\[\text{but} \quad J_{\text{final}} \;\ne\; J_{\text{global min}} \quad\text{in general}\]
The catch. Monotone decrease guarantees a local minimum, and nothing more. \(J\) is not convex in the pair (it is not even a continuous function of the assignment), so different initialisations reach different answers. Phase A Lecture 7's distinction between local and global minima, in its most consequential appearance so far.
Lloyd's algorithm, step by step — and how much the initialisation mattersInteractive
cluster 1cluster 2cluster 3centroids
3
0
3
Step 0 shows the initial centroids. Advance the iteration slider and watch assignments and centroids alternate. The inertia in the readout never rises — that is the theorem. Now change the initialisation seed several times at a fixed step count and watch the final inertia change: that is the local-minimum problem. Switch on k-means++ and the spread of outcomes narrows sharply.
Common trap

k-means++. The fix for bad initialisation is not to try harder at random. Choose the first centroid uniformly, then choose each subsequent one with probability proportional to \(D(\vx)^2\), the squared distance to the nearest already-chosen centroid. This spreads the initial centroids out, and it comes with a guarantee: the expected inertia is within \(O(\log K)\) of the global optimum.

It costs almost nothing and is the default in every library. Run it several times anyway (n_init=10) and keep the lowest inertia — the guarantee is in expectation, not per run.

3What k-means assumes

The objective \(\sum_i\|\vx_i-\vm_{c(i)}\|^2\) encodes strong assumptions, and reading them off tells you exactly when the method will fail.

Gaussian mixtures relax the first four at once.

4Gaussian mixture models

The model

Each point is generated by first picking a component, then drawing from that component's Gaussian:

\[ p(\vx) = \sum_{k=1}^{K}\pi_k\ \mathcal{N}\big(\vx \mid \vm_k, \mS_k\big), \qquad \pi_k\ge0,\ \sum_k\pi_k=1. \]

The latent variable \(z_i\in\{1..K\}\) says which component generated \(\vx_i\). We never observe it; that is what makes this hard and what EM is for.

Compared with \(k\)-means: \(\mS_k\) lets clusters be elongated and differently oriented, \(\pi_k\) lets them have different sizes, and the posterior over \(z\) gives soft assignments.

Why not just maximise the log-likelihood directly?

\[\ell(\vt) = \sum_{i=1}^{n}\log\left(\sum_{k=1}^{K}\pi_k\,\mathcal{N}(\vx_i\mid\vm_k,\mS_k)\right)\]

The log is outside a sum, so it does not decompose. Compare Lecture 04, where the log turned a product of per-point terms into a clean sum and everything simplified. Here it cannot, the objective is non-concave, and setting the gradient to zero gives coupled equations with no closed-form solution.

EM for a Gaussian mixture0 / 5
\[\gamma_{ik} := P(z_i=k \mid \vx_i;\vt) = \frac{\pi_k\,\mathcal{N}(\vx_i\mid\vm_k,\mS_k)}{\sum_{j=1}^K \pi_j\,\mathcal{N}(\vx_i\mid\vm_j,\mS_j)}\]
E-step: compute the responsibilities. This is Bayes' theorem (Phase A Lecture 14): prior \(\pi_k\) times likelihood \(\mathcal{N}(\vx_i\mid\cdot)\), normalised. \(\gamma_{ik}\) is the posterior probability that point \(i\) came from component \(k\), and \(\sum_k\gamma_{ik}=1\) for every \(i\). These are the soft assignments.
\[N_k := \sum_{i=1}^{n}\gamma_{ik} \qquad\text{— the effective number of points in component } k\]
Soft counts. Not an integer: a point split 70/30 between two components contributes 0.7 to one count and 0.3 to the other. Note \(\sum_k N_k = n\).
\[\textbf{M-step: } \quad \vm_k \leftarrow \frac{1}{N_k}\sum_{i=1}^{n}\gamma_{ik}\,\vx_i\]
Weighted mean. Exactly the k-means update, except every point contributes to every component in proportion to its responsibility. This is the MLE for a Gaussian mean (Phase A Lecture 14) with weights.
\[\mS_k \leftarrow \frac{1}{N_k}\sum_{i=1}^{n}\gamma_{ik}\,(\vx_i-\vm_k)(\vx_i-\vm_k)^\top, \qquad \pi_k \leftarrow \frac{N_k}{n}\]
Weighted covariance and mixing weight. Again the weighted versions of the standard MLEs. The mixing weight is the fraction of total responsibility the component claims — the obvious estimate, and the one the algebra produces.
\[\ell(\vt^{(t+1)}) \;\ge\; \ell(\vt^{(t)}) \quad\text{at every iteration}\]
EM never decreases the likelihood. The argument: build a lower bound on \(\ell\) using Jensen's inequality applied to the concave \(\log\), with the responsibilities as the mixing weights. The E-step makes the bound tight at the current parameters; the M-step maximises the bound. So \(\ell(\vt^{t+1})\ge \text{bound}(\vt^{t+1}) \ge \text{bound}(\vt^{t}) = \ell(\vt^{t})\). This is the evidence lower bound or ELBO, and the same argument underlies variational inference and VAEs. I am marking the full derivation deferred — it needs Jensen's inequality and KL divergence, and it is worth a lecture of its own.
k-means versus a Gaussian mixture on the same dataInteractive
component 1component 2component 3means and 1σ / 2σ ellipses
1
0
25
Point colour is the assignment — hard for k-means, blended by responsibility for the mixture. On spherical clusters the two agree. On elongated ones k-means slices them the wrong way because its objective can only draw straight perpendicular bisectors, while the mixture fits the covariance and recovers the true groups. Switch between methods at shape 1 and 3 and the difference is stark.

5k-means is a limiting case of a Gaussian mixture

Shrink the covariance to zero and EM becomes Lloyd's algorithm0 / 4
\[\mS_k = \epsilon\mI \ \text{ for all } k, \qquad \pi_k = \tfrac1K\]
Restrict the mixture. Spherical components of equal, fixed size and equal weight. We will let \(\epsilon\to0\).
\[\gamma_{ik} = \frac{\exp\!\big(-\|\vx_i-\vm_k\|^2/2\epsilon\big)}{\sum_j \exp\!\big(-\|\vx_i-\vm_j\|^2/2\epsilon\big)}\]
The responsibilities become a softmax. The constant prefactors of the Gaussian density are identical across components and cancel, leaving softmax of the negative squared distances divided by \(2\epsilon\). Note \(1/2\epsilon\) is playing the role of an inverse temperature.
\[\epsilon\to0: \quad \gamma_{ik} \to \begin{cases}1 & k = \argmin_j\|\vx_i-\vm_j\|^2\\ 0 & \text{otherwise}\end{cases}\]
Softmax becomes argmax. Dividing by a vanishing \(\epsilon\) amplifies the differences between the distances without limit, so the largest term dominates completely. Soft assignment hardens into nearest-centroid assignment — the k-means E-step.
\[\vm_k = \frac{\sum_i \gamma_{ik}\vx_i}{\sum_i\gamma_{ik}} \;\longrightarrow\; \frac{1}{|C_k|}\sum_{i\in C_k}\vx_i\]
And the M-step becomes the cluster mean. With 0/1 responsibilities the weighted mean is the ordinary mean over the assigned points. ∎
k-means is EM for a spherical, equal-weight Gaussian mixture in the zero-variance limit. That is why it inherits both the monotone-improvement guarantee and the local-minimum problem — they are properties of alternating optimisation, not of either algorithm specifically.
Check yourself
Running EM on a Gaussian mixture, one component's covariance shrinks towards zero and the log-likelihood shoots off to \(+\infty\). What has happened?
Correct. Correct, and this is worth understanding properly because it is a real defect of maximum likelihood for mixtures, not an implementation error.

If \(\vm_k = \vx_i\) exactly for one point and \(\mS_k=\sigma^2\mI\), then that point's density contribution is \(\frac{1}{2\pi\sigma^2}\), which \(\to\infty\) as \(\sigma\to0\). The log-likelihood therefore has no finite maximum — it has singularities wherever a component sits on a data point. This is exactly the pathology from Phase A Lecture 7: the supremum is not attained.

Recall that a density is not a probability (Phase A Lecture 13): it can exceed 1, and there is nothing stopping it exceeding \(10^6\).

Standard fixes: add \(\epsilon\mI\) to every covariance (a floor — this is what reg_covar does in scikit-learn); tie the covariances across components; or put a prior on \(\mS_k\) and do MAP instead of MLE, which is the principled version. Detecting a collapse and reinitialising that component also works and is what many implementations do.
Not quite. EM is behaving exactly as designed: it is monotonically increasing the likelihood, and the likelihood happens to be unbounded. The monotone-increase theorem never promised a finite limit — it promised no decrease. When the objective has a singularity, faithfully climbing it takes you into the singularity.
Not quite. Collapse is more likely with too many components, not too few, because a surplus component has nothing better to do than latch onto an individual point. It also becomes more likely in higher dimensions, where a full covariance has \(d(d+1)/2\) parameters per component and there may be fewer points per component than parameters.
k-means and a Gaussian mixture, both from scratchPython · NumPy

6Exercises

★

You run \(k\)-means with \(K=2,3,\dots,10\) and record the inertia \(J\).

  1. What must be true about \(J\) as \(K\) increases, and why?
  2. So why can you not choose \(K\) by minimising \(J\)?
  3. What is \(J\) when \(K=n\)?
  4. Name two defensible ways to choose \(K\).

1. \(J\) is non-increasing in \(K\). Any clustering with \(K\) centroids can be reproduced with \(K+1\) by adding a redundant centroid at an existing location (which changes nothing) — so the \(K+1\) optimum is at least as good. In practice \(J\) strictly decreases, since an extra centroid can always split some cluster.

2. Because a criterion that always prefers larger \(K\) does not select anything. It is exactly the \(R^2\) problem from Lecture 02: a measure of fit that cannot get worse when you add capacity is useless for model selection. You need a penalty for complexity, or a held-out estimate.

3. \(J=0\). Each point is its own centroid, so every distance is zero. Perfect fit, zero information — the clustering analogue of the lookup table from Lecture 01.

4. Two defensible options.

  • A penalised criterion. Fit a Gaussian mixture instead and use BIC \(= -2\ell + p\log n\), which explicitly charges for parameters and therefore has an interior minimum. This is principled but leans on the mixture model being roughly right.
  • The downstream task. If the clusters feed something you can measure — a classifier, a recommendation, a segmentation someone will act on — tune \(K\) by that metric with proper validation. Same argument as choosing PCA's \(k\) in Lecture 08: an external criterion beats an internal one whenever you have access to it.

The elbow method and the silhouette score are the common alternatives. Both are reasonable heuristics; neither is a principle, and both reward the kind of structure the algorithm was built to find.

★★

Prove the k-means M-step is exactly optimal: given a fixed assignment, the centroid minimising \(\sum_{i\in C_k}\|\vx_i-\vm\|^2\) is the mean.

Then show that the objective can be written entirely in terms of pairwise distances,

\[ \sum_{i\in C_k}\|\vx_i-\vm_k\|^2 = \frac{1}{2|C_k|}\sum_{i,j\in C_k}\|\vx_i-\vx_j\|^2, \]

and say why that identity is useful.

The M-step. Let \(g(\vm)=\sum_{i\in C_k}\|\vx_i-\vm\|^2\). Using Phase A Lecture 12's rules,

\[\grad_{\vm} g = \sum_{i\in C_k} -2(\vx_i-\vm) = -2\sum_{i\in C_k}\vx_i + 2|C_k|\vm \;\stackrel{!}{=}\;\vzero \;\Longrightarrow\; \vm = \frac{1}{|C_k|}\sum_{i\in C_k}\vx_i.\]

The Hessian is \(2|C_k|\mI\succ0\), so \(g\) is strictly convex and this is the unique global minimum (Phase A Lecture 7). ∎ Note it is the same computation as "the mean minimises squared deviation" (Phase A Lecture 13), lifted to vectors.

The pairwise identity. Write \(m=|C_k|\) and \(\vm_k=\frac1m\sum_i\vx_i\). Expand the right-hand side:

\[\sum_{i,j}\|\vx_i-\vx_j\|^2 = \sum_{i,j}\big(\|\vx_i\|^2 - 2\vx_i^\top\vx_j + \|\vx_j\|^2\big) = 2m\sum_i\|\vx_i\|^2 - 2\Big\|\sum_i\vx_i\Big\|^2 = 2m\sum_i\|\vx_i\|^2 - 2m^2\|\vm_k\|^2.\]

And the left-hand side:

\[\sum_i\|\vx_i-\vm_k\|^2 = \sum_i\|\vx_i\|^2 - 2\vm_k^\top\sum_i\vx_i + m\|\vm_k\|^2 = \sum_i\|\vx_i\|^2 - m\|\vm_k\|^2.\]

Dividing the first display by \(2m\) gives exactly the second. ∎

Why it is useful — three reasons.

It removes the centroid. The objective depends only on distances between data points, never on any point in the ambient space. So the method extends immediately to settings where averaging is undefined — strings, graphs, permutations — as long as you can compute pairwise distances. That is kernel k-means, and it is why the kernel trick of Lecture 12 applies here too.

It gives an interpretation. Inertia is (up to the \(2|C_k|\) factor) the total pairwise dissimilarity within each cluster. "Minimise distance to the centre" and "minimise average distance between members" are the same instruction.

It explains the spherical bias. Because only Euclidean distances enter, the objective is invariant to rotation but not to anisotropic scaling. A cluster that is long and thin has large internal pairwise distances and is therefore penalised, which is precisely why k-means prefers to cut it in half. The bias is visible directly in the objective.

★★

For a one-dimensional two-component mixture with known equal variances \(\sigma^2\) and equal weights:

  1. Write the responsibility \(\gamma_{i1}\) explicitly and show it is a sigmoid.
  2. What happens to it as \(\sigma\to0\)? As \(\sigma\to\infty\)?
  3. Relate this to the temperature in a softmax, and to the k-means limit.

1. With \(\pi_1=\pi_2=\tfrac12\) the prefactors and weights cancel:

\[\gamma_{i1} = \frac{\exp\!\big(-\tfrac{(x_i-\mu_1)^2}{2\sigma^2}\big)} {\exp\!\big(-\tfrac{(x_i-\mu_1)^2}{2\sigma^2}\big)+\exp\!\big(-\tfrac{(x_i-\mu_2)^2}{2\sigma^2}\big)} = \frac{1}{1+\exp\!\big(\tfrac{(x_i-\mu_1)^2-(x_i-\mu_2)^2}{2\sigma^2}\big)}.\]

Expand the exponent: \((x-\mu_1)^2-(x-\mu_2)^2 = -2x(\mu_1-\mu_2)+\mu_1^2-\mu_2^2\), which is linear in \(x\). So

\[\gamma_{i1} = \sigma\!\left(\frac{(\mu_1-\mu_2)}{\sigma^2}\,x_i - \frac{\mu_1^2-\mu_2^2}{2\sigma^2}\right)\]

— a logistic sigmoid in \(x\), with slope \((\mu_1-\mu_2)/\sigma^2\) and its 0.5 crossing at the midpoint \(\frac{\mu_1+\mu_2}{2}\). ∎

This is the generative-versus-discriminative connection of Lecture 10, appearing early: a Gaussian mixture with shared variance induces exactly a logistic posterior, which is why Gaussian naive Bayes and logistic regression are so closely related.

2. The slope is \((\mu_1-\mu_2)/\sigma^2\).

\(\sigma\to0\): the slope diverges, the sigmoid becomes a step function at the midpoint, and every point is assigned with probability 1 to its nearer mean. Hard assignment — the k-means E-step.

\(\sigma\to\infty\): the slope goes to zero and \(\gamma_{i1}\to\tfrac12\) for every point. The components are so diffuse that the data carries no information about which generated what, and the posterior reverts to the prior. EM then makes no progress: both means converge to the global mean.

3. Write \(\gamma_{ik}=\operatorname{softmax}_k(-d_{ik}^2/T)\) with \(T=2\sigma^2\). Then \(\sigma^2\) is a temperature in the statistical-mechanics sense. High temperature gives a soft, near-uniform distribution; low temperature concentrates all mass on the minimum. \(T\to0\) turns softmax into argmax — the same limit that appears in Gumbel-softmax, in simulated annealing, and in the temperature parameter of a language model's sampler.

So the k-means/GMM relationship is one instance of a general pattern: a hard combinatorial algorithm is often the zero-temperature limit of a soft probabilistic one, and the soft version is differentiable while the hard one is not. That observation is the engine behind a great deal of modern method design.

★★★

Coding. Investigate what clustering does when there are no clusters, and how to tell.

  1. Generate 500 points uniformly at random in the unit square. Run \(k\)-means for \(K=1..10\) and plot inertia. Is there an elbow?
  2. Compute the gap statistic: compare \(\log J_K\) on your data against the mean \(\log J_K\) on uniform reference data of the same extent. What does it say?
  3. Repeat both on data that genuinely has three clusters.
  4. Also compute the silhouette score for both datasets. Which diagnostic is most trustworthy, and what is the honest conclusion about detecting the absence of structure?
import numpy as np
rng = np.random.default_rng(0)

def kmeans(X, K, seed=0, T=100):
    r = np.random.default_rng(seed)
    M = X[r.choice(len(X), K, replace=False)].copy()
    for _ in range(T):
        D = ((X[:,None,:]-M[None,:,:])**2).sum(-1); a = D.argmin(1)
        newM = np.array([X[a==k].mean(0) if (a==k).any() else M[k] for k in range(K)])
        if np.allclose(newM, M): break
        M = newM
    D = ((X[:,None,:]-M[None,:,:])**2).sum(-1); a = D.argmin(1)
    return float(D[np.arange(len(X)), a].sum()), a

def silhouette(X, a):
    D = np.sqrt(((X[:,None,:]-X[None,:,:])**2).sum(-1)); s = []
    for i in range(len(X)):
        own = a == a[i]; own[i] = False
        if own.sum() == 0: s.append(0.0); continue
        ai = D[i, own].mean()
        bi = min(D[i, a==k].mean() for k in set(a) - {a[i]})
        s.append((bi-ai)/max(ai, bi))
    return float(np.mean(s))

def gap(X, Kmax=10, B=20):
    lo, hi = X.min(0), X.max(0); out = []
    for K in range(1, Kmax+1):
        Jd = min(kmeans(X, K, seed=s)[0] for s in range(5))
        ref = [np.log(min(kmeans(rng.uniform(lo, hi, X.shape), K, seed=s)[0]
                          for s in range(3))) for _ in range(B)]
        out.append((K, np.log(Jd), np.mean(ref), np.mean(ref)-np.log(Jd), np.std(ref)))
    return out

def clustered(n=500):
    c = np.array([[0,0],[4,0],[2,3.5]])
    return np.vstack([c[k] + 0.5*rng.normal(size=(n//3+1,2)) for k in range(3)])[:n]

for name, X in (("uniform noise", rng.uniform(0,1,(500,2))), ("three clusters", clustered())):
    print(f"\\n=== {name} ===")
    print(f"{'K':>3} {'inertia':>10} {'gap':>9} {'sd_ref':>8} {'silhouette':>11}")
    for K, lJ, lref, g, sd in gap(X):
        sil = silhouette(X, kmeans(X, K, seed=0)[1]) if K > 1 else float('nan')
        print(f"{K:3d} {np.exp(lJ):10.3f} {g:9.4f} {sd:8.4f} {sil:11.4f}")

1. On uniform noise, inertia falls smoothly and steadily — roughly like \(K^{-1}\) in two dimensions, since \(K\) cells each shrink in area. There is no elbow, but the curve looks perfectly plausible, and a determined eye will find a bend somewhere around \(K=3\) or \(4\). This is the central weakness of the elbow method: it produces an answer whether or not there is one.

2. The gap statistic compares \(\log J_K\) against what you would get on structureless data of the same extent. On uniform noise the gap stays near zero at every \(K\) (well within one reference standard deviation) — correctly reporting no structure, and the only diagnostic here that does. Tibshirani's rule is to choose the smallest \(K\) with \(\text{gap}(K)\ge\text{gap}(K+1)-s_{K+1}\); on noise no \(K\) qualifies beyond 1.

3. On the three-cluster data the inertia curve has a sharp, unmistakable elbow at \(K=3\); the gap statistic peaks at \(K=3\) and is several reference standard deviations above zero; and the silhouette score peaks at \(K=3\) at around 0.7–0.8. All three agree, which is what agreement is for.

4. Which to trust, and the honest conclusion. The silhouette on uniform noise typically comes out around 0.3–0.4 — a value many people would read as "moderate structure". It is not zero, because silhouette measures whether the found partition is internally coherent, and \(k\)-means will always produce a reasonably coherent partition of anything. Silhouette can validate a clustering; it cannot tell you whether clusters exist.

The gap statistic is the only one of the three with a built-in null hypothesis, and that is exactly why it can detect the absence of structure. The general principle is worth taking away: a diagnostic without a reference distribution cannot distinguish signal from its absence — which is the same lesson as "compare against the majority-class baseline" in Lecture 07, and "compare against random subspaces" in Lecture 08.

Practical advice: always run your clustering pipeline on shuffled or resampled data as a null, and report what it produces alongside your real result.


Next: Part V. We return to classification, but from the other direction — modelling \(p(\vx\mid y)\) rather than \(p(y\mid\vx)\). Lecture 10 covers naive Bayes and the generative-versus-discriminative distinction, which turns out to be one of the sharpest ideas in the subject.