PROMETHEUS OS
NATIVE AI OPERATING SYSTEM
[INIT] Prometheus OS loading...
[CORE] Neural interface initializing...
[AI] LLM router connecting...
[MEM] Memory graph loading...
[AGT] Agent chains on standby...
[SEC] Security protocols active...
[NET] Provider mesh established...
[UI] Workspace rendering...
[DONE] All systems nominal.
Welcome to PROMETHEUS OS.
READY · OFFLINE · SECURE · PRIVATE
🎨 THEME ENGINE
PROMETHEUS OS · 23 THEMES
Trial version: Dark + Light themes only.
Full theme access with PROMETHEUS OS license.
Dark theme active
Experience all 23 themes in PROMETHEUS OS

Unsupervised Learning: Clustering and Dimensionality Reduction

Intermediate 19 min· 4 exercises

Cluster unlabeled data with k-means, hierarchical clustering, DBSCAN and Gaussian mixtures, reduce dimensionality with PCA, and use t-SNE and UMAP for visualization without over-reading the pictures.

By the end of this lesson you will be able to
  • Execute the k-means algorithm by hand and explain why it converges to a local minimum of the within-cluster sum of squares
  • Choose the number of clusters using the elbow method, silhouette scores and domain constraints
  • Compare k-means, hierarchical clustering, DBSCAN and Gaussian mixture models by the cluster shapes and data conditions each handles
  • Derive principal component analysis as variance maximization and connect it to the eigendecomposition of the covariance matrix and the SVD
  • Interpret explained-variance ratios to choose how many principal components to keep
  • Explain what t-SNE and UMAP preserve and identify the common misreadings of their plots

Before this: The ML Workflow: Framing, Data, Splits and Leakage, Linear Algebra for AI, Probability and Statistics for AI

Most of the world's data has no labels. Customer logs, sensor streams, documents and images arrive without anyone saying which group they belong to or which features matter. Unsupervised learning finds structure in that data on its own: clustering groups similar points, and dimensionality reduction compresses many features into a few that keep the important variation. Practitioners use these tools for customer segmentation, anomaly screening, compressing features before a supervised model, deduplicating data, and, very often, simply looking at a high-dimensional dataset before deciding what to do with it.

Intuition: sorting a pile without instructions

Imagine someone dumps a thousand photos on a table and asks you to "organize them." Nobody tells you the categories. You might group them by who is in them, by location, or by colour. Each choice is a different notion of similarity, and each produces a different, equally valid grouping. That is the central fact of unsupervised learning: the algorithm finds the structure that its similarity measure and assumptions can see. There is no ground truth to score against, so judgment about the distance metric, the scaling of features, and the shape of clusters you expect matters more than in supervised learning.

Dimensionality reduction is the same pile viewed differently. If every photo is described by a million pixel values, but most of the variation comes from a few factors (lighting, pose, who is in the frame), you can describe each photo with a handful of numbers and lose little. PCA finds the directions of greatest variation; t-SNE and UMAP try to keep neighbours close in a 2D picture.

k-means clustering

The objective

Given nn points x1,…,xn∈Rdx_1, \dots, x_n \in \mathbb{R}^d and a number of clusters kk, k-means looks for cluster assignments ci∈{1,…,k}c_i \in \{1,\dots,k\} and centroids μ1,…,μk∈Rd\mu_1,\dots,\mu_k \in \mathbb{R}^d that minimize the within-cluster sum of squares (WCSS, called inertia in scikit-learn):

J=∑i=1n∥xi−μci∥2.J = \sum_{i=1}^{n} \lVert x_i - \mu_{c_i} \rVert^2 .

Finding the global minimum is NP-hard in general, so we use Lloyd's algorithm, which alternates two steps:

  1. Assignment step: give each point to its nearest centroid, ci=arg⁡min⁡j∥xi−μj∥2c_i = \arg\min_j \lVert x_i - \mu_j \rVert^2.
  2. Update step: move each centroid to the mean of its assigned points, μj=1∣Sj∣∑i∈Sjxi\mu_j = \frac{1}{|S_j|}\sum_{i \in S_j} x_i, where SjS_j is the set of points assigned to cluster jj.

