Principal Component Analysis (PCA)
pca is the linear algebra exam question that escaped into industry. given a cloud of points in \(\mathbb{R}^d\), it finds the orthogonal directions along which the cloud spreads the most, and lets you throw away the rest. π two apparently different questions β “which directions carry the most variance?” and “which subspace loses the least when i project onto it?” β turn out to have the same answer, and that answer is an eigendecomposition.
setup
data \(x_1, \dots, x_n \in \mathbb{R}^d\), assumed centred: \(\sum_i x_i = 0\) (subtract the mean first; everything below silently requires this). the sample covariance matrix is
\begin{equation} S = \frac{1}{n}\sum_{i=1}^{n} x_i x_i^\top \in \mathbb{R}^{d \times d}, \end{equation}
symmetric and positive semi-definite, so it has an orthonormal eigenbasis \(S v_j = \lambda_j v_j\) with \(\lambda_1 \ge \lambda_2 \ge \dots \ge \lambda_d \ge 0\). π the variance of the data projected onto a unit vector \(w\) is \(\operatorname{Var}(w^\top x) = w^\top S w\).
variance maximisation
the first component
find the unit direction of maximal projected variance:
\begin{equation} \max_{w \in \mathbb{R}^d} \; w^\top S w \quad \text{subject to} \quad w^\top w = 1. \end{equation}
the constraint matters β without it, scale \(w\) up and the “variance” grows forever. form the lagrangian
\begin{equation} \mathcal{L}(w, \lambda) = w^\top S w - \lambda\,(w^\top w - 1), \end{equation}
and set the gradient to zero:
\begin{equation} \nabla_w \mathcal{L} = 2 S w - 2 \lambda w = 0 \;\;\Longleftrightarrow\;\; S w = \lambda w. \end{equation}
every stationary point is an eigenvector of \(S\), and at such a point the objective evaluates to
\begin{equation} w^\top S w = w^\top (\lambda w) = \lambda. \end{equation}
so the achievable variances are exactly the eigenvalues, and the maximiser is the top eigenvector \(v_1\): the first principal component, carrying variance \(\lambda_1\) (Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon, 2020).
the rest, by deflation
the second component solves the same problem with the extra constraint \(w \perp v_1\). restricting \(S\) to the subspace orthogonal to \(v_1\) leaves a symmetric matrix whose top eigenvector is \(v_2\), and by induction the \(k\)-th component is the \(k\)-th eigenvector. π collecting the top \(k\) as columns of \(W_k = [v_1 \cdots v_k]\), the scores \(z_i = W_k^\top x_i \in \mathbb{R}^k\) are the low-dimensional code, with the tidy properties that the coordinates of \(z\) are uncorrelated and \(\operatorname{Var}(z_{(j)}) = \lambda_j\).
the reconstruction view
now the other question: among all \(k\)-dimensional subspaces, which one loses the least when we project onto it? for an orthonormal \(W \in \mathbb{R}^{d \times k}\) (so \(W^\top W = I_k\)), the projection of \(x\) is \(W W^\top x\) and the average squared reconstruction error is
\begin{align*} J(W) &= \frac{1}{n}\sum_{i=1}^{n} \big\lVert x_i - W W^\top x_i \big\rVert^2 \\ &= \frac{1}{n}\sum_{i=1}^{n} \left( \lVert x_i \rVert^2 - x_i^\top W W^\top x_i \right) \\ &= \operatorname{tr}(S) - \operatorname{tr}\!\big(W^\top S W\big), \end{align*}
using \(\lVert x - WW^\top x\rVert^2 = x^\top x - x^\top W W^\top x\) (pythagoras: projection and residual are orthogonal). the first term is fixed, so minimising reconstruction error is exactly maximising the projected variance \(\operatorname{tr}(W^\top S W)\) β the two views coincide. the trace is maximised over orthonormal \(W\) by the top-\(k\) eigenvectors,1 giving optimal error
\begin{equation} J(W_k^\ast) = \sum_{j=k+1}^{d} \lambda_j, \end{equation}
the sum of the discarded eigenvalues. small trailing eigenvalues mean the cloud genuinely lives near a \(k\)-flat, and pca finds it (Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon, 2020).
computation via the svd
stack the centred data as rows of \(X \in \mathbb{R}^{n \times d}\) and take the (thin) singular value decomposition
\begin{equation} X = U \Sigma V^\top, \qquad U \in \mathbb{R}^{n \times r},\; \Sigma = \operatorname{diag}(\sigma_1, \dots, \sigma_r),\; V \in \mathbb{R}^{d \times r}. \end{equation}
then \(S = \frac{1}{n} X^\top X = V \frac{\Sigma^2}{n} V^\top\), so
- the right singular vectors \(V\) are the principal components,
- eigenvalues come from singular values as \(\lambda_j = \sigma_j^2 / n\),
- the score matrix is \(Z = X V = U \Sigma\) β no second pass over the data.
in practice you compute pca via the svd of \(X\), not by forming \(S\) and calling an eigensolver: forming \(X^\top X\) squares the condition number, so half your significant digits evaporate before the eigensolver even starts. π when \(d \gg n\) (genomics, images) the trick inverts: eigendecompose the \(n \times n\) gram matrix \(\frac{1}{n} X X^\top\) instead, which shares the nonzero spectrum, then map back β this is also the doorway to kernel pca below.
how many components?
the eigenvalue spectrum is the budget sheet. define the proportion of variance explained by the top \(k\):
\begin{equation} \mathrm{pve}(k) = \frac{\sum_{j=1}^{k} \lambda_j}{\sum_{j=1}^{d} \lambda_j}. \end{equation}
- scree plot: plot \(\lambda_j\) against \(j\) and look for the elbow where signal gives way to noise floor.
- threshold: keep the smallest \(k\) with \(\mathrm{pve}(k) \ge 0.9\) (or whatever your downstream task forgives).
- scale first: pca is not scale-invariant. a feature measured in millimetres will dominate one measured in kilometres for no honest reason β standardise features (pca on the correlation matrix) unless their units are genuinely commensurate (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome, 2009).
whitening
pca decorrelates; whitening goes one step further and equalises. with \(\Lambda = \operatorname{diag}(\lambda_1, \dots, \lambda_k)\), the pca-whitened code
\begin{equation} z_i = \Lambda^{-1/2}\, W_k^\top x_i \end{equation}
has identity covariance: every direction contributes unit variance. useful before algorithms that assume isotropy (nearest neighbours, some optimisers). zca whitening \(z_i = W \Lambda^{-1/2} W^\top x_i\) (full \(W\), rotated back into the original basis) is the whitening transform closest to the identity β images stay looking like images. caveat: dividing by \(\sqrt{\lambda_j}\) for tiny \(\lambda_j\) amplifies noise \(1/\sqrt{\lambda_j}\)-fold, so regularise with \(\Lambda + \varepsilon I\) or truncate first.
kernel pca, sketched
pca can only find flat structure. kernel pca runs the gram-matrix formulation in a feature space \(\varphi(x)\) you never compute, touching data only through \(k(x, x’) = \varphi(x)^\top \varphi(x’)\) (Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome, 2009):
- centre the kernel matrix: \(\tilde{K} = H K H\) with \(H = I - \tfrac{1}{n}\mathbf{1}\mathbf{1}^\top\) (feature-space centring done implicitly).
- eigendecompose \(\tilde{K} \alpha_j = n \lambda_j \alpha_j\); normalise so \(\lambda_j\, n\, \alpha_j^\top \alpha_j = 1\), which makes the implicit feature-space component unit length.
- project a point via kernel evaluations only: \(z_{(j)}(x) = \sum_{i=1}^{n} \alpha_{j,i}\, \tilde{k}(x_i, x)\).
with an rbf kernel this unrolls spirals and separates concentric rings that vanilla pca is congenitally blind to. costs: \(O(n^2)\) memory, \(O(n^3)\) eigendecomposition, and no cheap inverse map from code space back to inputs (the “pre-image problem”). for visualisation-grade nonlinear reduction the modern defaults are t-sne and umap (McInnes, Leland and Healy, John and Melville, James, 2020), which optimise neighbourhood preservation rather than variance β see also kernel methods for the general kernel trick, and the curse of dimensionality for why any of this is necessary.2
from scratch
both routes β eigendecomposition of the covariance and svd of the centred data β on a synthetic 5-d dataset with a planted 2-d signal subspace plus isotropic noise:
import numpy as np
rng = np.random.default_rng(42)
# 200 points in 5-d with a planted 2-d signal subspace
n, d = 200, 5
latent = rng.normal(size=(n, 2)) @ np.diag([3.0, 1.5]) # strong directions
basis, _ = np.linalg.qr(rng.normal(size=(d, 2))) # random orthonormal 5x2
X = latent @ basis.T + 0.3 * rng.normal(size=(n, d)) # + isotropic noise
def pca_eig(X, k):
Xc = X - X.mean(axis=0)
S = Xc.T @ Xc / (len(X) - 1) # sample covariance
lam, W = np.linalg.eigh(S) # ascending order
lam, W = lam[::-1], W[:, ::-1] # descending
return lam[:k], W[:, :k], Xc
def pca_svd(X, k):
Xc = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(Xc, full_matrices=False)
lam = s**2 / (len(X) - 1) # singular values -> eigenvalues
return lam[:k], Vt[:k].T, Xc
lam_e, W_e, Xc = pca_eig(X, 5)
lam_s, W_s, _ = pca_svd(X, 5)
print("eigenvalues (eig):", np.round(lam_e, 4))
print("eigenvalues (svd):", np.round(lam_s, 4))
# columns may differ by sign; align before comparing
signs = np.sign(np.sum(W_e * W_s, axis=0))
print("max |W_eig - W_svd| after sign fix:",
f"{np.abs(W_e - W_s * signs).max():.2e}")
pve = lam_e / lam_e.sum()
print("proportion of variance explained:", np.round(pve, 4))
print("cumulative: ", np.round(np.cumsum(pve), 4))
# reconstruction error for k = 1..5 equals the sum of discarded eigenvalues
for k in range(1, 6):
Z = Xc @ W_e[:, :k] # project
err = np.mean(np.sum((Xc - Z @ W_e[:, :k].T)**2, axis=1))
print(f"k={k}: mean sq reconstruction error {err:.4f} "
f"sum of discarded eigenvalues {(lam_e[k:].sum() * (n-1)/n):.4f}")
eigenvalues (eig): [8.9681 1.958 0.1099 0.0868 0.072 ]
eigenvalues (svd): [8.9681 1.958 0.1099 0.0868 0.072 ]
max |W_eig - W_svd| after sign fix: 9.55e-15
proportion of variance explained: [0.8011 0.1749 0.0098 0.0078 0.0064]
cumulative: [0.8011 0.976 0.9858 0.9936 1. ]
k=1: mean sq reconstruction error 2.2156 sum of discarded eigenvalues 2.2156
k=2: mean sq reconstruction error 0.2674 sum of discarded eigenvalues 0.2674
k=3: mean sq reconstruction error 0.1580 sum of discarded eigenvalues 0.1580
k=4: mean sq reconstruction error 0.0716 sum of discarded eigenvalues 0.0716
k=5: mean sq reconstruction error 0.0000 sum of discarded eigenvalues 0.0000
everything the theory promised, numerically: the two factorisations agree to machine precision (up to sign β eigenvectors are only defined up to \(\pm 1\)), the planted 2-d subspace announces itself as two dominant eigenvalues carrying \(97.6\%\) of the variance, and the reconstruction error at each \(k\) equals the discarded eigenvalue mass exactly (the \((n-1)/n\) factor in the check just reconciles the two variance conventions).
see also
- k-means clustering β the other half of ch. 14: compress rows instead of columns
- gaussian mixture models β probabilistic latent structure rather than geometric
- autoencoders β pca with the linearity constraint deleted
- kernel methods β the machinery behind kernel pca
- curse of dimensionality β why high-dimensional clouds need flattening at all
the ky fan theorem: \(\max_{W^\top W = I_k} \operatorname{tr}(W^\top S W) = \sum_{j=1}^k \lambda_j\), attained by any orthonormal basis of the top-\(k\) eigenspace. the \(k=1\) case is the rayleigh quotient bound used in the lagrangian derivation. ↩︎
the autoencoder connection, precisely: a linear autoencoder \(x \mapsto W_2 W_1 x\) with a \(k\)-unit bottleneck and squared loss attains its global minimum exactly when \(W_2 W_1\) is the projector onto the top-\(k\) principal subspace (baldi & hornik 1989) β gradient descent on it rediscovers pca, though without orthogonality or ordering of the individual units. replace the linear maps with deep nonlinear encoders/decoders and you get the modern autoencoder family (Goodfellow, Ian, 2016).
References
Deisenroth, Marc Peter and Faisal, A. Aldo and Ong, Cheng Soon (2020). Mathematics for Machine Learning, Cambridge University Press.
Goodfellow, Ian (2016). Deep Learning, MIT Press.
Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome (2009). The Elements of Statistical Learning, Springer.
McInnes, Leland and Healy, John and Melville, James (2020). UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction. ↩︎
Backlinks (6)
1. Autoencoders /wiki/ml/dl/autoencoders/
an autoencoder is a network trained to do the one thing that sounds useless: output its own input. the trick is the obstacle course in the middle β a bottleneck, a corruption, a penalty β that makes verbatim copying impossible, so the network is forced to learn what about the input is worth keeping. π the family tree below runs from the linear special case (which is pca wearing a trenchcoat) to the variational autoencoder, which turns the whole construction into a generative model (Goodfellow, Ian, 2016).
2. Gaussian Mixture Models /wiki/ml/unsupervised/gaussian-mixtures/
a single gaussian is a committed statement: one bump, symmetric, thin tails. real data is usually several stories overlaid β different regimes, different subpopulations β and a gaussian mixture says so explicitly: each point was generated by one of \(k\) gaussians, we just don’t get told which. π fitting one is the canonical latent-variable problem, and the algorithm that fits it β expectation-maximisation β is one of the great workhorses of statistics.
3. Digits /tags/digits/
The scikit-learn digits dataset: 1,797 tiny 8x8 greyscale images of handwritten digits, and the workhorse of every sklearn tutorial that needs a multiclass problem which loads instantly and fits in L2 cache.
Provenance
The data is the Optical Recognition of Handwritten Digits (optdigits) set from the UCI machine learning repository, created by E. Alpaydin and C. Kaynak at Bogazici University and donated in July 1998. It originates in Kaynak’s 1995 MSc thesis on combining multiple classifiers, and the companion paper is Alpaydin and Kaynak, Cascading Classifiers, Kybernetika 34(4), 1998.
4. K-means Clustering /wiki/ml/unsupervised/k-means-clustering/
k-means is unsupervised learning’s hello world: pick \(k\) prototype points, assign every datum to its nearest prototype, move each prototype to the centre of its flock, repeat. π it is fast, it always terminates, and it is wrong in ways that are so instructive that every clustering course starts here anyway.
5. Wiki /wiki/
Knowledge is a paradox. The more one understand, the more one realises the vastness of his ignorance.