Machine Learning

Unsupervised learning

Unsupervised learning finds structure in data that has no answers attached, grouping similar things together without ever being told what the groups are.

Read these first

On this page 6
  1. Why it exists
  2. How it works
  3. Where you have already seen it
  4. The honest part
  5. Remember this
  6. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

Unsupervised learning is finding structure in data when nobody has given you any answers.

Picture emptying a full laundry bag onto the bed. Nobody tells you what to do. Within a minute you have piles — socks here, shirts there, towels in the corner. You were never taught a rule for "towel-ness". You grouped by what felt similar.

That is unsupervised learning. There is no answer key. The pattern comes out of the data itself.

Why it exists

In the real world, most data arrives with no labels at all.

A shop has ten years of purchase records. Nobody wrote "this is a bargain hunter" next to each customer. A hospital has millions of readings with no diagnosis attached. A phone company has call logs with nothing marked.

Labelling all of that by hand would take years and cost a fortune. Unsupervised learning is what you use when the labels do not exist and never will.

It also answers a different sort of question. Supervised learning asks "is this one spam?". Unsupervised learning asks "what kinds of thing are even in here?" You ask that second question before you know what to look for.

How it works

The most common form is clustering, meaning putting similar items into groups.

BEFORE — a pile of customers, no labels

   spend
     ^
     |  o   o
     |    o                       o = one customer
     |          o
     |             o    o
     |               o
     +--------------------> visits per month


AFTER — the algorithm draws the groups

   spend
     ^
     |  (A   A)
     |   (A)                      A = group one
     |          (B)               B = group two
     |            (B    B)
     |              (B)
     +--------------------> visits per month

Here is the part people miss. The algorithm found two groups. It did not name them.

It does not know that group A means "rare visitor who spends big". It does not know that group B means "regular who spends small". A human looks at the groups and gives them names. The machine does the sorting. You do the meaning.

Where you have already seen it

  • Spotify and YouTube. Songs and videos get grouped by who listens to what, and you get shown the neighbouring items.
  • Google Photos grouping faces. It gathers all photos of one person before you ever type a name.
  • Shopping apps. Customers get sorted into segments, and each segment sees different offers.
  • News apps. Articles covering the same event get bundled together automatically.
  • Fraud and fault detection. Anything that sits far from every group gets flagged as odd, which is called anomaly detection.

The honest part

Read this twice, because it trips up almost everyone.

Unsupervised learning often has no single right answer. Two sensible people can group the same customers differently and both be correct. Group by spending, or by how often they visit, or by what they buy — three different, defensible answers.

This is genuinely different from supervised learning. There you can check each prediction against a real label, and count how many you got right. Here, there is nothing to check against.

Two more things that surprise beginners:

  • You usually have to say how many groups you want before the algorithm runs. Ask for three groups and you get three, whether or not three is sensible.
  • The group numbers mean nothing. Run it twice and group 0 may become group 1. The membership is stable, the numbering is not.

None of this makes the method useless. It makes it a tool for exploring, rather than a tool for deciding.

Remember this

  • Unsupervised learning works on data with no answers attached, and finds structure by itself.
  • Clustering puts similar items into groups. Naming those groups is a human job.
  • There is often no single correct grouping, so treat the result as a starting point rather than a verdict.

What to learn next

  • Supervised learning — the other half of the field, where answers do exist.
  • Clustering — the algorithms behind grouping, in detail.
  • Embeddings — turning words and images into numbers so they can be grouped at all.

Developer — Code and libraries.

Setup

bash
pip install scikit-learn numpy

Clustering customers

KMeans is the standard starting point. You tell it how many groups you want, and it finds centres that sit in the middle of the groups.

segments.py
import numpy as np
from sklearn.cluster import KMeans

# Nobody has told us what these groups are. There are no labels here.
# Each row is one customer: [visits per month, average bill in rupees]
customers = np.array([
    [1, 250], [2, 300], [1, 220], [2, 280],
    [12,  90], [14, 110], [11,  85], [13, 120],
])