Each step can only lower JJ or leave it unchanged: the assignment step picks the best centroid for each point given fixed centroids, and the mean is the point that minimizes squared distance to a set. Since there are finitely many assignments, the algorithm stops after a finite number of iterations, but only at a local minimum. That is why implementations run several random restarts (n_init) and keep the best, and why k-means++ initialization (pick each new initial centroid with probability proportional to its squared distance from the nearest centroid already chosen) is the standard default.

Worked iteration

Six points in 2D: A=(1,1)A=(1,1), B=(2,1)B=(2,1), C=(1,2)C=(1,2), D=(5,4)D=(5,4), E=(6,5)E=(6,5), F=(5,6)F=(5,6). Use k=2k=2 with a deliberately poor start: μ1=(1,1)\mu_1=(1,1), μ2=(2,1)\mu_2=(2,1).

Iteration 1, assignment. Squared distances to (μ1,μ2)(\mu_1, \mu_2):

Pointto μ1=(1,1)\mu_1=(1,1)to μ2=(2,1)\mu_2=(2,1)Cluster
A (1,1)011
B (2,1)102
C (1,2)121
D (5,4)25182
E (6,5)41322
F (5,6)41342

J=0+0+1+18+32+34=85J = 0 + 0 + 1 + 18 + 32 + 34 = 85.

Iteration 1, update. μ1=mean(A,C)=(1,1.5)\mu_1 = \text{mean}(A, C) = (1, 1.5). μ2=mean(B,D,E,F)=(2+5+6+54,1+4+5+64)=(4.5,4)\mu_2 = \text{mean}(B, D, E, F) = \left(\tfrac{2+5+6+5}{4}, \tfrac{1+4+5+6}{4}\right) = (4.5, 4).

Iteration 2, assignment. For B: ∥B−μ1∥2=(2−1)2+(1−1.5)2=1.25\lVert B-\mu_1\rVert^2 = (2-1)^2 + (1-1.5)^2 = 1.25, ∥B−μ2∥2=2.52+32=15.25\lVert B-\mu_2\rVert^2 = 2.5^2 + 3^2 = 15.25, so B moves to cluster 1. A, C stay in cluster 1; D, E, F stay in cluster 2. With these assignments, evaluated against the current centroids, J=0.25+1.25+0.25+0.25+3.25+4.25=9.5J = 0.25+1.25+0.25+0.25+3.25+4.25 = 9.5.

Iteration 2, update. μ1=mean(A,B,C)=(4/3,4/3)≈(1.33,1.33)\mu_1 = \text{mean}(A,B,C) = (4/3, 4/3) \approx (1.33, 1.33), μ2=mean(D,E,F)=(16/3,5)≈(5.33,5)\mu_2 = \text{mean}(D,E,F) = (16/3, 5) \approx (5.33, 5).

Iteration 3. No point changes cluster, so the algorithm has converged. Final J=4.0J = 4.0. The objective fell 85→9.5→4.085 \to 9.5 \to 4.0, exactly as the monotonicity argument promises.

Choosing k

There is no universally correct kk; use several signals together.

  • Elbow method. Plot JJ against kk. The best achievable JJ never increases as kk grows (with k=nk=n distinct points it is zero), so look for the "elbow" where extra clusters stop buying much reduction. Elbows are often ambiguous.
  • Silhouette score. For point ii, let a(i)a(i) be its mean distance to other points in its own cluster and b(i)b(i) the mean distance to points in the nearest other cluster. Then s(i)=b(i)−a(i)max⁡{a(i),b(i)}∈[−1,1].s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}} \in [-1, 1]. Values near 1 mean well-separated, near 0 mean on a boundary, negative means probably misassigned. The mean silhouette over all points is a standard model-selection score. In the example above, for A: a(A)=mean(1,1)=1a(A) = \text{mean}(1, 1) = 1, b(A)=mean(25,41,41)≈5.94b(A) = \text{mean}(\sqrt{25}, \sqrt{41}, \sqrt{41}) \approx 5.94, so s(A)≈(5.94−1)/5.94≈0.83s(A) \approx (5.94-1)/5.94 \approx 0.83.
  • Gap statistic, BIC (for mixtures), stability across resamples, and most importantly the use case: a marketing team that can run four campaigns needs four segments, whatever the elbow says.
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

