给你三个方程、三个未知数,中学的做法是一个个消元。但如果方程有一万个呢?如果方程比未知数多、根本没有精确解呢?线性代数把”解方程”这件事换了一个看法:矩阵不是一张数字表,而是一台把向量变成向量的机器。看懂这台机器在做什么,消元、最小二乘、PCA、神经网络里的 W @ x 就都是同一件事。
前置:无。会用 numpy 更好。
矩阵乘向量:列的组合
先看一个 的方程组:
“行视角”是把每个方程画成平面上一条直线,解就是两线交点 。这在二维很直观,到了十维就画不出来了。
Strang 更喜欢”列视角”。把方程写成
问题变成:两根列向量各取多少份,加起来恰好等于右边的 ?这就是 的真正含义。 是 的列的线性组合,系数是 的分量。
import numpy as np
A = np.array([[2., -1.], [-1., 2.]])
x = np.array([1., 2.])
# 两种算法结果一样,但第二种才是"列视角"
print(A @ x)
print(x[0] * A[:, 0] + x[1] * A[:, 1])输出:
[0. 3.]
[0. 3.]这个看法马上带来一个问题:对所有 都能解吗?只要两列不共线,它们的组合能铺满整个平面,答案是能。如果两列共线(比如第二列是第一列的 3 倍),组合只能落在一条直线上,大多数 无解。” 的列的所有组合”有个名字,叫列空间 。 有解,当且仅当 。
矩阵乘矩阵也用同样的眼光看。 的第 列等于 乘 的第 列,也就是 的列按 第 列给的系数组合。所以 的每一列都在 里。还有一种拆法同样有用:AB = \sum_k (\text{Ak 列})(\text{Bk 行}),即若干个秩 1 矩阵之和。后面讲 SVD 时会再用到它。
顺带一提,矩阵乘法不满足交换律: 和 一般不相等,甚至形状都可能不同。从变换的角度看这很自然,“先旋转再拉伸”和”先拉伸再旋转”是两件不同的事。但它满足结合律 ,这条性质在工程上很值钱:同样的结果,括号加在不同位置,计算量可能差几个数量级。比如 是 、 是 、 是一个向量,先算 再算 只要两次矩阵乘向量,约 次运算;先算 则要约 次。反向传播从输出往输入逐层乘雅可比矩阵,就是在利用这一点。
一个矩阵也可以看成一个线性变换:它把空间里每个向量搬到新的位置,同时保持直线还是直线、原点不动。 的第一列就是基向量 被送到的位置,第二列是 被送到的位置。知道了基向量去哪,任何向量去哪都确定了,这就是为什么一个矩阵能完整描述一个线性变换。神经网络里的一层 y = W @ x,就是先做一个这样的变换,再接非线性(见 从线性回归到神经网络)。
A 是 3×2 矩阵,两列线性无关。关于 Ax = b,哪个说法对?
两根无关的列只能张成三维空间里的一个平面,b 在这个平面上才能被组合出来;列无关意味着组合系数唯一。
"任意 b 都有解"需要列空间是整个三维空间,两列做不到。方程多于未知数通常导致无解而不是无穷多解。"一定无解"忽略了 b 恰好在平面上的情况。
消元:计算机怎么解方程
列视角告诉我们解是否存在,但还没告诉我们怎么求。计算机用的是高斯消元,这和中学的做法一样,只是组织得更整齐。
取
第一步用第 1 行消掉下面两行的第一列:第 2 行减去 2 倍第 1 行,第 3 行加上 1 倍第 1 行。第二步用新的第 2 行消第 3 行的第二列。最后得到上三角矩阵 ,从最后一个方程往回代就能解出所有未知数。
每一次”第 行减去 倍第 行”都是左乘一个初等矩阵。把这些乘数 收集起来,正好放在一个下三角矩阵 的对应位置,于是
这件事的好处是: 和 只依赖于 。如果要对同一个 解很多个不同的 ,只要分解一次(约 次浮点运算),之后每个 只需要两次三角回代(约 )。
import numpy as np
A = np.array([[2., 1, 1], [4, -6, 0], [-2, 7, 2]])
def lu(A):
# 不做行交换的教学版;主元为 0 时会坏,正好说明为什么需要 pivoting
n = len(A); U = A.copy(); L = np.eye(n)
for j in range(n):
for i in range(j + 1, n):
L[i, j] = U[i, j] / U[j, j]
U[i] -= L[i, j] * U[j]
return L, U
L, U = lu(A)
print(L); print(U)
print(np.allclose(L @ U, A))输出:
[[ 1. 0. 0.]
[ 2. 1. 0.]
[-1. -1. 1.]]
[[ 2. 1. 1.]
[ 0. -8. -2.]
[ 0. 0. 1.]]
True 的对角线 叫主元。这个函数在主元为 0 时会除以零;主元很小时会把舍入误差放大。实际的库(LAPACK、np.linalg.solve)会做部分主元选择:每一列挑绝对值最大的元素换到主元位置,于是分解变成 , 是置换矩阵。
主元也回答了”可不可逆”的问题:消元后 个主元全非零, 就可逆;有一列找不到非零主元,说明这一列能被前面的列组合出来, 是奇异的。行列式 就是主元的乘积(再乘上行交换带来的 ),上面这个例子是 。行列式的几何意义是变换对体积的缩放倍数: 表示单位立方体被映成体积 16 的平行六面体, 表示空间被压扁了一维。
实际计算里几乎从不显式求 。解 时用 np.linalg.solve,它内部就是 LU 分解加回代,比先求逆再乘更快也更准。
四个子空间:解长什么样
消元做到底,能把任意 矩阵化成行最简形 。数一数主元个数,就得到矩阵最重要的一个数:秩 。秩是线性无关列的最大个数,也等于线性无关行的最大个数。
看一个秩不满的例子:
第三行等于前两行之和,第二列是第一列的 2 倍,所以秩是 2。四个未知数、两个有效方程,必然有两个自由变量。
import numpy as np
A = np.array([[1., 2, 2, 2], [2, 4, 6, 8], [3, 6, 8, 10]])
print("rank:", np.linalg.matrix_rank(A))
# 零空间的一组基:SVD 里奇异值为 0 的右奇异向量
_, s, Vt = np.linalg.svd(A)
N = Vt[2:].T
print("singular values:", s.round(4))
print("A @ N ≈ 0:", np.allclose(A @ N, 0))输出:
rank: 2
singular values: [18.4656 1.0102 0. ]
A @ N ≈ 0: True的所有解组成零空间 ,维数是 。 的通解是”一个特解 + 零空间里任意向量”,因为零空间里的向量被 送到 0,加上它不改变 。
Strang 把每个矩阵配上四个子空间:
| 子空间 | 住在哪 | 维数 |
|---|---|---|
| 列空间 | ||
| 零空间 | ||
| 行空间 | ||
| 左零空间 |
维数的两条等式(,)叫秩-零化度定理。更深的一层是正交:行空间和零空间在 里互相垂直,列空间和左零空间在 里互相垂直。原因直接来自定义: 说的正是 和 的每一行点积为 0。
于是 做的事可以这样描述:任何输入 拆成行空间分量和零空间分量,零空间分量被直接压成 0,行空间分量被一一对应地送到列空间。 在行空间和列空间之间是一个可逆的映射,这是理解伪逆和 SVD 的钥匙。
A 是 5×3 矩阵,秩为 2。下面哪些成立?
零空间维数是 n 减 r,即 3 减 2 等于 1;左零空间维数是 m 减 r,即 5 减 2 等于 3。
列空间维数等于秩 2,不是列数 3。列空间只是 R^5 里的一个平面,大多数 b 不在上面,所以一定有 b 使方程无解。
正交与投影:没有解时找最好的解
现实中的方程组常常没有解。测了 100 个点想拟合一条直线 ,100 个方程、2 个未知数, 几乎不可能恰好落在 的列空间里。
既然 解不出来,退而求其次:在列空间里找离 最近的点 。几何上,最近点就是 往列空间做垂线的垂足,误差 必须垂直于列空间,也就是垂直于 的每一列:
这就是正规方程。投影矩阵 把任何向量投到列空间上,它满足 (投两次和投一次一样)。
拿三个点 拟合直线:
import numpy as np
t = np.array([0., 1, 2]); b = np.array([6., 0, 0])
A = np.column_stack([np.ones(3), t])
xhat = np.linalg.solve(A.T @ A, A.T @ b)
e = b - A @ xhat
print("c, d =", xhat)
print("误差 e =", e)
print("A^T e =", (A.T @ e).round(12)) # 误差与每一列正交
print("lstsq:", np.linalg.lstsq(A, b, rcond=None)[0])输出:
c, d = [ 5. -3.]
误差 e = [ 1. -2. 1.]
A^T e = [0. 0.]
lstsq: [ 5. -3.]最佳直线是 ,误差向量 与两列都正交。最小二乘就是一次投影,从线性回归到神经网络 里的线性回归在没有正则化时解的正是这个方程。
正规方程在数值上有缺点: 的条件数是 的平方,列接近共线时误差会被放大。更好的办法是先把 的列变成标准正交的。
标准正交向量 两两垂直、长度为 1,排成矩阵 满足 。有了它,投影公式退化成 ,最小二乘变成 ,不需要解任何方程。Gram-Schmidt 正是把任意一组无关向量变成标准正交向量的过程:每来一个新向量,减去它在已有方向上的投影,剩下的部分归一化。把这个过程记下来就是 分解, 是上三角。np.linalg.lstsq 用的是 QR 或 SVD 而不是正规方程。
正交矩阵还有一个性质:,它只旋转或反射,不拉伸。这在数值计算里很宝贵,因为误差不会被放大。
最小二乘解要求误差 e = b - A x̂ 满足什么?
最近点是 b 在列空间上的垂足,所以误差必须垂直于整个列空间,等价于与每一列点积为 0,写出来就是正规方程。
e 等于 0 只在 b 恰好落在列空间时发生,那是有精确解的情形。e 与 b 正交不是最近点条件。各分量相同是没有依据的猜测。
特征值:找到不被转向的方向
到目前为止研究的是 ,一个”静态”问题。另一类问题是”动态”的:反复乘同一个矩阵,,一百步之后会怎样?直接算 太蠢,我们需要一个看法让矩阵的幂变简单。
大多数向量被 作用后会改变方向。但有一些特殊方向, 只把它拉长或缩短:
是特征向量, 是特征值。沿着这个方向,乘 100 次 就是乘 。如果 矩阵有 个无关的特征向量,把它们排成 ,就有 ,于是 ,只需要对对角线上的数取幂。
斐波那契数列是经典例子。 写成 ,其中 。
import numpy as np
A = np.array([[1., 1], [1, 0]])
# A 对称,用 eigh 得到实特征值(升序)和正交特征向量
lam, S = np.linalg.eigh(A)
print("特征值:", lam)
# 大的特征值主导长期行为:F_k ≈ φ^k / √5
k = 30
Ak = S @ np.diag(lam**k) @ S.T # 正交矩阵的逆就是转置
print("F_30 via eig:", round(Ak[0, 1]))
print("φ^30/√5 :", lam.max()**k / np.sqrt(5))输出:
特征值: [-0.61803399 1.61803399]
F_30 via eig: 832040
φ^30/√5 : 832040.0000002412两个特征值是 。 的那部分每步都在衰减,所以 很快就由黄金比例 主导。这个思路推广开来:长期行为由绝对值最大的特征值决定。这也是幂迭代的原理(PageRank 就是求一个巨大矩阵的主特征向量),也是 RNN 梯度消失/爆炸的数学根源:反复乘同一个权重矩阵,特征值大于 1 的方向指数爆炸,小于 1 的方向指数衰减(见 序列模型与注意力的起源)。
特征值怎么求? 有非零解意味着 奇异,即 。这是一个 次多项式,但这只适合手算小矩阵。数值库用的是 QR 算法这样的迭代法。两个不用算的检查:特征值之和等于迹(对角线之和),特征值之积等于行列式。斐波那契矩阵的迹是 1,行列式是 ,两个特征值 和 正好满足。
并不是所有矩阵都能对角化。 只有一个特征方向;旋转矩阵的特征值是复数。但有一类矩阵总是表现完美:
对称矩阵 的特征值都是实数,特征向量可以选成两两正交,于是 (谱定理)。如果所有特征值都为正,就叫正定矩阵,等价于对任意非零 有 。 是一个二次函数,正定说明它的图像是一个向上开口的碗,有唯一的最低点。优化里,损失函数在极小值附近的 Hessian 是正定的;Hessian 最大和最小特征值之比(条件数)越大,碗越扁,梯度下降越容易来回震荡。协方差矩阵 至少是半正定的,因为 。
矩阵 A 的特征值是 0.5 和 1.2,从一个一般的初始向量开始反复做 u = A @ u,很多步之后会怎样?
把初始向量分解到两个特征方向上,第 k 步两部分分别乘 0.5 的 k 次方和 1.2 的 k 次方,前者衰减、后者增长,最终由 1.2 那一项主导。
只有所有特征值绝对值都小于 1 才会收敛到 0。来回摆动需要负特征值。保持长度需要特征值绝对值为 1(如正交矩阵)。
SVD:每个矩阵都是旋转、拉伸、旋转
特征分解有两个限制:只对方阵有意义,而且不一定存在。数据矩阵(1000 个样本 × 50 个特征)是长方形的,我们需要一个对任何矩阵都成立的分解。
奇异值分解说:任何 矩阵都可以写成
()和 ()是正交矩阵, 是对角线上为 的长方形对角阵。几何上: 先旋转输入, 沿坐标轴拉伸, 再旋转输出。任何线性变换都把单位圆映成一个椭圆, 就是椭圆的半轴长。
它和前面所有内容都接上了。 的列是 的特征向量, 的列是 的特征向量, 是对应的特征值。非零奇异值的个数就是秩 ; 的前 列是行空间的标准正交基,后 列是零空间的基, 同理给出列空间和左零空间的基。四个子空间一次全部拿到。
按”列乘行”展开,
每一项是秩 1 矩阵,按重要性从大到小排好了。只保留前 项,得到的 是所有秩不超过 的矩阵里离 最近的(Eckart–Young 定理),误差的谱范数恰好是 。
import numpy as np
rng = np.random.default_rng(0)
# 构造一个"本质上是秩 3"的 100x80 矩阵,再加一点噪声
A = rng.normal(size=(100, 3)) @ rng.normal(size=(3, 80)) + 0.01 * rng.normal(size=(100, 80))
U, s, Vt = np.linalg.svd(A, full_matrices=False)
print("前 6 个奇异值:", s[:6].round(3))
for k in [1, 2, 3, 10]:
Ak = U[:, :k] * s[:k] @ Vt[:k]
rel = np.linalg.norm(A - Ak) / np.linalg.norm(A)
print(f"k={k:2d} 相对误差={rel:.4f} 参数量={k*(100+80)} vs {100*80}")输出:
前 6 个奇异值: [100.118 87.259 85.266 0.182 0.178 0.175]
k= 1 相对误差=0.7730 参数量=180 vs 8000
k= 2 相对误差=0.5403 参数量=360 vs 8000
k= 3 相对误差=0.0055 参数量=540 vs 8000
k=10 相对误差=0.0047 参数量=1800 vs 8000前三个奇异值远大于其余(其余只是噪声),保留 3 项就把相对误差降到约 0.5%(已经是噪声水平,再加到 10 项也几乎不再下降),存储只要原来的 7%。这个”大部分信息集中在少数方向上”的现象在真实数据里极常见,于是有了一串应用:
- PCA:把数据中心化后做 SVD, 的前几列就是方差最大的方向,投影上去就是降维。
- 伪逆:(非零 取倒数),对任何矩阵给出最小范数的最小二乘解,
np.linalg.pinv就是这么算的。 - 条件数: 衡量解方程时误差会被放大多少倍。
- 低秩近似:LoRA 微调把权重更新限制成 两个瘦矩阵的乘积,背后的假设正是”更新矩阵是近似低秩的”(见 后训练)。
关于 A = U Σ V^T,哪些说法正确?
SVD 不要求方阵也不要求可对角化,这是它比特征分解强的地方。非零奇异值个数等于秩,截断 SVD 的最优性就是 Eckart-Young 定理。
奇异值是 A^T A 特征值的平方根,总是非负实数;A 本身的特征值可以是负数甚至复数,只有对称半正定矩阵两者才一致。
线性代数与机器学习
回头看,机器学习里的矩阵计算几乎都落在上面几节里:
| 场景 | 背后的线性代数 |
|---|---|
全连接层 y = W @ x | 线性变换;输出是 W 列的组合 |
| 一个 batch 的前向 | 矩阵乘矩阵,GPU 上的 GEMM |
| 线性回归闭式解 | 投影与正规方程 |
| 注意力分数 | 所有 query 与 key 的点积表,见 Transformer 架构 |
| PCA、embedding 降维 | SVD |
| LoRA | 低秩分解 |
| 优化难易 | Hessian 的条件数 |
| RNN 梯度爆炸/消失 | 反复乘矩阵,特征值决定增长 |
还有一个容易忽略的工程事实:矩阵乘法 乘 需要 次浮点运算,它是现代 AI 硬件优化的头号目标。为什么矩阵乘能跑满 GPU 而逐元素加法不能,是 资源核算与 Scaling Laws 里算术强度要回答的问题。
一个形状为 (4096, 4096) 的权重矩阵,用秩 16 的 B @ A 来表示它的更新,参数量大约是原来的多少?
B 是 4096×16、A 是 16×4096,共 2 乘 4096 乘 16 约 13 万个参数,而原矩阵约 1678 万,比例约 0.8%。
乘出来的矩阵形状确实是 4096×4096,但存储和训练的只是两个瘦矩阵,参数量由它们决定,不是由乘积形状决定。
记住这三件事
- 是 的列的线性组合; 有解当且仅当 在列空间里,解的自由度由零空间决定。
- 没有精确解时,最小二乘就是把 投影到列空间,误差垂直于每一列。
- 特征值决定反复作用的长期行为;SVD 对任何矩阵成立,按重要性把它拆成秩 1 矩阵之和,截断即最优低秩近似。
延伸阅读
- MIT 18.06 Linear Algebra(Gilbert Strang,OCW)
- Gilbert Strang,《Introduction to Linear Algebra》第 5/6 版,以及论文 The Fundamental Theorem of Linear Algebra
- 3Blue1Brown:线性代数的本质(几何直觉)
- MIT 18.065 Matrix Methods in Data Analysis(SVD、低秩与 ML)
- Trefethen & Bau,《Numerical Linear Algebra》(QR、条件数、数值稳定性)