km = KMeans(n_clusters=2, n_init=10, random_state=0).fit(customers)

print("group number for each customer:", km.labels_)
print("centre of each group:")
print(km.cluster_centers_.round(1))
Output
group number for each customer: [1 1 1 1 0 0 0 0]
centre of each group:
[[ 12.5 101.2]
 [  1.5 262.5]]

Reading this correctly matters more than running it.

  • labels_ holds one group number per input row. The first four customers landed in group 1, the last four in group 0.
  • cluster_centers_ is the average position of each group. Group 0 sits at 12.5 visits and about 101 rupees. Group 1 sits at 1.5 visits and 262.5 rupees.
  • Those centres are the whole payoff. "Frequent small spenders" and "rare big spenders" are business categories you can act on. The algorithm never used those words.
  • n_init=10 runs the whole algorithm ten times from different random starts and keeps the best result. K-means gets stuck in poor solutions, so more restarts means more reliability.
  • random_state=0 makes the run reproducible. Without it, group numbering can flip between runs.

The trap that catches everyone

K-means measures distance. Distance depends on units. Change the units and you change the answer.

units_matter.py
import numpy as np
from sklearn.cluster import KMeans

# The very same four customers, written twice with different money units.
# [visits per month, average bill]
in_rupees = np.array([[1, 2500], [2, 3000], [10, 2600], [11, 3100]])
in_thousands = np.array([[1, 2.5], [2, 3.0], [10, 2.6], [11, 3.1]])

for name, data in [("bill in rupees   ", in_rupees), ("bill in thousands", in_thousands)]:
    labels = KMeans(n_clusters=2, n_init=10, random_state=0).fit(data).labels_
    print(name, "->", labels)
Output
bill in rupees    -> [0 1 0 1]
bill in thousands -> [1 1 0 0]

Identical customers. Identical algorithm. Completely different groups.

With bills in rupees, the money column spans hundreds while visits span ten. Distance is dominated by money, so the algorithm groups by bill size. Divide the bills by a thousand and visits take over, so it groups by visit frequency.

Neither answer is a bug. K-means faithfully answered the question you asked, and the units silently changed the question.

The fix is to put every feature on a comparable scale before clustering, using StandardScaler:

scaled.py
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

in_rupees = np.array([[1, 2500], [2, 3000], [10, 2600], [11, 3100]])

# Rewrites every column to mean 0 and standard deviation 1
scaled = StandardScaler().fit_transform(in_rupees)
labels = KMeans(n_clusters=2, n_init=10, random_state=0).fit(scaled).labels_
print("after scaling:", labels)
Output
after scaling: [1 1 0 0]

Scaling made the rupee version agree with the thousands version. Neither column can shout over the other any more.

Scaling does not remove the decision — it makes the decision explicit. You are declaring that a one-standard-deviation move in visits matters as much as one in spending. That is a judgement about your business, not a fact about your data.

Choosing the number of groups

There is no n_clusters that is objectively right. There are only ways to make an informed choice. The silhouette score measures how comfortably each point sits inside its own group, compared with the nearest other group. It runs from -1 to 1, and higher is better.

how_many_groups.py
import numpy as np
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

customers = np.array([
    [1, 250], [2, 300], [1, 220], [2, 280],
    [12,  90], [14, 110], [11,  85], [13, 120],
])

for k in (2, 3, 4):
    labels = KMeans(n_clusters=k, n_init=10, random_state=0).fit_predict(customers)
    print(f"k={k}  silhouette={silhouette_score(customers, labels):.3f}")
Output
k=2  silhouette=0.787
k=3  silhouette=0.679
k=4  silhouette=0.618

Two groups scores highest here, which matches how the data was built. On real data the curve is usually flatter and far less decisive. Treat the score as evidence, not as an answer.

Common mistakes

