很多人对线性代数的记忆,都停在期末前突击的那两周:把系数摆成一个矩阵,把某一行乘个倍数加到另一行上,最后在草稿纸角落圈出 x、y、z 三个数字,交卷。我当年也是这么过来的,分数不算难看,但工作之后真正遇到需要动手建模的场合——图像坐标的变换、一批观测数据的拟合、节点之间的流量分配——脑子里第一反应从来不是“这里该用线性代数”,而是“我能不能写两层循环硬算”。这个反差挺刺人的:我学的其实不是线性代数,我学的是一套叫“高斯消元”的手工流程,而矩阵、向量、线性组合、秩这些词,从来没在我脑子里形成过任何图像。所以这次重学,我把目标写得很死:不追求刷题量,只求每写下一个式子,能立刻说出它在空间里干了什么。下面这一篇就是我第一轮重学的整理,重点覆盖方程组、消元、矩阵乘法、列空间与秩这条主线,适合和我一样“会算但不会看”的人对照着过一遍。
1. 第二次翻开线性代数,我把学习顺序整个倒了过来
第一次学的时候,教材的顺序是:先定义矩阵,再讲矩阵的加法、数乘、乘法,然后引入方程组,最后讲向量空间。这个顺序在逻辑上没毛病,但对自学者极不友好,因为你前三分之二的时间都在跟一堆没有动机的运算规则打交道,等到向量空间出现时,人已经疲了。这次我换了个走法:先把“解方程组”当成主线问题,矩阵和乘法都是为解决这个问题、描述这个过程才被发明出来的工具。顺序一换,很多原来觉得莫名其妙的规定,一下子就有了理由。
1.1 第一次学完留下的三个后遗症
第一个后遗症是把矩阵当成一个装数字的方框。知道它是 m 行 n 列,知道元素记作 a_ij,但不知道“行”和“列”各自代表什么,也不知道为什么矩阵乘法非得是行乘列、还得要求左边列数等于右边行数。这种状态下,矩阵乘法就是一条需要背下来的规则,稍微变形就慌。
第二个后遗症是把消元当成一套固定动作。记住的是“先拿第一行消掉下面所有 x,再拿第二行消掉下面所有 y”,但从来没问过:为什么只能做这三种行变换?为什么交换两行不改变解?为什么某一行乘以零就不行?这些“为什么”当时被一句“显然”带过去了,实际上它们才是整件事的骨架。
第三个后遗症是把“有没有解”当成运气。算到最后出现 0 = 1,就知道无解;出现 0 = 0,就知道有无穷多解。这当然不算错,但这只是现象层面的判断,背后的结构——增广矩阵的秩和系数矩阵的秩是否相等——完全没有建立起来。结果就是,一旦问题从 3 个未知数变成 300 个,这套“算着看”的办法就彻底失效了。
1.2 把顺序倒过来:先问“空间里发生了什么”,再问“怎么算”
重学时我给自己定的第一条规矩:任何代数操作,必须能翻译成一句几何描述,说不出来就说明没懂。比如“矩阵乘向量”这件事,教材上的定义是一行一行乘出来得到一个新向量;但换个读法,它就是“用这个向量的各个分量,去线性组合矩阵的各列”。这两种读法算出来的结果完全一样,可理解成本天差地别。
再比如“方程组有解”这件事。按行看,是若干个超平面求公共交点;按列看,是问“目标向量 b 能不能由系数矩阵的各列拼出来”。后一种读法直接把问题导向了列空间,而列空间又是秩、是维数、是最小二乘的入口。我在笔记里把这两种视角并排写,左边代数、右边几何,写着写着就发现,原来教材里那些看起来毫不相干的章节,其实是从不同角度描述同一个对象。
倒过来学的另一个好处是,动机先于定义。先有了“我要判断这个系统有没有解、解有多大自由度”的需求,再去看秩的定义,就不会觉得它是个凭空冒出来的数字;先有了“我要把两个变换接连作用”的需求,再去看矩阵乘法的定义,就不会觉得那条行乘列的规则是拍脑袋定的。
1.3 笔记怎么记:一半写式子,一半写人话
我的笔记页面分成两栏。左栏写完整的推导过程,一步不省;右栏只写一句话,回答“这一步在干什么”。举个具体的样子:
| 左栏(推导) | 右栏(人话) |
|---|---|
| r₂ ← r₂ − 2r₁ | 把第二个方程里多余的 x 消掉 |
| 得到 y + z = 1 | 现在第二个方程只含 y 和 z,降了一个维度 |
| r₃ ← r₃ − 3r₁,再 r₃ ← r₃ − r₂ | 第三个方程被彻底消成 0 = 0,说明它是前两个的“重复信息” |
| 回代得 x = 1 + z | 解不是三个数,是一条直线上的所有点 |
另外我单独维护了一张“概念三栏表”,每个概念强制填三格:代数定义、几何图像、实际用途。填不满的格子就是我的知识漏洞。比如“秩”这一行,代数定义是“主元的个数”,几何图像是“列向量张成的空间的维数”,实际用途是“判断方程组解的自由度、判断数据是否冗余”。这三格能同时说顺,才算真的过关。
提示:三栏表里“实际用途”那一格最容易糊弄自己。写“用于机器学习”这种话没意义,要写到“在最小二乘拟合里,秩亏意味着参数不唯一,需要加正则项”这个颗粒度。
2. 方程组的解到底长什么样:三种结局和两套图像
很多人对方程组的理解停在“解出一组数”,但真实问题里,“解出来”往往是最不重要的一步。更重要的是先知道:这个系统到底有没有解?如果有,是唯一一个还是一族?这一族解的“形状”是什么?这三个问题的答案,决定了你后面该用求解器、该用最小二乘、还是该回去检查建模是不是写错了。
2.1 两个方程、三个方程,分别在空间里画什么
二元一次方程在平面上是一条直线。两个二元方程凑成一个系统,就是问两条直线有没有公共点:交于一点是唯一解,完全重合是无穷多解,平行不重合是无解。这是最直观的入口,但它有个陷阱——这个图像是“行图像”,画的是每个方程自己的解集,而不是整个系统的解。
到了三元,单个方程是一个平面。三个方程就是三个平面求公共点。结果有五种可能:交于一点、交于一条直线、交于一个平面(三个平面重合)、完全不相交、两两相交但三者无公共点。后两种情况在代数上都表现为“无解”,但在几何上是完全不同的两件事,这点在建模时很重要——前者说明约束互相矛盾,后者往往说明某个方程写错了。
我一直觉得“三个平面”这个图是理解秩的最佳入口。三个平面交于一点,说明三个法向量线性无关,系数矩阵满秩;交于一条直线,说明有一个法向量是另外两个的组合,秩降了一维,同时那条交线正好就是零空间平移过去的结果。
2.2 行图像和列图像,看的是同一件事的两面
行图像是“每个方程画一个图形,求公共部分”。列图像完全换了个问法:把方程组写成
x·(第一列) + y·(第二列) + z·(第三列) = b
这时候问题变成:用系数矩阵的三列,能不能拼出右边的 b?如果能,怎么拼?有多少种拼法?
这个转换是整个线性代数的分水岭。我第一遍学的时候完全没意识到它的重要性,直到我看懂下面这个对照关系,才觉得脑子里的灯亮了:
| 问题 | 行视角的说法 | 列视角的说法 |
|---|---|---|
| 有没有解 | 这些图形有没有公共点 | b 在不在各列张成的集合里 |
| 解唯一吗 | 公共点是不是只有一个 | 各列是否线性无关 |
| 解的自由度 | 公共部分的维数 | 各列的线性关系有多少种 |
| 无解时怎么办 | 找不到公共点 | b 不在列空间里,只能找最近的投影 |
这个对照表我建议每个人都手抄一遍。抄完之后你会发现,教材后面讲的列空间、零空间、秩、正交投影,全都是在把这张表的右半边展开讲,一个概念都没多出来。
2.3 一个 3×3 例子的完整手算推演
光说抽象没用,拿个具体系统走一遍。考虑:
x + 2y + z = 3 2x + 5y + 3z = 7 3x + 7y + 4z = 10第一步,消掉后两行里的 x:
- r₂ ← r₂ − 2r₁,得 y + z = 1
- r₃ ← r₃ − 3r₁,得 y + z = 1
这时候已经能看出问题了:第二行和第三行变成了一模一样的东西。继续 r₃ ← r₃ − r₂,第三行变成 0 = 0。它没有提供任何新信息,是一句“废话”。
第二步,把它化成最简阶梯形。用 r₂ 消掉 r₁ 里的 y:
- r₁ ← r₁ − 2r₂,得 x − z = 1
于是最简阶梯形是:
1 0 -1 | 1 0 1 1 | 1 0 0 0 | 0第三步,读数。第一行说 x − z = 1,第二行说 y + z = 1。z 没有主元,可以随便取,所以它是自由变量。把 z 记作参数 t:
- x = 1 + t
- y = 1 − t
- z = t
写成向量形式就是 (x, y, z) = (1, 1, 0) + t·(1, −1, 1)。
这行式子的信息量比“解出来”大得多。前半段 (1, 1, 0) 是一个特解,代回去三次方程全成立;后半段 t·(1, −1, 1) 是齐次解,也就是让左边等于零的所有方向。整个解集是一条过 (1,1,0)、方向为 (1,−1,1) 的直线。验证一下齐次部分:把 (1,−1,1) 代进系数矩阵,第一行 1−2+1=0,第二行 2−5+3=0,第三行 3−7+4=0,确实全零。
2.4 唯一解、无穷多解、无解,用同一套动作区分
上面那个例子属于“无穷多解”。另外两种结局其实走的是同一套流程,只是读数的地方不一样。举两个最小的例子对照:
唯一解的情形:
x + 2y = 3 3x + 5y = 4消元 r₂ ← r₂ − 3r₁,得 −y = −5,即 y = 5;回代 x = 3 − 10 = −7。两个变量对应两个主元,一个自由变量都没有,解就一个点。
无解的情形:
x + 2y = 3 2x + 4y = 7消元 r₂ ← r₂ − 2r₁,得 0 = 1。这时候不要急着写“无解”就完事,要看清楚发生了什么:第二行的左边被完全消干净了,但右边剩下一个非零常数。用列视角说,就是 b 的分量里有一部分落在列空间之外,无论怎么组合两列都追不上。
把三种情况归纳成一套判定动作,我记的是这个顺序:
- 化到阶梯形,数一数系数矩阵有几个主元,记作 r;
- 看增广矩阵有没有出现“左边全零、右边非零”的行,有就是无解;
- 没有这种行的话,比较 r 和未知数个数 n:r = n 是唯一解,r < n 是无穷多解,自由变量个数正好是 n − r。
这个流程的价值在于它不依赖具体数字,未知数是 3 个还是 3000 个,动作完全一样。后面第 5 节我会把这套动作升级成“秩”的语言,但本质没变。
3. 高斯消元的真正作用:把结构从噪声里剥出来
刚学的时候我以为高斯消元就是“解方程的手法”,重学之后我更愿意把它理解成一个信息压缩过程。一堆方程混在一起时,你看不出哪些是独立的、哪些是重复的、哪些是矛盾的;消元的过程就是逐步把这些信息剥开,最后剩下的非零行数,就是这个系统真正的“有效信息量”。这个过程里,每一个操作都必须可逆,否则信息就丢了。
3.1 初等行变换凭什么不改变解集
消元只用三种操作:交换两行、某行乘以一个非零常数、某行加上另一行的倍数。这三种操作的共同点是——它们都可以原样撤销。交换两次就换回来;乘 k 再乘 1/k 就还原;加了一行再加它的相反数就还原。可逆,意味着它是一一对应的映射,不会把两个不同的解合并成一个,也不会凭空造出新解。
用矩阵的语言说更清楚。每一种行变换,都等价于在左边乘上一个可逆矩阵 E:
| 行变换 | 对应的左乘矩阵 | 是否可逆 |
|---|---|---|
| 交换第 i、j 行 | 单位矩阵交换第 i、j 行 | 是,它自己是自己的逆 |
| 第 i 行乘 k(k≠0) | 单位矩阵第 i 个对角元改成 k | 是,逆是乘 1/k |
| 第 i 行加上第 j 行的 k 倍 | 单位矩阵的 (i,j) 位置填 k | 是,逆是填 −k |
关键在于:如果 E 可逆,那么 Ex = 0 只有零解,所以 EAx = 0 和 Ax = 0 的解集完全一样。对增广矩阵也成立,Ax = b 和 EAx = Eb 是同一组约束的两种写法。这就是为什么整个消元过程可以放心地做下去——你做的每一步都在换一副“眼镜”看同一个系统,系统本身没动。
反过来看,也有些操作是绝对不能做的:某行乘 0(信息直接丢失,不可逆)、把两行相加的结果覆盖到其中一行而没记录(这倒没问题,因为可逆)、两边同时平方(这不是线性操作,会引入增根)。理解了“可逆”这条底线,你就不会在解方程时犯那些莫名其妙丢解、增解的错误。
3.2 阶梯形做到哪一步可以停
这里有个很实用的区分,很多人(包括当年的我)是含糊的:
- 行阶梯形:每个非零行的首个非零元(主元)在上一行主元的右边,全零行放在最下面。做到这一步,已经足够判断解的情况了。
- 行最简形:在阶梯形基础上,每个主元都化成 1,并且主元所在列的其他元素全化成 0。做到这一步,解可以直接读出来,不用回代。
什么时候停?我的经验是:判断“有没有解、有几个解”,做到阶梯形就够;要写出解的表达式,做到最简形更省事。手算的时候我倾向于直接做到最简形,因为回代那一步特别容易抄错符号,尤其是变量多的时候,回代的出错率比多做几次行变换高得多。
另外有一个常被忽略的点:行最简形是唯一的,而行阶梯形不唯一。也就是说,同一组方程,你和别人做出来的阶梯形可能长得不一样,但最简形一定一致。所以如果要在笔记里记录“标准答案”,记最简形;如果只是自己推,做到阶梯形就往下走也行。
3.3 主元、自由变量,以及解的自由度从哪来
主元是每一行第一个非零元所在的列。有主元的列叫主元列,没主元的列叫自由列,自由列对应的变量就是自由变量。自由变量的个数 = 未知数个数 − 主元个数,这个差值就是解的“自由度”——解集是一个几维的仿射子空间。
为什么自由变量可以随便取?因为当你把它当成参数留在式子里时,主元变量就被它唯一确定了。从几何上看,自由列意味着这一列能被前面的主元列线性组合出来,所以它在提供“新方向”这件事上是冗余的,你可以沿着这个冗余方向任意滑动而不改变结果。
举个更极端的例子,如果一个系统的系数矩阵是
1 2 3 2 4 6两行成比例,主元只有一个,自由变量有两个,解集是一条平面(如果相容的话)。这时候你手里的信息其实只有一条约束,另外两个维度全是自由的。这个结论在建模时非常有价值——它告诉你,凭现有数据根本定不下来唯一的答案,你需要补充约束(正则项、先验知识、额外的测量),而不是反复调求解器。
3.4 手算和代码互验:我的固定流程
手算不能省,因为手算建立的是“手感”;但代码也不能不用,因为它是唯一可靠的验算手段。我的流程是三步:
**第一步,手算到最简形,写出参数化解。**这一步必须动笔,不能在心里过。
**第二步,用数值库验证秩和特解。**以第 2.3 节那个系统为例:
import numpy as np A = np.array([[1., 2., 1.], [2., 5., 3.], [3., 7., 4.]]) b = np.array([3., 7., 10.]) print(np.linalg.matrix_rank(A)) # 2.0 print(np.linalg.matrix_rank(np.column_stack([A, b]))) # 2.0,秩相等说明相容 x, residuals, rank, sv = np.linalg.lstsq(A, b, rcond=None) print(x, rank)这里有个现象值得一提:如果直接对 A 调np.linalg.solve,程序会抛异常,因为 A 是奇异的。这正是“系数矩阵不满秩”在代码层面的表现。改用lstsq就能拿到一个解,输出大概在[1, 1, 0]附近。它给的是最小范数解——在所有满足 A x = b 的向量里,挑长度最短的那个。你可以自己验证:通解 (1+t, 1−t, t) 的长度平方是 (1+t)²+(1−t)²+t² = 3 + 3t²,显然 t = 0 时最短,正好就是 (1,1,0)。
**第三步,用符号计算核对精确形式。**浮点数只能告诉你“大概是多少”,符号计算才能确认“自由度是不是 1、参数化形式对不对”:
import sympy as sp x, y, z = sp.symbols('x y z') sol = sp.linsolve([x + 2*y + z - 3, 2*x + 5*y + 3*z - 7, 3*x + 7*y + 4*z - 10], (x, y, z)) print(sol) # 形如 {(t + 1, 1 - t, t)}输出是一个含参数的集合,参数个数就是自由度。这种核对方式比肉眼检查可靠得多,尤其是 4×5、5×6 这种规模,手算极容易漏。
注意:
lstsq返回的是最小范数解,不是“特解”本身。如果你需要的是和手算一致的那组特解(比如令自由变量全取 0),要么自己按 RREF 回代,要么在代码里显式地把自由变量置零再解主元方程。这两者不要混着用,否则验算时会被“对不上”吓一跳。
3.5 数值上的坑:主元太小,误差会被放大一万倍
手算时我们默认“数就是数”,但实际跑数值计算时,选哪个做主元会显著影响精度。原因很简单:消元时的乘数是“被消元 / 主元”,主元越小,乘数越大,原方程里的舍入误差就会被放大同样的倍数。
看这个系统:
1e-8·x₁ + x₂ = 1 x₁ + x₂ = 2如果机械地拿第一行第一列做主元,乘数是 1 / 1e-8 = 1e8。第二行减去第一行的 1e8 倍之后,两个系数分别变成 1 − 1 = 0 和 1 − 1e8 ≈ −1e8,右边的 2 − 1e8 也接近 −1e8。两个大数相减得到一个小数,有效数字大幅流失,最后解出来的 x₁ 可能只有一两位可信。
但如果一开始就交换两行,拿系数为 1 的那行做主元,整个过程风平浪静,没有任何大数相减。这就是列主元消去法(每次在待消列里挑绝对值最大的元素换到主元位置)的动机。主流数值库内部都做了这一步,所以你自己写求解器时也别省。
我的实际习惯是:只要看到一个系数和同列其他系数相差三四个数量级以上,就先做一次行交换。这不是洁癖,是真吃过亏——早期我用自己写的小脚本解一个病态系统,跑出来的结果和手算差了 30%,排查了半天才定位到主元选择上。后来学乖了,判断矩阵条件数比反复调参靠谱得多。
4. 矩阵乘法那条“行乘列”规则,为什么只能长成这个样子
如果只能保留一个“重学收获”,我会选这一条:**矩阵乘法不是被定义的,是被推出来的。**它之所以长成这样,是因为我们要让“矩阵乘向量”恰好等于“列的线性组合”,又要让“矩阵乘矩阵”恰好等于“两个变换的接连作用”。这两条要求一摆出来,乘法规则就只有一种写法,没有第二种可能。
4.1 从复合变换倒推乘法的定义
假设有两个线性变换,先用 B 作用在向量上,再用 A 作用。也就是先算 y = Bx,再算 z = Ay,合起来 z = A(Bx)。既然每一步都是线性的,合起来也一定是线性的,那么一定存在一个矩阵 C 使得 z = Cx。这个 C 就定义成 AB。
按照这个思路,C 的第 j 列是什么?取 x = e_j(第 j 个分量是 1、其余是 0 的单位向量)。先算 B e_j,结果恰好是 B 的第 j 列;再把它送进 A,得到 A 乘以 B 的第 j 列。所以:
AB 的第 j 列 = A 乘以 B 的第 j 列。
这条规则换成元素写法,就是那个熟悉的行乘列公式:
(AB)_ij = Σ_k a_ik · b_kj
看,它不是天上掉下来的,是“复合变换”这条要求逼出来的唯一结果。我建议每个人都自己推一遍这个结论,推完之后,乘法规则就再也不会忘了,因为你记住的是理由,不是符号。
4.2 列视角:矩阵乘向量就是列的线性组合
把上面那条结论用在向量上,就得到本节最重要的一句话:
A x = x₁·(A 的第 1 列) + x₂·(A 的第 2 列) + … + xₙ·(A 的第 n 列)
这句话是整个线性代数的枢纽。它把“乘法”翻译成了“组合”,于是“方程组有没有解”就等价于“b 能不能由 A 的列组合出来”。我第一次真正看懂这句话,是在把第 2 节那个 3×3 例子重写了一遍之后:
x·(1, 2, 3) + y·(2, 5, 7) + z·(1, 3, 4) = (3, 7, 10)这时候不需要任何消元,你也能一眼看出为什么有无穷多解——第三列 (1,3,4) 恰好等于第二列减第一列 (2−1, 5−2, 7−3),它在“提供新方向”上是多余的。而右边的 (3,7,10) 恰好等于第一列加第二列乘以……你自己试两下就发现,它其实就在前两列张成的平面里。所以整个问题被压缩成了二维的,多出来的那一维随便取。
这种“先看列之间的关系,再动笔算”的习惯,能省下大量时间。手算 3×3、4×4 的时候,先扫一眼有没有明显的列线性关系,往往比硬消元快得多。
4.3 四种等价读法,以及各自适合的场合
同一句 AB,至少有四种读法。它们算出来的东西完全一样,但用起来各有场景:
| 读法 | 具体含义 | 什么时候用 |
|---|---|---|
| 行乘列 | 结果的第 i 行第 j 列,等于 A 的第 i 行点乘 B 的第 j 列 | 手算单个元素、写代码内层循环 |
| 列的视角 | AB 的第 j 列 = A 乘以 B 的第 j 列 | 理解“先 B 后 A”的复合,分析列空间 |
| 行的视角 | AB 的第 i 行 = A 的第 i 行乘以 B,即 B 的行的线性组合 | 分析行空间、做行化简 |
| 外积求和 | AB = Σ_k (A 的第 k 列) ⊗ (B 的第 k 行) | 秩的分析、低秩近似、分块计算 |
第四种读法最容易被忽略,但价值最大。它把一个 m×n 乘 n×p 的乘法,拆成了 n 个“列乘行”的秩一矩阵相加。这意味着rank(AB) ≤ min(rank A, rank B),因为结果最多是若干个秩一矩阵的和,而秩一矩阵加起来不会超过项数。这条不等式在很多地方会用到——比如你做降维,用一个 k 维的中间层去近似一个大矩阵,那近似的秩一定不超过 k。
举个具体的小例子体会一下外积分解。设
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
那么 AB = 第一列⊗第一行 + 第二列⊗第二行:
- (1,3) 与 (5,6) 组合,得到 [[5,6],[15,18]]
- (2,4) 与 (7,8) 组合,得到 [[14,16],[28,32]]
- 相加得 [[19,22],[43,50]]
用行乘列验证:左上 1×5+2×7 = 19,右上 1×6+2×8 = 22,左下 3×5+4×7 = 43,右下 3×6+4×8 = 50。对上了。这个分解看起来啰嗦,但它把“矩阵乘法为什么会增加秩的上限”讲得明明白白。
4.4 交换律失效不是缺陷,是几何事实
AB ≠ BA,这件事在算术里很反直觉,在几何里却再自然不过。“先旋转 90 度再沿 x 轴拉长”和“先沿 x 轴拉长再旋转 90 度”,画在纸上就是两个不同的结果。乘法不交换,只是如实反映了变换顺序不可互换。
给一个最小的反例,两个都很好画:
A = [[1, 1], [0, 1]] (保持 x 轴不动,把上方往右推)
B = [[1, 0], [1, 1]] (保持 y 轴不动,把右边往上推)
AB = [[2, 1], [1, 1]] BA = [[1, 1], [1, 2]]
两个结果不一样,而且都能肉眼验证:AB 的左上角是 2,说明 x 方向被拉长了两次;BA 的右下角是 2,说明 y 方向被拉长了两次。同一个“推”的动作,顺序一换,被拉长的方向就换了。
这也解释了一个常被忽略的事实:矩阵乘法不交换,但结合律成立。(AB)C = A(BC),因为“先 C 再 B 再 A”这个顺序是确定的,你从哪一头开始算中间结果,最终效果一样。结合律成立让我们可以随意加括号优化计算顺序,这在实现大规模乘法时非常关键。
4.5 分块乘法:把大问题拆成小问题
当矩阵有明显的分块结构时,可以直接按块做乘法,规则和普通乘法一模一样,只要保证块的维度对得上。比如把 A 按列分成两块、B 按行分成两块:
A = [A₁ | A₂],B = [B₁ ; B₂]
那么 AB = A₁B₁ + A₂B₂。这其实就是外积分解的另一种写法。分块的价值在于两点:一是可以把大矩阵拆成能塞进缓存的小块,提升计算效率;二是当某些块是零、是单位阵、是对角阵时,可以直接跳过大量运算。
举个实际会遇到的场景:一个系统如果被写成
[A₁₁ A₁₂] [x] [b₁] [ 0 A₂₂] [y] = [b₂]
这种上三角分块形式,就可以先解 A₂₂ y = b₂,把 y 求出来,再代回去解 A₁₁ x = b₁ − A₁₂ y。原本一个大系统被拆成两个小系统,规模一下子降下来了。这类结构在电路网络、结构力学、时序信号处理里非常常见,认出它的存在比硬解快十倍。
5. 用列空间和秩回答“有没有解”,比解出来更重要
前面几节做的事情,说白了都是在为这一节铺路。真正到了工程里,你遇到的系统往往大到没法手算,甚至大到求解器都要跑很久。这时候最有价值的判断不是“解是多少”,而是“这个系统结构如何”——满秩还是亏秩、相容还是矛盾、条件数好不好。这些判断都可以在不实际求解的前提下做出来,靠的就是列空间和秩。
5.1 列空间:所有“能到达”的点的集合
列空间的定义很朴素:A 的所有列的线性组合构成的集合,记作 Col(A)。它的几何意义是“这个变换能够到达的所有点的集合”。Ax = b 有解,等价于 b ∈ Col(A),一句话就说完了。
这个说法比“消元消出来看看”高一个层次,因为它给了你一个可以提前判断的工具。比如你先算出 A 的列空间是三维空间里的一个平面,那么只要 b 不在这个平面上,无论你用什么求解器、迭代多少次,都不可能得到精确解。这时候正确的做法是改问题——要么检查建模是不是漏了变量,要么退而求其次找最近点(最小二乘)。
列空间的维数就是秩。所以“满秩”这件事的真正含义是:**列空间铺满了整个目标空间,任何 b 都能被拼出来。**对于方阵来说,满秩就意味着任何 b 都有唯一解;对于长方形矩阵,如果行数大于列数(方程比未知数多),列空间永远铺不满,只能对特定的 b 有解。
5.2 秩的几种等价说法,以及手算时怎么快速判断
秩是那种“定义有好几个、必须都认识”的概念。我整理了一下,至少有五种等价说法,任何一条成立,其余全成立:
| 说法 | 具体表述 | 适用场合 |
|---|---|---|
| 主元个数 | 行阶梯形中非零行的数目 | 手算、教学 |
| 列空间维数 | 线性无关的列的极大数目 | 分析数据冗余 |
| 行空间维数 | 线性无关的行的极大数目 | 分析约束是否重复 |
| 最大非零子式阶数 | 存在 r 阶子式非零,所有 r+1 阶全为零 | 理论证明、小矩阵手算 |
| 奇异值中非零的个数 | 数值计算下的实际秩判定 | 处理浮点数据 |
手算小矩阵时,最快的是第一种:直接消成阶梯形,数非零行。但对于那种“看起来满秩、其实差一点点”的矩阵,手算很容易漏。这时候看第五种更靠谱——数值上我们不会问“是不是严格为零”,而是问“有多少个奇异值大于某个阈值”。这个阈值怎么定,是个经验活,通常取最大奇异值乘以机器精度再乘个系数。
举个具体例子,判断这个矩阵的秩:
1 2 1 2 5 3 3 7 4肉眼先扫一遍:第三行 = 第一行 + 第二行(逐元素相加,1+2=3,2+5=7,1+3=4)。这一眼就说明三行线性相关,秩最多是 2。再看前两行显然不成比例,所以秩恰好是 2。整个过程不用动笔,比消元快得多。这就是“先看关系再动手”的价值。
再看增广矩阵,如果右端项是 (3, 7, 10),那么 3 + 7 = 10,右端项也满足同样的线性关系,说明整个增广矩阵的秩仍然是 2,和系数矩阵相等,系统相容。如果右端项换成 (3, 7, 11),那么增广矩阵的秩就变成 3,严格大于系数矩阵的秩 2,系统矛盾,无解。
5.3 零空间:那些“被压成零”的方向
零空间 N(A) 是所有满足 Ax = 0 的向量构成的集合。它的几何意义是“被这个变换压扁到原点的方向”。在解方程组时,零空间就是齐次解;在参数化解里,它就是那个可以任意伸缩的方向。
零空间和自由变量是同一件事的两种说法。自由变量的个数,等于零空间的维数。这件事有一个非常漂亮的定量关系,叫秩—零化度定理:
dim N(A) + rank(A) = n(n 是列数,也就是未知数个数)
为什么成立?因为消元之后,n 列被分成了两部分:主元列和自由列。主元列的个数是 rank(A),自由列的个数是 n − rank(A)。每一个自由列都对应一个“把它自己设成 1、其他自由列设成 0、主元变量相应调整”的齐次解,这些解互相线性无关,正好张成零空间。所以零空间维数就是自由列个数,定理得证。
这条定理在实际工作里极其有用。比如你有一个 1000 个变量、800 个独立约束的系统,那么零空间维数是 200,意味着解集是一个 200 维的仿射子空间——解根本不唯一,你有 200 个自由度可以调。如果你需要的是一组确定的参数,就必须再加约束,比如要求解的长度最小、或者要求某些分量取特定值。
5.4 用一个例子把四个子空间串起来
线性代数里有四个基本子空间:列空间、零空间、行空间、左零空间。教材上一般分开讲,学完还是散的。我用第 2 节那个例子把它们串一遍,因为它们本来就是同一个矩阵的四个侧面。
矩阵和右端项:
A = [1 2 1; 2 5 3; 3 7 4],b = (3, 7, 10)| 子空间 | 记号 | 张成它的向量 | 维数 | 直观含义 |
|---|---|---|---|---|
| 列空间 | Col(A) | (1,2,3)、(2,5,7) | 2 | 变换能到达的所有点,一个过原点的平面 |
| 零空间 | N(A) | (1,−1,1) | 1 | 被压到原点的方向,一条直线 |
| 行空间 | Row(A) | (1,2,1)、(2,5,3) | 2 | 约束真正起作用的方向 |
| 左零空间 | N(Aᵀ) | (1,1,−1) | 1 | 约束之间成比例的关系 |
几个关系一一对上:
- 列空间维数 = 行空间维数 = 2,这就是秩;
- 零空间维数 = 3 − 2 = 1,自由变量一个;
- 左零空间维数 = 3 − 2 = 1,对应于“三行之间有一个线性关系”;
- 那个线性关系就是 第一行 + 第二行 − 第三行 = 0,写成向量就是 (1, 1, −1);
- 相容的条件正好是 b 也要满足同样的关系:(1,1,−1)·(3,7,10) = 3 + 7 − 10 = 0,成立,所以有解。
这一张表把整章的内容全串起来了。我特别想强调的是最后那条:左零空间里的向量,就是约束之间的“检验式”。每找到一组“行之间的线性关系”,你就得到一个对 b 的检验条件。这个思想在数据校验、纠错编码、冗余传感器的交叉验证里到处都是——冗余的测量之间必然存在某种线性关系,一旦实测数据破坏了这个关系,就说明有传感器出问题了。
6. 这一轮重学里,我固化下来的几个动作
写到这里,公式讲得差不多了,最后说点偏操作的。重学最大的收获未必是某个具体知识点,而是几个能反复用的小习惯。这些习惯本身不值钱,但坚持做几周之后,看矩阵的感觉确实会变。
6.1 每算一步,强制翻译成一句话
这条听起来很傻,但极其有效。具体做法是:每做完一次行变换,在旁边的空白处写一句“这一步把什么消掉了、维度降了多少”。写不出来就说明这一步是机械操作,停下来想清楚再往下走。
这个习惯的副作用是手算速度会变慢,一开始很不适应,尤其是考试式的刷题。但坚持两三周之后,速度会反超——因为你不再需要反复检查“我刚才到底做了什么”,每一步都有据可查,出错能立刻定位到具体那一步。我现在检查别人(或者几天前的自己)的推导,第一眼就是看有没有这种批注,没有的话基本可以断定是照抄步骤、没理解。
6.2 手算打底,数值验证,符号兜底
前面第 3.4 节讲过流程,这里补一句为什么非要三段都做。手算建立的是结构感,你知道每一步为什么成立;数值验证建立的是防错网,它能在几秒内告诉你结果对不对;符号计算建立的是精确性,它能把自由度的个数、参数化的形式一次说清楚。
只用手算,容易在小规模问题上自我感觉良好,一到 5×5 以上就崩;只用数值,容易对着小数点后的一堆数字发懵,不知道哪个是误差、哪个是真值;只用符号,算得慢,而且大矩阵符号计算基本跑不动。三个一起用,各管一段,效率最高。
还有一个细节:数值验证时不要只验“解代回去成不成立”,那是必要条件不是充分条件。真正该验的是秩——系数矩阵的秩、增广矩阵的秩,以及两者是否相等。这三个数一报出来,解的情况就定了,比反复代回去可靠。
6.3 一张常翻的误解清单
下面这些是我自己踩过、也见过别人踩的坑,隔一段时间就翻一遍。它们都不是计算错误,而是理解层面的错位,错了之后很难自查出来:
- 把“矩阵相等”理解成“元素逐个相等”就完事:这没错,但忽略了背后的含义——两个矩阵相等意味着它们作为线性变换完全等价,元素只是其中一种可能的表示。
- 认为消元会改变解集:只要三种行变换都做对了,解集不变。如果算出来的解代回去不成立,问题一定出在某一步算错,而不是“方法有问题”。
- 把自由变量理解成“随便取的常数”:它可以随便取,但每取一组值就确定一个不同的解,不同的取值对应解集里不同的点,不是“取什么都一样”。
- 认为方阵一定可逆:方阵可逆的充要条件是满秩,也就是行列式非零。行列式为 0 的方阵在几何上把空间压扁了一个维度,不可逆。
- 把秩当成一个“算出来的数字”:秩的核心是维数,是结构信息。看到一个秩亏的矩阵,第一反应应该是“哪一列是冗余的、冗余关系是什么”,而不是“秩等于几”。
- 用浮点计算判断秩时直接和 0 比较:数值计算里几乎不会出现严格的 0,判断秩要看奇异值相对大小,配合条件数一起看。
- 以为 AB = 0 就意味着 A = 0 或 B = 0:这是标量代数的直觉,在矩阵里完全不成立,两个非零矩阵相乘得零矩阵是常事,秩一矩阵乘自己的正交补就是零。
这张清单我会一直往下加。每次遇到“这个结果怎么和我预想的不一样”,先别急着怀疑代码,回头翻一遍清单,八成能对上号。
说到底,这一轮重学让我最踏实的一点是:线性代数不是一堆需要记住的运算规则,而是一套把高维问题翻译成空间语言的工具。同一个方程组,行视角看是求交,列视角看是问能不能拼出来,秩的语言看是问结构够不够。三种说法描述同一件事,能自由切换,才算真的会了。下一篇我打算顺着这个思路往下走,把矩阵的逆、行列式和可逆性这几件事放到“变换能不能撤销”这个框架里重新捋一遍,那部分我之前也一直是半懂不懂的状态。