TAI BUI
Back to curriculum
BuildPython, Julia

Singular Value Decomposition

Every matrix, of any shape or rank, factors into a rotation, a scale, and another rotation. No exceptions. Master that one decomposition and PCA, recommender systems, and least-squares regression stop looking like separate problems.

TIME: ~120 minutesPREREQUISITES: Phase 1, Lessons 01 (Linear Algebra Intuition), 02 (Vectors & Matrices Operations), 03 (Matrix Transformations)

Learning Objectives

  • Implement SVD via power iteration and explain the geometric meaning of U, Sigma, and V^T
  • Apply truncated SVD for image compression and measure the compression ratio vs reconstruction error
  • Compute the Moore-Penrose pseudoinverse via SVD to solve overdetermined least-squares systems
  • Connect SVD to PCA, recommendation systems (latent factors), and Latent Semantic Analysis in NLP

The Problem

Say you're holding a 1000x2000 matrix: user-movie ratings, a document-term frequency table, or the pixel values of an image, it doesn't matter which. You need to compress it, denoise it, find the structure hiding inside it, or solve a least-squares system against it. Eigendecomposition can't help here. It only works on square matrices, and even then only when the matrix has a full set of linearly independent eigenvectors.

SVD carries none of those restrictions. Any matrix, any shape, any rank, factors into three pieces that expose exactly what the matrix does to space, geometrically. Nothing else in linear algebra generalizes this far.

Warm-up0/3
1. A 3 x 2 matrix is multiplied by a 2 x 4 matrix. What is the shape of the product?Warm-up
A 3 by 2 grid of numbers multiplied by a 2 by 4 grid of numbers, with a question mark for the shape of the result.
2. Two nonzero vectors have a dot product of exactly zero. What does that tell you about them?Warm-up
Two vectors, u at (2, 0) and v at (0, 3), with their dot product computed as 0.
3. A rotation matrix is applied to a vector. What happens to the vector's length?Warm-up
A vector and the same vector after a 50 degree rotation, both drawn from the same starting point, with no lengths labeled.
Check: The Problem0/2
1. Why can SVD be applied to a 1000 x 2000 matrix when eigendecomposition cannot?Explain
A matrix labeled 1000 rows by 2000 columns, with two candidate methods, eigendecomposition and SVD, each marked with a plain question mark.
2. A teammate wants to use np.linalg.eig directly on a 1000 x 2000 ratings matrix to find its structure. What goes wrong?Analyze
A code snippet showing a 1000 by 2000 matrix A, followed by a call to np.linalg.eig on it, marked with a question mark.

The Concept

What SVD does geometrically

SVD factors any matrix A into three pieces, A = U * Sigma * V^T, that correspond to three geometric operations applied in sequence: rotate, scale, rotate. That holds regardless of the matrix's shape.

A = U * Sigma * V^T

      m x n     m x m    m x n    n x n
     (any)    (rotate)  (scale)  (rotate)

Given any matrix A, SVD factors it into:

  • V^T rotates vectors in the input space (n-dimensional)
  • Sigma scales along each axis (stretches or compresses)
  • U rotates the result into the output space (m-dimensional)

Mermaid diagram

Hand SVD a matrix and it hands back exactly what that matrix does to a sphere of inputs: first rotate it by V^T, then stretch it into an ellipsoid by Sigma, then rotate the ellipsoid by U. The singular values are the lengths of that ellipsoid's axes.

The full decomposition

For a matrix A with shape m x n:

A = U * Sigma * V^T

where:
  U     is m x m, orthogonal (U^T U = I)
  Sigma is m x n, diagonal (singular values on the diagonal)
  V     is n x n, orthogonal (V^T V = I)

The singular values sigma_1 >= sigma_2 >= ... >= sigma_r > 0
where r = rank(A)

The columns of U are the left singular vectors, the columns of V are the right singular vectors, and the diagonal entries of Sigma are the singular values, which are always non-negative and sorted in decreasing order by convention.

Left singular vectors, singular values, right singular vectors

Each piece carries its own geometric job.

Right singular vectors (columns of V): an orthonormal basis for the input space (R^n), specifically the directions in that space the matrix sends to orthogonal directions on the output side. Treat them as the domain's natural coordinate system.

