The singular value decomposition
The singular value decomposition (lesson 3.8.1)
0 of 10 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Low-rank approximation
Low-rank approximation (lesson 3.8.2)
0 of 10 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Coding exercise
The best rank-k copy
Write two functions with NumPy, using np.linalg.svd(A, full_matrices=False) as in lessons 3.8.1 and 3.8.2.
best_rank_k(A, k)returns as a float array with the shape ofA; gives zeros. RaiseValueErrorifAis not 2-D, or ifkis not a whole number from 0 to .rank_for_share(A, share)returns the smallest whose copy keeps at leastshareof the squared size, . RaiseValueErrorunless , and ifAis all zeros.
Rounding matters in the second function. 's first piece keeps exactly 60% of its squared size, yet the computed share can come out a hair below 0.6. Count a share within of the target as reaching it.
The tests use and from the lessons, a rank-1 table, a random matrix and a wide one. A version that adds up instead of fails them.
See the tests
import numpy as np
B = np.array([[2, 0], [1, 1], [0, 2]]) # lesson 3.8.1's tall matrix
M = np.array([[6, 4, 2, 1], [4, 3, 2, 1], [2, 2, 2, 2], [1, 1, 2, 4], [0, 1, 2, 5]]) # lesson 3.8.2
T = np.outer([1, 2, 3], [1, 2, 3]) # a table of rank 1
def _raises(f, what):
try:
f()
except ValueError:
return
raise AssertionError(f"expected ValueError for {what}")
def test_tall_rank_one():
"""The rank-1 copy of lesson 3.8.1's B is the 3 × 2 table of ones"""
expect_close(best_rank_k(B, 1), np.ones((3, 2)), what="best_rank_k(B, 1)", abs_tol=1e-9)
def test_full_rank_gives_back_the_matrix():
"""Keeping both pieces of B gives B back"""
expect_close(best_rank_k(B, 2), B, what="best_rank_k(B, 2)", abs_tol=1e-9)
def test_zero_pieces():
"""k = 0 keeps nothing: a 3 × 2 array of zeros"""
out = np.asarray(best_rank_k(B, 0))
expect_equal(out.shape, (3, 2), what="shape of best_rank_k(B, 0)")
expect_close(out, np.zeros((3, 2)), what="best_rank_k(B, 0)", abs_tol=1e-12)
def test_errors_of_m2():
"""For lesson 3.8.2's M, the rank-2 copy is off by σ₃ = 0.6488 (2-norm) and 0.7232 (Frobenius)"""
s = np.linalg.svd(M, compute_uv=False)
err = M - np.asarray(best_rank_k(M, 2))
expect_close(np.linalg.norm(err, 2), s[2], what="2-norm of M − M₂", rel=1e-9)
expect_close(np.linalg.norm(err), np.sqrt(s[2] ** 2 + s[3] ** 2), what="Frobenius norm of M − M₂", rel=1e-9)
def test_random_matrix():
"""A random 6 × 4 matrix: the rank-2 copy has rank 2 and the error the rules predict"""
A = np.random.default_rng(38).standard_normal((6, 4))
s = np.linalg.svd(A, compute_uv=False)
A2 = np.asarray(best_rank_k(A, 2))
expect_equal(int(np.linalg.matrix_rank(A2)), 2, what="rank of best_rank_k(A, 2)")
expect_close(np.linalg.norm(A - A2), np.sqrt(np.sum(s[2:] ** 2)), what="Frobenius error", rel=1e-9)
def test_wide_matrix_and_lists():
"""A wide 2 × 3 matrix given as a list of lists works: its rank-1 copy is σ₁u₁v₁ᵀ"""
A = [[3, 0, 4], [0, 1, 0]]
expect_close(best_rank_k(A, 1), [[3, 0, 4], [0, 0, 0]], what="best_rank_k(wide, 1)", abs_tol=1e-9)
def test_bad_k():
"""k = −1, k = 3 for the 3 × 2 B, and k = 1.5 raise ValueError"""
_raises(lambda: best_rank_k(B, -1), "k = -1")
_raises(lambda: best_rank_k(B, 3), "k = 3 for a 3 × 2 matrix")
_raises(lambda: best_rank_k(B, 1.5), "k = 1.5")
def test_not_a_matrix():
"""A 1-D array is not a matrix: ValueError"""
_raises(lambda: best_rank_k(np.array([1.0, 2.0, 3.0]), 1), "a 1-D array")
def test_shares_of_m():
"""For M, 90% and 99.5% need k = 2, 99.9% needs k = 3 and 100% needs all 4"""
expect_equal(rank_for_share(M, 0.9), 2, what="rank_for_share(M, 0.9)")
expect_equal(rank_for_share(M, 0.995), 2, what="rank_for_share(M, 0.995)")
expect_equal(rank_for_share(M, 0.999), 3, what="rank_for_share(M, 0.999)")
expect_equal(rank_for_share(M, 1.0), 4, what="rank_for_share(M, 1.0)")
def test_shares_use_squares():
"""B's first piece keeps exactly 60% of the squared size (6 of 10), so 60% needs k = 1"""
expect_equal(rank_for_share(B, 0.6), 1, what="rank_for_share(B, 0.6)")
def test_rank_one_table():
"""The rank-1 table keeps 100% with one piece, despite rounding in its other singular values"""
expect_equal(rank_for_share(T, 1.0), 1, what="rank_for_share(T, 1.0)")
def test_bad_share():
"""A share of 0, 1.5 or −0.1, or a zero matrix, raises ValueError"""
for share in (0, 1.5, -0.1):
_raises(lambda: rank_for_share(M, share), f"share = {share}")
_raises(lambda: rank_for_share(np.zeros((2, 2)), 0.5), "a zero matrix")