X, _ = make_blobs(n_samples=600, centers=4, cluster_std=0.8, random_state=0)
X = StandardScaler().fit_transform(X)        # k-means is distance-based: scale first

for k in range(2, 8):
    km = KMeans(n_clusters=k, n_init=10, random_state=0).fit(X)
    print(k, round(km.inertia_, 1), round(silhouette_score(X, km.labels_), 3))

The loop prints inertia and mean silhouette for each kk; on this synthetic data the silhouette peaks at the true value of 4. Note StandardScaler: k-means uses Euclidean distance, so a feature measured in dollars will swamp one measured in years unless you scale.

What k-means assumes

k-means implicitly assumes clusters are roughly spherical, similar in size and similar in spread, because it assigns by distance to a single centre. It fails on elongated clusters, nested rings, and clusters of very different densities, and it must assign every point, including outliers, which drag centroids around.

Hierarchical clustering

Agglomerative clustering starts with every point as its own cluster and repeatedly merges the two closest clusters until one remains. The full merge history is a tree called a dendrogram; cutting it at a height gives a flat clustering, so you can inspect many values of kk from one run. The key choice is the linkage, the definition of distance between clusters:

LinkageDistance between clustersBehaviour
Singleclosest pair of pointsfinds chained, irregular shapes; prone to "chaining" through noise
Completefarthest pair of pointscompact clusters of similar diameter
Averagemean of all pairwise distancesa compromise
Wardincrease in total within-cluster variance from mergingsimilar to k-means; the usual default

Agglomerative clustering needs pairwise distances, so naive implementations cost O(n2)O(n^2) memory and at least O(n2)O(n^2) time; it is practical for thousands to tens of thousands of points, not millions. Its advantages are the dendrogram, the freedom to use any distance (for example edit distance between strings), and determinism.

DBSCAN: density-based clustering

DBSCAN (Ester, Kriegel, Sander and Xu, 1996) defines clusters as dense regions separated by sparse ones. It has two parameters: a radius ε\varepsilon and a count min_samples. A core point has at least min_samples points within distance ε\varepsilon (counting itself in scikit-learn). Clusters are formed by connecting core points that lie within ε\varepsilon of one another, plus the non-core border points within reach of them. Everything else is labelled noise (label -1 in scikit-learn).

Strengths: no need to choose kk, arbitrary cluster shapes (rings, crescents), and explicit outlier detection. Weaknesses: one global ε\varepsilon cannot fit clusters of very different densities, and choosing ε\varepsilon is fiddly. A common heuristic is the k-distance plot: sort every point's distance to its min_samples-th nearest neighbour and pick ε\varepsilon near the knee. HDBSCAN, available as sklearn.cluster.HDBSCAN in recent scikit-learn versions, removes the single-ε\varepsilon limitation by building a hierarchy over density levels and keeping the most stable clusters; it is usually the better first try for density-based clustering.

Gaussian mixture models

A Gaussian mixture model (GMM) is a probabilistic model that says each point was generated by first picking a component jj with probability πj\pi_j, then drawing from a Gaussian N(μj,Σj)\mathcal{N}(\mu_j, \Sigma_j):

p(x)=∑j=1kπj N(x∣μj,Σj),∑jπj=1.p(x) = \sum_{j=1}^{k} \pi_j \, \mathcal{N}(x \mid \mu_j, \Sigma_j), \qquad \sum_j \pi_j = 1 .