Singular values (diagonal of Sigma): the scaling factors. The i-th singular value says how much the matrix stretches vectors along the i-th right singular vector; a value of zero means that direction gets crushed entirely.

Left singular vectors (columns of U): an orthonormal basis for the output space (R^m). The i-th left singular vector is where the i-th right singular vector lands, once scaling has been applied.

One equation ties all three together:

A * v_i = sigma_i * u_i

The matrix A takes the i-th right singular vector v_i,
scales it by sigma_i, and maps it to the i-th left singular vector u_i.

Read coordinate by coordinate, that's the complete picture of what any matrix does.

Outer product form

The SVD can be written as a sum of rank-1 matrices:

A = sigma_1 * u_1 * v_1^T + sigma_2 * u_2 * v_2^T + ... + sigma_r * u_r * v_r^T

Each term sigma_i * u_i * v_i^T is a rank-1 matrix (an outer product).
The full matrix is the sum of r such matrices, where r is the rank.

This form is the foundation of low-rank approximation: each term layers in one more pattern, ranked by importance, so the first captures the single most dominant structure and every term after adds a smaller correction on top. Truncate the sum early and you get the best possible approximation at that rank.

Rank-1 approx:    A_1 = sigma_1 * u_1 * v_1^T
                  (captures the dominant pattern)

Rank-2 approx:    A_2 = sigma_1 * u_1 * v_1^T + sigma_2 * u_2 * v_2^T
                  (captures the two most important patterns)

Rank-k approx:    A_k = sum of top k terms
                  (optimal by the Eckart-Young theorem)

Relationship to eigendecomposition

SVD and eigendecomposition are closely related: the singular values and vectors of A come directly from the eigenvalues and eigenvectors of A^T A and A A^T.

A^T A = V * Sigma^T * U^T * U * Sigma * V^T
      = V * Sigma^T * Sigma * V^T
      = V * D * V^T

where D = Sigma^T * Sigma is a diagonal matrix with sigma_i^2 on the diagonal.

So:
- The right singular vectors (V) are eigenvectors of A^T A
- The singular values squared (sigma_i^2) are eigenvalues of A^T A

Similarly:
A A^T = U * Sigma * V^T * V * Sigma^T * U^T
      = U * Sigma * Sigma^T * U^T

So:
- The left singular vectors (U) are eigenvectors of A A^T
- The eigenvalues of A A^T are also sigma_i^2

