把矩阵拆成几个方向上的作用,就能看清它怎样拉伸向量、哪些信息在变换后消失,以及少保留几个方向会损失多少。线性代数概览介绍向量、秩、正交投影和最小二乘;这里从一个二阶矩阵算起,再用一个三行两列的矩阵完成 SVD、重构和低秩近似。矩阵均取实数,向量按列书写,T 表示转置。
特征向量:变换后仍留在同一条直线上
对方阵 B,若非零向量 v 满足 Bv=λv,则 v 是特征向量,λ 是特征值。MIT 18.06SC 的特征值讲义从这个关系出发:v 所在的直线在变换下保持不变。λ>0 时方向不变,λ<0 时反向,λ=0 时被映到零。零向量不能作为特征向量,否则任何 λ 都满足等式。
取
B=[2112].
由 (B−λI)v=0,要有非零解,必须使 B−λI 奇异。因此先解特征方程:
det(B−λI)=(2−λ)2−1=λ2−4λ+3=(λ−3)(λ−1)=0.
得到 λ1=3、λ2=1。再分别求零空间:
- 当 λ=3,B−3I=[−111−1],所以 v2=v1。
- 当 λ=1,B−I=[1111],所以 v2=−v1。
归一化后选取
q1=21[11],q2=21[1−1].
直接相乘可查得 Bq1=3q1、Bq2=q2。任意 x=c1q1+c2q2 都满足 Bx=3c1q1+c2q2:沿第一条轴放大三倍,沿第二条轴保持原长度。
实 n×n 矩阵能写成 B=PΛP−1,且 P,Λ 都为实矩阵,当且仅当它有 n 个线性无关的实特征向量。实矩阵也可能有复特征值;上面的不变直线解释适用于实特征向量。例如 J=[1011] 的特征值 1 重复两次,但 J−I 的零空间只有 (1,0)T 张成的一条直线,无法组成二维基。
对称性为什么带来正交分解
MIT 的对称矩阵讲义给出的谱定理是:实对称矩阵有实特征值,并且可以选出一组完整的标准正交特征向量。对不同特征值 λi=λj,正交性可直接推出:
λjqiTqj=qiTBqj=(Bqi)Tqj=λiqiTqj,qiTqj=0.
有重复特征值时,同一特征空间内任意选取的两个向量未必正交,但可以在其中选标准正交基。令 Q=[q1 q2],则 QTQ=I、Q−1=QT,上面的例子可以写成
B=Q[3001]QT=3q1q1T+q2q2T.
右侧每一项都是“先投影到一条轴,再乘以特征值”。对称性不保证特征值为正;这个例子的两个特征值恰好都为正。
长方形矩阵的 SVD 与因子尺寸
三行两列的矩阵把二维输入映到三维输出,输入与输出无法直接用 Av=λv 比较。MIT 的 SVD 讲义说明任意实矩阵都存在 SVD,用两组标准正交方向连接输入与输出:
A=UΣVT,Avi=σiui.
vi 是输入空间的右奇异向量,ui 是输出空间的左奇异向量,奇异值按 σ1≥⋯≥σp≥0 排列,p=min(m,n)。先用 VT 取输入坐标,再由 Σ 拉伸,最后由 U 映回输出坐标。
| 形式,A∈Rm×n | U | Σ | VT |
|---|
| 完整 SVD | m×m | m×n | n×n |
| 经济型 SVD,p=min(m,n) | m×p | p×p | p×n |
| 只留非零项,r=rank(A) | m×r | r×r | r×n |
完整形式的 U,V 是正交方阵;后两种形式中 U,V 的列向量标准正交,但矩阵可能不是方阵。经济型形式仍可包含零奇异值,不能把 p 和秩 r 混为一谈。
SciPy 线性代数教程用 U, s, Vh 表示返回结果;s 是一维奇异值数组,Vh 对实矩阵就是 VT,并不是 V。scipy.linalg.svd 的接口文档列出了 full_matrices=False 对应的经济型尺寸。
从两个奇异分量重构矩阵
取
A=20−202−2,ATA=[8448]=4B.
因为 ATAvi=σi2vi,前面的特征向量可以直接复用:v1=q1、v2=q2,对应特征值 12 和 4,所以
σ1=12=23,σ2=2.
对非零奇异值,用 ui=Avi/σi 求左奇异向量:
Av1Av2=22−22,=2−20,u1u2=6111−2;=211−10.
这里 u1Tu2=0,两者长度都是 1。令 Up=[u1 u2],Σp=diag(23,2),Vp=[v1 v2],便得到尺寸为 (3×2)(2×2)(2×2) 的经济型 SVD。完整形式再补上 u3=(1,1,1)T/3,并在 Σ 底部补一行零;ATu3=0。
把乘积展开成外积,每个非零分量的秩都是 1:
A=σ1u1v1T+σ2u2v2T=11−211−2+1−10−110=20−202−2.
同时把 ui,vi 都乘以 −1,外积不变。对实对称矩阵,奇异值是特征值的绝对值;负特征值的符号要由左右奇异向量的相对方向承担。只有正半定情形才可以让奇异值直接等于特征值。
截断后,误差怎样计算
设 1≤k<r,只保留最大的 k 项:
Ak=i=1∑kσiuiviT.
Stanford CS168 的低秩近似讲义,第 5 节给出的结论是:Ak 在所有秩不超过 k 的矩阵中,使 Frobenius 范数误差最小。这里 ∥M∥F2=∑a,bMab2。不同外积在这个内积下正交,因为
⟨uiviT,ujvjT⟩F=(uiTuj)(viTvj).
所以残差的平方范数就是被舍弃项的平方和:
∥A−Ak∥F=i=k+1∑rσi2,∥A−Ak∥2=σk+1.
矩阵的 2 范数表示单位输入向量受到的最大长度放大;残差沿 vk+1 达到上述值。若 k≥r,重构误差为零。
本例保留第一项,得到
A1=11−211−2,A−A1=1−10−110.
于是 ∥A−A1∥F=2,∥A∥F=12+4=4,相对 Frobenius 误差为 1/2。保留的平方范数比例则是 12/(12+4)=75%。保留 75% 的平方范数,与相对误差为 25% 不是同一个说法:此处被舍弃的平方误差占 25%,取平方根后才是相对范数误差。
PCA:平方奇异值变成方差
scikit-learn 的 PCA 文档说明,PCA 在中心化后的数据上寻找解释方差最大的正交分量;中心化不会自动把各特征缩放到相同尺度。
令 X∈RN×d 的每行是一条观测,每列已经减去均值,且 N>1。采用样本协方差的 N−1 分母,与 PCA 接口的解释方差一致,代入完整 SVD X=UΣVT 得到
C=N−1XTX=VN−1ΣTΣVT.
因此,vi 是主成分方向,σi2/(N−1) 是对应的方差。保留前 k 个方向时,得分矩阵为 Z=XVk=UkΣk,重构为 ZVkT=Xk;原数据未中心化时还要加回均值。
本例的 A 每列之和都是零,可以直接作为 X。N=3,协方差矩阵为 C=[4224],主成分方差为 6 和 2。第一主成分的三个得分是 (2,2,−22)T;乘以 v1T 就重构出 A1,解释方差比例为 6/(6+2)=75%。
聚类与降维讨论怎样选择尺度、拟合数据和下游评价。这里的 75% 只度量这些坐标中的方差保留情况,不能据此判断分类任务是否保留了足够的信息。
最小二乘:零奇异值和小奇异值各意味着什么
对 minx∥Ax−b∥22,MIT 的伪逆讲义从 SVD 出发,对非零奇异值取倒数。最小欧氏范数的最小二乘解是
x∗=A+b=i=1∑rσiuiTbvi.
这是先把 b 投影到列空间,再逐方向撤销拉伸。其余最小二乘解为 x∗+z,其中 Az=0;这些零空间方向与 x∗ 正交,故 x∗ 的范数最小。
对本例取 b=(1,0,0)T,两个方向上的系数分别是 1/(62) 和 1/(22),得到
x∗=[1/3−1/6],Ax∗=2/3−1/3−1/3,b−Ax∗=31111.
残差正是左零空间方向,AT(b−Ax∗)=0。A 有满列秩,系数唯一;换成 A1 后,A1v2=0,给任一解加上 tv2 都不会改变预测。线性模型的系数可辨识,要求输入方向不能被矩阵完全抹掉。
小但非零的奇异值对应另一个问题:除以它会放大数据扰动。固定 A,若 δb=εui,则 δx=(ε/σi)vi。例如 D=diag(1,0.001),第二个输出坐标的扰动 10−6 会变成系数扰动 0.001。
对满列秩矩阵,谱条件数为 κ2(A)=∥A∥2∥A+∥2=σ1/σn。MIT 数值方法讲义第 5 讲把这些范数与 ATA 的最大、最小特征值联系起来,并说明 κ2(ATA)=κ2(A)2:后者的特征值是平方奇异值。本例分别为 3 和 3;D 的条件数则为 1000。数值分析区分问题的敏感性与算法的稳定性;普通最小二乘与正则化进一步讨论求解器和系数解释。
为降维截断 SVD,是在控制矩阵重构误差;为求解截断伪逆,则会把某些系数方向舍弃,改变估计结果。阈值应结合数据误差与用途来选,不能把一个很小的非零奇异值直接等同于数学上的零。
用 Python 复核分量
以下代码只用标准库,检查上面推导出的特征向量和奇异分量,并计算重构、秩一误差及最小二乘系数。辅助函数接收列表;点积的两个向量必须等长,矩阵每行的长度必须等于输入向量的长度,否则抛出 ValueError。
from math import sqrt, isclose
def dot(x, y):
if len(x) != len(y):
raise ValueError("Vector lengths must match")
return sum(a * b for a, b in zip(x, y))
def mv(matrix, vector):
return [dot(row, vector) for row in matrix]
B = [[2, 1], [1, 2]]
vectors = [[1 / sqrt(2), 1 / sqrt(2)],
[1 / sqrt(2), -1 / sqrt(2)]]
for eigenvalue, v in zip([3, 1], vectors):
assert all(isclose(a, eigenvalue * b, abs_tol=1e-12)
for a, b in zip(mv(B, v), v))
A = [[2, 0], [0, 2], [-2, -2]]
s = [sqrt(12), 2.0]
left = [[a / sigma for a in mv(A, v)]
for sigma, v in zip(s, vectors)]
assert isclose(dot(left[0], left[1]), 0, abs_tol=1e-12)
assert all(isclose(dot(u, u), 1) for u in left)
components = [[[sigma * u[i] * v[j] for j in range(2)]
for i in range(3)]
for sigma, u, v in zip(s, left, vectors)]
reconstructed = [[sum(c[i][j] for c in components) for j in range(2)]
for i in range(3)]
assert all(isclose(reconstructed[i][j], A[i][j], abs_tol=1e-12)
for i in range(3) for j in range(2))
A1 = components[0]
error = sqrt(sum((A[i][j] - A1[i][j]) ** 2
for i in range(3) for j in range(2)))
b = [1, 0, 0]
x = [sum(v[j] * dot(u, b) / sigma
for sigma, u, v in zip(s, left, vectors)) for j in range(2)]
print("eigenvalues:", [3, 1])
print("singular values:", [round(t, 6) for t in s])
print("reconstructed:", [[round(t, 6) for t in row] for row in reconstructed])
print("rank-one:", [[round(t, 6) for t in row] for row in A1])
print(f"Frobenius error: {error:.6f}")
print(f"retained variance: {s[0] ** 2 / sum(t * t for t in s):.6f}")
print("least-squares x:", [round(t, 6) for t in x])
运行输出:
eigenvalues: [3, 1]
singular values: [3.464102, 2.0]
reconstructed: [[2.0, 0.0], [0.0, 2.0], [-2.0, -2.0]]
rank-one: [[1.0, 1.0], [1.0, 1.0], [-2.0, -2.0]]
Frobenius error: 2.000000
retained variance: 0.750000
least-squares x: [0.333333, -0.166667]