It is fitted with the expectation-maximization (EM) algorithm. The E-step computes each point's responsibility rij=πjN(xi∣μj,Σj)/p(xi)r_{ij} = \pi_j \mathcal{N}(x_i\mid\mu_j,\Sigma_j) / p(x_i), a soft assignment. The M-step re-estimates πj\pi_j, μj\mu_j, Σj\Sigma_j as responsibility-weighted averages. Like k-means it converges to a local optimum of its objective (here the log-likelihood). In fact k-means is the limiting case of a GMM with equal, spherical covariances shrinking to zero, so soft assignments become hard.

GMMs add three things k-means lacks: soft membership (a customer can be 70% one segment), elliptical clusters of different sizes and orientations via full covariance matrices, and a likelihood, which allows model selection with the Bayesian information criterion (BIC) and lets you flag low-density points as anomalies. The price is more parameters (a full covariance has d(d+1)/2d(d+1)/2 entries per component), so in high dimensions use covariance_type="diag" or reduce dimensionality first.

Dimensionality reduction with PCA

Derivation

Centre the data so each feature has mean zero, and stack it in a matrix X∈Rn×dX \in \mathbb{R}^{n \times d}. The sample covariance matrix is S=1nX⊤XS = \frac{1}{n} X^\top X (many texts use 1n−1\frac{1}{n-1}; the directions found are identical). We want the unit vector ww such that projecting the data onto it, zi=w⊤xiz_i = w^\top x_i, has maximum variance:

max⁡w  Var⁡(z)=w⊤Swsubject tow⊤w=1.\max_{w} \; \operatorname{Var}(z) = w^\top S w \quad \text{subject to} \quad w^\top w = 1 .

Add a Lagrange multiplier λ\lambda: L(w,λ)=w⊤Sw−λ(w⊤w−1)\mathcal{L}(w,\lambda) = w^\top S w - \lambda (w^\top w - 1). Setting the gradient with respect to ww to zero gives 2Sw−2λw=02Sw - 2\lambda w = 0, that is

Sw=λw.S w = \lambda w .

So ww must be an eigenvector of SS, and the variance it captures is w⊤Sw=λw⊤w=λw^\top S w = \lambda w^\top w = \lambda. The best direction is the eigenvector with the largest eigenvalue; this is the first principal component. Repeating the argument with the extra constraint that each new direction is orthogonal to the earlier ones yields the remaining eigenvectors in decreasing order of eigenvalue. Because SS is symmetric and positive semidefinite, its eigenvectors can be chosen orthogonal and its eigenvalues are real and non-negative.

The SVD route. Write the centred data as X=UΣV⊤X = U \Sigma V^\top (singular value decomposition). Then X⊤X=VΣ2V⊤X^\top X = V \Sigma^2 V^\top, so the columns of VV are the principal directions and the eigenvalues are λj=σj2/n\lambda_j = \sigma_j^2 / n. Libraries compute PCA through the SVD of XX rather than forming X⊤XX^\top X, because it is more numerically stable; for very large data they use randomized SVD.

Explained variance

The fraction of total variance captured by component jj is

EVRj=λj∑l=1dλl.\text{EVR}_j = \frac{\lambda_j}{\sum_{l=1}^{d} \lambda_l}.

A common rule is to keep the smallest number of components whose cumulative ratio passes a target such as 90% or 95%, or to look for an elbow in the scree plot of λj\lambda_j.

Worked example

Four centred points: (2,2),(−2,−2),(1,−1),(−1,1)(2,2), (-2,-2), (1,-1), (-1,1). Using 1n\frac{1}{n}:

  • Var⁡(x1)=(4+4+1+1)/4=2.5\operatorname{Var}(x_1) = (4+4+1+1)/4 = 2.5, Var⁡(x2)=2.5\operatorname{Var}(x_2) = 2.5,
  • Cov⁡(x1,x2)=(4+4−1−1)/4=1.5\operatorname{Cov}(x_1,x_2) = (4 + 4 - 1 - 1)/4 = 1.5.