Clustering unscaled features. Covered above. It is the single most frequent error in unsupervised work.

Assuming the group numbers are stable. labels_ values are arbitrary identifiers. Never store "customer is in group 2" as a permanent fact across retraining runs.

Leaving n_init at its default while comparing runs. In scikit-learn 1.4 and later the default became n_init="auto". Set it explicitly so your results are reproducible across versions.

Forcing k-means onto shapes it cannot handle. K-means assumes roughly round, similarly sized groups. For long curved shapes, or groups of very different density, try DBSCAN. It finds the number of groups by itself and marks outliers as -1.

Try it yourself

Add one unusual customer to segments.py: append [7, 700] — medium visits with an enormous bill.

Rerun it. The result is worse than a small shift. That single customer takes an entire cluster for itself. Your two genuine customer groups get merged into the other one:

Output
group number for each customer: [0 0 0 0 0 0 0 0 1]
centre of each group:
[[  7.  181.9]
 [  7.  700. ]]

One row out of nine destroyed the segmentation. Centroids are averages, and averages are not robust to extremes.

This is a real limitation of k-means, not a quirk of this dataset. It is a large part of why DBSCAN and median-based methods exist. DBSCAN would label that customer as noise rather than building a group around them.

What to learn next

  • Clustering — DBSCAN, hierarchical clustering, and choosing between them.
  • Feature engineering — scaling and transforms, which decide clustering results.
  • Embeddings — how text and images become numeric vectors you can cluster.

Researcher — Mathematics and papers.

The k-means objective

K-means minimises within-cluster sum of squares (WCSS), also called inertia.

minimise  J = SUM_{j=1..k}  SUM_{x in C_j}  || x - mu_j ||^2
  • k — number of clusters, fixed in advance
  • C_j — the set of points assigned to cluster j
  • mu_j — the centroid of cluster j, equal to the mean of the points in C_j
  • || . || — Euclidean (L2) norm

This objective explains the method's behaviour completely. Squared Euclidean distance is why k-means prefers spherical, equal-variance clusters. It is also why the method is sensitive to feature scale, and why single outliers move centroids so much.

Lloyd's algorithm and its cost

The standard solver alternates two steps until assignments stop changing.

repeat:
   assignment step:  C_j  <- { x : argmin_j || x - mu_j ||^2 }
   update step:      mu_j <- mean( C_j )

Each step never increases J, and the number of distinct assignments is finite, so the algorithm terminates. It terminates at a local minimum. It has no guarantee of reaching the global one.

Cost per iteration:  O(n * k * d)
Total:               O(n * k * d * i)
  • n — number of points
  • k — number of clusters
  • d — number of dimensions
  • i — iterations to convergence, typically small in practice

Exact k-means is NP-hard for k >= 2 in general dimension (Aloise et al., 2009; Dasgupta, 2008). Worst-case iteration counts are superpolynomial (Arthur & Vassilvitskii, 2006). Smoothed analysis explains why practice is nonetheless fast (Arthur, Manthey & Röglin, 2011).

k-means++ (Arthur & Vassilvitskii, 2007) seeds centroids with probability proportional to squared distance from the nearest existing centroid. It gives an expected O(log k)-competitive solution and is the default initialisation in scikit-learn.

Model-based alternatives

Gaussian mixture models replace hard assignment with a probabilistic generative model.

p(x) = SUM_{j=1..k}  pi_j * N( x | mu_j, Sigma_j )
  • pi_j — mixing weight of component j, non-negative and summing to one
  • mu_j — mean vector of component j
  • Sigma_j — covariance matrix of component j, allowing elliptical, differently-oriented clusters
  • N — the multivariate normal density

Fitted by Expectation-Maximisation (Dempster, Laird & Rubin, 1977), which monotonically increases the likelihood lower bound. K-means is the limiting case of EM on a GMM. Take isotropic covariance sigma^2 * I, let sigma -> 0, and use hard assignment.

