18.06 第 21 讲:特征值与特征向量
- 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 讲:期末复习
这一讲要找的是一类特殊的向量:矩阵 乘上去以后,方向不变,只是被拉长或压短。一般的向量做不到,但 有两条这样的直线,沿 放大 4 倍,沿 放大 2 倍,迹 6 和行列式 8 恰好就是这两个倍数的和与积。我把 Strang 黑板上的几个矩阵逐个交给 NumPy:一个旋转矩阵没有任何实特征向量,一个三角矩阵的两个特征向量被压成了同一条线,后者的特征向量矩阵条件数是 。
视频:Lecture 21: Eigenvalues and eigenvectors(OCW 页面与字幕) 教材:《Introduction to Linear Algebra》第 6 版 §6.1 Introduction to Eigenvalues : Ax = λx 前置:第 20 讲:Cramer 法则、逆矩阵与体积 一句话:特征向量是满足 的非零向量, 是使 奇异的数,也就是 的根。
- 开头他先说这是特征值与特征向量这个大主题的第一讲,后面要占掉课程的大半。这一讲只回答“它们是什么”,“为什么要它们”留给后面,所以不必着急。
- 投影矩阵和置换矩阵那两段(先猜答案,再回头验证)值得认真看:他在不写任何行列式的情况下,靠图和直觉把特征值说了出来。本篇第二节用代码算过一遍,看视频时可以快一点。
- 他推导 和 例子的那段要认真听,特别是他指出“6 是迹,8 是行列式”的地方,这是这一讲里最容易用上的结论。
- 旋转矩阵和三角矩阵两个“出问题”的例子在视频最后,本篇第四、五节有对应的数值。他说重复特征值是“18.06 里一切麻烦的来源”,这句话下一讲就会兑现。
方向不变的向量
矩阵对向量做了什么? Strang 的开场是这样的:矩阵 像函数,微积分里放进去一个数 ,出来 ;线性代数里放进去一个向量 ,出来 。大多数向量进去和出来的方向不一样。但有一些向量,出来的 和进去的 平行,这些就是特征向量:
“平行”被他放宽到包括方向相反( 为负)和压成零()。这个倍数 叫特征值。 并没有什么特别:它说的是 ,所以特征值为 0 的特征向量就是零空间里的非零向量, 奇异等价于 0 是它的一个特征值。这是前面几讲的旧账,现在换了个说法。
方程里 和 都未知,还乘在一起,消元派不上用场。他说需要一个好办法,那个办法要用到行列式。在讲办法之前,他先挑了几个“已经知道矩阵在干什么”的例子,看看答案长什么样。
先放一张图,把“方向不变”画出来。矩阵是后面要算的 ,灰圈是单位圆上的所有 ,蓝色椭圆是它们的像 。黑色箭头是一个普通向量 和它的像 ,方向转了;绿色和红色是两个特征向量,浅色是进去的,深色是出来的,同一条线上,只是长度变了。图里的 4 和 2 来自第三节的代码输出:
上面这张图由下面这个脚本生成(需要 matplotlib,在当前目录写出 SVG;图中的数据与前面的代码块相同):
import matplotlibimport matplotlib.pyplot as pltimport numpy as np
def main() -> None: matplotlib.use("Agg") plt.rcParams["svg.hashsalt"] = "la1806"
A = np.array([[3.0, 1.0], [1.0, 3.0]]) values, vectors = np.linalg.eigh(A)
theta = np.linspace(0.0, 2.0 * np.pi, 400) circle = np.vstack([np.cos(theta), np.sin(theta)]) ellipse = A @ circle
fig, ax = plt.subplots(figsize=(6, 4)) ax.plot(*circle, color="0.6", lw=1.2, label="unit circle $x$") ax.plot(*ellipse, color="C0", lw=1.2, label="image $Ax$")
arrow = dict(arrowstyle="-|>", lw=1.8, shrinkA=0, shrinkB=0) for lam, v, c in zip(values, vectors.T, ["C3", "C2"]): ax.annotate( "", xy=lam * v, xytext=(0, 0), arrowprops=dict(color=c, **arrow) ) ax.annotate( "", xy=v, xytext=(0, 0), arrowprops=dict(color=c, alpha=0.5, **arrow), ) ax.text( *(lam * v * 1.08), f"$\\lambda={lam:.0f}$", color=c, ha="center", va="center", )
x = np.array([1.0, 0.0]) ax.annotate( "", xy=A @ x, xytext=(0, 0), arrowprops=dict(color="k", **arrow) ) ax.annotate( "", xy=x, xytext=(0, 0), arrowprops=dict(color="k", alpha=0.5, **arrow) ) ax.text(*(A @ x + np.array([0.3, -0.45])), "$Ax$ turns", ha="left")
ax.set_aspect("equal") ax.set_xlim(-4.6, 4.6) ax.set_ylim(-4.6, 4.6) ax.axhline(0, color="0.85", lw=0.6) ax.axvline(0, color="0.85", lw=0.6) ax.legend(loc="lower right", fontsize=8) fig.tight_layout() fig.savefig( "l21-eigen-directions.svg", metadata={"Date": None}, )
if __name__ == "__main__": main()椭圆的两条轴恰好落在两个特征向量的方向上。这是对称矩阵才有的整齐性质,第 25 讲会回来解释。
投影矩阵:先猜后验
投影矩阵的特征值是多少? 这是他的第一个问题。取三维空间里的一个平面, 把任意向量 投到平面上。 本身不是特征向量,因为 方向变了。什么向量投影之后方向不变?本来就在平面里的向量:,。整个平面都是 的特征向量。垂直于平面的向量被投成零:,。三维空间里两个在平面里、一个垂直于平面,三个独立的特征向量,特征值是 1、1、0。
用代码验证。平面取 的两列张成的平面,投影矩阵 是第 15 讲的内容,这里先当现成的用:
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# 投影到 R^3 里的一个平面:平面由 M 的两列张成 M = np.array([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) P = M @ np.linalg.inv(M.T @ M) @ M.T print("P =") print(P)
values, vectors = np.linalg.eig(P) order = np.argsort(-values) print("特征值:", values[order])
# 平面里的向量原样不动,垂直于平面的向量被压成 0 in_plane = 2.0 * M[:, 0] - 3.0 * M[:, 1] normal = np.array([1.0, 1.0, -1.0]) print("M.T @ normal =", M.T @ normal) print("P @ in_plane == in_plane:", np.allclose(P @ in_plane, in_plane)) print("P @ normal == 0:", np.allclose(P @ normal, 0.0))
# 一个一般的 b:投影之后方向变了 b = np.array([1.0, 2.0, 4.0]) Pb = P @ b cosine = b @ Pb / (np.linalg.norm(b) * np.linalg.norm(Pb)) print("b 与 Pb 的夹角余弦:", round(cosine, 4))
# 每一对 (lambda, x) 都满足 A x = lambda x residual = np.linalg.norm(P @ vectors - vectors * values) print("||P X - X Lambda|| =", f"{residual:.1e}")
if __name__ == "__main__": main()P =[[ 0.6667 -0.3333 0.3333] [-0.3333 0.6667 0.3333] [ 0.3333 0.3333 0.6667]]特征值: [1. 1. 0.]M.T @ normal = [0. 0.]P @ in_plane == in_plane: TrueP @ normal == 0: Trueb 与 Pb 的夹角余弦: 0.992||P X - X Lambda|| = 1.7e-16特征值是 ,和他从图上读出来的一样。平面里的 原样不动,垂直于平面的 被压成零。随手取的 , 与 的夹角余弦是 0.992,不是 1,方向确实变了(偏得不多,因为我挑的 离平面本来就不远)。最后一行把 np.linalg.eig 给的整套 代回方程,残差 ,也就是舍入误差。
先求 λ,再求 x
怎么找? 他的技巧很短:把 移到左边,
我们要的 不是零向量,所以矩阵 必须奇异,行列式必须为零:
这叫特征方程。它里面已经没有 了,是 的 次多项式,有 个根(可能有重根)。他的顺序是先求 ,再对每个 解 : 是个奇异矩阵,要的是零空间,第 7 讲已经熟练地做过。自由变量取 1,得到一个特征向量。
他的主例子是
特征值是 4 和 2。 是 ,零空间里是 ; 是 ,零空间里是 。零空间是一整条直线,所以严格说不存在“那个”特征向量,而是一条线上的所有向量,我们只要选一个当代表。
方程里的 6 和 8 他特意问过学生:6 是 ,对角线之和,叫迹;8 是行列式。所以 的特征方程就是
两个根的和等于迹,积等于行列式。一般的 也一样:特征值之和等于迹,特征值之积等于行列式。不过 大了没有现成公式去解多项式,这个事实更多是拿来检查的。(他也说了, 之后求根就不好玩了,这部分交给计算机。)
下面这个脚本做四件事:置换矩阵 的两个特征向量;加 得到上面的 ;np.poly 取出特征多项式的系数,和迹、行列式对照;最后验证他警告的那件事,知道 和 的特征值,不等于知道 的特征值。
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# 置换矩阵:交换两个分量 P = np.array([[0.0, 1.0], [1.0, 0.0]]) x1 = np.array([1.0, 1.0]) x2 = np.array([-1.0, 1.0]) print("P @ (1, 1) =", P @ x1, ",P @ (-1, 1) =", P @ x2) print("特征值:", np.sort(np.linalg.eigvals(P))) print("迹 =", np.trace(P), ",行列式 =", round(np.linalg.det(P), 4))
# 加上 3 * 单位矩阵:特征值各加 3,特征向量不变 A = P + 3.0 * np.eye(2) print("A =", A.tolist()) print("特征值:", np.sort(np.linalg.eigvals(A))) print("A @ (1, 1) =", A @ x1, ",A @ (-1, 1) =", A @ x2)
# 特征方程 lambda^2 - 迹 * lambda + 行列式 = 0 的系数 print("np.poly(A) =", np.poly(A)) print("迹 =", np.trace(A), ",行列式 =", round(np.linalg.det(A), 4)) print("两个特征向量的内积:", x1 @ x2)
# 特征值的和与积并不能拆开:A + B 的特征值不是两边特征值的和 B = np.array([[1.0, 2.0], [0.0, 1.0]]) print("eig(A) =", np.sort(np.linalg.eigvals(A))) print("eig(B) =", np.sort(np.linalg.eigvals(B))) print("eig(A + B) =", np.sort(np.linalg.eigvals(A + B))) print("eig(A @ B) =", np.sort(np.linalg.eigvals(A @ B)))
if __name__ == "__main__": main()P @ (1, 1) = [1. 1.] ,P @ (-1, 1) = [ 1. -1.]特征值: [-1. 1.]迹 = 0.0 ,行列式 = -1.0A = [[3.0, 1.0], [1.0, 3.0]]特征值: [2. 4.]A @ (1, 1) = [4. 4.] ,A @ (-1, 1) = [-2. 2.]np.poly(A) = [ 1. -6. 8.]迹 = 6.0 ,行列式 = 8.0两个特征向量的内积: 0.0eig(A) = [2. 4.]eig(B) = [1. 1.]eig(A + B) = [2.2679 5.7321]eig(A @ B) = [1.1716 6.8284]读几个结果。
- 置换矩阵交换两个分量, 交换后不变,; 交换后变成 ,。迹是 0,所以知道一个特征值是 1,另一个就必须是 。他还特意指出:一个元素全不为负的矩阵,也可以有负的特征值。
- 加 以后,特征值 变成 ,各加了 3;两个特征向量 和 完全没动。原因是 两边各加 ,就得到 。
np.poly(A)给出 ,对应 ,迹 6.0,行列式 8.0,全部对上。 - 两个特征向量的内积是 0.0,垂直。他提前剧透过:对称矩阵的特征值是实数,特征向量互相垂直,这不是巧合,是定理,第 25 讲证明。
- 最后一组: 的特征值是 2 和 4,我取的 的特征值是 1 和 1。如果特征值能拆开相加, 的特征值应该落在 和 上,实际算出来是 2.2679 和 5.7321。 的特征值 1.1716 和 6.8284 也不是 和 。
最后这点在他的黑板上写成一个“错误论证”:,,相加得 。毛病在于 的特征向量一般不是 的特征向量, 有自己的 。特征值不是线性的,也不能相乘。只有 这种每个向量都是特征向量的矩阵,论证才成立。
旋转:实矩阵,复特征值
什么情况下没有特征向量? 取 90 度旋转矩阵 (他写成 的形式,并说它是“非常正交”的矩阵)。把每个向量转 90 度,哪个向量会和自己平行?没有。
方程给出同样的警告。迹是 0,所以两个特征值加起来是 0,一正一负;行列式是 1,所以乘起来是 。两个实数一正一负,积不可能是正的。特征方程是 ,根是 :实矩阵出现了复特征值。他在这里半开玩笑地说,复数到底还是要进 18.06 了(“Boo”)。
import numpy as np
def rotation(theta: float) -> np.ndarray: c, s = np.cos(theta), np.sin(theta) return np.array([[c, -s], [s, c]])
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# Strang 的 90 度旋转:他写成 [[0, -1], [1, 0]] Q = rotation(np.pi / 2) values, vectors = np.linalg.eig(Q) print("Q =") print(Q) print("特征值:", values) print("特征向量(按列):") print(vectors) print("Q.T == -Q:", np.allclose(Q.T, -Q)) print( "迹 =", round(np.trace(Q), 4), ",行列式 =", round(np.linalg.det(Q), 4) ) print("lambda^2 + 1 = 0 的根:", np.roots([1.0, 0.0, 1.0]))
# 复特征向量满足 Q v = lambda v v = vectors[:, 0] print("Q @ v == lambda * v:", np.allclose(Q @ v, values[0] * v))
# 转 30 度:特征值是 exp(+-i theta),模长是 1,幅角是 +-30 度 R = rotation(np.deg2rad(30.0)) mu = np.linalg.eigvals(R) print("30 度旋转的特征值:", mu) print("模长:", np.abs(mu), ",幅角(度):", np.rad2deg(np.angle(mu)))
if __name__ == "__main__": main()Q =[[ 0. -1.] [ 1. 0.]]特征值: [0.+1.j 0.-1.j]特征向量(按列):[[0.7071+0.j 0.7071-0.j ] [0. -0.7071j 0. +0.7071j]]Q.T == -Q: True迹 = 0.0 ,行列式 = 1.0lambda^2 + 1 = 0 的根: [-0.+1.j 0.-1.j]Q @ v == lambda * v: True30 度旋转的特征值: [0.866+0.5j 0.866-0.5j]模长: [1. 1.] ,幅角(度): [ 30. -30.]三点。
np.linalg.eig对实矩阵 返回了复数特征值 ,特征向量是复向量 和它的共轭, 验证通过。复特征值总是成共轭对出现,这对实矩阵是定理。- :反对称。他的结论是:对称矩阵特征值是实的,反对称矩阵特征值是纯虚数,两者之间的一般矩阵是实部与虚部的混合。
- 我顺手把 90 度换成 30 度:特征值是 ,模长是 1,幅角是 。旋转矩阵的特征值就是 ,模长为 1,意味着只转不伸缩。
重复特征值:特征向量不够用
还有更糟的情况吗? 把 左下角的 1 改成 0,得到三角矩阵 。三角矩阵的行列式是对角线之积,特征值直接读出来:3 和 3,重复的特征值。他说,重复特征值是 18.06 里一切麻烦的来源。
麻烦在特征向量上。,零空间只有 这一条线。第二个 要找一个和 无关的特征向量,找不到。这个矩阵是 ,却只有一个独立的特征向量,他管它叫“退化”矩阵。
import numpy as np
def main() -> None: np.set_printoptions(precision=4, suppress=True)
# Strang 把 [[3, 1], [1, 3]] 的左下角改成 0,得到三角矩阵 A = np.array([[3.0, 1.0], [0.0, 3.0]]) values, vectors = np.linalg.eig(A) print("特征值(读对角线就行):", values) print("np.linalg.eig 给的特征向量(按列):") print(vectors)
# A - 3I 的零空间只有一条线:秩是 1,不是 0 shifted = A - 3.0 * np.eye(2) print("A - 3I 的秩:", np.linalg.matrix_rank(shifted)) print( "(1, 0) 是特征向量:", np.allclose(A @ [1.0, 0.0], 3.0 * np.array([1.0, 0.0])), ) print( "(0, 1) 是特征向量:", np.allclose(A @ [0.0, 1.0], 3.0 * np.array([0.0, 1.0])), )
# 两列特征向量几乎平行:行列式接近 0,矩阵几乎不可逆 print("特征向量矩阵的行列式:", f"{np.linalg.det(vectors):.2e}") print("特征向量矩阵的秩:", np.linalg.matrix_rank(vectors)) print("特征向量矩阵的条件数:", f"{np.linalg.cond(vectors):.2e}")
# 对照:特征值不同的矩阵,特征向量矩阵可逆得很好 S = np.array([[3.0, 1.0], [1.0, 3.0]]) _, sv = np.linalg.eig(S) print( "[[3, 1], [1, 3]] 的特征向量矩阵条件数:", round(np.linalg.cond(sv), 4) )
if __name__ == "__main__": main()特征值(读对角线就行): [3. 3.]np.linalg.eig 给的特征向量(按列):[[ 1. -1.] [ 0. 0.]]A - 3I 的秩: 1(1, 0) 是特征向量: True(0, 1) 是特征向量: False特征向量矩阵的行列式: 6.66e-16特征向量矩阵的秩: 1特征向量矩阵的条件数: 3.00e+15[[3, 1], [1, 3]] 的特征向量矩阵条件数: 1.0np.linalg.eig 不会告诉你“找不到”,它老老实实地返回了两列: 和 ,第二列只是第一列的相反数。行列式 ,秩 1,条件数 ,已经逼近双精度的极限 。对照一下, 的特征向量矩阵条件数是 1.0,两个特征向量垂直,不能更好了。
下一讲会看到,这个“特征向量不够”的矩阵为什么不能对角化。教训是:函数返回了一个矩阵,不代表里面的列真的构成了基,要自己看秩或条件数。(我的运气好一点:它至少没有崩溃。)
在控制里
离散系统 的解是 ,它会不会衰减,看的就是 的特征值。如果 恰好是特征向量,,每一步乘一个 。所以 衰减, 发散, 不增不减;复特征值的幅角只管转,不影响模长。稳定性的判据是谱半径(特征值模长的最大值)是否小于 1,也就是所有特征值都在复平面的单位圆内。
我用三个形如 的矩阵验证。这种矩阵等于 倍的旋转,,特征值是 ,所以 是精确的。最后一个例子是视频里没有的,我自己加的:特征值全在单位圆内,但矩阵远离对称,状态的模长依然可以先涨后落。
import numpy as np
def simulate(A: np.ndarray, x0: np.ndarray, steps: int) -> np.ndarray: """返回 x[0], x[1], ..., x[steps] 的模长。""" x = x0.copy() norms = [np.linalg.norm(x)] for _ in range(steps): x = A @ x norms.append(np.linalg.norm(x)) return np.array(norms)
def main() -> None: np.set_printoptions(precision=4, suppress=True) x0 = np.array([1.0, 1.0])
systems = { "稳定": np.array([[0.9, 0.2], [-0.2, 0.9]]), "临界": np.array([[0.0, 1.0], [-1.0, 0.0]]), "发散": np.array([[1.0, 0.2], [-0.2, 1.0]]), } for name, A in systems.items(): radius = np.max(np.abs(np.linalg.eigvals(A))) norms = simulate(A, x0, 100) print( f"{name}:谱半径 = {radius:.4f}," f"||x[100]|| = {norms[-1]:.4g}," f"radius^100 * ||x[0]|| = {radius**100 * norms[0]:.4g}" )
# 特征值在单位圆内,模长也可能先涨后落 N = np.array([[0.9, 5.0], [0.0, 0.9]]) norms = simulate(N, x0, 100) print("非对称的例子:谱半径 =", np.max(np.abs(np.linalg.eigvals(N)))) print( f" 峰值 ||x[k]|| = {norms.max():.2f}(k = {norms.argmax()})," f"||x[100]|| = {norms[-1]:.4f}" )
if __name__ == "__main__": main()稳定:谱半径 = 0.9220,||x[100]|| = 0.0004183,radius^100 * ||x[0]|| = 0.0004183临界:谱半径 = 1.0000,||x[100]|| = 1.414,radius^100 * ||x[0]|| = 1.414发散:谱半径 = 1.0198,||x[100]|| = 10.05,radius^100 * ||x[0]|| = 10.05非对称的例子:谱半径 = 0.9 峰值 ||x[k]|| = 19.76(k = 9),||x[100]|| = 0.0148稳定的那个谱半径 0.9220,100 步后模长 0.0004183;发散的那个谱半径 1.0198,100 步后涨到 10.05;临界的(90 度旋转)转了 100 步也只是转,模长一直是 。前三行里实际模长和“谱半径的 100 次方乘初始模长”完全一致。
最后一个例子需要小心。 就是上一节那类“重复特征值、一条特征向量”的矩阵,特征值只有 0.9,模长最终一定会衰减,但中间先冲到了 19.76(第 9 步),100 步后才回落到 0.0148。所以特征值回答的是“最终去哪里”,不回答“途中走多远”。对控制来说,这就是为什么只看极点位置有时不够。第 28 讲会把这种矩阵正式叫作 Jordan 块。
下一讲 Strang 会把特征向量收集成矩阵 ,把特征值放进对角矩阵 ,写出 ,矩阵的幂 一下子就变得简单。刚才那个特征向量不够用的三角矩阵为什么写不成这种形式,也会在那里解释:18.06 第 22 讲:对角化与矩阵的幂。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!