So S=(2.51.51.52.5)S = \begin{pmatrix} 2.5 & 1.5 \\ 1.5 & 2.5 \end{pmatrix}. The characteristic equation (2.5−λ)2−1.52=0(2.5-\lambda)^2 - 1.5^2 = 0 gives λ=2.5±1.5\lambda = 2.5 \pm 1.5, so λ1=4\lambda_1 = 4, λ2=1\lambda_2 = 1. The eigenvectors are w1=12(1,1)w_1 = \frac{1}{\sqrt 2}(1,1) and w2=12(1,−1)w_2 = \frac{1}{\sqrt 2}(1,-1). The first component explains 4/(4+1)=80%4/(4+1) = 80\% of the variance. Projecting (2,2)(2,2) onto w1w_1 gives z=(2+2)/2≈2.83z = (2+2)/\sqrt 2 \approx 2.83; projecting (1,−1)(1,-1) gives 00, because that point lies entirely along w2w_2.

import numpy as np
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA

X, y = load_digits(return_X_y=True)          # 1,797 images, 64 pixel features
pca = PCA().fit(X)
cum = np.cumsum(pca.explained_variance_ratio_)
k95 = int(np.argmax(cum >= 0.95)) + 1
print(f"components for 95% variance: {k95} of {X.shape[1]}")

Z = PCA(n_components=k95).fit_transform(X)   # compressed features
print(Z.shape)

On the digits dataset roughly 30 of 64 components keep 95% of the variance (the exact count prints when you run it). Two cautions: PCA is scale-sensitive, so standardize features measured in different units, and PCA is linear, so it cannot unroll curved structure.

t-SNE and UMAP: visualization, not measurement

t-SNE (van der Maaten and Hinton, 2008) converts distances in the original space into neighbour probabilities, using a Gaussian around each point whose width is set by the perplexity (roughly the effective number of neighbours, commonly 5 to 50). It then places points in 2D so that a heavy-tailed Student-t distribution over 2D distances matches those probabilities, minimizing the KL divergence between the two. The heavy tail lets moderately distant points spread apart, which produces the characteristic well-separated islands.

UMAP (McInnes, Healy and Melville, 2018) builds a weighted k-nearest-neighbour graph, interprets it as a fuzzy topological structure, and optimizes a low-dimensional layout with a cross-entropy objective. Its main knobs are n_neighbors (local versus global emphasis) and min_dist (how tightly points pack). It is usually faster than exact t-SNE, scales to millions of points, can transform new data, and tends to keep somewhat more global arrangement, although neither method guarantees global geometry.

Both are excellent for looking: spotting that labelled classes separate, finding mislabelled examples, seeing whether embeddings from a model group by topic. Both are dangerous for measuring:

  • Cluster sizes mean little. t-SNE expands dense clusters and contracts sparse ones.
  • Distances between clusters mean little. Two islands far apart on the plot may not be far apart in the data.
  • Hyperparameters change the picture. Different perplexities or n_neighbors values can produce visibly different layouts from the same data, and random noise can form apparent clusters at low perplexity.
  • Runs differ. Different seeds give different layouts. Always try several.
  • Do not cluster on the 2D output and present the clusters as findings; cluster in the original or PCA space and use the plot to illustrate.

A standard recipe is PCA to about 50 dimensions first (removes noise and speeds things up), then t-SNE or UMAP to 2D.

from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

X, y = load_digits(return_X_y=True)
X50 = PCA(n_components=50, random_state=0).fit_transform(X)
for perp in (5, 30, 50):                       # compare several perplexities
    emb = TSNE(n_components=2, perplexity=perp, init="pca",
               random_state=0).fit_transform(X50)
    print(perp, emb.shape)                     # plot emb coloured by y

UMAP lives in the separate umap-learn package (import umap; umap.UMAP(n_neighbors=15, min_dist=0.1).fit_transform(X50)).

Evaluating clusters

Without labels, use internal metrics: silhouette, Davies-Bouldin (lower is better), Calinski-Harabasz (higher is better). They reward compact, separated clusters and therefore favour k-means-like shapes, so they can mislead for DBSCAN on non-convex data. When some labels exist, external metrics such as the adjusted Rand index and normalized mutual information compare a clustering with them. The most important check is qualitative: inspect samples from each cluster and ask whether a domain expert would find the grouping useful, and test stability by re-running on bootstrap samples.

