18.06 第 22 讲:对角化与矩阵的幂
- 118.06 第 1 讲:方程组的行图与列图
- 218.06 第 2 讲:用矩阵做消元
- 318.06 第 3 讲:矩阵乘法的四种看法与逆矩阵
- 418.06 第 4 讲:分解成 A = LU
- 518.06 第 5 讲:转置、置换与向量空间
- 618.06 第 6 讲:列空间与零空间
- 718.06 第 7 讲:解 Ax = 0,主变量与特解
- 818.06 第 8 讲:解 Ax = b,行最简形 R
- 918.06 第 9 讲:线性无关、基与维数
- 1018.06 第 10 讲:四个基本子空间
- 1118.06 第 11 讲:矩阵空间、秩 1 矩阵与小世界图
- 1218.06 第 12 讲:图、网络与关联矩阵
- 1318.06 第 13 讲:第一次测验复习
- 1418.06 第 14 讲:正交向量与正交子空间
- 1518.06 第 15 讲:投影到子空间
- 1618.06 第 16 讲:投影矩阵与最小二乘
- 1718.06 第 17 讲:正交矩阵与 Gram-Schmidt
- 1818.06 第 18 讲:行列式的性质
- 1918.06 第 19 讲:行列式公式与代数余子式
- 2018.06 第 20 讲:Cramer 法则、逆矩阵与体积
- 2118.06 第 21 讲:特征值与特征向量
- 2218.06 第 22 讲:对角化与矩阵的幂本文
- 2318.06 第 23 讲:微分方程与 exp(At)
- 2418.06 第 24 讲:Markov 矩阵与 Fourier 级数
- 2518.06 第 24b 讲:第二次测验复习
- 2618.06 第 25 讲:对称矩阵与正定性
- 2718.06 第 26 讲:复矩阵与快速 Fourier 变换
- 2818.06 第 27 讲:正定矩阵与极小值
- 2918.06 第 28 讲:相似矩阵与 Jordan 标准形
- 3018.06 第 29 讲:奇异值分解
- 3118.06 第 30 讲:线性变换及其矩阵
- 3218.06 第 31 讲:基变换与图像压缩
- 3318.06 第 32 讲:第三次测验复习
- 3418.06 第 33 讲:左逆、右逆与伪逆
- 3518.06 第 34 讲:期末复习
上一讲找到了 ,这一讲回答“找到之后拿它干什么”:把特征向量排成矩阵 , 就被拆成 ,乘一百次 变成只对对角线上的几个数做一百次方。我拿 Fibonacci 的 2 × 2 矩阵试了一下,两个特征值算出的 是 ,和 Python 整数迭代的精确值只差 (相对误差),而这个精确值有 21 位。
视频:Lecture 22: Diagonalization and powers of A(OCW 页面与字幕) 教材:《Introduction to Linear Algebra》第 6 版 §6.2 Diagonalizing a Matrix 前置:第 21 讲:特征值与特征向量 一句话:有 个线性无关的特征向量时 ,于是 , 的幂是增长还是衰减,只看特征值的绝对值是否小于 1。
- 开头 的那段推导要认真看:他逐列算 ,再把 从各列里“剥”到右边,是矩阵乘法“按列算”这个看法最漂亮的一次应用。
- 、 的特征值那一段很短,本篇第一个代码块已经算过,倍速即可。
- 讲“哪些矩阵不能对角化”的那段值得听完:他用 区分代数重数和几何重数,下面第三节用代码重现了。
- Fibonacci 例子是全讲的重点,从“把二阶问题变成一阶方程组”的小技巧到特征值估计增长率都在里面。他最后没有把公式算完,只留下一句“思想最重要”,我在第四节替他算完。
从 到
假设 有 个线性无关的特征向量 ,把它们放进 的各列。 等于什么? 按列算: 乘第 列得到 ,所以
倒数第二步是第 3 讲的“按列乘”:想让数 去乘第 列,就把它放在右边那个矩阵的第 列的第 个位置上。 是大写的 lambda,对角线上放特征值。
因为有 个线性无关的特征向量, 可逆,两边可以乘 :
Strang 说这是继消元的 、Gram-Schmidt 的 之后的第三种分解,整章都会看到“一个 夹着一个 ”的形状。他还教了一个记法: 乘的是 (特征向量),所以 写在 的左边; 放在右边把整件事变对角。
好处在哪? 平方一下就看到了:
中间的 抵消成单位矩阵。 的特征向量还是 的列,特征值是 (他用另一个更短的推导也得到这个结论: 两边再乘一次 ,得到 )。推广到 次:
他对比了 : 自乘一百次,是一百个 排成一排,什么也化简不了;而 自乘一百次,中间九十九对 全部抵消。这是 不具备的能力。
下面用一个我自己挑的 2 × 2 矩阵 验证(字幕里这一段没有具体数字。我挑它的理由很不浪漫:特征值是整数 5 和 2,手算也能核对):
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True) A = np.array([[4.0, 1.0], [2.0, 3.0]])
# 特征向量放进 S 的列,特征值放进对角阵 Lam eigenvalues, S = np.linalg.eig(A) Lam = np.diag(eigenvalues) print("特征值:", eigenvalues) print("S =\n", S) print("A @ S 等于 S @ Lam:", np.allclose(A @ S, S @ Lam)) print("inv(S) @ A @ S =\n", np.linalg.inv(S) @ A @ S)
# A^k = S Lam^k S^{-1}:只有对角线上的数在做 k 次方 k = 10 Ak = S @ np.diag(eigenvalues**k) @ np.linalg.inv(S) print("A^10 =\n", Ak) print( "和 matrix_power 一致:", np.allclose(Ak, np.linalg.matrix_power(A, k)) )
# A^2 的特征值是平方,特征向量不变 values_sq, _ = np.linalg.eig(A @ A) print("A^2 的特征值:", np.sort(values_sq)) print("A 的特征值平方:", np.sort(eigenvalues**2))
if __name__ == "__main__": main()特征值: [5. 2.]S = [[ 0.7071 -0.4472] [ 0.7071 0.8944]]A @ S 等于 S @ Lam: Trueinv(S) @ A @ S = [[ 5. -0.] [ 0. 2.]]A^10 = [[6510758. 3254867.] [6509734. 3255891.]]和 matrix_power 一致: TrueA^2 的特征值: [ 4. 25.]A 的特征值平方: [ 4. 25.]四件事都对上了。A @ S 等于 S @ Lam,inv(S) @ A @ S 是对角阵( 是浮点数里的负零,不是错误)。 用 算出来,和 np.linalg.matrix_power 一致,而前者只对两个数 和 做了十次方。最后两行是 的特征值 ,正好是 的特征值 的平方。
np.linalg.eig 返回的特征向量已经单位化(第一列 就是 ),所以 的列长度都是 1。特征向量可以随意缩放, 不受影响。
稳定:幂什么时候趋于零
会不会趋于零? 这个信息不在主元里( 的主元对此无话可说),而在特征值里。 中 和 不动,只有 在变,对角线上是 。所以
他特意写了绝对值:特征值可以是负数,也可以是复数,取的是模。他同时提醒,这个推导用了 可逆的前提,没有 个特征向量的矩阵这里没有证。(据我所知,结论对那些矩阵依然成立,要靠后面的 Jordan 形式,第 28 讲再说。)
我想试试“看特征值,不看矩阵的元素”到底有多可靠,于是造了一个元素很大但特征值很小的矩阵,和一个元素温和但有一个特征值超过 1 的矩阵:
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# 两个矩阵,一个特征值都小于 1,一个有 1.1 stable = np.array([[0.9, 5.0], [0.0, 0.5]]) unstable = np.array([[1.1, 0.0], [0.0, 0.1]]) for name, A in [("stable", stable), ("unstable", unstable)]: values = np.linalg.eigvals(A) print(name, "特征值:", values, ",最大模:", np.abs(values).max()) for k in [1, 10, 50, 100, 200]: norm = np.linalg.norm(np.linalg.matrix_power(A, k), 2) print(f" k = {k:3d},||A^k|| = {norm:.4e}")
# 稳定的那个先涨后落:看过程中范数的峰值 norms = [ np.linalg.norm(np.linalg.matrix_power(stable, k), 2) for k in range(1, 201) ] peak = int(np.argmax(norms)) + 1 print("stable 的范数峰值:", f"{max(norms):.4f}", ",出现在 k =", peak)
if __name__ == "__main__": main()stable 特征值: [0.9 0.5] ,最大模: 0.9 k = 1,||A^k|| = 5.1041e+00 k = 10,||A^k|| = 4.3602e+00 k = 50,||A^k|| = 6.4628e-02 k = 100,||A^k|| = 3.3308e-04 k = 200,||A^k|| = 8.8470e-09unstable 特征值: [1.1 0.1] ,最大模: 1.1 k = 1,||A^k|| = 1.1000e+00 k = 10,||A^k|| = 2.5937e+00 k = 50,||A^k|| = 1.1739e+02 k = 100,||A^k|| = 1.3781e+04 k = 200,||A^k|| = 1.8991e+08stable 的范数峰值: 7.5861 ,出现在 k = 3stable 的两个特征值是 和 ,都小于 1,所以 最终掉到 步后的 。但注意它不是单调下降的: 时范数是 5.10, 时涨到峰值 7.59,才开始掉。右上角那个 5 让矩阵先把向量拉长,然后特征值才接管。这是我觉得“稳定”这个词有点误导人的地方:特征值保证最终衰减,不保证每一步都衰减。
unstable 的一个特征值是 ,范数 时 2.59, 时 , 时 。注意 ,和输出里的范数吻合: 几乎就是 。
哪些矩阵不能对角化
Strang 在这里讲了三件事,我用三种矩阵对应着做:
- 特征值互不相同,一定能对角化。 每个特征值至少带一个特征向量,不同特征值的特征向量自动线性无关(证明在教材里)。他提到用 MATLAB 的
eig(rand(10))取随机矩阵,会得到十个互不相同的特征值。 - 特征值重复,可能能也可能不能。 单位矩阵的特征值 重复十次,但每个向量都是特征向量,随便挑十个线性无关的就行, 已经是对角阵。
- 三角矩阵 是坏例子。 特征值是 和 (),但 的零空间只有一维,特征向量只有 这一个方向。特征值作为根的重数叫代数重数(这里是 2),特征向量空间的维数叫几何重数(这里是 1),几何重数小于代数重数就不能对角化。
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# 随机 10 x 10:特征值两两不同,S 可逆 rng = np.random.default_rng(1806) M = rng.standard_normal((10, 10)) values, V = np.linalg.eig(M) gaps = np.abs(values[:, None] - values[None, :]) + np.eye(10) print("随机矩阵的最小特征值间距:", f"{gaps.min():.4f}") print("S 的秩:", np.linalg.matrix_rank(V)) print("cond(S) =", f"{np.linalg.cond(V):.1f}") rebuilt = V @ np.diag(values) @ np.linalg.inv(V) print("重构误差:", f"{np.linalg.norm(rebuilt - M):.2e}")
# 单位矩阵:特征值 1 重复 10 次,但每个向量都是特征向量 values, S = np.linalg.eig(np.eye(10)) print("单位矩阵的特征值:", values[:3], "...") print("单位矩阵的 S 的秩:", np.linalg.matrix_rank(S))
# 三角矩阵 [[2, 1], [0, 2]]:特征值 2 和 2,但只有一个方向 A = np.array([[2.0, 1.0], [0.0, 2.0]]) values, S = np.linalg.eig(A) print("特征值:", values) print("S =\n", S) geometric = 2 - np.linalg.matrix_rank(A - 2.0 * np.eye(2)) print("A - 2I 的零空间维数(几何重数):", geometric) print("S 的秩:", np.linalg.matrix_rank(S)) print("cond(S) =", f"{np.linalg.cond(S):.2e}")
# 硬把 S 求逆再重构:得到的是 2I,不是 A rebuilt = S @ np.diag(values) @ np.linalg.inv(S) print("重构结果:\n", rebuilt) print("重构误差:", f"{np.linalg.norm(rebuilt - A):.2e}")
if __name__ == "__main__": main()随机矩阵的最小特征值间距: 0.6635S 的秩: 10cond(S) = 21.9重构误差: 1.77e-14单位矩阵的特征值: [1. 1. 1.] ...单位矩阵的 S 的秩: 10特征值: [2. 2.]S = [[ 1. -1.] [ 0. 0.]]A - 2I 的零空间维数(几何重数): 1S 的秩: 1cond(S) = 4.50e+15重构结果: [[2. 0.] [0. 2.]]重构误差: 1.00e+00随机的 矩阵:最小的特征值间距是 0.6635,特征值确实互不相同; 的秩是 10,条件数 21.9,用 重构回原矩阵的误差是 。(实矩阵的特征值可以是复数,这里 是复矩阵,NumPy 自动处理了。)
单位矩阵的 eig 给出的 秩为 10,没有任何问题。
问题出在 上。几何重数是 1,eig 还是交了两列: 和 ,它们平行, 的秩是 1,条件数 (大约就是 ,数值上和奇异没有区别)。硬着头皮把 求逆再重构,得到的是 而不是 ,误差范数恰好是 1.00: 夹在任何可逆的 之间都还是 ,所以不可能还原出右上角那个 1。
这和第 21 讲的零空间是同一件事:特征向量是 的零空间,零空间太小,特征向量就凑不够。
Fibonacci:幂的一个实际用处
这是他在课上做的例子,也是全讲最值钱的地方。Fibonacci 数 满足 。问题是:第 100 个是多少?它们增长得多快?
这不是 的形状,它是二阶的标量方程。他用一个小技巧把它变成一阶方程组:令 ,补一个平凡的方程 ,
于是 ,第二个分量就是 。他说“这就是最紧凑的公式”,但要真的算出 就得靠特征值。
他手算 (和递推式本身长得一模一样),再用求根公式:
他先用迹和行列式核对:(迹),(行列式)。他还顺口说 是对称矩阵,所以特征值是实数、特征向量正交,这个等第 25 讲。特征向量他用 的小技巧: 的第二行是 ,所以 。
然后是这一讲的思想:把 写成特征向量的组合 ,每一块各走各的:
,所以第一块越来越大,第二块越来越小,增长率由大特征值 决定。下面我替他把这个算完,并和精确值对照:
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True) A = np.array([[1.0, 1.0], [1.0, 0.0]]) u0 = np.array([1.0, 0.0]) # (F1, F0)
values, S = np.linalg.eig(A) print("特征值:", values) print("迹 =", np.trace(A), ",和 =", values.sum()) print("行列式 =", np.linalg.det(A), ",积 =", values.prod()) golden = (1 + np.sqrt(5)) / 2 print("(1 + sqrt(5)) / 2 =", golden)
# 把 u0 写成特征向量的组合:u0 = S c c = np.linalg.solve(S, u0) print("c =", c)
# u_k = S Lam^k c,第二个分量就是 F_k k = 100 u = S @ (values**k * c) print("用特征值算的 F100 =", f"{u[1]:.6e}")
# 用 Python 整数精确迭代作对照 a, b = 0, 1 # F0, F1 for _ in range(k): a, b = b, a + b print("精确的 F100 =", a) print("相对误差:", f"{abs(u[1] - a) / a:.2e}")
# 只留大特征值那一项,再四舍五入 approx = golden**k / np.sqrt(5) print("只用 lambda1^100 / sqrt(5) =", f"{approx:.6e}") print("第二个特征值的 100 次方:", f"{values.min() ** k:.2e}")
if __name__ == "__main__": main()特征值: [ 1.618 -0.618]迹 = 1.0 ,和 = 1.0行列式 = -1.0 ,积 = -0.9999999999999999(1 + sqrt(5)) / 2 = 1.618033988749895c = [ 0.8507 -0.5257]用特征值算的 F100 = 3.542248e+20精确的 F100 = 354224848179261915075相对误差: 3.33e-15只用 lambda1^100 / sqrt(5) = 3.542248e+20第二个特征值的 100 次方: 1.26e-21特征值是 和 ,和为 1、积为 (浮点里是 )。系数 是因为 eig 把特征向量单位化了,不是教材里常见的 (对应未单位化的 )。
用 算出来是 ,精确值是 ,相对误差 。双精度大约只有 16 位有效数字, 有 21 位,所以最后几位是对不上的,但前 15 位全对。只留 这一项的结果也是 。第二项 只有 ,比第一项小 41 个数量级,在这个尺度上等于没有。
这解释了为什么他说“ 大约是 ”。更具体地看增长率和四舍五入公式:
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True) A = np.array([[1.0, 1.0], [1.0, 0.0]]) u = np.array([1.0, 0.0]) # (F1, F0) golden = (1 + np.sqrt(5)) / 2
# 一步一步乘 A:看增长率逼近 lambda1,以及何时四舍五入失效 print(" k F_k F_{k+1}/F_k") exact = [0, 1] for k in range(1, 41): u = A @ u exact.append(exact[-1] + exact[-2]) if k in (1, 2, 5, 10, 20, 40): print(f"{k:2d} {int(exact[k]):9d} {u[0] / u[1]:.10f}") print("lambda1 =", f"{golden:.10f}")
# F_k = round(lambda1^k / sqrt(5)) 能撑到多大的 k bad = 0 for k in range(2, 80): guess = round(golden**k / np.sqrt(5)) if k >= len(exact): exact.append(exact[-1] + exact[-2]) if guess != exact[k]: bad = k break print("四舍五入公式第一次出错的 k:", bad)
if __name__ == "__main__": main() k F_k F_{k+1}/F_k 1 1 1.0000000000 2 1 2.0000000000 5 5 1.600000000010 55 1.618181818220 6765 1.618033998540 102334155 1.6180339887lambda1 = 1.6180339887四舍五入公式第一次出错的 k: 71从 的 2.0 开始, 时 1.6181818182, 时 1.6180339985, 时 1.6180339887,已经和 在十位小数上一致了。不到 40 步,增长率就被特征值锁定了。
因为 小于 ,把 四舍五入就是 (课上没讲这句,教材里有)。这个公式在双精度下能撑到 , 开始失败: 已经是 15 位数,我猜是 golden**k 的浮点误差已经接近 0.5 了,没有进一步验证。(顺便一提,这是个很好的例子:数学上严格成立的公式,在浮点里有保质期。)
他把整件事总结成一句话:当系统按一阶方程 演化,特征值已经告诉你解是爆炸还是衰减,要精确公式就把 展开成特征向量的组合,让每一块各走各的。下一讲就是它在连续时间的版本,。
在控制里
离散时间系统 就是这一讲的 ,只是名字不同。第二节的结论原样搬过来:状态趋于零,当且仅当 的所有特征值落在单位圆内,。这个判据在控制里叫“极点在单位圆内”。
连续系统的 PID 离散化之后(见 PID 控制 04:离散化),闭环极点从 平面的左半平面变成 平面的单位圆内部,判断稳定性的方法就是算特征值的模。第二节 stable 矩阵的那个先涨后落的峰值也值得记住:极点都在圆内只保证最终收敛,过程里状态可以先冲到初值的好几倍(那个例子里范数峰值是 7.59,初值 1),实际系统里这是超调,不是错误。
下一步
这一讲处理的是 ,一步一步走。如果时间是连续的,,每个特征向量的系数就不是 ,而是 , 也变成矩阵指数 。同样的 ,换一个函数。见 18.06 第 23 讲:微分方程与 exp(At)。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!











