Purpose

The SVD is the decomposition that works for every matrix: no squareness, no symmetry, no full-rank requirement. This note derives it from the eigendecomposition of , defines the pseudoinverse through it, and states Eckart-Young, which is the license behind every truncated-SVD compression and PCA plot. It leans on Eigenvalues and Diagonalization and finishes the least-squares story from Orthogonality, Projections, and Least Squares.

Deriving the SVD from

Let . The matrix is symmetric and positive semidefinite, so by the spectral theorem it has an orthonormal eigenbasis with real eigenvalues . Define the singular values , and for each define

These are orthonormal: , which is 1 when and 0 otherwise. Completing to an orthonormal basis of and collecting everything in matrices gives

with and orthogonal and diagonal with . This is Strang’s construction in 18.06 lecture 29. The rank of is the number of nonzero singular values, and the compact form writes as a sum of rank-one pieces, largest first.

Sanity check both eigenproblems: and , so the right singular vectors diagonalize and the left ones diagonalize , sharing the nonzero spectrum .

Geometry

The SVD says every linear map is rotate, stretch, rotate. rotates the input so the axes line up with , stretches axis by (killing axes past rank ), and rotates into output space. The image of the unit sphere under any is a hyperellipse whose semi-axes have lengths pointing along (Trefethen and Bau open the whole subject with this picture in lecture 4). is the largest stretch factor any unit vector experiences, and the smallest nonzero one; their ratio is the condition number.

flowchart LR
    X["input x"] --> VT["V transpose: rotate input axes onto right singular vectors"]
    VT --> SIG["Sigma: stretch axis i by sigma i, drop axes past rank r"]
    SIG --> U["U: rotate onto left singular vectors in output space"]
    U --> Y["output Ax"]
    style VT fill:#e3f2fd
    style SIG fill:#e8f5e9
    style U fill:#e3f2fd

The Moore-Penrose pseudoinverse

Invert what is invertible and leave the rest alone:

is the unique matrix satisfying the four Penrose conditions: , , and both and symmetric (Golub and Van Loan, ch. 5). When is square and invertible, . When has full column rank, , the least-squares operator from the normal equations.

The general statement: is always a least-squares solution of , and among all least-squares solutions it is the one with minimum norm. Rank deficiency makes the minimizer non-unique, adding any null-space component leaves the residual unchanged, and is the choice with zero null-space component. This is exactly what np.linalg.lstsq returns, since its SVD-based driver applies a truncated pseudoinverse (numpy.linalg.lstsq docs).

SVD, pseudoinverse, and least squares are one package

Undo the three-step geometry in reverse: rotate with , divide by the nonzero (zero out the rest), rotate back with . That is , and it solves least squares in every rank case at once:

  • Square invertible : , the exact solution.
  • Full column rank: , the normal-equations solution.
  • Rank deficient: is the minimum-norm least-squares solution, the unique minimizer with no null-space component.

The normal equations only cover the middle case; the SVD route degrades gracefully through all three.

Eckart-Young: best low-rank approximation

Truncate the rank-one sum after terms: . The Eckart-Young-Mirsky theorem says no rank- matrix does better:

with attaining both (Mirsky extended the result to every unitarily invariant norm). The singular value spectrum is therefore a complete answer to “how compressible is this matrix”: if the decay fast, a few rank-one terms capture almost everything, and tells you exactly the error of stopping at .

PCA is this theorem applied to data. Center the data matrix (subtract column means), take its SVD, and the right singular vectors are the principal components; the variance along component is , and the fraction of variance explained by the top components is (Stanford STATS 305C). The repo has a small image-compression demo of exactly this truncation in content/math/linear-algebra/pca_image_compression.py.

NumPy

import numpy as np
 
rng = np.random.default_rng(0)
A = rng.standard_normal((6, 4)) @ np.diag([10.0, 3.0, 0.5, 0.01])
 
U, s, Vh = np.linalg.svd(A, full_matrices=False)
print(s)                                  # descending: [25.57  5.64  0.95  0.01]
print(np.allclose(U @ np.diag(s) @ Vh, A))  # True
 
k = 2                                     # rank-2 truncation
Ak = U[:, :k] @ np.diag(s[:k]) @ Vh[:k]
print(np.linalg.norm(A - Ak, 2), s[k])    # equal, per Eckart-Young
 
Apinv = Vh.T @ np.diag(1/s) @ U.T         # pseudoinverse by hand
print(np.allclose(Apinv, np.linalg.pinv(A)))  # True

Conventions from the numpy.linalg.svd docs that trip people up: the function returns Vh , not ; singular values come back sorted descending as a 1-D array, so reconstruction needs np.diag; and full_matrices=True (the default) returns square and padded with basis vectors for the null spaces, while full_matrices=False gives the compact economy shapes. np.linalg.pinv applies an rcond cutoff before inverting singular values, because dividing by a tiny that is really numerical noise amplifies error by ; treating near-zero singular values as exactly zero is the stable choice for ill-conditioned systems.

Sources