Common pitfalls

  • Not scaling features before k-means, hierarchical clustering, DBSCAN or PCA, so the feature with the biggest units dominates.
  • Trusting one k-means run. Use multiple initializations (n_init) and a fixed random_state for reproducibility.
  • Forcing k-means onto non-spherical data. Rings and crescents need DBSCAN, HDBSCAN, spectral clustering or a GMM.
  • Treating the elbow as objective. It is a heuristic; combine it with silhouette, stability and business constraints.
  • Reading t-SNE or UMAP plots as maps. Cluster sizes and inter-cluster gaps are not reliable quantities.
  • Fitting PCA on all data before a train/test split. PCA is a learned transformation; fit it on training data only, ideally inside a pipeline.
  • High-dimensional distances. In hundreds of dimensions, distances concentrate and nearest neighbours become less meaningful. Reduce dimensions or use a representation (such as learned embeddings with cosine similarity) built for the task.

In practice

  • Start by looking. PCA to 2 components and a UMAP or t-SNE plot are cheap first diagnostics on any new dataset or set of embeddings.
  • Segmentation: standardize, try k-means for a few kk values with silhouette scores, then check with a GMM if soft membership or elliptical segments make sense. Profile each segment with feature means so stakeholders can name it.
  • Anomaly screening: DBSCAN/HDBSCAN noise labels and low GMM likelihoods are useful first-pass flags, covered further in the anomaly detection lesson.
  • Embeddings: modern pipelines cluster vectors from neural encoders (text or image embeddings). Normalize them and use cosine distance (for k-means, L2-normalizing first makes Euclidean distance equivalent in ranking to cosine).
  • Compression: PCA before a supervised model can remove noise and collinearity, but tree ensembles rarely benefit; test it with cross-validation rather than assuming.
  • Scale: for millions of points use MiniBatchKMeans, randomized PCA, and approximate nearest-neighbour libraries.

Key takeaways

  • k-means alternates nearest-centroid assignment and mean updates, monotonically reducing within-cluster sum of squares to a local minimum, so use k-means++ and several restarts.
  • Choose the number of clusters with several signals (elbow, silhouette, BIC, stability) plus the business constraint, never a single heuristic.
  • Hierarchical clustering gives a dendrogram for any k, DBSCAN/HDBSCAN finds arbitrary shapes and labels noise, and Gaussian mixtures give soft, elliptical, likelihood-based clusters.
  • PCA finds orthogonal directions of maximum variance: the eigenvectors of the covariance matrix, computed in practice through the SVD, with eigenvalues giving explained variance.
  • t-SNE and UMAP are visualization tools whose cluster sizes and inter-cluster distances are not trustworthy measurements.
  • Distance-based methods require feature scaling, and learned transforms like PCA must be fitted on training data only.

Terms in this lesson

Inertia (WCSS)
The sum of squared distances from each point to its assigned cluster centroid, the objective k-means minimizes.
k-means++
An initialization that picks each new starting centroid with probability proportional to its squared distance from the nearest centroid already chosen.
Silhouette coefficient
For a point, (b - a) / max(a, b), where a is its mean distance to its own cluster and b its mean distance to the nearest other cluster.
Dendrogram
A tree recording the sequence and heights of merges in hierarchical clustering, which can be cut at any level to produce a flat clustering.
Core point
In DBSCAN, a point with at least min_samples points within radius epsilon.
Responsibility
In a Gaussian mixture, the posterior probability that a given point was generated by a given component.
Principal component
A unit direction of maximal remaining variance, equal to an eigenvector of the data covariance matrix.
Explained variance ratio
A component's eigenvalue divided by the sum of all eigenvalues, the share of total variance it captures.
Perplexity
The t-SNE parameter that sets each point's Gaussian neighbourhood width, roughly the effective number of neighbours considered.