This connection tells you three things:

  1. Singular values are always real and non-negative, because they are square roots of eigenvalues of a positive semi-definite matrix: one whose eigenvalues never go negative, which A^T A always satisfies.
  2. You could compute SVD via eigendecomposition of A^T A, but that squares the condition number (the ratio between a matrix's largest and smallest singular value, which controls how much numerical error gets amplified) and loses numerical precision. Dedicated SVD algorithms avoid this.
  3. When A is square and symmetric positive semi-definite, SVD and eigendecomposition are the same thing.

Truncated SVD: low-rank approximation

The Eckart-Young-Mirsky theorem states that the best rank-k approximation to A (in both Frobenius and spectral norm) is obtained by keeping only the top k singular values and their corresponding vectors:

A_k = U_k * Sigma_k * V_k^T

where:
  U_k     is m x k  (first k columns of U)
  Sigma_k is k x k  (top-left k x k block of Sigma)
  V_k     is n x k  (first k columns of V)

Approximation error = sigma_{k+1}  (in spectral norm)
                    = sqrt(sigma_{k+1}^2 + ... + sigma_r^2)  (in Frobenius norm)

Spectral norm measures that leftover error by its single largest singular value. Frobenius norm measures the same error by the square root of the sum of all its squared entries. Different rulers, same leftover.

That's a provable optimum, not a rule of thumb: no other rank-k matrix sits closer to A than this one.

ComponentRelative magnitudeKept in rank-3 approx?
sigma_1LargestYes
sigma_2LargeYes
sigma_3Medium-largeYes
sigma_4MediumNo (error)
sigma_5Medium-smallNo (error)
sigma_6SmallNo (error)
sigma_7Very smallNo (error)
sigma_8TinyNo (error)

Keep top 3: A_3 captures the three largest singular values. Error = remaining values (sigma_4 through sigma_8).

If singular values decay fast, a small k captures most of the matrix. If they decay slowly, the matrix has no low-rank structure.

Image compression with SVD

A grayscale image is a matrix of pixel intensities. An 800x600 image has 480,000 values. SVD lets you approximate it with far fewer.

Original image: 800 x 600 = 480,000 values

SVD with rank k:
  U_k:      800 x k values
  Sigma_k:  k values
  V_k:      600 x k values
  Total:    k * (800 + 600 + 1) = k * 1401 values

  k=10:   14,010 values   (2.9% of original)
  k=50:   70,050 values  (14.6% of original)
  k=100: 140,100 values  (29.2% of original)

  The compression ratio improves as k gets smaller,
  but visual quality degrades.

The key insight: natural images have rapidly decaying singular values. The first few capture broad structure, shapes and gradients; the later ones capture fine detail and noise. Truncating at rank 50 often produces an image nearly indistinguishable from the original while using 85% less storage.

SVD for recommendation systems

The Netflix Prize made this famous. You have a user-movie ratings matrix where most entries are missing.

             Movie1  Movie2  Movie3  Movie4  Movie5
  User1      [  5      ?       3       ?       1  ]
  User2      [  ?      4       ?       2       ?  ]
  User3      [  3      ?       5       ?       ?  ]
  User4      [  ?      ?       ?       4       3  ]

  ? = unknown rating

The idea: this ratings matrix has low rank, because users don't have completely independent tastes. A handful of latent factors (action vs. drama, old vs. new, cerebral vs. visceral) explain most of what any given user prefers.

SVD on the (filled-in) ratings matrix decomposes it into:

  • U: user profiles in latent factor space
  • Sigma: importance of each latent factor
  • V^T: movie profiles in latent factor space

A user's predicted rating for a movie is the dot product of their user profile with the movie's profile (weighted by singular values). The low-rank approximation fills in the missing entries.

In practice, you use variants like Simon Funk's incremental SVD or ALS (alternating least squares) that handle missing data directly. But the core idea is the same: latent factor decomposition via SVD.

SVD in NLP: Latent Semantic Analysis

Latent Semantic Analysis (LSA), also called Latent Semantic Indexing (LSI), applies SVD to a term-document matrix.

             Doc1   Doc2   Doc3   Doc4
  "cat"      [  3      0      1      0  ]
  "dog"      [  2      0      0      1  ]
  "fish"     [  0      4      1      0  ]
  "pet"      [  1      1      1      1  ]
  "ocean"    [  0      3      0      0  ]

After SVD with rank k=2:

  Each document becomes a point in 2D "concept space."
  Each term becomes a point in the same 2D space.
  Documents about similar topics cluster together.
  Terms with similar meanings cluster together.

  "cat" and "dog" end up near each other (land pets).
  "fish" and "ocean" end up near each other (water concepts).
  Doc1 and Doc3 cluster if they share similar topics.

LSA was one of the first methods that actually worked for capturing semantic similarity from raw text. Synonymous terms tend to show up in similar documents, so SVD naturally groups them into the same latent dimensions. Modern word embeddings, Word2Vec, GloVe, descend from this same idea.

SVD for noise reduction

Noisy data has signal concentrated in the top singular values and noise spread across all singular values. Truncating removes the noise floor.

Clean signal singular values:

ComponentMagnitudeType
sigma_1Very largeSignal
sigma_2LargeSignal
sigma_3MediumSignal
sigma_4Near zeroNegligible
sigma_5Near zeroNegligible

Noisy signal singular values (noise adds to all):

ComponentMagnitudeType
sigma_1Very largeSignal
sigma_2LargeSignal
sigma_3MediumSignal
sigma_4SmallNoise
sigma_5SmallNoise
sigma_6SmallNoise
sigma_7SmallNoise

Mermaid diagram

Signal processing, scientific measurement, data cleaning: any time a matrix is corrupted by additive noise, truncated SVD gives you a principled way to separate signal from noise.

Pseudoinverse via SVD

The Moore-Penrose pseudoinverse A+ generalizes matrix inversion to non-square and singular matrices. SVD makes computing it trivial.

If A = U * Sigma * V^T, then:

A+ = V * Sigma+ * U^T

where Sigma+ is formed by:
  1. Transpose Sigma (swap rows and columns)
  2. Replace each non-zero diagonal entry sigma_i with 1/sigma_i
  3. Leave zeros as zeros

For A (m x n):      A+ is (n x m)
For Sigma (m x n):  Sigma+ is (n x m)

The pseudoinverse solves least-squares problems. If Ax = b has no exact solution (overdetermined system), then x = A+ b is the least-squares solution (minimizes ||Ax - b||).

Overdetermined system (more equations than unknowns):

  [1  1]         [3]
  [2  1] x   =   [5]       No exact solution exists.
  [3  1]         [6]

  x_ls = A+ b = V * Sigma+ * U^T * b

  This gives the x that minimizes the sum of squared residuals.
  Same result as the normal equations (A^T A)^(-1) A^T b,
  but numerically more stable.

Numerical stability advantages

Computing eigendecomposition of A^T A squares the singular values (eigenvalues of A^T A are sigma_i^2). This squares the condition number, amplifying numerical errors.

Example:
  A has singular values [1000, 1, 0.001]
  Condition number of A: 1000 / 0.001 = 10^6

  A^T A has eigenvalues [10^6, 1, 10^{-6}]
  Condition number of A^T A: 10^6 / 10^{-6} = 10^{12}

  Computing SVD directly: works with condition number 10^6
  Computing via A^T A:     works with condition number 10^{12}
                           (6 extra digits of precision lost)

Modern SVD algorithms (Golub-Kahan bidiagonalization) work directly on A, never forming A^T A. This is why you should always prefer np.linalg.svd(A) over np.linalg.eig(A.T @ A).

Connection to PCA

PCA IS SVD on centered data. Not an analogy: the literal same computation.

Given data matrix X (n_samples x n_features), centered (mean subtracted):

Covariance matrix: C = (1/(n-1)) * X^T X

PCA finds eigenvectors of C. But:

  X = U * Sigma * V^T    (SVD of X)

  X^T X = V * Sigma^2 * V^T

  C = (1/(n-1)) * V * Sigma^2 * V^T

So the principal components are exactly the right singular vectors V.
The explained variance for each component is sigma_i^2 / (n-1).

In sklearn, PCA is implemented using SVD, not eigendecomposition.
It is faster and more numerically stable.

Everything covered in Lesson 10 about dimensionality reduction is SVD under the hood, and PCA is SVD's most common application in machine learning.

Check: The Concept0/32
1. What does a low-rank approximation of a matrix mean?Define
A table listing 8 singular-value components from largest to smallest, with a yes or no column for whether each is kept when truncating to rank 3.
2. For a matrix A of shape m x n, what are the shapes of U, Sigma, and V in its SVD, and how are the singular values ordered?Define
A matrix A labeled m rows by n columns, equal to three boxes U, Sigma, and V transpose each marked with a blank question-mark shape, plus a blank ordering of the singular values.
3. What is a singular value of a matrix A?Define
A 2 by 2 matrix factored into U, a highlighted diagonal matrix Sigma with entries 3.26 and 1.84, and V transpose, with U and V left as placeholder letters.
Illustrative matrix, computed with numpy, not from the lesson.
4. What is a left singular vector?Define
A 2 by 2 matrix U with its first column highlighted, labeled as one left singular vector of the illustrative matrix.
Illustrative matrix, computed with numpy, not from the lesson.
5. What is a right singular vector?Define
A 2 by 2 matrix V with its first column highlighted, labeled as one right singular vector of the illustrative matrix.
Illustrative matrix, computed with numpy, not from the lesson.
6. Using the SVD A = U * Sigma * V^T, how is the Moore-Penrose pseudoinverse A+ computed, and what is it used for?Define
A 3 by 2 matrix times an unknown vector x equal to [3, 5, 6], with a note that no exact solution exists.
From the lesson's own pseudoinverse example.
7. In terms of SVD, what is the rank of a matrix?Define
A 2 by 2 matrix B whose Sigma factor has one diagonal entry of about 5 and a second diagonal entry that is essentially zero.
Illustrative matrix, computed with numpy, not from the lesson.
8. What is the condition number of a matrix in terms of singular values?Define
Two boxes showing the illustrative matrix's largest singular value, 3.26, and smallest singular value, 1.84.
Illustrative matrix, computed with numpy, not from the lesson.
9. What is a latent factor in the low-rank space SVD finds?Define
A ratings matrix with missing cells marked by question marks, next to a blank two-axis space with a few unlabeled points representing users or movies.
10. What is the Frobenius norm of a matrix?Define
A 2 by 2 matrix with entries 3, 1, 0, and 2, with a note that a total-size measure can be built from them.
Illustrative matrix, not from the lesson.
11. What does the Eckart-Young theorem guarantee?Define
Two candidate rank-k matrices approximating the same original matrix, truncated SVD on one side and some other rank-k matrix B on the other, with a question mark between them.
12. In SVD A = U * Sigma * V^T, what geometric operation does each factor represent?Explain
A flow diagram: input space, two blank question-mark steps, and output space, with nothing filled in yet.
13. What does the equation A * v_i = sigma_i * u_i tell you?Explain
A vector v drawn in one box, an arrow labeled A pointing to a second box, with the vector A times v drawn there, longer than v.
Illustrative matrix, computed with numpy, not from the lesson.
14. What does writing the SVD as the sum sigma_1*u_1*v_1^T + sigma_2*u_2*v_2^T + ... reveal?Explain
A square filled in once by a big layer, then again by a smaller nested layer on top, labeled term 1 plus term 2 plus more terms.
15. How are the right singular vectors V of A related to eigendecomposition?Explain
A flow diagram: A, then A transpose A, then eigen-decompose, ending in a blank question-mark box.
16. Why are singular values always real and non-negative?Explain
The illustrative matrix A and the matrix A transpose A, with its two off-diagonal entries highlighted and equal, showing it is symmetric.
Illustrative matrix, computed with numpy, not from the lesson.
17. Why does truncating an image's SVD at rank 50 often look almost identical to the original?Explain
A bar chart of 8 singular values for a structured pattern, dropping from a tall first bar down to nearly flat by the fifth or sixth bar.
Computed with numpy from the lesson's sin-times-cos signal, plus a touch of noise.
18. Why does sklearn implement PCA using SVD instead of eigendecomposition of the covariance matrix?Explain
A flow diagram: centered data X branching into two paths, one forming the covariance matrix then eigen-decomposing it, the other running SVD on X directly.
19. You're building a movie recommender where most user-movie rating cells are empty. After filling the gaps with row means and running SVD, how does the resulting low-rank reconstruction predict a rating a user never gave?Explain
The user-movie ratings table with User2's Movie1 cell outlined, asking how a low-rank model would predict that missing rating.
20. In Latent Semantic Analysis, what does applying SVD with rank k=2 to a term-document matrix do?Explain
A table of word counts for five terms, cat, dog, fish, pet, and ocean, across four documents, with a note that SVD maps terms and documents into a shared 2D space.
21. A matrix has singular values 4, 3, 2, 1. What is the spectral-norm error of its best rank-2 approximation?Apply
A bar chart of four singular values, 4, 3, 2, and 1, with the first two bars kept and the last two dropped, asking for the spectral-norm error of the rank-2 approximation.
22. A matrix has singular values 4, 3, 2, 1. What is the Frobenius-norm error of its best rank-2 approximation?Apply
A bar chart of the same four singular values, 4, 3, 2, and 1, with the first two bars kept and the last two dropped, asking for the Frobenius-norm error of the rank-2 approximation.
23. An 800x600 image (480,000 values) is compressed with SVD at rank k. How many values are stored, and why is that smaller?Apply
An 800 by 600 image labeled as 480,000 pixel values, with a note that rank-k SVD stores only the truncated U, Sigma, and V pieces instead.
24. A is a 3 x 2 matrix with SVD A = U * Sigma * V^T. What are the shapes of A+ and of x = A+ b when b has 3 entries?Apply
A 3 by 2 matrix A in its SVD with U and Sigma shapes left blank and V transpose given as 2 by 2, asking for the shapes of the pseudoinverse and its product with a 3-entry vector.
25. Sigma has non-zero diagonal entries 4 and 2, then a 0. What are the diagonal entries of Sigma+ (before accounting for its transposed shape)?Apply
A 3 by 3 diagonal matrix Sigma with entries 4, 2, and 0 highlighted, asking for the diagonal entries of its pseudoinverse Sigma plus.
26. A has singular values [1000, 1, 0.001]. What is the condition number of the matrix A^T A you would decompose when computing SVD through eigendecomposition?Apply
Three boxes showing A's singular values, 1000, 1, and 0.001, with A's own condition number given as 1,000,000, asking for the condition number of A transpose A.
27. You run SVD on a noisy sensor matrix and its singular values drop off sharply after the third one, then stay small and flat. What does this pattern tell you, and how should you clean up the signal?Analyze
A bar chart of 8 singular values for a sensor matrix, with the first three bars noticeably taller than the rest, which sit close together.
Illustrative matrix, computed with numpy.
28. A teammate computes PCA by eigendecomposing the covariance matrix. When one feature sits at a much smaller scale, the components are off in the fourth decimal place. Which cause best explains it?Analyze
A small table with feature A in the hundreds and feature B under 1, illustrating one feature sitting at a much smaller scale than the other.
Illustrative numbers, not from the lesson.
29. You must fit a least-squares model whose design matrix is nearly rank-deficient and accuracy matters. Which approach fits?Design
A bar chart of three singular values for a design matrix, two clearly sized bars and a third bar that is nearly invisible, almost zero.
Illustrative matrix, computed with numpy.
30. You are building a search system and want a query for 'dog' to also surface documents that only say 'cat' and 'pet'. Which design fits the lesson's ideas?Design
The term-document count table with the dog row highlighted, asking how a query for dog could also surface documents that only mention cat and pet.
31. Your ratings matrix is huge and almost entirely empty, so filling every gap with row means feels crude. Which approach does the lesson suggest in practice?Design
A large grid of cells representing users and movies, almost all of them blank, with only a scattering of cells holding a rating.
Illustrative scale, not an exact matrix.
32. You have to denoise a data matrix and do not know the true signal rank. Which plan is most consistent with the lesson?Design
A bar chart of 10 singular values that decrease gradually from the first few down to a lower, fairly even level, with no single sharp cutoff marked.
Illustrative matrix, computed with numpy.

Build It

Step 1: SVD from scratch using power iteration

The idea: find the largest singular value and its vectors with power iteration on A^T A (or A A^T), then deflate the matrix and repeat for the next one.

python
import numpy as np
 
def power_iteration(M, num_iters=100):
    n = M.shape[1]
    v = np.random.randn(n)
    v = v / np.linalg.norm(v)
 
    for _ in range(num_iters):
        Mv = M @ v
        v = Mv / np.linalg.norm(Mv)
 
    eigenvalue = v @ M @ v
    return eigenvalue, v
 
def svd_from_scratch(A, k=None):
    m, n = A.shape
    if k is None:
        k = min(m, n)
 
    sigmas = []
    us = []
    vs = []
 
    A_residual = A.copy().astype(float)
 
    for _ in range(k):
        AtA = A_residual.T @ A_residual
        eigenvalue, v = power_iteration(AtA, num_iters=200)
 
        if eigenvalue < 1e-10:
            break
 
        sigma = np.sqrt(eigenvalue)
        u = A_residual @ v / sigma
 
        sigmas.append(sigma)
        us.append(u)
        vs.append(v)
 
        A_residual = A_residual - sigma * np.outer(u, v)
 
    U = np.column_stack(us) if us else np.empty((m, 0))
    S = np.array(sigmas)
    V = np.column_stack(vs) if vs else np.empty((n, 0))
 
    return U, S, V

Step 2: Test and compare with NumPy

python
np.random.seed(42)
A = np.random.randn(5, 4)
 
U_ours, S_ours, V_ours = svd_from_scratch(A)
U_np, S_np, Vt_np = np.linalg.svd(A, full_matrices=False)
 
print("Our singular values:", np.round(S_ours, 4))
print("NumPy singular values:", np.round(S_np, 4))
 
A_reconstructed = U_ours @ np.diag(S_ours) @ V_ours.T
print(f"Reconstruction error: {np.linalg.norm(A - A_reconstructed):.8f}")

Step 3: Image compression demo

python
def compress_image_svd(image_matrix, k):
    U, S, Vt = np.linalg.svd(image_matrix, full_matrices=False)
    compressed = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
    return compressed
 
image = np.random.seed(42)
rows, cols = 200, 300
image = np.random.randn(rows, cols)
 
for k in [1, 5, 10, 20, 50]:
    compressed = compress_image_svd(image, k)
    error = np.linalg.norm(image - compressed) / np.linalg.norm(image)
    original_size = rows * cols
    compressed_size = k * (rows + cols + 1)
    ratio = compressed_size / original_size
    print(f"k={k:>3d}  error={error:.4f}  storage={ratio:.1%}")

Step 4: Noise reduction

python
np.random.seed(42)
clean = np.outer(np.sin(np.linspace(0, 4*np.pi, 100)),
                 np.cos(np.linspace(0, 2*np.pi, 80)))
noise = 0.3 * np.random.randn(100, 80)
noisy = clean + noise
 
U, S, Vt = np.linalg.svd(noisy, full_matrices=False)
denoised = U[:, :5] @ np.diag(S[:5]) @ Vt[:5, :]
 
print(f"Noisy error:    {np.linalg.norm(noisy - clean):.4f}")
print(f"Denoised error: {np.linalg.norm(denoised - clean):.4f}")
print(f"Improvement:    {(1 - np.linalg.norm(denoised - clean) / np.linalg.norm(noisy - clean)):.1%}")

Step 5: Pseudoinverse

python
A = np.array([[1, 1], [2, 1], [3, 1]], dtype=float)
b = np.array([3, 5, 6], dtype=float)
 
U, S, Vt = np.linalg.svd(A, full_matrices=False)
S_inv = np.diag(1.0 / S)
A_pinv = Vt.T @ S_inv @ U.T
 
x_svd = A_pinv @ b
x_lstsq = np.linalg.lstsq(A, b, rcond=None)[0]
x_pinv = np.linalg.pinv(A) @ b
 
print(f"SVD pseudoinverse solution:  {x_svd}")
print(f"np.linalg.lstsq solution:   {x_lstsq}")
print(f"np.linalg.pinv solution:    {x_pinv}")
Check: Build It0/8
1. What does power iteration converge to when it is repeatedly applied to A^T A?Define
A flow diagram: a random vector, multiplied by a matrix, normalized, and repeated many times, ending in a question mark.
2. In svd_from_scratch, after the first singular triple is found, the code sets A_residual = A_residual - sigma * np.outer(u, v). What is the largest singular value of the new A_residual?Apply
The lesson's own deflation code: computing one singular triple, then subtracting sigma times the outer product of u and v from the residual matrix.
3. The test uses np.random.randn(5, 4) and calls svd_from_scratch(A) with k=None. How many singular values does S_ours contain?Apply
The lesson's own test code: seeding numpy, building a 5 by 4 random matrix, and calling svd_from_scratch with k left as None.
4. In the image demo, the matrix is 200 x 300 and k=20. What does the printed storage show?Apply
The lesson's own image-compression loop over rank values 1, 5, 10, 20, and 50, for a 200 by 300 matrix, with k equal to 20 called out.
5. In the pseudoinverse step, x_svd, x_lstsq, and x_pinv are all computed for the same A and b. How do the three printed solutions compare?Apply
The lesson's own three ways of solving the overdetermined system: its own pseudoinverse, numpy's lstsq, and numpy's built-in pinv.
6. The image demo compresses np.random.randn(200, 300), yet at k=50 the relative error is still large, unlike with a real photo. What is the best diagnosis?Analyze
A random 200 by 300 matrix compressed at rank 50 with a relative error of 0.67, next to a note that a real photo at the same rank usually looks nearly identical.
Computed with numpy.
7. svd_from_scratch is called on a 6 x 5 matrix of rank 2 with k=None, and returns only 2 singular values. Which line explains this?Analyze
The lesson's svd_from_scratch code with the k = min(m, n) line and the eigenvalue-under-1e-10 break both shown, asking which line explains why only 2 singular values come back for a rank-2 matrix.
8. Someone reuses the pseudoinverse code, S_inv = np.diag(1.0 / S), on a rank-deficient A with one singular value that is exactly 0. What goes wrong?Analyze
A code snippet dividing 1 by every entry of S, including a zero entry, marked with a question mark.

Use It

Full working demos live in code/svd.py. Run it and you'll see SVD applied back to back: image compression, recommendation systems, latent semantic analysis, noise reduction.

bash
python svd.py

The Julia version in code/svd.jl runs the same four demos through Julia's native svd() function and its LinearAlgebra package.

bash
julia svd.jl

Ship It

This lesson produces:

  • outputs/skill-svd.md - a skill for knowing when to reach for SVD in a real project, and how to apply it once you do
Author's Note

The numerical stability warning above isn't academic. The first time it cost me real debugging time, I'd computed PCA by hand: eigendecompose the covariance matrix, take the top eigenvectors, done. It worked fine until one feature happened to sit at a much smaller scale than the rest, and the components I got back were subtly off, not garbage, just wrong enough in the fourth decimal place to make a downstream model twitchy in a way that took a full afternoon to trace back to the PCA step. Switching to np.linalg.svd on the centered data directly fixed it immediately, because SVD never forms A^T A and never squares that condition number in the first place. If you only carry one operational habit out of this lesson, make it this: reach for SVD before you reach for eigendecomposition, even when they're mathematically equivalent on paper. On real data, they aren't equivalent in practice.

Exercises

  1. Implement the full SVD from scratch without using power iteration. Instead, compute the eigendecomposition of A^T A to get V and the singular values, then compute U = A V Sigma^{-1}. Compare numerical accuracy with your power iteration version and with NumPy.

  2. Load a real grayscale image (or convert one to grayscale). Compress it at ranks 1, 5, 10, 25, 50, 100. For each rank, compute the compression ratio and the relative error. Find the rank where the image becomes visually acceptable.

  3. Build a tiny recommendation system. Create a 10x8 user-movie ratings matrix with some known entries. Fill missing entries with row means. Compute SVD and reconstruct a rank-3 approximation. Use the reconstructed matrix to predict the missing ratings. Verify that the predictions are reasonable.

  4. Create a 100x50 document-term matrix with 3 synthetic topics. Each topic has 5 associated terms. Add noise. Apply SVD and verify that the top 3 singular values are much larger than the rest. Project documents into the 3D latent space and check that documents from the same topic cluster together.

  5. Generate a clean low-rank matrix (rank 3, size 50x40) and add Gaussian noise at different levels (sigma = 0.1, 0.5, 1.0, 2.0). For each noise level, find the optimal truncation rank by sweeping k from 1 to 40 and measuring reconstruction error against the clean matrix. Plot how the optimal k changes with noise level.

Key Terms

TermWhat people sayWhat it actually means
SVD"Factor any matrix"Decompose A into U Sigma V^T where U and V are orthogonal and Sigma is diagonal with non-negative entries. Works for any matrix of any shape.
Singular value"How important this component is"The i-th diagonal entry of Sigma. Measures how much the matrix stretches along the i-th principal direction. Always non-negative, sorted in decreasing order.
Left singular vector"Output direction"A column of U. The direction in output space that the i-th right singular vector maps to (after scaling by sigma_i).
Right singular vector"Input direction"A column of V. The direction in input space that the matrix maps to the i-th left singular vector (after scaling by sigma_i).
Truncated SVD"Low-rank approximation"Keep only the top k singular values and their vectors. Produces the provably best rank-k approximation to the original matrix (Eckart-Young theorem).
Rank"True dimensionality"The number of non-zero singular values. Tells you how many independent directions the matrix actually uses.
Pseudoinverse"Generalized inverse"V Sigma+ U^T. Inverts non-zero singular values, leaves zeros as zeros. Solves least-squares problems for non-square or singular matrices.
Condition number"How sensitive to errors"sigma_max / sigma_min. A large condition number means small input changes cause large output changes. SVD reveals this directly.
Latent factor"Hidden variable"A dimension in the low-rank space discovered by SVD. In recommendations, a latent factor might correspond to genre preference. In NLP, it might correspond to a topic.
Frobenius norm"Total matrix size"Square root of the sum of squared entries. Equals the square root of the sum of squared singular values. Used to measure approximation error.
Eckart-Young theorem"SVD gives the best compression"For any target rank k, the truncated SVD minimizes the approximation error over all possible rank-k matrices.
Power iteration"Find the biggest eigenvector"Repeatedly multiply a random vector by the matrix and normalize. Converges to the eigenvector with the largest eigenvalue. The building block of many SVD algorithms.

Further Reading

Code

Ships With