Chapter 11 Singular Value Decomposition and Its Applications
The history of the singular value decomposition (SVD) shows how theory, computation, and applications push one another forward.
In 1873 the Italian mathematician Eugenio Beltrami studied real bilinear forms; in 1874 the French mathematician Camille Jordan obtained related results. Erhard Schmidt studied integral operators and approximation problems in 1907, Carl Eckart and Gale Young established the result on the best low-rank approximation of a matrix in 1936, and Mirsky extended the approximation theory to unitarily invariant norms in 1960. These results dealt separately with the existence of the decomposition, with operators, and with best approximation, and together they form a theory that developed step by step; without historical evidence, one should not claim that these scholars were unaware of each other or that their results were repeatedly forgotten. History and original sources
Beyond the theory, computational methods are just as essential. The SVD can be built by finding the eigenvalues of A∗A, but forming this Gram matrix directly may lose relative accuracy in the small singular values. For a matrix of full column rank, κ2(A∗A)=κ2(A)2; this is a matter of numerical conditioning, and it does not mean that every input error is amplified quadratically.
Algorithms did not first appear in 1970. Jacobi-type methods already existed in the 1950s; Golub and Kahan developed the bidiagonalization approach in 1965, and Golub and Reinsch went on in 1970 to give algorithms for the SVD and for least squares. These methods avoid making the explicit formation of the Gram matrix a required step, and they laid an important foundation for reliable numerical computation. Hestenes 1958, Golub–Kahan 1965
The teaching power of the SVD comes from a clear geometric picture: once orthonormal bases are chosen for the input and the output, Avi=σiui (i<r), and the remaining input basis vectors fall into the null space. The SVD provides compatible orthogonal coordinates for the four fundamental subspaces; the nonzero singular values give the stretching factors—the SVD does not turn every vector into another unit vector.
Applications have also carried these ideas beyond the classroom. The Netflix Prize spurred regularized low-rank factor models for ratings with missing entries; these models share the low-rank viewpoint of the SVD, but they do not fill in the missing values arbitrarily and then run an ordinary SVD, nor are they the same as exact nuclear-norm completion. PCA, the pseudoinverse, and data compression have made singular values a common language of data analysis.
This line of development reminds us that theoretical correctness, computational feasibility, and the needs of applications together determine how much power a mathematical tool can exert.
This chapter follows the path of theory, computation, and applications, breaking the SVD into material that can be understood section by section.
§11.1 first answers the most fundamental question: what exactly does this theorem say, and why does it hold for every matrix? We rigorously prove the existence of the decomposition A=UΣV∗ and examine its meaning again and again from three angles: algebra, subspaces, and geometry. A∗A is the bridge connecting the SVD with the spectral theorem of Chapter 9; under the SVD, each of the four fundamental subspaces receives an orthonormal basis; and the geometric picture of “rotation—stretch—rotation” makes the action of a matrix clear at a glance. By the end of this section you will hold a key that opens the door to every application that follows.
In §11.2 this key faces its first test: in what sense is the low-rank approximation given by the truncated SVD “the best”? This is precisely the question Eckart and Young studied in 1936. Three matrix norms—the spectral norm, the Frobenius norm, and the nuclear norm—are all completely determined by the singular values, and the theorem’s answer is clean and decisive: among all matrices of the same rank bound, the truncated SVD has the smallest error, and the discarded singular values tell exactly how much is lost. This is the theoretical peak of the chapter, and the three application sections that follow are all its echoes.
§11.3 brings this optimality into the setting of data science. The “directions that explain the most variation in the data” sought by principal component analysis (PCA) are, in the language of the SVD, the right singular vectors; numerically, performing the SVD directly on the data matrix rather than on the covariance matrix avoids the very problem Golub worked to solve: the condition number being quietly squared during the computation. A complete demonstration on the Iris dataset takes this procedure from formulas into practice.
§11.4 changes the question from “finding directions” to “solving equations”: rectangular or singular matrices have no inverse, but the Moore–Penrose pseudoinverse, through the SVD, gracefully extends the meaning of “inversion” and provides the geometrically most natural solution to least-squares problems. As you read this section, heed the warning that “the reciprocals of small singular values blow up”—it leads to ridge regression regularization, and it also gives you your first reliable tool for handling any ill-conditioned linear problem in the future.
§11.5 then carries the SVD to an unexpected shore. Quantum entanglement—what Einstein called “spooky action at a distance”—is, mathematically, exactly a singular value problem for the coefficient matrix of a bipartite pure state. The decomposition Schmidt built for integral operators in 1907 became, after his death, a core language of quantum information theory, which is probably one of the most surprising “late applications” in the history of mathematics. This section is a mathematical deepening of the quantum mechanics framework of Chapter 10, and also the book’s final statement of the theme of “the deep unity of linear algebra and the physical world.”
Having read the whole chapter, you will find that singular values are not isolated numbers but a ranking of the “signal strength” of a matrix in each direction—truncate them, and you are compressing; invert them, and you are solving; measure them, and you are quantifying entanglement. The same set of numbers ties together three worlds—mathematics, data, and physics—and also the long development of theory and applications from 1873 to the present day.
11.1 Definition and Proof of the Singular Value Decomposition¶
From Diagonalization to the SVD: Breaking the Square-Matrix Barrier¶
In Chapter 8 we witnessed the power of diagonalization: for a diagonalizable square matrix A∈Kn×n, there exists an invertible matrix P such that
where Λ is a diagonal matrix whose diagonal entries are the eigenvalues of A. This decomposition reveals the essential structure of the matrix—in a suitable basis, the linear transformation is nothing more than independent stretching along each direction.
Chapter 9 went further: for a symmetric matrixA=A⊤ (real field) or a Hermitian matrixA=A∗ (complex field), the situation is even better—we can choose an orthogonal matrixQ (or a unitary matrixU) to diagonalize it:
This is the spectral theorem—the orthogonal diagonalization of symmetric/Hermitian matrices. The beauty of orthogonal diagonalization is that the transformations Q and Q⊤ are both geometric rotations (they preserve lengths and angles), so the whole linear transformation can be understood as “rotate—stretch—rotate back.”
But the real world presents a harsh fact: the vast majority of important matrices are neither square nor symmetric.
Consider the following practical scenarios:
Image matrices: for a 1920×1080 picture, the matrix of pixel values is rectangular
Data matrices: recording the ratings of 1000 customers on 50 products gives a 1000×50 matrix, far from square
Document-term matrices: 10000 documents containing 5000 terms give a 10000×5000 matrix
Clearly these matrices cannot use the eigendecomposition directly. We need a more general decomposition that can handle an arbitrary m×n matrix.
By the spectral theorem of Chapter 9, A∗A and AA∗ can both be orthogonally diagonalized, and all their eigenvalues are nonnegative real numbers. This is the theoretical foundation of the SVD.
Historical note. The work on bilinear forms by the Italian mathematician Beltrami in 1873 and the French mathematician Camille Jordan in 1874 is the early source of the SVD. Schmidt studied integral operators and approximation theory in 1907; Eckart and Young gave the result on the best low-rank approximation of a matrix in 1936, and Mirsky extended it to unitarily invariant norms in 1960. These works developed, respectively, the theory of the decomposition, of operators, and of approximation; they should not be lumped together as a single theorem that was repeatedly forgotten.
11.1.1 Definition of the SVD: The Real and Complex Cases¶
Uniqueness theorem. For a given matrix A, its singular values are uniquely determined (counting multiplicity). The singular vectors, however, are not unique.
When m>n, the lower part of Σ has many all-zero rows, and the corresponding last m−n columns of U do not actually take part in the matrix multiplication. We can omit these redundant parts:
where uivi∗ is a rank-one matrix (also called a dyad or an outer product).
Geometric interpretation. Every matrix A can be decomposed into a weighted sum of r rank-one matrices, with the singular values σi as weights. These rank-one components are arranged in decreasing order of importance (size of the singular value), so:
σ0u0v0∗ is the “most important” component of A
σ1u1v1∗ is the next most important component
and so on
import numpy as np
# NumPy print format: 4 decimal places, suppress scientific notation for tiny values
np.set_printoptions(precision=4, suppress=True, linewidth=100)
def print_header(title):
print(f"\n{'='*60}")
print(f" {title}")
print(f"{'='*60}")
def print_step(step, desc):
print(f"\n▶ Step {step}: {desc}")
print("-" * 30)
def compare_print(label, actual, expected_desc):
"""Show the computed result next to the expected description for readability"""
print(f"[{label} computed]:")
print(actual)
print(f"[{label} expected]: {expected_desc}")
# ============================================================
# Example: SVD of a 2×2 matrix
# ============================================================
print_header("Example: Verifying the SVD of a 2×2 Matrix")
A = np.array([[3, 0],
[4, 5]], dtype=float)
print(f"Original matrix A:\n{A}")
# --- Step 1 ---
print_step(1, "Compute $A^T A$")
ATA = A.T @ A
compare_print("A^T A", ATA, "[[25, 20], [20, 25]]")
# --- Step 2 ---
print_step(2, "Solve for the eigenvalues and singular values")
eigenvalues, eigenvectors = np.linalg.eigh(ATA)
# sort: largest to smallest
idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx]
V = eigenvectors[:, idx]
sigma = np.sqrt(eigenvalues)
print(f"Eigenvalues (λ): {eigenvalues}")
print(f"Singular values (σ): {sigma}")
print(f"Expected σ: [3√5, √5] ≈ [6.7082, 2.2361]")
# --- Step 3 ---
print_step(3, "Matrix of right singular vectors V")
compare_print("V", V, "1/√2 * [[1, 1], [1, -1]]")
# --- Step 4 ---
print_step(4, "Compute the left singular vectors u_i = (1/σ_i) A v_i")
u0 = A @ V[:, 0] / sigma[0]
u1 = A @ V[:, 1] / sigma[1]
U = np.column_stack([u0, u1])
print(f"u0: {u0} (expected 1/√10 * [1, 3])")
print(f"u1: {u1} (expected 1/√10 * [3, -1])")
print(f"\nMatrix of left singular vectors U:\n{U}")
# --- Step 5 ---
print_step(5, "Verify the reconstruction A = U Σ V^T")
Sigma = np.diag(sigma)
A_reconstructed = U @ Sigma @ V.T
print(f"Reconstruction:\n{A_reconstructed}")
print(f"Matches A: {np.allclose(A_reconstructed, A)}")
# --- Step 6 ---
print_step(6, "Outer-product expansion")
A_outer = (sigma[0] * np.outer(u0, V[:, 0]) +
sigma[1] * np.outer(u1, V[:, 1]))
print(f"Sum of outer products:\n{A_outer}")
# ============================================================
print_header("Example: SVD of a Rank-One Matrix")
# ============================================================
A2 = np.array([[2, 4],
[1, 2],
[3, 6]], dtype=float)
# --- Step 1: display the matrix and observe its linear dependence ---
print_step(1, "Observe the matrix structure: the geometric meaning of rank one")
print("A =\n", A2)
print(f" col_0(A) = {A2[:, 0]}")
print(f" col_1(A) = {A2[:, 1]}")
print(f" col_1 = 2 × col_0? → {np.allclose(A2[:, 1], 2 * A2[:, 0])}")
print(" ∴ rank(A) = 1; the two columns are collinear, so we expect only 1 nonzero singular value.")
# --- Step 2: compute A^T A ---
print_step(2, "Compute $A^T A$")
ATA2 = A2.T @ A2
print("A^T A =\n", ATA2)
compare_print(
label = "A^T A",
actual = ATA2,
expected_desc = "14 × [[1,2],[2,4]]"
)
print("Expected =\n", 14 * np.array([[1, 2], [2, 4]]))
# --- Step 3: eigenvalues → singular values σᵢ = √λᵢ ---
print_step(3, "Find the eigenvalues and singular values $\\sigma_i = \\sqrt{\\lambda_i}$")
eigenvalues2, eigenvectors2 = np.linalg.eigh(ATA2)
idx2 = np.argsort(eigenvalues2)[::-1] # descending order
eigenvalues2 = eigenvalues2[idx2]
eigenvectors2 = eigenvectors2[:, idx2]
print(f" λ₀ = 14 × (1² + 2²) = 14 × 5 = {14 * 5}")
print(f" λ₁ = 0 (a rank-one matrix must have n-1 zero eigenvalues)")
compare_print(
label = "eigenvalues [λ₀, λ₁]",
actual = np.round(eigenvalues2, 10),
expected_desc= "[70, 0]"
)
sigma2 = np.sqrt(eigenvalues2[0])
print(f" σ₀ = √70 ≈ {sigma2:.6f}")
# --- Step 4: right singular vector v₀ ---
print_step(4, "Right singular vector $\\mathbf{v}_0$ (unit eigenvector of $A^T A$)")
v0 = eigenvectors2[:, 0]
compare_print(
label = "v₀",
actual = v0,
expected_desc= "1/√5 × [1, 2]"
)
print(f" Expected: {np.array([1, 2]) / np.sqrt(5)}")
# --- Step 5: left singular vector u₀ = (1/σ₀) A v₀ ---
print_step(5, "Left singular vector $\\mathbf{u}_0 = \\frac{1}{\\sigma_0} A \\mathbf{v}_0$")
u0_2 = A2 @ v0 / sigma2
compare_print(
label = "u₀",
actual = u0_2,
expected_desc= "1/√14 × [2, 1, 3]"
)
print(f" Expected: {np.array([2, 1, 3]) / np.sqrt(14)}")
# --- Step 6: verify the SVD: A = σ₀ u₀ v₀^T (rank-one expansion) ---
print_step(6, "Verify the SVD reconstruction: $A = \\sigma_0 \\, \\mathbf{u}_0 \\mathbf{v}_0^T$")
A2_reconstructed = sigma2 * np.outer(u0_2, v0)
print("Reconstruction =\n", np.round(A2_reconstructed, 10))
print("Original A =\n", A2)
print(f" Reconstruction correct? → {np.allclose(A2_reconstructed, A2)}")
coeff_check = np.sqrt(70) / np.sqrt(14) / np.sqrt(5)
print(f"\n Coefficient check: the factor in front of the outer product σ₀/(√14·√5) = √70 / √14 / √5 = {coeff_check:.6f} (expected 1.0)")
# --- Step 7: cross-check with NumPy SVD ---
print_step(7, "Cross-check with NumPy SVD")
U_np2, s_np2, Vt_np2 = np.linalg.svd(A2, full_matrices=False)
compare_print(
label = "singular values s",
actual = s_np2,
expected_desc= "[√70, 0]"
)
print(f" Expected: [{np.sqrt(70):.6f}, 0.000000]")
print(f" NumPy reconstruction correct? → {np.allclose(U_np2 @ np.diag(s_np2) @ Vt_np2, A2)}")
11.1.2 The SVD and the Four Fundamental Subspaces¶
Recall from Chapter 6 that every matrix A∈Cm×n defines four fundamental subspaces:
Here Row(A) is spanned by row vectors written horizontally; to place it in the input space of upright (column) vectors we must take the conjugate transpose, obtaining Col(A∗)—an ordinary transpose is not enough. The corresponding two orthogonal decompositions are
Let A=UΣV∗ be the SVD of a matrix of rank r, and let vi be the i-th column of V. Then:
import numpy as np
np.set_printoptions(precision=4, suppress=True, linewidth=100)
# ── Helper functions ────────────────────────────────────────────────────────────────
def print_header(title):
print("\n" + "=" * 60)
print(f" {title}")
print("=" * 60)
def print_step(step, desc):
print(f"\n▶ Step {step}: {desc}")
print("-" * 40)
def compare_print(label, actual, expected_desc):
print(f" {label}")
print(f" computed : {actual}")
print(f" expected : {expected_desc}")
# ============================================================
print_header("Example: Verifying the Four Fundamental Subspaces with the SVD")
# ============================================================
A = np.array([[1, 1, 0],
[0, 1, 1]], dtype=float)
# --- Step 1 ---
print_step(1, "Define the matrix A and compute rank(A)")
print(f" A =\n{A}")
compare_print("rank(A)", np.linalg.matrix_rank(A), "2")
# --- Step 2 ---
print_step(2, "Null space Null(A): solve Ax = 0")
null_vec = np.array([1, -1, 1]) / np.sqrt(3)
print(f" Null-space basis v₂ = [1, -1, 1] / √3 = {null_vec}")
compare_print("A @ v₂", np.round(A @ null_vec, 10), "0 (should be the zero vector)")
# --- Step 3 ---
print_step(3, "Compute A^T A and compare with the formula")
ATA = A.T @ A
print(f" A^T A =\n{ATA}")
print(f" Expected:\n [[1,1,0],[1,2,1],[0,1,1]]")
# --- Step 4 ---
print_step(4, "Eigendecomposition of A^T A (descending order)")
eigenvalues, eigenvectors = np.linalg.eigh(ATA) # eigh returns ascending order
eigenvalues = eigenvalues[::-1] # switch to descending λ₀ ≥ λ₁ ≥ λ₂
eigenvectors = eigenvectors[:, ::-1]
compare_print("eigenvalues [λ₀, λ₁, λ₂]",
np.round(eigenvalues, 10),
"[3, 1, 0]")
sigma = np.sqrt(eigenvalues[:2]) # nonzero singular values σᵢ = √λᵢ
compare_print("singular values [σ₀, σ₁]",
np.round(sigma, 6),
"[√3, 1] ≈ [1.7321, 1.0000]")
# --- Step 5 ---
print_step(5, "Compare with the right singular vectors vᵢ in the text (bases of Row(A) and ker(A))")
v0_doc = np.array([ 1, 2, 1]) / np.sqrt(6)
v1_doc = np.array([ 1, 0, -1]) / np.sqrt(2)
v2_doc = np.array([ 1, -1, 1]) / np.sqrt(3)
V = eigenvectors # computed [v₀ | v₁ | v₂]
for i, (v_doc, label) in enumerate([(v0_doc, "v₀"), (v1_doc, "v₁"), (v2_doc, "v₂")]):
v_calc = V[:, i]
consistent = np.isclose(abs(np.vdot(v_calc, v_doc)) / (np.linalg.norm(v_calc) * np.linalg.norm(v_doc)), 1.0)
print(f"\n {label} text = {v_doc}")
print(f" computed = {v_calc}")
print(f" same direction: {consistent}")
print(f"\n Check A @ v₂ ≈ 0 : {np.allclose(A @ v2_doc, 0)}")
# --- Step 6 ---
print_step(6, "Compute the left singular vectors uᵢ = (1/σᵢ) A vᵢ (basis of Col(A))")
u0 = A @ V[:, 0] / sigma[0]
u1 = A @ V[:, 1] / sigma[1]
U = np.column_stack([u0, u1])
print(f" U = [u₀ | u₁] =\n{np.round(U, 6)}")
# --- Step 7 ---
print_step(7, "Verify the reconstruction: A = U Σ V^T")
Sigma = np.diag(sigma)
A_rec = U @ Sigma @ V[:, :2].T
print(f" U Σ V^T =\n{np.round(A_rec, 10)}")
compare_print("reconstruction correct", np.allclose(A_rec, A), "True")
# --- Step 8 ---
print_step(8, "Cross-check with NumPy SVD")
U_np, s_np, Vt_np = np.linalg.svd(A, full_matrices=True)
compare_print("NumPy singular values [σ₀, σ₁]",
np.round(s_np, 6),
f"[√3, 1] ≈ {np.round(sigma, 6)}")
A_np_rec = U_np @ np.diag([s_np[0], s_np[1]]) @ Vt_np[:2]
compare_print("NumPy reconstruction correct", np.allclose(A_np_rec, A), "True")
11.1.3 Geometric Intuition: From the Unit Sphere to an Ellipsoid¶
The most beautiful geometric interpretation of the SVD is this: every linear transformation maps the unit sphere to an ellipsoid, and the SVD describes precisely the directions and lengths of the principal axes of this ellipsoid.
Question. What is the geometric shape of this image set?
Answer. It is an ellipsoid in Rm! More precisely, it is an r-dimensional ellipsoid (where r=rank(A)), embedded in an r-dimensional subspace of the m-dimensional space (namely the image Col(A)).
V⊤ is an orthogonal matrix, corresponding to a rigid rotation (or reflection). It rotates x from the original coordinate system into the coordinate system of the right singular vectors{v0,v1,…,vn−1}:
In the end, the directions of the principal axes of the ellipsoid are the left singular vectors u0,u1,…,ur−1, and the lengths of the principal axes are the corresponding singular values σ0,σ1,…,σr−1.
The Variational Viewpoint: Directions of Maximal Stretching¶
Another way to understand the SVD is through optimization: the singular vectors are the directions in which A produces the largest/smallest stretching.
Geometric meaning:
v0 is the direction that A stretches the most
when r>0, among the unit vectors in ker(A)⊥, vr−1 attains the smallest stretching σr−1
vr,…,vn−1 are compressed to zero by A
This variational viewpoint plays a central role in principal component analysis (PCA, §11.3)—PCA is essentially a search for the directions of greatest variation in the data, and these directions are exactly the right singular vectors of the data matrix!
import numpy as np
import matplotlib.pyplot as plt
# --- Color scheme: book palette ---
C_BG, C_GRID, C_AXIS = "#F8F8F8", "#D6D6D6", "#000000"
C_V1, C_V2, C_T1, C_WARN = "#57068C", "#006385", "#2AD2C9", "#FF5D47"
# Fonts: the English edition needs no CJK font, so nothing is downloaded when _lang == 'en';
# the Chinese editions use this same block to fetch Noto Sans TC/SC where no CJK font is installed
import os, urllib.request
import matplotlib.font_manager as fm
_lang = 'en'
_cjk = ['Microsoft JhengHei', 'PingFang TC', 'Noto Sans CJK TC', 'Noto Sans TC']
_have = {f.name for f in fm.fontManager.ttflist}
if _lang != 'en' and not _have & set(_cjk):
_font = os.path.join(os.path.expanduser('~'), '.cache', 'fonts', 'NotoSansTC.ttf')
try:
if not os.path.exists(_font):
os.makedirs(os.path.dirname(_font), exist_ok=True)
urllib.request.urlretrieve('https://github.com/google/fonts/raw/main/ofl/notosanstc/NotoSansTC%5Bwght%5D.ttf', _font + '.part')
os.replace(_font + '.part', _font)
fm.fontManager.addfont(_font)
_have.add('Noto Sans TC')
except OSError as err:
print('Could not download the CJK font; Chinese text in figures may not display:', err)
plt.rcParams['font.family'] = [f for f in _cjk if f in _have] + ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# the symmetric matrix of the example: σ0 = 4, σ1 = 2
A = np.array([[3., 1.],
[1., 3.]])
U, sigma, Vt = np.linalg.svd(A)
t = np.linspace(0, 2 * np.pi, 400)
circle = np.vstack([np.cos(t), np.sin(t)]) # unit circle
ellipse = A @ circle # image: an ellipse
fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(11, 5), subplot_kw=dict(aspect='equal'))
for ax in (ax0, ax1):
ax.set_facecolor(C_BG)
ax.grid(True, color=C_GRID, linewidth=0.8, alpha=0.7)
ax.axhline(0, color=C_GRID, linewidth=0.8)
ax.axvline(0, color=C_GRID, linewidth=0.8)
ax.set_xlim(-4.8, 4.8)
ax.set_ylim(-4.8, 4.8)
# left panel: the unit circle and right singular vectors v_i on the input side
ax0.plot(circle[0], circle[1], color=C_T1, linewidth=2)
for i, c in enumerate([C_V1, C_V2]):
v = Vt[i]
ax0.annotate('', xy=v, xytext=(0, 0),
arrowprops=dict(arrowstyle='-|>', color=c, lw=2.5))
ax0.text(*(v * 1.3), f'$\\mathbf{{v}}_{i}$', color=c, fontsize=13, ha='center')
ax0.set_title('Input: unit circle and right singular vectors vᵢ', color=C_AXIS)
# right panel: the ellipse on the output side, principal axes = σᵢ uᵢ
ax1.plot(ellipse[0], ellipse[1], color=C_WARN, linewidth=2)
for i, c in enumerate([C_V1, C_V2]):
u_scaled = sigma[i] * U[:, i]
ax1.annotate('', xy=u_scaled, xytext=(0, 0),
arrowprops=dict(arrowstyle='-|>', color=c, lw=2.5))
ax1.text(*(u_scaled * 1.15), f'$\\sigma_{i}\\mathbf{{u}}_{i}$', color=c, fontsize=13, ha='center')
ax1.set_title('Output: ellipse with axis directions uᵢ and lengths σᵢ', color=C_AXIS)
fig.suptitle('A = [[3, 1], [1, 3]]: unit circle → ellipse (σ0 = 4, σ1 = 2)', color=C_AXIS)
plt.tight_layout()
plt.show()
print(f"Singular values σ = {np.round(sigma, 4)} (theoretical values [4, 2])")
In §11.1 we established the basic theory of the SVD and saw how the singular values describe the geometric structure by which a matrix maps the unit sphere to an ellipsoid. This section explores the relationship between singular values and matrix norms in depth and proves one of the most important applications of the SVD—the Eckart–Young theorem: the truncated SVD gives the best low-rank approximation of a matrix.
In Chapter 2 we became familiar with the concept of the l2 (Euclidean) norm of a vector, which abstracts the intuitive notion of length. In fact, l2 is just one special case of a whole family of lp norms. We introduce them here together, since they will be used later in the analysis of matrix norms.
Different lp norms define different “balls” (the set of all vectors with ∥v∥p≤1). In the plane, the shapes of these unit balls reveal the geometric essence of each norm:
p=1: a diamond (Manhattan distance)
p=2: the standard circle (Euclidean distance)
p→∞: a square (maximum-coordinate distance)
The figure drawn by the code below clearly shows the geometric process by which, as p increases, the unit ball gradually “inflates” from a diamond into a circle and finally approaches a square. This intuition is essential for understanding the properties of matrix norms later on.
Question. How should we define the “size” of a matrix?
There are two most natural lines of generalization:
Induced norm: regard the matrix as an operator and measure its “maximum amplification factor” on vectors
Entrywise norm: regard the matrix as a high-dimensional vector and define a norm directly on all of its entries
These two lines lead to the two great families of matrix norms. The spectral norm, the Frobenius norm, and the nuclear norm introduced below are all unitarily invariant norms and can be described uniformly by the singular values; not every induced norm or entrywise norm has this property.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
# Fonts: the English edition needs no CJK font, so nothing is downloaded when _lang == 'en';
# the Chinese editions use this same block to fetch Noto Sans TC/SC where no CJK font is installed
import os, urllib.request
import matplotlib.font_manager as fm
_lang = 'en'
_cjk = ['Microsoft JhengHei', 'PingFang TC', 'Noto Sans CJK TC', 'Noto Sans TC']
_have = {f.name for f in fm.fontManager.ttflist}
if _lang != 'en' and not _have & set(_cjk):
_font = os.path.join(os.path.expanduser('~'), '.cache', 'fonts', 'NotoSansTC.ttf')
try:
if not os.path.exists(_font):
os.makedirs(os.path.dirname(_font), exist_ok=True)
urllib.request.urlretrieve('https://github.com/google/fonts/raw/main/ofl/notosanstc/NotoSansTC%5Bwght%5D.ttf', _font + '.part')
os.replace(_font + '.part', _font)
fm.fontManager.addfont(_font)
_have.add('Noto Sans TC')
except OSError as err:
print('Could not download the CJK font; Chinese text in figures may not display:', err)
plt.rcParams['font.family'] = [f for f in _cjk if f in _have] + ['DejaVu Sans']
# fix the display of minus signs
plt.rcParams['axes.unicode_minus'] = False
fig, axes = plt.subplots(1, 5, figsize=(14, 3), subplot_kw=dict(aspect='equal'))
p_values = [0.5, 1, 2, 4, np.inf]
titles = [r'$l_{0.5}$', r'$l_1$', r'$l_2$', r'$l_4$', r'$l_\infty$']
colors = ['#e74c3c', '#e67e22', '#2ecc71', '#3498db', '#9b59b6']
theta = np.linspace(0, 2 * np.pi, 1000)
for ax, p, title, color in zip(axes, p_values, titles, colors):
if np.isinf(p):
# l_∞ unit ball: a square
square = plt.Polygon(
[[-1,-1],[1,-1],[1,1],[-1,1]], closed=True,
facecolor=color, alpha=0.25, edgecolor=color, linewidth=2
)
ax.add_patch(square)
else:
# parametrize by angle: in the direction of angle theta, the boundary point of the unit ball is
# (cos θ, sin θ) / ||(cos θ, sin θ)||_p
cos_t = np.cos(theta)
sin_t = np.sin(theta)
norm_p = (np.abs(cos_t)**p + np.abs(sin_t)**p) ** (1.0 / p)
x = cos_t / norm_p
y = sin_t / norm_p
ax.fill(x, y, alpha=0.25, color=color)
ax.plot(x, y, color=color, linewidth=2)
ax.axhline(0, color='gray', linewidth=0.5, linestyle='--')
ax.axvline(0, color='gray', linewidth=0.5, linestyle='--')
ax.set_xlim(-1.5, 1.5)
ax.set_ylim(-1.5, 1.5)
ax.set_title(title, fontsize=14)
ax.set_xticks([-1, 0, 1])
ax.set_yticks([-1, 0, 1])
ax.tick_params(labelsize=9)
fig.suptitle(r'Unit balls $\{\mathbf{v} : \|\mathbf{v}\|_p \leq 1\}$ of the $l_p$ norms in $\mathbb{R}^2$',
fontsize=13, y=1.02)
plt.tight_layout()
plt.savefig('lp_unit_balls.svg', bbox_inches='tight')
plt.show()
11.2.1 Characterizing Matrix Norms by Singular Values¶
Geometric meaning. The spectral norm measures the maximum stretching ratio of unit vectors under the matrix A.
Geometric intuition. Recall from §11.1.3 that the matrix A maps the unit sphere to an ellipsoid, and the length of the longest principal axis of the ellipsoid is precisely σ0. The spectral norm captures exactly this “maximum stretching.”
Geometric meaning. The Frobenius norm measures the “total energy” of a matrix—it combines the contributions of all singular values (the square root of the sum of squares). This will play a key role in the low-rank approximation that follows.
Applications. The nuclear norm plays a central role in matrix completion and low-rank optimization problems. It is a convex relaxation of the rank of a matrix—the rank function rank(A) is nonconvex, whereas the nuclear norm ∥A∥∗ is convex and can serve as a surrogate for the rank in optimization.
The following is an idealized noise-free matrix completion model. The Netflix Prize competition spurred regularized low-rank factor models for data with missing entries; its methods should not be equated directly with the exact nuclear-norm completion constraint below:
Xmin∥X∥∗subject to Xij=Mij for observed entries
Summary of the Geometric and Physical Meaning of the Norms¶
Norm
Definition
Formula in singular values
Geometric/physical meaning
Spectral norm∣A∣2
max∣x∣=1∣Ax∣
σ0
the longest principal axis of the ellipsoid; the maximum amplification factor
Frobenius norm∣A∣F
$\sqrt{\sum_{ij}
a_{ij}
^2}$
Nuclear norm∣A∣∗
∑iσi
∑iσi
a convex surrogate for the rank of the matrix; the sum of the lengths of the principal axes
11.2.2 The Eckart–Young Theorem: Best Low-Rank Approximation¶
One of the most important applications of the SVD is to provide the best low-rank approximation of a matrix. This result was proved by Carl Eckart and Gale Young in 1936 and is now called the Eckart–Young theorem (or the Eckart–Young–Mirsky theorem, which includes Leon Mirsky’s 1960 extension to unitarily invariant norms).
Given a matrix A∈Cm×n of rank r, we wish to find a matrix B of rank at most k (k<r) such that B is “closest” to A.
What does “closest” mean? That is, which norm should serve as the measure of distance?
What is the optimal solution? Can it be constructed explicitly?
The Eckart–Young theorem answers both questions perfectly.
The Frobenius-Norm Version of the Eckart–Young Theorem¶
A complete proof of the general case requires the von Neumann trace inequality or the Mirsky inequality and is beyond the scope of this book; still, it is worth marking clearly exactly where the gap in the proof lies and which tools this section has already prepared.
The Spectral-Norm Version of the Eckart–Young Theorem¶
Comparing the two norm versions:
Norm
Minimum error
Geometric/statistical meaning
Frobenius norm
∑i=kr−1σi2
the square root of the total squared error; the square root of the discarded Frobenius energy
Spectral norm
σk
the maximum error; the largest of the discarded singular values (σk)
An important observation. The Frobenius norm takes into account the contributions of all discarded singular values, while the spectral norm cares only about the largest discarded singular value. In practice:
if the singular values decay rapidly (for example σi∼e−i), both norms give a very good approximation
if the singular values decay slowly (for example σi∼i−1), more terms may need to be kept
The Eckart–Young theorem reveals the central place of the SVD in data science:
1. Image compression
An m×n grayscale image can be regarded as a matrix A∈Rm×n. Storing it in full requires mn numbers. But if we keep the first k singular values, we only need to store
If the signal has low-rank structure, the signal-to-noise ratio is high enough, and the main signal modes are separable from the noise, then the large singular values may be dominated by the signal; small singular values may also contain important signal.
The truncated SVD Ak is singular-mode filtering, which in general is not the same as low-pass filtering. Under the low-rank signal assumption above:
keep the large singular values (possibly dominated by the signal)
discard the small singular values (possibly dominated by noise, but weak signals may also be lost)
3. Latent Semantic Analysis (LSA)
In text mining, the document-term matrix A is usually high-dimensional and sparse (m documents, n terms; mn is large but most entries are 0).
The truncated SVD maps the high-dimensional term space to a low-dimensional “concept space”:
Left singular vectorsui: the coordinates of documents in concept space
Right singular vectorsvi: the coordinates of terms in concept space
Singular valuesσi: the “importance” of the i-th concept
Keeping the first k concepts (typically k∼100−300) makes it possible to:
find semantically similar documents (even if they share no terms)
reduce dimensionality and improve search efficiency
remove interference from synonyms and polysemous words
For a fixed m×n image, the Frobenius energy is ∥A∥F2 and the mean squared error is ∥A−Ak∥F2/(mn). Minimizing the error norm, its square, or the mean squared error gives the same optimal matrix, although the values differ.
11.3 Application 1: Principal Component Analysis (PCA)¶
The Eckart–Young theorem of §11.2 (Theorem 8) is a purely matrix-optimization result: among all matrices of rank at most k, the truncated SVD X~k is the one nearest to the original matrix. This conclusion by itself carries no statistical meaning. But as soon as the matrix holds data—each row a sample, each column a feature—the “best low-rank approximation” immediately takes on a statistical identity: it is precisely principal component analysis (PCA).
So what exactly does PCA do? The best-known answer is “find the directions of greatest variation in the data,” which is the viewpoint of Hotelling (1933). This section, however, adopts a more fundamental viewpoint that fits §11.2 more closely—that of Pearson (1901): “the low-dimensional plane that best fits the data.” In other words, the essence of PCA is not “finding the directions of greatest variation” but “finding the best low-dimensional projection”; the directions of greatest variation are merely a by-product of this best projection. This order matters, because it determines how we understand PCA; we return in §11.3.2 to discuss “the directions of greatest variation” as an observation.
The measure used throughout this section is variance. Experiment 5 (Blocks C and D) has prepared the tool: the total variance Vtotal=tr(S)=m−11∥X~∥F2. With it, the argument of §11.3.1 proceeds in two steps. First, the Frobenius Pythagorean decomposition guarantees that any orthogonal projection conserves the total variance—dimensionality reduction neither creates nor erases variance out of nothing; it only divides it between the approximation and the error. Second, the Eckart–Young theorem picks out the best one among all projections of the same dimension, namely the one given by the right singular vectors of the SVD. Conservation is the universal background; optimization is the real protagonist of PCA.
This section proceeds in two steps. §11.3.1 establishes the core logic (conservation + best projection); §11.3.2 first points out that the best directions of the SVD are exactly “the directions of greatest variation in the data” (an incidental observation, together with a variational characterization by variance maximization), and then, from this nature of “looking only at linear variance,” marks the limits of PCA—it fails on curved manifolds. The rank-by-rank transfer of variance, and the application of PCA to real data to see it reveal cluster structure, are left to the PCA experiment notebook of this chapter for a full demonstration.
import numpy as np
import matplotlib.pyplot as plt
# --- Color scheme (Accent Mix) ---
C_BG = "#F8F8F8"
C_GRID = "#D6D6D6"
C_AXIS = "#000000"
C_V1 = "#57068C" # 0-th principal component (v0)
C_V2 = "#006385" # 1st principal component (v1)
C_T1 = "#2AD2C9" # best-fit plane
C_AUX = "#AB82C5" # data points
C_WARN = "#FF5D47" # perpendiculars (residuals)
# Fonts: the English edition needs no CJK font, so nothing is downloaded when _lang == 'en';
# the Chinese editions use this same block to fetch Noto Sans TC/SC where no CJK font is installed
import os, urllib.request
import matplotlib.font_manager as fm
_lang = 'en'
_cjk = ['Microsoft JhengHei', 'PingFang TC', 'Noto Sans CJK TC', 'Noto Sans TC']
_have = {f.name for f in fm.fontManager.ttflist}
if _lang != 'en' and not _have & set(_cjk):
_font = os.path.join(os.path.expanduser('~'), '.cache', 'fonts', 'NotoSansTC.ttf')
try:
if not os.path.exists(_font):
os.makedirs(os.path.dirname(_font), exist_ok=True)
urllib.request.urlretrieve('https://github.com/google/fonts/raw/main/ofl/notosanstc/NotoSansTC%5Bwght%5D.ttf', _font + '.part')
os.replace(_font + '.part', _font)
fm.fontManager.addfont(_font)
_have.add('Noto Sans TC')
except OSError as err:
print('Could not download the CJK font; Chinese text in figures may not display:', err)
plt.rcParams['font.family'] = [f for f in _cjk if f in _have] + ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
np.set_printoptions(precision=4, suppress=True, linewidth=100)
def print_header(title):
print("=" * 60)
print(f" {title}")
print("=" * 60)
def print_step(step, desc):
print(f"\n▶ Step {step}: {desc}")
print("-" * 40)
# ============================================================
print_header("3D Ellipsoidal Normal Distribution | PCA Best-Fit Plane")
# ============================================================
print_step(1, "Generate random points from an ellipsoidal normal distribution (off the origin, axes not aligned with the coordinate axes)")
rng = np.random.default_rng(7)
mu = np.array([3.0, 2.0, 1.0])
theta = np.deg2rad(35)
axis = np.array([1.0, 1.0, 1.0]) / np.sqrt(3)
K = np.array([[0, -axis[2], axis[1]],
[axis[2], 0, -axis[0]],
[-axis[1], axis[0], 0]])
R = np.eye(3) + np.sin(theta) * K + (1 - np.cos(theta)) * (K @ K) # Rodrigues rotation
scales = np.array([3.0, 1.6, 0.3]) # standard deviations of the three ellipsoid axes (largest to smallest; axis 2 is very flat)
Sigma = R @ np.diag(scales**2) @ R.T
n = 60 # fewer points, to avoid visual clutter in 3D
X = rng.multivariate_normal(mean=mu, cov=Sigma, size=n)
print(f" Number of samples n = {n}, center μ = {mu}")
print(f" Standard deviations of the three ellipsoid axes (largest to smallest) = {np.round(np.sort(scales)[::-1], 3)}")
print_step(2, "Center, take the SVD, and let the first two principal components span the best-fit plane")
x_bar = X.mean(axis=0)
X_tilde = X - x_bar
U, sigma, Vt = np.linalg.svd(X_tilde, full_matrices=False)
v0, v1, v2 = Vt[0], Vt[1], Vt[2]
rho = sigma**2 / (sigma**2).sum()
print(f" Variance share of each principal component ρ = {np.round(rho, 4)} (the first two explain {rho[0]+rho[1]:.1%} together)")
print(f" 0-th principal component v0 = {v0}")
print(f" 1st principal component v1 = {v1}")
print_step(3, "Compute the foot of the perpendicular from each point to the plane (residual along the 2nd principal component v2)")
c2 = X_tilde @ v2 # coordinate of each point along v2 (the residual)
X_proj = X - np.outer(c2, v2) # points projected onto the plane (feet of perpendiculars)
print(f" Mean perpendicular residual |c2| = {np.mean(np.abs(c2)):.3f} (smaller means the points hug the plane more closely)")
print_step(4, "Plot: original 3D point cloud + perpendiculars + PCA best-fit plane (semi-transparent)")
fig = plt.figure(figsize=(7.5, 7))
ax = fig.add_subplot(111, projection='3d')
ax.set_facecolor(C_BG)
# --- best-fit plane spanned by v0, v1 (semi-transparent surface), drawn first so the perpendiculars lie on top ---
extent = 1.2 * np.max(np.abs(X_tilde @ Vt[:2].T))
s_grid, t_grid = np.meshgrid(np.linspace(-extent, extent, 12),
np.linspace(-extent, extent, 12))
plane_pts = (x_bar[:, None, None]
+ v0[:, None, None] * s_grid
+ v1[:, None, None] * t_grid)
ax.plot_surface(plane_pts[0], plane_pts[1], plane_pts[2],
color=C_T1, alpha=0.35, edgecolor='none')
# --- perpendiculars: segments from each point to its foot on the plane, to strengthen the 3D sense of depth ---
for i in range(n):
ax.plot([X[i, 0], X_proj[i, 0]],
[X[i, 1], X_proj[i, 1]],
[X[i, 2], X_proj[i, 2]],
color=C_WARN, linewidth=0.8, alpha=0.55, zorder=1)
# --- feet of perpendiculars (projected points on the plane), smaller and semi-transparent ---
ax.scatter(X_proj[:, 0], X_proj[:, 1], X_proj[:, 2],
s=8, color=C_WARN, alpha=0.4, zorder=2)
# --- original data points ---
ax.scatter(X[:, 0], X[:, 1], X[:, 2], s=22, color=C_AUX, alpha=0.9,
edgecolor=C_AXIS, linewidth=0.3, label='data points', zorder=3)
# --- arrows for the principal component directions ---
ax.quiver(*x_bar, *(v0 * sigma[0] / np.sqrt(n - 1) * 1.8), color=C_V1, linewidth=2.5, label='0-th principal component v0')
ax.quiver(*x_bar, *(v1 * sigma[1] / np.sqrt(n - 1) * 1.8), color=C_V2, linewidth=2.5, label='1st principal component v1')
ax.set_xlabel('x', color=C_AXIS)
ax.set_ylabel('y', color=C_AXIS)
ax.set_zlabel('z', color=C_AXIS)
ax.set_title('Ellipsoidal normal point cloud and PCA best-fit plane (with perpendiculars)', color=C_AXIS)
ax.xaxis.pane.set_facecolor(C_BG)
ax.yaxis.pane.set_facecolor(C_BG)
ax.zaxis.pane.set_facecolor(C_BG)
ax.grid(True, color=C_GRID, linewidth=0.6, alpha=0.6)
ax.legend(loc='upper left', fontsize=8)
plt.tight_layout()
plt.show()
11.3.1 From Low-Rank Approximation to Variance Decomposition¶
The Eckart–Young theorem of §11.2.2 (Theorem 8) is a matrix-optimization result. This section translates it into the language of statistics and clarifies the true logical order of PCA: first comes the invariant “every projection conserves the total variance,” and then Eckart–Young picks the best projection from among them.
We continue with the centered data matrix X~∈Rm×n of Experiment 5 (samples as rows), the sample covariance matrix S=m−11X~⊤X~, and the total variance
(For readers who have not done Experiment 5, one line suffices: tr(S)=∑jm−11∑ix~ij2=m−11∥X~∥F2.) Take the SVD of X~ and write it as an outer-product expansion (§11.1):
Step 1: Every Orthogonal Projection Conserves the Total Variance¶
Dimensionality reduction means projecting the data onto some low-dimensional subspace. Take any orthogonal projection matrix Π∈Rn×n (not yet specified); the projected data are X~Π and the residual is X~(I−Π). How do the variances of these two divide the total variance?
The whole point of this equality lies in the word “conservation”: no matter which subspace we project onto, the total variance splits exactly into two pieces, approximation and error, and the total does not change one bit. So dimensionality reduction is never a question of “how much variance can be preserved”—that holds automatically; the real question is “within the fixed total, how to keep as much variance as possible in the approximation and lose as little as possible to the error.” This leads to Step 2.
The choice made by PCA is thus justified: we choose the right singular vectors of the SVD not because these directions are special in themselves, but because projecting onto the subspace they span preserves the most variance among all projections of the same dimension. As for the geometric appearance of these best projection directions—they happen to be “the directions of greatest variation in the data”—we leave that to §11.3.2 as an observation.
11.3.2 Directions of Greatest Variation, and the Limitations of PCA¶
Look back at the best projection Πk chosen in §11.3.1. Taking k=1, its direction is v0, which preserves the most variance among all unit directions; in turn, v0,v1,… are “the directions of variation in the data from largest to smallest”—the 0-th, the 1st, ... principal components. This is also the best-known description of PCA: finding the directions of greatest variation in the data. But we must stress its place in the logic of this chapter: it is an observation that falls out of the “best projection” incidentally, not the goal we are pursuing. What we want throughout is the best low-rank approximation; the direction of greatest variation is just how it looks when k=1.
Let us write the observation above as a precise step-by-step definition. The 0-th principal component v0 is the unit vector that maximizes the variance of the projected data:
(Since X~ is already centered, the projection scores X~v have mean zero, so their variance is m−11∥X~v∥2.) This is exactly the variational characterization of the SVD in §11.1.3 (Theorem 4)! The 1st principal component v1 is the vector that maximizes the variance among directions orthogonal to v0:
Continuing in this way, the i-th principal component vi is the direction of maximal variance orthogonal to the first i principal components v0,…,vi−1. This characterization—“successively maximize the variance in the orthogonal complement”—arrives at the same place as the “best projection” of §11.3.1: the former picks greedily one direction at a time, the latter chooses the whole subspace at once, and both select the right singular vectors of the SVD.
The two are not equally “strong,” however. Eckart–Young is a global result: it weighs all k-dimensional subspaces simultaneously and determines the entire optimal subspace in one stroke. The greedy algorithm is local (greedy): it picks one direction after another, at each step performing only a one-dimensional maximization under the constraint “orthogonal to the directions already chosen,” and never goes back. In general, local greed is a weaker, operational result—stepwise optimality does not guarantee global optimality. In this problem, however, the two are exactly equivalent: each step maximizes the variance in the orthogonal complement of the first i−1 principal components and selects precisely the next singular direction, and the accumulated steps span exactly the globally optimal subspace of Eckart–Young. This coincidence of “greedy equals global” stems from the nested structure of the eigenspaces of a symmetric positive semidefinite matrix and is not a property enjoyed by optimization in general—and precisely because of it, PCA can reach the global optimum by the plainest step-by-step procedure.
It is precisely this nature of “looking only at linear variance” that marks the limits of PCA. Once the structure of the data is not linear, the conservation of §11.3.1 still holds (the total variance is always conserved), but “keeping the directions of greatest variance” need not mean “keeping the most useful structure.” Here are several limitations to remember:
Linearity assumption: PCA can capture only linear relationships. For nonlinear structures (spirals, rings), kernel PCA or manifold learning (t-SNE, UMAP) is needed.
Global method: PCA looks for the globally optimal linear subspace and is insensitive to local structure.
Sensitivity to outliers: variance maximization can be dominated by outliers. Robust alternatives include Robust PCA and L1-PCA.
Interpretability: principal components are linear combinations of the original features and often have a less clear meaning than the original features.
Unsupervised: PCA does not use labels, so in classification/regression tasks it may discard directions useful for prediction. In that case, supervised dimensionality reduction (such as Linear Discriminant Analysis, LDA) should be considered.
The first of these is the most fatal and deserves to be seen with your own eyes. Consider the classic Swiss roll: the data lie on a rolled-up two-dimensional sheet in three-dimensional space. Below, the height direction y is deliberately stretched so that it becomes the direction of greatest variance (the colors mark four segments cut along t). The left panel shows the original 3D structure—the spiral is clearly visible, and the height axis is just a noise direction superimposed on it. The middle panel takes PCA’s default first two principal components PC0–PC1, but PC0 is exactly the height axis that has nothing to do with the structure, so the four segments are stirred into overlapping bands and no spiral can be seen. The right panel takes PC1–PC2 (the spiral plane), which PCA regards as secondary, and there the four segments separate clearly along the spiral. The lesson is sharp: PCA always picks the directions of greatest variance, but the greatest variance is not necessarily the most meaningful direction—when an irrelevant high-variance axis gets mixed in, PCA’s default projection actually hides the structure. Remedy: at the linear level, one can look at the secondary principal components instead or perform feature selection first; to truly “unroll” the spiral (the spiral in the right panel is still curled), one needs manifold learning (Isomap, LLE) or kernel PCA, which measure distance along the manifold rather than along Euclidean straight lines.
import numpy as np
import matplotlib.pyplot as plt
# --- Color scheme (Accent Mix) ---
C_BG = "#F8F8F8" # canvas background
C_GRID = "#D6D6D6" # grid lines
C_AXIS = "#000000" # axes / title text
C_V1 = "#57068C" # violet
C_V2 = "#006385" # deep blue
C_T1 = "#2AD2C9" # teal
C_WARN = "#FF5D47" # orange
# Fonts: the English edition needs no CJK font, so nothing is downloaded when _lang == 'en';
# the Chinese editions use this same block to fetch Noto Sans TC/SC where no CJK font is installed
import os, urllib.request
import matplotlib.font_manager as fm
_lang = 'en'
_cjk = ['Microsoft JhengHei', 'PingFang TC', 'Noto Sans CJK TC', 'Noto Sans TC']
_have = {f.name for f in fm.fontManager.ttflist}
if _lang != 'en' and not _have & set(_cjk):
_font = os.path.join(os.path.expanduser('~'), '.cache', 'fonts', 'NotoSansTC.ttf')
try:
if not os.path.exists(_font):
os.makedirs(os.path.dirname(_font), exist_ok=True)
urllib.request.urlretrieve('https://github.com/google/fonts/raw/main/ofl/notosanstc/NotoSansTC%5Bwght%5D.ttf', _font + '.part')
os.replace(_font + '.part', _font)
fm.fontManager.addfont(_font)
_have.add('Noto Sans TC')
except OSError as err:
print('Could not download the CJK font; Chinese text in figures may not display:', err)
plt.rcParams['font.family'] = [f for f in _cjk if f in _have] + ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
np.set_printoptions(precision=4, suppress=True, linewidth=100)
def print_header(title):
print("=" * 60)
print(f" {title}")
print("=" * 60)
def print_step(step, desc):
print(f"\n▶ Step {step}: {desc}")
print("-" * 40)
# ============================================================
print_header("Swiss Roll | The Pitfall of PCA's Choice of Axes")
# ============================================================
print_step(1, "Generate Swiss roll data (the height y is deliberately stretched to be the direction of greatest variance)")
rng = np.random.default_rng(0)
n = 1600
t = 1.5 * np.pi * (1 + 2 * rng.random(n)) # arc-length parameter along the roll
h = 60.0 * rng.random(n) # height: unrelated to t, but with the greatest variance
X = np.column_stack([t * np.cos(t), h, t * np.sin(t)])
cls = np.digitize(t, np.quantile(t, [0.25, 0.5, 0.75])) # cut into 4 segments along t
seg_colors = [C_V1, C_V2, C_T1, C_WARN]
print(f" Number of samples n = {n}, original dimension = {X.shape[1]}")
print_step(2, "Center, take the SVD, and obtain the principal component scores")
X_tilde = X - X.mean(axis=0)
U, sigma, Vt = np.linalg.svd(X_tilde, full_matrices=False)
Z = X_tilde @ Vt.T
rho = sigma**2 / (sigma**2).sum()
print(f" Variance share of each principal component ρ = {np.round(rho, 3)}")
print(f" Alignment of PC0 with the original axes |cos⟨v_i, e_j⟩| = {np.round(np.abs(Vt[0]), 2)} (≈ the height axis)")
print_step(3, "Plot: original 3D structure vs. two PCA projections")
fig = plt.figure(figsize=(15, 4.8))
# --- left panel: original 3D Swiss roll structure ---
ax0 = fig.add_subplot(1, 3, 1, projection='3d')
ax0.set_facecolor(C_BG)
for c in range(4):
mask = cls == c
ax0.scatter(X[mask, 0], X[mask, 1], X[mask, 2], s=6, color=seg_colors[c], label=f'segment {c}')
ax0.set_title('Original 3D structure (height axis has greatest variance)', color=C_AXIS)
ax0.set_xlabel('x', color=C_AXIS)
ax0.set_ylabel('y (height, greatest variance)', color=C_AXIS)
ax0.set_zlabel('z', color=C_AXIS)
ax0.xaxis.pane.set_facecolor(C_BG)
ax0.yaxis.pane.set_facecolor(C_BG)
ax0.zaxis.pane.set_facecolor(C_BG)
ax0.grid(True, color=C_GRID, linewidth=0.6, alpha=0.6)
ax0.legend(markerscale=2, fontsize=7, loc='upper left')
# --- middle panel: first 2 PCA components PC0-PC1 ---
ax1 = fig.add_subplot(1, 3, 2)
ax1.set_facecolor(C_BG)
ax1.grid(True, color=C_GRID, linewidth=0.8, alpha=0.7)
for c in range(4):
mask = cls == c
ax1.scatter(Z[mask, 0], Z[mask, 1], s=8, color=seg_colors[c], label=f'segment {c}')
ax1.set_title('PCA first 2 components PC0–PC1 (greatest variance): overlap', color=C_AXIS)
ax1.set_xlabel('PC0', color=C_AXIS)
ax1.set_ylabel('PC1', color=C_AXIS)
ax1.legend(markerscale=2, fontsize=7)
# --- right panel: secondary PCA plane PC1-PC2 ---
ax2 = fig.add_subplot(1, 3, 3)
ax2.set_facecolor(C_BG)
ax2.grid(True, color=C_GRID, linewidth=0.8, alpha=0.7)
for c in range(4):
mask = cls == c
ax2.scatter(Z[mask, 1], Z[mask, 2], s=8, color=seg_colors[c])
ax2.set_title('PCA secondary plane PC1–PC2 (spiral plane): separated', color=C_AXIS)
ax2.set_xlabel('PC1', color=C_AXIS)
ax2.set_ylabel('PC2', color=C_AXIS)
plt.tight_layout()
plt.show()
11.4 Application 2: The Moore–Penrose Pseudoinverse¶
In the discussion of systems of linear equations in Chapter 6, we saw that for a square matrix A∈Rn×n, if A is invertible, then the equation Ax=b has the unique solution x=A−1b. Real-world problems, however, often involve non-square or rank-deficient matrices:
Overdetermined systems: m>n, more equations than unknowns, usually no exact solution
Underdetermined systems: m<n, fewer equations than unknowns, usually infinitely many solutions
Rank-deficient square matrices: rank(A)<n, not invertible even though square
Question. Can we define a “generalized inverse” that exists for every matrixA∈Rm×n and reduces to the ordinary inverse when A is invertible?
This section takes a path from the concrete to the abstract: first we let the simplest case, diagonal matrices, reveal the rudiments of the answer (§11.4.1); then we use the SVD, a “universal rotation,” to carry the same trick over unchanged to an arbitrary matrix, obtaining the general least-squares solution (§11.4.2); only at the end do we go back and verify that the matrix so constructed satisfies the four Penrose conditions, and cite its uniqueness theorem—this is the formal theory of the Moore–Penrose pseudoinverse (§11.4.3).
11.4.1 Starting from Diagonal Matrices: The Rudiments of the Pseudoinverse¶
In a system of equations with an arbitrary matrix, the variables are entangled with one another, and it is not easy to see at a glance how “inversion” should be generalized. But in a system with a diagonal matrix, the components do not interfere with one another, and the answer can be read off term by term.
The trivial case. If Σ=diag(σ0,…,σn−1) is an n×n square matrix and all σi=0, then Σx=b can be solved term by term: xi=bi/σi, that is, Σ−1=diag(1/σ0,…,1/σn−1)—the familiar inverse matrix, with nothing new.
The really interesting case is the rank-deficient one:
This example states the rule clearly: the pseudoinverse Σ+ of a diagonal matrix (including a rectangular diagonal matrix) is the matrix obtained by taking reciprocals of the nonzero diagonal entries, transposing the shape, and filling all other positions with zeros. A column corresponding to a zero diagonal entry (a free variable) naturally becomes a zero row in Σ+—because setting that variable to zero is exactly the choice that minimizes the norm; a row corresponding to a zero diagonal entry (an equation that cannot be satisfied) is ignored, contributes nothing to the solution, but leaves an irreducible residual.
This intuition holds for every diagonal matrix, square or rectangular, full rank or rank-deficient. The problem is that a general matrix A is not diagonal, and its components are entangled with one another. The next subsection uses the SVD to “untangle” them, reducing the general case exactly to the diagonal case already solved here.
11.4.2 Generalizing to Least Squares with the SVD¶
The key idea is to use the SVD to “rotate” the problem for an arbitrary matrix into the diagonal problem already solved in §11.4.1. Let A=UΣV⊤∈Rm×n and consider
Change of coordinates.U and V are both orthogonal matrices, and multiplying by an orthogonal matrix does not change the length of a vector (for any w, ∥Uw∥=∥w∥). Therefore
—exactly the diagonal problem already solved in §11.4.1! Moreover, ∥x∥=∥y∥ guarantees that the minimum-norm solution found in y-coordinates is still the minimum-norm solution after changing back to x-coordinates. By §11.4.1, this solution is y^=Σ+c (reciprocals of the nonzero singular values, zeros elsewhere). Changing back to the original coordinates:
This is the construction formula for the pseudoinverse—it is not assumed out of thin air, but is the inevitable result of carrying the solution method for diagonal matrices over unchanged to general matrices along the “universal rotation” of the SVD. Let us state this argument as a formal theorem:
We have now constructed the same matrix A+=VΣ+U⊤ concretely twice—first seeing its logic in the rudimentary diagonal case of §11.4.1, then carrying it over to general matrices with the SVD in §11.4.2 and verifying that it does give the least-squares, minimum-norm solution. This subsection closes the theory: we verify that the constructed matrix satisfies the four Penrose conditions; for uniqueness we cite Penrose’s theorem without proving it here—this is precisely the characterization Penrose gave in 1955, and it is the origin of the name “Moore–Penrose pseudoinverse.”
Through the SVD, the pseudoinverse has an extremely concise expression (this is precisely the construction formula we derived in §11.4.2; here we take a different angle and formally prove that it satisfies the Penrose axioms):
This formula shows clearly that the pseudoinverse takes the reciprocal of the singular value in each term of the outer-product expansion of A and swaps the left and right singular vectors.
Output: we obtain the unique vector in the row space
Diagram:
In the diagram, the null space ker(A) is not connected to any arrow: the range of A+ is the row space Row(A), and the solution A+b never has a null-space component (it is orthogonal to the null space).
Key properties:
AA+ is the orthogonal projection onto Col(A)
A+A is the orthogonal projection onto Row(A)
A+b always lies in the row space (that is, A+b⊥ker(A))
import numpy as np
np.set_printoptions(precision=4, suppress=True)
print_header("Pseudoinverse Check: The Four Penrose Conditions and the Least-Squares Solution")
A = np.array([[1., 1.],
[1., -1.],
[1., 0.]])
b = np.array([2., 0., 3.])
print_step(1, "Construct the pseudoinverse A⁺ = V Σ⁺ U^T from the SVD")
U, s, Vt = np.linalg.svd(A, full_matrices=True)
Sp = np.zeros((2, 3))
Sp[0, 0], Sp[1, 1] = 1 / s[0], 1 / s[1]
A_pinv = Vt.T @ Sp @ U.T
print("A⁺ =\n", A_pinv)
print("Agrees with np.linalg.pinv? →", np.allclose(A_pinv, np.linalg.pinv(A)))
print_step(2, "Verify the four Penrose conditions")
P = A_pinv
print(f" Condition 1 A A⁺ A = A → {np.allclose(A @ P @ A, A)}")
print(f" Condition 2 A⁺ A A⁺ = A⁺ → {np.allclose(P @ A @ P, P)}")
print(f" Condition 3 (A A⁺)^T = A A⁺ → {np.allclose((A @ P).T, A @ P)}")
print(f" Condition 4 (A⁺ A)^T = A⁺ A → {np.allclose((P @ A).T, P @ A)}")
print_step(3, "Least-squares solution and residual (compare with the hand computation in the example)")
x_hat = A_pinv @ b
r = b - A @ x_hat
print(f" x̂ = {x_hat} (expected [5/3, 1] ≈ [{5/3:.4f}, 1.0000])")
print(f" ‖r‖² = {r @ r:.6f} (expected 8/3 ≈ {8/3:.6f})")
print(f" Residual ⊥ Col(A)? → {np.allclose(A.T @ r, 0)}")
11.4.4 Numerical Instability and Ill-Conditioned Problems¶
Although the pseudoinverse solves the least-squares problem perfectly in theory, in numerical computation it can be extremely unstable!
The Problem of Small Singular Values in Ill-Conditioned Matrices¶
Given a tolerance threshold τ≥0, let k=#{i:0≤i<r,σi>τσ0}. If 0<k<r, this is equivalent to σk−1>τσ0≥σk; when k=0 we take the zero matrix, and when k=r all positive singular values are kept. For the zero matrix we set k=0 separately.
Effect:
Advantage: lowers the noise sensitivity of the truncated directions, but introduces truncation bias
Disadvantage: it is not a strict least-squares solution, and it introduces bias
This leads to a more systematic regularization method—ridge regression.
The amplification along small-singular-value directions is suppressed, trading bias for lower noise sensitivity.
2. The bias-variance trade-off
λ→0+: the solution approaches the minimum-norm least-squares solution A+b; in the rank-deficient case one cannot simply substitute λ=0 into the inverse-matrix formula
λ→∞: x^λ→0, with small variance and large bias
Moderate λ: strikes a balance between bias and variance
Ridge regression is singular-mode filtering that suppresses the amplification along small-singular-value directions; the size of a singular value is in general not a temporal or spatial frequency, so it cannot be called low-pass filtering across the board.
Cross-validation: choose the λ that minimizes the prediction error on a validation set
The L-curve method: plot log∥Ax^λ−b∥ against log∥x^λ∥ and choose the λ at the “corner”
Generalized cross-validation (GCV): a statistical method that balances bias and variance automatically
Mode-transition scale: when λ=σi2, fi=1/2. To approximately keep the first k modes (0<k<r), one may consider σk2≲λ≲σk−12, but the parameter must be chosen according to the noise and the data; this is not an optimality theorem
The code below actually scans λ from 10-18 to 10-2 for the ill-conditioned system of Example 14 and draws the L-curve: the horizontal axis is the norm of the solution ∥x^λ∥ and the vertical axis is the residual ∥Ax^λ−b∥ (log-log axes). The corner of the L-curve is a heuristic candidate for the parameter; the marker in the figure only demonstrates a preset parameter and does not compute the corner or the optimum automatically.
import numpy as np
import matplotlib.pyplot as plt
C_BG, C_GRID, C_AXIS = "#F8F8F8", "#D6D6D6", "#000000"
C_V1, C_WARN = "#57068C", "#FF5D47"
print_header("An Ill-Conditioned System in Practice: Singular Values, Condition Number, and the L-Curve")
eps = 1e-8
A = np.array([[1., 1.],
[1., 1. + eps]])
b = np.array([1., 1.]) + np.array([1e-6, 0.]) # noisy observation
print_step(1, "Singular values and condition number (compare with the corrected figures in the text)")
U, s, Vt = np.linalg.svd(A)
print(f" σ0 = {s[0]:.4f}, σ1 = {s[1]:.3e} (theory ε/2 = {eps/2:.1e})")
print(f" κ(A) = σ0/σ1 = {s[0]/s[1]:.3e} (theory ≈ 4×10⁸)")
print(f" 1/σ1 = {1/s[1]:.3e}")
print_step(2, "Pseudoinverse solution vs. ridge regression solution")
x_pinv = np.linalg.pinv(A) @ b
print(f" pseudoinverse solution x̂ = {np.round(x_pinv, 3)}")
print(" → the noise 10⁻⁶ along the ill-conditioned direction v1 is amplified into an O(10²) shift; the solution is held hostage by the noise")
lam_demo = 1e-6
x_ridge = Vt.T @ ((s / (s**2 + lam_demo)) * (U.T @ b))
print(f" ridge solution (λ=10⁻⁶) x̂_λ = {np.round(x_ridge, 6)}")
print(" → keeps the information in the well-conditioned direction (x0 + x1 ≈ 1) and strongly suppresses the ill-conditioned direction (x0 − x1), greatly reducing the sensitivity in that direction")
print_step(3, "Scan λ and plot the L-curve")
lams = np.logspace(-18, -2, 80)
sol_norm, res_norm = [], []
for lam in lams:
x_l = Vt.T @ ((s / (s**2 + lam)) * (U.T @ b))
sol_norm.append(np.linalg.norm(x_l))
res_norm.append(np.linalg.norm(A @ x_l - b))
fig, ax = plt.subplots(figsize=(6.8, 5.2))
ax.set_facecolor(C_BG)
ax.grid(True, color=C_GRID, linewidth=0.8, alpha=0.7, which='both')
ax.loglog(sol_norm, res_norm, '-o', color=C_V1, markersize=3.5, linewidth=1.5)
i_demo = int(np.argmin(np.abs(np.log(lams) - np.log(1e-15)))) # preset demonstration parameter, not an automatically found corner
ax.loglog(sol_norm[i_demo], res_norm[i_demo], 'o', color=C_WARN,
markersize=11, label='demo parameter λ ≈ 10⁻¹⁵ (not chosen automatically)')
ax.set_xlabel('‖x̂_λ‖ (norm of the solution)', color=C_AXIS)
ax.set_ylabel('‖A x̂_λ − b‖ (residual)', color=C_AXIS)
ax.set_title('L-curve: lower right = under-regularized (solution blows up),\nupper left = over-regularized (residual rises)', color=C_AXIS, fontsize=11)
ax.legend(fontsize=9)
plt.tight_layout()
plt.show()
print(f" Marked demo λ ≈ {lams[i_demo]:.0e}; σ1² = {s[1]**2:.1e}. This figure does not compute the optimal λ")
11.5 ◆Application 3: The Schmidt Decomposition and Quantum Entanglement¶
Quantum entanglement is one of the most mysterious and most important phenomena of quantum mechanics—Einstein called it “spooky action at a distance”—and it is also the theoretical foundation of technologies such as quantum computing, quantum communication, and quantum sensing. The SVD provides a precise mathematical tool for quantifying entanglement—the Schmidt decomposition: given the state vector of a composite quantum system, the Schmidt decomposition reveals the structure and strength of the entanglement between the subsystems.
The role of mathematics. The SVD/Schmidt decomposition provides:
A quantitative tool: how can the “strength” of entanglement be measured?
Structural understanding: what is the internal structure of an entangled state?
A computational method: how can high-dimensional entangled states be handled efficiently?
11.5.1 Matricizing the State Vector and the Schmidt Decomposition¶
The Tensor Product Structure of Composite Quantum Systems¶
Two views of the tensor product, revisited
In Chapter 4 we introduced the abstract definition of the tensor product space. Before entering this section, let us become familiar with it again through a concrete example and emphasize a key idea that recurs throughout quantum mechanics: the state vector of a composite system can be viewed either as a “long vector” or as a “matrix.” These two views do not contradict each other; rather, they are where the SVD comes into play.
Consider two quantum systems:
System A: Hilbert space HA, dimension dA, orthonormal basis {∣i⟩A}i=0dA−1
System B: Hilbert space HB, dimension dB, orthonormal basis {∣j⟩B}j=0dB−1
The state space of the composite system A⊗B is the tensor product space
Σ∈RdA×dB is a diagonal matrix whose diagonal entries are the singular values σ0≥σ1≥⋯≥0
Let the rank be r=rank(C), so that the first r singular values are nonzero.
The meaning of the Schmidt decomposition:
Conciseness: whereas dA×dB coefficients were originally needed, the Schmidt decomposition needs only r coefficients (r≤min(dA,dB))
Symmetry: the descriptions of the two subsystems A and B are completely symmetric, each with its own orthonormal basis
Uniqueness: the Schmidt coefficients are unique and directly describe the structure of the entanglement
# ============================================================
print_header("Schmidt Decomposition: Numerical Check for a Bell State")
# ============================================================
# Bell state |Φ+⟩ = (1/√2)(|00⟩ + |11⟩)
# coefficient matrix C (row 0 corresponds to A=|0⟩, row 1 to A=|1⟩)
C = np.array([[1/np.sqrt(2), 0],
[0, 1/np.sqrt(2)]])
print_step(1, "Take the SVD of the coefficient matrix C (i.e., the Schmidt decomposition)")
U, s, Vh = np.linalg.svd(C)
print("Schmidt coefficients λ_k =", s)
print("(theoretical values: λ_0 = λ_1 = 1/√2 ≈", round(1/np.sqrt(2), 4), ")")
print_step(2, "Schmidt rank = number of nonzero singular values")
r = np.sum(s > 1e-10)
print(f"Schmidt rank = {r} → {'entangled state (r > 1)' if r > 1 else 'separable state (r = 1)'}")
print_step(3, "Check the normalization condition Σ λ_k² = 1")
print(f"Σ λ_k² = {np.sum(s**2):.6f} (should be 1.0)")
print_step(4, "von Neumann entanglement entropy S = -Σ λ_k² log₂(λ_k²)")
S = -np.sum(s**2 * np.log2(s**2 + 1e-15))
print(f"S = {S:.4f} bit (maximal entanglement entropy of a Bell state = 1 bit ✓)")
Proof. This is immediate. Schmidt rank 1 means there is only one nonzero Schmidt coefficient, in which case ∣ψ⟩=λ0∣α0⟩A∣β0⟩B is separable. Conversely, if the Schmidt rank is ≥2, there are at least two terms, and the state cannot be written as a single tensor product. □
Schmidt Rank and the von Neumann Entanglement Entropy¶
The Schmidt rankr is the coarsest measure of entanglement:
Schmidt rank r
Degree of entanglement
Physical meaning
r=1
no entanglement
separable state; the subsystems are completely independent
r=2
partial entanglement
the simplest entanglement, with two Schmidt terms
r=min(dA,dB)
full rank
possibly maximal entanglement (if all λk are equal)
But the Schmidt rank is not fine enough. For example:
The partial trace and the projective measurement of §10.4 are related but different operations, and they deserve to be distinguished carefully.
Measuring B and the conditional pure state of A
For a composite pure state ∣ψ⟩AB=∑i,jcij∣i⟩A∣j⟩B, perform a rank-1 projective measurement of B in any orthonormal basis; if outcome j0 has positive probability, the conditional state of A is the definite pure state obtained by normalizing ∑ici,j0∣i⟩A. The A states corresponding to different outcomes need not be orthogonal.
When measuring in the Schmidt basis {∣βk⟩B}, ∣ψ⟩AB=∑kλk∣αk⟩A∣βk⟩B, and each outcome k of positive probability corresponds to ∣αk⟩A, giving orthogonal conditional states in one-to-one correspondence. A definite conditional pure state does not mean that measuring any observable on it shows no statistical fluctuation.
The partial trace corresponds to the average with “outcomes discarded”
Still measuring B in the basis {∣j⟩B}, the probability of outcome j is
Only when P(j)>0 is the corresponding conditional pure state of A defined, namely ∣ϕj⟩A=P(j)1∑icij∣i⟩A, with density matrix ρA(j)=∣ϕj⟩A⟨ϕj∣.
If the outcome is not read after the measurement (or B simply cannot be accessed), the effective state of A is the probability-weighted average of all the conditional states:
This weighted average does not depend on the choice of measurement basis for B and equals the partial trace TrB(ρAB). We verify this explicitly below in the language of matrix indices.
Why the partial trace does not depend on the measurement basis of B
Suppose we choose another orthonormal basis {∣j~⟩B} of B, related to the original computational basis {∣k⟩B} by a unitary matrix W:
In other words, changing the basis amounts to replacing the coefficient matrix C with CW.
Intuition. A change of basis for B turns the coefficient matrix into CW, where the bar denotes entrywise conjugation. The product in the partial trace is CWW⊤C∗=CC∗, so the result does not depend on the measurement basis of B.
Reduced Density Matrices and the Schmidt Decomposition¶
The reduced density matrix describes the state of a subsystem. For a pure state ∣ψ⟩AB, the reduced density matrix of system A is defined as
where TrB denotes the partial trace over system B.
Deeper implications:
The Schmidt decomposition of a pure state ∣ψ⟩AB directly gives:
The mixedness of the subsystems: ρA and ρB are mixed states (unless r=1)
The equivalence of entanglement and mixedness: the degree of entanglement of a pure state of the composite system = the degree of mixedness of the subsystems
Conservation of information: the von Neumann entropies of the two subsystems are equal
This shows that entanglement is the unity of global purity and local mixedness.
Purification is a profound concept: every mixed state can be regarded as the reduced density matrix of some larger pure state.
Physical meaning:
A mixed state can be regarded as the reduced state of a larger pure state; in this purified description, the mixedness comes from the entanglement between the system and the auxiliary system.
If an interaction builds up non-negligible entanglement between the system and its environment, then ignoring the environment may produce mixing and decoherence in the corresponding basis. Interaction by itself does not guarantee that this happens; the purification theorem is an existence result and does not specify the dynamics of any actual environment.
Core insights:
SVD = Schmidt decomposition: the SVD of linear algebra is, in quantum mechanics, the Schmidt decomposition
Singular values = a measure of entanglement: the distribution of the singular values directly reflects the strength of entanglement
Reduction = marginalization: taking the partial trace amounts to “integrating out” a subsystem, corresponding to the average state of §10.4 (measuring and discarding the outcome)
Purification = the inverse operation: given a mixed state, purification constructs a “parent” pure state whose reduction is exactly the original mixed state; the unitary freedom on system B corresponds to the different ways of purifying
11.5.3 The Schmidt Structure of Multipartite Systems¶
For a bipartite pure state, the Schmidt coefficients are uniquely determined, although the Schmidt bases need not be. Tripartite or larger systems differ in an essential way: given a tripartite pure state ∣ψ⟩ABC, one can perform a Schmidt decomposition separately for different bipartitions—
The Schmidt coefficients for different bipartitions are in general different, but they must satisfy compatibility conditions on the reduced states; a single bipartition cannot fully describe multipartite entanglement.
The difference between the GHZ state and the W state lies not only in the numerical results above; it also reflects the essential classification of tripartite entanglement.
then ∣ψ⟩ and ∣ϕ⟩ are said to be equivalent under stochastic local operations and classical communication (SLOCC). SLOCC-equivalent states can be converted into each other in the sense of entanglement resources.
One can prove that GHZ and W do not belong to the same SLOCC equivalence class; that is, no invertible local operation converts one into the other. For n qubits, the number of SLOCC equivalence classes grows with n as follows: two classes for n=2, six classes for n=3, and already infinitely many for n≥4 (the infinitely many classes for n=4 can be organized into nine families).
Tensor Decompositions: Higher-Dimensional Generalizations of the Schmidt Decomposition¶
the coefficients cijk form a third-order tensor T∈CdA×dB×dC. Two natural generalizations of the SVD are:
Tucker decomposition: T≈G×0UA×1UB×2UC, where G is the core tensor and UA,UB,UC are unitary matrices.
CP decomposition (Canonical Polyadic): T=∑rλrar⊗br⊗cr, a sum of pure tensor products of vectors.
Unlike the SVD of a matrix, computing the rank of a third-order tensor (the CP rank) is NP-hard, and a best low-rank approximation may not exist (the rank may not be attained). This is one of the roots of the difficulty of classifying entanglement in many-body quantum systems.
Entanglement as a Resource: The Operational Interpretation of the von Neumann Entropy¶
For a pure state ∣ψ⟩AB, the von Neumann entropy S(ρA) is not only a measure of entanglement; it also has a precise operational meaning:
This theorem is the quantum-information analogue of Shannon’s theorem: the entanglement entropy is the “exchange rate” of entanglement as a resource. For pure states, the entanglement cost (the number of Bell states needed for preparation) and the distillable entanglement are equal, both being S(ρA). For mixed states, entanglement conversion is in general irreversible, and the cost may exceed the distillable entanglement; among mixed states there are even bound entangled states, which are entangled but from which no entanglement can be distilled.
Under the resource model above, the logarithm of the Schmidt rank gives a lower bound on the amount of quantum communication, describing the “intrinsic complexity” of a pure state.
import numpy as np
np.set_printoptions(precision=4, suppress=True)
print_header("W State vs. GHZ State: Partial-Trace Check After Losing One Particle")
def partial_trace_C(psi):
"""Take Tr_C of a 3-qubit pure state |ψ⟩ (basis |abc⟩ in lexicographic order) and return the 4×4 ρ_AB"""
rho = np.outer(psi, psi.conj())
rho_AB = np.zeros((4, 4), dtype=complex)
for ab in range(4):
for ab2 in range(4):
for c in range(2):
rho_AB[ab, ab2] += rho[2 * ab + c, 2 * ab2 + c]
return rho_AB
def concurrence_2qubit(rho):
"""Concurrence of a two-qubit mixed state (Wootters formula): C > 0 ⟺ entangled"""
sy = np.array([[0, -1j], [1j, 0]])
R = rho @ np.kron(sy, sy) @ rho.conj() @ np.kron(sy, sy)
ev = np.sqrt(np.abs(np.sort(np.linalg.eigvals(R).real)[::-1]))
return max(0.0, ev[0] - ev[1] - ev[2] - ev[3])
W = np.zeros(8)
W[[0b100, 0b010, 0b001]] = 1 / np.sqrt(3)
GHZ = np.zeros(8)
GHZ[[0b000, 0b111]] = 1 / np.sqrt(2)
print_step(1, "W state: spectrum and entanglement of ρ_AB")
rho_W = partial_trace_C(W)
print(" ρ_AB =\n", np.real_if_close(rho_W)) # display only: imaginary parts are dropped only within tolerance; ρ_AB itself is unchanged
print(f" eigenvalues = {np.round(np.linalg.eigvalsh(rho_W)[::-1], 4)} (expected [2/3, 1/3, 0, 0])")
print(f" Tr(ρ²) = {np.trace(rho_W @ rho_W).real:.4f} < 1 → mixed state") # ρ is Hermitian, so Tr(ρ²) is real
print(f" concurrence C(ρ_AB) = {concurrence_2qubit(rho_W.astype(complex)):.4f} (expected 2/3 > 0 → still entangled: the robustness of the W state)")
print_step(2, "GHZ state: spectrum and entanglement of ρ_AB")
rho_G = partial_trace_C(GHZ)
print(" ρ_AB =\n", np.real_if_close(rho_G))
print(f" eigenvalues = {np.round(np.linalg.eigvalsh(rho_G)[::-1], 4)} (expected [1/2, 1/2, 0, 0])")
print(f" concurrence C(ρ_AB) = {concurrence_2qubit(rho_G.astype(complex)):.4f} (expected 0 → the entanglement vanishes completely with the lost particle)")
This chapter started from a natural and pressing question: when the spectral theorem for symmetric matrices meets the non-square data that are everywhere in the real world, can we keep the elegant geometric picture of “rotation—stretch—rotation”? §11.1 gave an affirmative answer. Through the bridge A∗A, every matrix A∈Cm×n has a decomposition A=UΣV∗; the singular values uniquely describe the geometric structure by which the matrix stretches the unit sphere into an ellipsoid; the four fundamental subspaces each receive an orthonormal basis under the SVD; and the three viewpoints of algebra, subspaces, and geometry confirm one another, together laying the theoretical foundation of the whole chapter.
With singular values as “descriptors” in hand, §11.2 asked about their optimality: among all matrices of rank at most k, the one closest to A is exactly the truncated SVD Ak, with the error given precisely by the discarded singular values. The Eckart–Young theorem is the theoretical crown of the chapter: it turns “best approximation,” a problem that would otherwise require laborious optimization, into something read off directly from the SVD. The three matrix norms—the spectral norm, the Frobenius norm, and the nuclear norm—are all completely determined by the singular values, further highlighting the status of singular values as the essential invariants of a matrix.
This optimality immediately found its statistical incarnation in §11.3: principal component analysis (PCA) is essentially the truncated SVD of the centered data matrix; the right singular vectors give the directions of greatest variation in the data, and the squared singular values (after normalization) are the amounts of variation explained by the principal components. Taking the SVD of the data matrix directly, rather than finding the eigenvalues of the covariance matrix, avoids numerically the trap of squaring the condition number—a point textbooks seldom stress but one that is crucial in actual computation. The limitations of PCA—its helplessness in the face of nonlinear manifolds—also prompt readers to think about when they need to go beyond linear tools.
§11.4 turned “best approximation” from dimensionality reduction of data to the solution of equations: the Moore–Penrose pseudoinverse A+=VΣ+U∗, by taking reciprocals of the nonzero singular values, gracefully extends the concept of matrix inversion and gives the minimum-norm least-squares solution. The numerical reality that the reciprocals of small singular values amplify noise explosively led to ridge regression regularization: using the parameter λ to smoothly suppress the contributions of small singular values, trading a controllable bias for the stability of the solution. The pseudoinverse, regularization, and singular value truncation are unified in the SVD framework as different choices along the same spectrum.
Finally, §11.5 carried the SVD into the territory of quantum physics: taking the SVD of the coefficient matrix of a bipartite pure state is precisely the Schmidt decomposition. The distribution of the Schmidt coefficients describes the entanglement structure precisely, the von Neumann entropy quantifies the strength of entanglement, and the entanglement distillation theorem gives the entropy an operational meaning. From condition numbers to entanglement entropy, the same mathematics of singular values speaks the same language in strikingly different fields.
This chapter follows directly from Chapter 9 (inner product spaces and symmetric matrices) and Chapter 10 (introduction to quantum mechanics). The spectral theorem of Chapter 9 is the cornerstone of the existence proof in §11.1: A∗A is a Hermitian positive semidefinite matrix, and its orthogonal diagonalization guarantees the existence of the SVD; the language of positive definiteness established in Chapter 9 (⟨x∣A∗A∣x⟩=∥Ax∥2≥0) is reused here directly. An earlier bridge comes from Chapter 5: the statistical context of covariance matrices and PCA is precisely activated in §11.3, and block-matrix thinking also runs through the account of how the three forms of the SVD (full, reduced, truncated) relate. The discussion of the solution structure of systems of linear equations in Chapter 6 finds its final, completed form in §11.4—the pseudoinverse unifies “generalized inversion” for the three cases of overdetermined, underdetermined, and rank-deficient systems.
The Dirac notation, tensor product spaces, and density matrices of Chapter 10 become the linguistic foundation of the Schmidt decomposition in §11.5; conversely, the Schmidt decomposition provides quantitative mathematical tools for the qualitative picture of entanglement described in Chapter 10—the entanglement entropy, the separability criterion, and the spectrum of the reduced density matrix all rely on the singular value structure of the SVD. Reading the two chapters side by side, the reader will see how linear algebra and quantum mechanics translate deeply into each other on the common platform of complex inner product spaces.
As the last chapter of the book, the SVD plays the role of the terminal station: it integrates all the main tools of the book—vector spaces, linear mappings, determinants, eigenvalues, inner products, positive definiteness, tensor products—and, over the broadest class of matrices, fulfills the core promise of linear algebra: “decompose in order to understand.”
The SVD solves an old and universal problem: given a linear mapping, how can we find an input basis and an output basis in which the mapping looks as “simple” as possible—diagonal, ordered, and geometrically intuitive? Symmetric matrices are only a special case of this problem; the SVD is its complete answer. It is both the end point of the theory and the starting point of computation: in numerical linear algebra, the SVD is the standard algorithm for the rank, the condition number, the pseudoinverse, and least squares, and almost every large-scale computation involving matrices ultimately comes back to the singular value decomposition.
In the broader scientific picture, the “correlation structure” revealed by the SVD is everywhere: spatial correlation in image compression, feature correlation in datasets, particle entanglement in quantum systems—mathematically they are all the same thing, the distribution of the singular values of a matrix (or tensor). After studying this chapter, whenever readers meet the words “dimensionality reduction,” “regularization,” or “entanglement measure,” the image of singular values will come naturally to mind: a row of positive numbers arranged by size, each marking the “signal strength” in some direction, and truncating them means making a well-founded trade-off. This way of seeing is precisely the mark of linear algebra rising from a technical tool to a way of thinking.