Check your understanding

  1. 1.

    Why does Lloyd's k-means algorithm always terminate?

  2. 2.

    Your data contains two concentric rings plus scattered noise points. Which method is the best first choice?

  3. 3.

    A covariance matrix has eigenvalues 6, 3, 1. What fraction of variance do the first two principal components explain?

  4. 4.

    On a t-SNE plot, cluster A appears three times larger than cluster B and far from cluster C. What can you safely conclude?

  5. 5.

    For a point, the mean distance to its own cluster is 2 and to the nearest other cluster is 5. What is its silhouette value?

Exercises

Work each problem before opening the solution.

  1. Exercise 1Core

    Run k-means by hand on the 1D points {1,2,3,10,11,20}\{1, 2, 3, 10, 11, 20\} with k=2k=2 and initial centroids μ1=1\mu_1 = 1, μ2=3\mu_2 = 3. Show each assignment and update step until convergence and report the final inertia.

    Show solution

    Iteration 1. Assign: 1 to μ1\mu_1 (distance 0 vs 2); 2 is equidistant (1 vs 1) so break ties toward the lower index, cluster 1; 3, 10, 11, 20 go to μ2\mu_2. Update: μ1=(1+2)/2=1.5\mu_1 = (1+2)/2 = 1.5, μ2=(3+10+11+20)/4=11\mu_2 = (3+10+11+20)/4 = 11.

    Iteration 2. Assign: 1, 2 to μ1\mu_1; 3 is 1.5 from μ1\mu_1 and 8 from μ2\mu_2, so cluster 1; 10, 11, 20 to μ2\mu_2. Update: μ1=(1+2+3)/3=2\mu_1 = (1+2+3)/3 = 2, μ2=(10+11+20)/3=41/3≈13.67\mu_2 = (10+11+20)/3 = 41/3 \approx 13.67.

    Iteration 3. Assign: 1, 2, 3 to μ1\mu_1; 10 is 8 from μ1\mu_1 and 3.67 from μ2\mu_2, cluster 2; 11 and 20 cluster 2. No change, so converged.

    Inertia: cluster 1: (1−2)2+0+(3−2)2=2(1-2)^2 + 0 + (3-2)^2 = 2. Cluster 2: (10−13.67)2+(11−13.67)2+(20−13.67)2=13.44+7.11+40.11=60.67(10-13.67)^2 + (11-13.67)^2 + (20-13.67)^2 = 13.44 + 7.11 + 40.11 = 60.67 (exactly 182/3182/3). Total ≈62.67\approx 62.67.

    Note that 20 is an outlier that pulls μ2\mu_2 up; a 3-cluster solution {1,2,3}, {10,11}, {20} would have inertia 2.5, which illustrates why inertia always falls as k grows.

  2. Exercise 2Core

    Compute the principal components and explained-variance ratios of the centred 2D data {(3,0),(−3,0),(0,1),(0,−1)}\{(3,0), (-3,0), (0,1), (0,-1)\} using the 1/n1/n covariance. Then project the point (3,0)(3,0) onto the first component.

    Show solution

    Var⁡(x1)=(9+9+0+0)/4=4.5\operatorname{Var}(x_1) = (9+9+0+0)/4 = 4.5. Var⁡(x2)=(0+0+1+1)/4=0.5\operatorname{Var}(x_2) = (0+0+1+1)/4 = 0.5. Cov⁡=(0+0+0+0)/4=0\operatorname{Cov} = (0+0+0+0)/4 = 0.

    S=(4.5000.5)S = \begin{pmatrix} 4.5 & 0 \\ 0 & 0.5 \end{pmatrix} is already diagonal, so the eigenvectors are the axes: w1=(1,0)w_1 = (1,0) with λ1=4.5\lambda_1 = 4.5 and w2=(0,1)w_2 = (0,1) with λ2=0.5\lambda_2 = 0.5.

    Explained variance ratios: 4.5/5=90%4.5/5 = 90\% and 0.5/5=10%0.5/5 = 10\%.

    Projection of (3,0)(3,0) onto w1w_1: z=3z = 3. When features are already uncorrelated, PCA simply sorts them by variance, which is why scaling matters: if you standardized both features, each would have variance 1 and PCA would have no preferred direction.

  3. Exercise 3Core

    Write scikit-learn code that compares k-means (k=2) and DBSCAN on make_moons(n_samples=500, noise=0.06, random_state=0) using the adjusted Rand index against the true labels. Explain the result you expect.

    Show solution
    from sklearn.datasets import make_moons
    from sklearn.preprocessing import StandardScaler
    from sklearn.cluster import KMeans, DBSCAN
    from sklearn.metrics import adjusted_rand_score
    
    X, y = make_moons(n_samples=500, noise=0.06, random_state=0)
    X = StandardScaler().fit_transform(X)
    
    km = KMeans(n_clusters=2, n_init=10, random_state=0).fit_predict(X)
    db = DBSCAN(eps=0.3, min_samples=5).fit_predict(X)
    
    print("k-means ARI:", round(adjusted_rand_score(y, km), 3))
    print("DBSCAN  ARI:", round(adjusted_rand_score(y, db), 3))
    print("DBSCAN clusters:", sorted(set(db)))
    

    Expected: k-means splits the moons with a roughly straight boundary, cutting each crescent, so its ARI is well below 1 (typically around 0.2 to 0.5). DBSCAN follows each dense crescent and should recover them almost perfectly (ARI near 1), possibly labelling a few points as noise (-1). If DBSCAN returns one cluster, eps is too large; if it returns many small clusters or lots of noise, eps is too small; a k-distance plot helps choose it.

  4. Exercise 4Challenge

    Show that for a fixed set of points x1,…,xmx_1,\dots,x_m, the point μ\mu minimizing ∑i∥xi−μ∥2\sum_i \lVert x_i - \mu \rVert^2 is the mean, and use this to prove the k-means update step never increases the objective. Then explain why a Gaussian mixture with shared covariance σ2I\sigma^2 I reduces to k-means as σ→0\sigma \to 0.

    Show solution

    Minimizer. f(μ)=∑i∥xi−μ∥2f(\mu) = \sum_i \lVert x_i - \mu\rVert^2. Gradient: ∇f=−2∑i(xi−μ)=−2(∑ixi−mμ)\nabla f = -2\sum_i (x_i - \mu) = -2\left(\sum_i x_i - m\mu\right). Setting it to zero gives μ=1m∑ixi\mu = \frac{1}{m}\sum_i x_i. The Hessian is 2mI2mI, positive definite, so this is the unique global minimum.

    Update step. The objective decomposes as J=∑j∑i∈Sj∥xi−μj∥2J = \sum_j \sum_{i\in S_j} \lVert x_i - \mu_j\rVert^2. With assignments fixed, each inner sum depends only on μj\mu_j, and replacing μj\mu_j with the mean of SjS_j minimizes it. So each term cannot increase, hence JJ cannot increase.

    GMM limit. With equal weights and covariance σ2I\sigma^2 I, the responsibility is rij=exp⁡(−∥xi−μj∥2/2σ2)/∑lexp⁡(−∥xi−μl∥2/2σ2)r_{ij} = \exp(-\lVert x_i-\mu_j\rVert^2/2\sigma^2) / \sum_l \exp(-\lVert x_i-\mu_l\rVert^2/2\sigma^2). Dividing numerator and denominator by the term for the nearest centroid, every other term contains exp⁡(−(dl2−dmin⁡2)/2σ2)\exp(-(d_l^2 - d_{\min}^2)/2\sigma^2), which goes to 0 as σ→0\sigma\to 0 when dl>dmin⁡d_l > d_{\min}. So rij→1r_{ij} \to 1 for the nearest centroid and 0 otherwise: the E-step becomes the hard assignment step. The M-step for means with 0/1 responsibilities is the cluster mean, which is the k-means update.

Further reading

Go further

Sources

Fact-checked and last reviewed 2026-11-05

Stay in the loop
Prometheus OS™ · Native AI OS · Built for the future © 2025 - 2026