Density-based methods make different assumptions again. DBSCAN (Ester et al., 1996) defines clusters as maximal density-connected sets under parameters eps and minPts. It discovers k rather than requiring it, handles non-convex shapes, and labels low-density points as noise. HDBSCAN (Campello et al., 2013) removes the global eps by building a hierarchy over varying density.

Dimensionality reduction

The second major family compresses rather than groups.

MethodPreservesNotes
PCA (Pearson 1901; Hotelling 1933)Global variance, linearClosed form via SVD; O(n d^2)
t-SNE (van der Maaten & Hinton, 2008)Local neighbourhoodsDistances between clusters are not meaningful
UMAP (McInnes et al., 2018)Local, some globalFaster than t-SNE; sensitive to n_neighbors

A caution worth repeating: t-SNE and UMAP plots invite over-reading. Cluster sizes, inter-cluster distances, and empty space are largely artefacts of the algorithm's parameters (Wattenberg, Viégas & Johnson, 2016). Never draw a conclusion from a t-SNE picture without confirming it in the original space.

Evaluation without ground truth

Internal indices score geometry rather than correctness.

Silhouette(i) = ( b(i) - a(i) ) / max( a(i), b(i) )
  • a(i) — mean distance from point i to other points in its own cluster
  • b(i) — mean distance from point i to points in the nearest other cluster
  • Result lies in [-1, 1]; values near 1 indicate a well-separated point

Other internal indices include Davies-Bouldin (1979) and Calinski-Harabasz (1974). All of them encode a geometric prior, and all favour the cluster shapes their own definition assumes. Silhouette computed with Euclidean distance will reliably prefer k-means output over DBSCAN output, regardless of which is more useful.

When labels do exist for validation, use Adjusted Rand Index (Hubert & Arabie, 1985) or Adjusted Mutual Information. Both are corrected for chance agreement.

Current state

Classical clustering is no longer where the field's attention sits. The dominant modern form of unsupervised learning is self-supervised representation learning, which invents a prediction task from unlabelled data.

  • Contrastive objectives: SimCLR (Chen et al., 2020), MoCo (He et al., 2020), and InfoNCE from CPC (van den Oord et al., 2018).
  • Non-contrastive: BYOL (Grill et al., 2020), which avoids negative pairs. Also DINO (Caron et al., 2021), whose attention maps segment objects without any segmentation labels.
  • Masked reconstruction: BERT (Devlin et al., 2019) for text, MAE (He et al., 2022) for images.

Language model pretraining is the largest unsupervised learning system ever deployed. Next-token prediction requires no human labels, which is exactly why it scaled.

The practical relationship to clustering has inverted. Current practice does not cluster raw features. It learns a representation self-supervised, then clusters in that embedding space. Euclidean distance is far more meaningful there than in the original coordinates.

Key references

  • Lloyd, S. (1982). Least Squares Quantization in PCM. IEEE Trans. Information Theory. Written in 1957, published in 1982.
  • Arthur, D. & Vassilvitskii, S. (2007). k-means++: The Advantages of Careful Seeding. SODA.
  • Ester, M., Kriegel, H.-P., Sander, J. & Xu, X. (1996). A Density-Based Algorithm for Discovering Clusters. KDD.
  • Dempster, A., Laird, N. & Rubin, D. (1977). Maximum Likelihood from Incomplete Data via the EM Algorithm. JRSS-B 39(1).
  • Rousseeuw, P. (1987). Silhouettes: A Graphical Aid to the Interpretation and Validation of Cluster Analysis. J. Comp. Appl. Math. 20.
  • Chen, T. et al. (2020). A Simple Framework for Contrastive Learning of Visual Representations. ICML.

What to learn next

  • Clustering — algorithm-level detail and selection criteria.
  • Embeddings — the representation spaces modern clustering operates in.
  • Autoencoders — unsupervised representation learning by reconstruction.