Singular value decomposition
Construct the SVD of any matrix from the spectral theorem applied to AᵀA, and read it as rotate–scale–rotate.
The eigendecomposition applies only to square matrices and only when a full eigenbasis exists. The singular value decomposition extends the same geometry to any matrix, square or rectangular, and always exists[1, 2]. It is the factorization that underlies PCA, low-rank approximation, pseudo-inverses, and numerical rank computation.
Constructing the SVD
Let . The matrix is symmetric and PSD (previous lesson: ). By the spectral theorem, it has a real orthonormal eigenbasis.
Step 1: diagonalize .
The eigenvalues are nonnegative by PSD. Order them . The singular values are .
Step 2: construct the left singular vectors. For each , define
These are unit vectors in : .
They are mutually orthogonal: for , .
Extend (where is the rank) to an orthonormal basis of by adding arbitrary orthonormal vectors.
Step 3: write . By construction for and for (those lie in ). Collecting into matrices:
i.e. , hence .
Here and are orthogonal, and is zero everywhere except for .
Geometric interpretation
applied to a vector :
- : rotate in the domain .
- : scale the -th coordinate by (and possibly change dimension).
- : rotate the result in the codomain .
Every linear map is a rotation, followed by axis-aligned scaling, followed by another rotation. The singular values are the scale factors along the principal axes.
The four fundamental subspaces, revisited
From the SVD with :
- Column space: .
- Left null space: .
- Row space: .
- Null space: .
These four subspaces are mutually orthogonal in their respective spaces, and their dimensions satisfy rank + nullity = (rank–nullity theorem confirmed).
Check it on arrays
import numpy as np
A = np.array([[3.0, 2.0, 1.0],
[0.0, 1.0, 4.0]]) # 2x3 matrix, rank 2
U, s, Vt = np.linalg.svd(A, full_matrices=True)
print("singular values:", np.round(s, 6))
print("U shape:", U.shape, "| Vt shape:", Vt.shape)
# Reconstruct: A = U Sigma V^T
Sigma = np.zeros_like(A)
Sigma[:len(s), :len(s)] = np.diag(s)
A_reconstructed = U @ Sigma @ Vt
print("reconstruction error:", np.max(np.abs(A - A_reconstructed))) # ~1e-14
# Verify U and V are orthogonal
print("U^T U:\n", np.round(U.T @ U, 10))
print("V^T V:\n", np.round(Vt @ Vt.T, 10))
# Rotate-scale-rotate: trace through on a specific vector
v = np.array([1.0, 0.0, 0.0])
step1 = Vt @ v # rotate in R^3
step2 = Sigma @ step1 # scale (and reduce to R^2)
step3 = U @ step2 # rotate in R^2
print("A @ v:", A @ v)
print("U Sigma V^T @ v:", np.round(step3, 10)) # sameThe reconstruction error is near machine epsilon; both and pass the orthogonality check.
Exercises
Exercise 1. Compute the singular values of by hand, using .
Solution
Step 1. Form , a matrix:
Step 2. This is already diagonal, so its eigenvalues are and (ordered largest first).
Step 3. The singular values are the square roots: and . The third row of contributes nothing, which is why has a zero row in .
Exercise 2. Show that the nonzero singular values of equal those of , so .
Solution
Step 1. Write the SVD with orthogonal. Transpose both sides: .
Step 2. This is a valid SVD of : and are orthogonal, and is the (rectangular) diagonal matrix carrying the same diagonal entries as , only reshaped. So has the identical set of singular values.
Step 3. The rank equals the count of nonzero singular values (the number of spanning the column space in the lesson). Since and share that count, , which is the row-rank-equals-column-rank fact.
Exercise 3 (code). For a random matrix, verify that the left
singular vectors from the construction match
the columns of that np.linalg.svd returns (up to sign), and that
.
Solution
The construction defines , so and . Each column of must match the constructed up to an overall sign (an SVD is unique only up to sign on paired singular vectors).
import numpy as np
rng = np.random.default_rng(0)
A = rng.normal(size=(4, 3))
U, s, Vt = np.linalg.svd(A, full_matrices=False)
V = Vt.T
for i, sigma in enumerate(s):
Av = A @ V[:, i]
u_constructed = Av / sigma
# ||A v_i|| == sigma_i
assert np.isclose(np.linalg.norm(Av), sigma)
# u_constructed equals column i of U up to a sign flip
assert np.allclose(u_constructed, U[:, i]) or \
np.allclose(u_constructed, -U[:, i])
print("singular values:", np.round(s, 6))Each reproduces , and the constructed left
singular vectors agree with svd's up to sign, confirming the Step 2
construction .
With the SVD in hand, the next lesson uses it to find the best rank- approximation to and to define PCA.
References
- [1]Trefethen, L. N., & Bau, D. (1997). Numerical Linear Algebra. SIAM. Existence and geometry of the SVD (Lectures 4-5).↩
- [2]Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations (4th ed.). Johns Hopkins University Press. The canonical reference for the SVD and its computation (doi:10.56021/9781421407944).↩