ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

图解线性代数:用Python复现矩阵变换与SVD的几何直觉

图解线性代数:用Python复现矩阵变换与SVD的几何直觉

简介:《线性代数的艺术》中文版PDF是一份面向线性代数初学者与进阶学习者的图形化学习资料,作者Kenji Hiranabe在Gilbert Strang《线性代数为所有人》理论基础上,用可视化方式重新诠释矩阵运算与分解。资源包内仅含1个PDF文件,大小约1.43MB,轻量便携,适合在电脑或平板上随时翻阅。内容从矩阵的四种视角切入,依次讲解向量点积与秩1矩阵、矩阵乘向量与矩阵乘矩阵的线性组合含义,并重点展开CR、LU、QR、QΛQT与奇异值分解五种矩阵分解方法,配有大量彩色示意图与章节交叉引用,便于对照Strang原书章节深入理解。目前已有573人学习浏览,适合希望摆脱纯公式推导、通过图形直觉把握线性代数核心结构的读者,可作为课堂辅助读物或考前梳理矩阵分解知识框架的参考笔记。

1. 一份把线性代数画成图的 PDF,为什么值得你花一晚上啃完

如果你和我一样,第一次翻开传统线性代数教材时被满页的矩阵下标和行列式展开劝退,那你大概能理解我看到《The-Art-of-Linear-Algebra-zh-CN.pdf》时的反应——它把矩阵乘法、特征值、奇异值分解这些抽象概念,全部画成了几何图形。这不是一本习题册,也不是考试速成笔记,而是一份用视觉直觉重建线性代数认知的图解文档。它解决的核心问题是:你背得出公式,却说不清矩阵到底在对空间做什么。适合谁?适合已经学过一遍线代但脑子里只有计算步骤、没有几何图像的工程师、研究生和自学者。我自己的血泪经验是,做机器学习调参时真正卡住你的不是代码,而是对矩阵分解的直觉缺失,这份文档恰好补的就是这块。

2. 从矩阵乘法到四个子空间:图解线代到底在画什么

2.1 矩阵不是数字表格,是空间变换的操作说明书

传统教材告诉你矩阵乘法是行乘列求和,但图解文档会先问你一个问题:一个 2×2 矩阵作用在平面上,到底发生了什么?答案是把单位正方形变成平行四边形。矩阵的每一列,就是基向量变换后的落点。这个视角一旦建立,行列式就不再是展开式,而是面积缩放因子——行列式为 0 意味着空间被压扁了,信息不可逆。

我一般会建议读者先拿纸笔画三个矩阵:旋转矩阵、缩放矩阵、剪切矩阵,分别看它们把单位正方形变成什么形状。这一步花不了二十分钟,但能让你后面看特征值的时候少走很多弯路。图解文档里对这部分处理得很克制,没有堆砌公式,而是用箭头和色块把“列向量就是新基”这件事讲透了。

2.2 四个子空间:把 Ax=b 的解结构画成一张地图

列空间、零空间、行空间、左零空间——这四个词在教材里通常出现在不同章节,学生很难把它们串起来。图解文档的做法是把它们画在同一张图上,用正交关系连接。列空间是 A 能到达的所有输出,零空间是被 A 压成零的所有输入,两者维度满足秩-零化度定理。

这里有一个关键直觉:如果 b 不在列空间里,Ax=b 就无解;如果零空间维度大于 0,解就不唯一。我见过太多工程师在调试最小二乘拟合时,搞不清为什么正规方程有时候解不稳定,根源就是对这四个空间的关系没有图像化理解。文档里用二维和三维的例子反复演示,虽然简单,但足够建立正确的心理模型。

2.3 特征值与特征向量:变换中不改变方向的那些线

特征值的几何意义是:矩阵作用在特征向量上,只拉伸不旋转。这个定义看起来简单,但真正理解它需要你同时看到三件事——特征向量张成的线、对应的拉伸倍数、以及不同特征值之间的竞争关系。图解文档用一组动态示意图展示了对称矩阵和非对称矩阵的区别:对称矩阵的特征向量正交,非对称矩阵的不一定。

我自己的习惯是,拿到一个矩阵先问它是不是对称的,因为对称矩阵的谱定理保证了实数特征值和正交特征基,这在数值计算里是巨大的便利。文档里对这部分没有深入数值算法,但视觉直觉给得很足,后续你去看幂迭代或 QR 算法时,脑子里会有图。

2.4 奇异值分解:所有矩阵都能拆成旋转-拉伸-旋转

SVD 是线性代数里最漂亮的分解,但也是最难用直觉理解的。图解文档把它画成三步:先旋转,再沿坐标轴拉伸,再旋转。左奇异向量是输入空间的正交基,右奇异向量是输出空间的正交基,奇异值是拉伸倍数。

这里有一个常见误解:很多人以为 SVD 只适用于方阵,其实任何 m×n 矩阵都有 SVD。文档里用长方形矩阵的例子专门澄清了这一点。我建议读者在看完这部分后,拿一个 3×2 的矩阵手算一遍 SVD,不用求精确值,只要把三个步骤的几何意义对应上就行。这一步做完,你再看 PCA、推荐系统里的隐语义模型,会顺畅很多。

3. 用 Python 把 PDF 里的图复现一遍:最小可运行代码

3.1 环境准备与依赖安装

图解文档里的图虽然精美,但如果你只是看,过两天就忘了。我的做法是把它里面的核心图用 Python 复现一遍,这样肌肉记忆和视觉记忆会同时建立。你需要的基础环境很简单:Python 3.9 以上、NumPy、Matplotlib。不需要深度学习框架,不需要 GPU。

# 创建虚拟环境,避免污染主环境 python -m venv linalg-vis source linalg-vis/bin/activate # Windows 用 linalg-vis\Scripts\activate # 安装三个核心库 pip install numpy matplotlib

这里没有指定版本号,因为这三个库的 API 在近几个大版本里足够稳定。如果你用的是较老的 Python 3.7,建议升级,否则 Matplotlib 的某些绘图参数会不兼容。安装完成后,用python -c "import numpy; print(numpy.__version__)"确认一下。

3.2 画一个矩阵对单位正方形的变换

下面这段代码复现的是文档里最基础的那张图:一个 2×2 矩阵如何把单位正方形变成平行四边形。我加了网格和箭头,方便你看到基向量的落点。

import numpy as np import matplotlib.pyplot as plt # 定义变换矩阵,这里选一个剪切+缩放的组合 A = np.array([[1.5, 0.8], [0.2, 1.2]]) # 单位正方形的四个顶点(逆时针) square = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]) # 最后一点回到起点,方便画闭合 # 对每个顶点做矩阵乘法,注意转置 transformed = (A @ square.T).T fig, ax = plt.subplots(figsize=(6, 6)) # 画原始正方形 ax.plot(square[:, 0], square[:, 1], 'b--', label='原始单位正方形') # 画变换后的平行四边形 ax.plot(transformed[:, 0], transformed[:, 1], 'r-', linewidth=2, label='变换后') # 画基向量变换前后的箭头 origin = np.array([0, 0]) ax.arrow(0, 0, A[0, 0], A[1, 0], head_width=0.08, color='green') ax.arrow(0, 0, A[0, 1], A[1, 1], head_width=0.08, color='orange') ax.set_xlim(-0.5, 3) ax.set_ylim(-0.5, 3) ax.set_aspect('equal') ax.grid(True, alpha=0.3) ax.legend() ax.set_title('矩阵 A 对单位正方形的变换') plt.show()

这段代码的逻辑很直接:square是原始形状的顶点坐标,A @ square.T做矩阵乘法,再转置回来得到变换后的坐标。关键参数是A矩阵本身,你可以改成任何 2×2 矩阵,观察形状变化。绿色箭头是第一列,橙色箭头是第二列,它们就是新的基向量。如果行列式为负,你会看到图形翻转,这是方向改变的可视化。

3.3 用特征向量看变换中的不变量

下面这段代码画一个对称矩阵的特征向量,并验证它们确实只被拉伸不旋转。

import numpy as np import matplotlib.pyplot as plt # 对称矩阵,保证有实特征值和正交特征向量 A = np.array([[2.0, 1.0], [1.0, 2.0]]) # 计算特征值和特征向量 eigenvalues, eigenvectors = np.linalg.eigh(A) # eigh 专门用于对称矩阵 print("特征值:", eigenvalues) print("特征向量:\n", eigenvectors) fig, ax = plt.subplots(figsize=(6, 6)) # 画原始特征向量方向 for i in range(2): v = eigenvectors[:, i] ax.arrow(0, 0, v[0], v[1], head_width=0.08, color='blue', alpha=0.5, label=f'原始 v{i+1}') # 画变换后的向量,应该在同一方向上,长度变为 eigenvalue 倍 for i in range(2): v = eigenvectors[:, i] Av = A @ v ax.arrow(0, 0, Av[0], Av[1], head_width=0.08, color='red', alpha=0.8, label=f'A v{i+1}') ax.set_xlim(-2, 2) ax.set_ylim(-2, 2) ax.set_aspect('equal') ax.grid(True, alpha=0.3) ax.legend() ax.set_title('对称矩阵的特征向量:只拉伸不旋转') plt.show()

这里用np.linalg.eigh而不是eig,是因为对称矩阵用eigh更稳定且保证实数输出。特征向量矩阵的每一列是一个特征向量,A @ v的结果应该等于eigenvalue * v。如果你把A改成非对称矩阵,比如[[1, 2], [0, 1]],你会发现特征向量不够用,这时候就需要 SVD 了。

3.4 手算一个 SVD 并验证三个步骤

SVD 的代码验证稍微复杂一点,但值得做。下面这段代码把 SVD 拆成旋转-拉伸-旋转三步,并画出来。

import numpy as np import matplotlib.pyplot as plt # 一个长方形矩阵,2×3 A = np.array([[1.0, 2.0, 0.5], [0.5, 1.0, 1.5]]) # 计算 SVD U, S, Vt = np.linalg.svd(A, full_matrices=False) print("U 形状:", U.shape) print("奇异值:", S) print("Vt 形状:", Vt.shape) # 验证 A = U @ diag(S) @ Vt Sigma = np.diag(S) A_reconstructed = U @ Sigma @ Vt print("重构误差:", np.linalg.norm(A - A_reconstructed)) # 画奇异值衰减,这是判断矩阵有效秩的常用方法 fig, axes = plt.subplots(1, 2, figsize=(10, 4)) axes[0].bar(range(1, len(S)+1), S, color='steelblue') axes[0].set_xlabel('奇异值序号') axes[0].set_ylabel('大小') axes[0].set_title('奇异值谱') # 画原始矩阵和秩-1 近似 A_approx = U[:, :1] @ np.diag(S[:1]) @ Vt[:1, :] axes[1].imshow(A, cmap='Blues', aspect='auto') axes[1].set_title('原始矩阵') axes[1].set_xticks([]) axes[1].set_yticks([]) plt.tight_layout() plt.show() print("原始矩阵:\n", A) print("秩-1 近似:\n", A_approx)

这段代码的关键参数是full_matrices=False,它返回紧凑形式的 SVD,对于长方形矩阵能节省内存。U是 2×2,S是长度为 2 的向量,Vt是 2×3。重构误差应该接近机器精度。秩-1 近似展示了 SVD 在降维中的核心用途:只保留最大的奇异值,就能抓住矩阵的主要结构。你可以把A换成图像矩阵,看看保留前几个奇异值能恢复出什么。

4. 图解线代在工程里的三个真实落点

4.1 最小二乘与投影:为什么正规方程有时候会翻车

最小二乘的几何意义是把 b 投影到 A 的列空间上。如果 A 的列向量近似线性相关,列空间会变得很“窄”,投影矩阵的条件数爆炸,数值解就不稳定。图解文档里用二维例子展示了这个现象:两条几乎平行的线,交点对扰动极其敏感。

工程上的常见做法是加正则项,也就是岭回归。从线性代数角度看,正则项相当于给 A 的对角线加了一个小量,把列空间“撑开”。我一般会先看奇异值谱,如果最小奇异值和最大奇异值差了好几个数量级,就会考虑正则化或者降维。这一步不需要复杂计算,np.linalg.svd跑一下就行。

4.2 PCA 不是黑匣子:它就是 SVD 在中心化数据上的应用

很多人把 PCA 当成一个调包操作,但如果你理解 SVD,PCA 就是透明的。把数据矩阵中心化后做 SVD,右奇异向量就是主成分方向,奇异值的平方除以样本数就是方差解释比例。图解文档里虽然没有专门讲 PCA,但 SVD 那部分的几何直觉直接迁移过来。

我自己的习惯是,做 PCA 之前先画奇异值谱,看拐点在哪里。如果奇异值衰减很慢,说明数据在各个方向上方差差不多,降维意义不大。如果前两三个奇异值占了 90% 以上,那降到二维可视化是合理的。这些判断不需要跑模型,几行 NumPy 就能搞定。

4.3 推荐系统里的隐语义模型:矩阵分解的工程权衡

推荐系统里的矩阵分解,本质上是在找一个低秩近似。用户-物品评分矩阵通常很大但很稀疏,直接做 SVD 不可行,因为缺失值太多。工程上的做法是用随机梯度下降或者交替最小二乘来拟合隐向量。但如果你理解 SVD 的几何意义,就知道隐向量的维度选择本质上是在偏差和方差之间权衡。

维度太低,欠拟合,推荐不准;维度太高,过拟合,泛化差。我一般会从 20 到 50 维开始试,看验证集上的 RMSE 曲线。图解文档里对低秩近似的可视化能帮你建立“为什么低秩能抓住主要模式”的直觉,这比调参本身更重要。

5. 避坑与排查:复现图解线代时最容易踩的五个坑

5.1 特征向量顺序与符号:别被输出结果搞晕

现象:用np.linalg.eig算特征向量,发现和手算结果差一个负号,或者顺序对不上。原因:特征向量的符号和顺序不是唯一的,NumPy 不保证按特征值大小排序,也不保证符号方向。解决:用np.linalg.eigh处理对称矩阵,它返回升序特征值;需要降序就手动翻转。符号问题不影响几何意义,画图时统一方向即可。

5.2 矩阵乘法维度不匹配:转置用错地方

现象:A @ square报错,提示维度不匹配。原因:square是 5×2 的顶点列表,而A是 2×2,矩阵乘法要求内维一致。解决:写成(A @ square.T).T,先转置再乘,最后转回来。或者直接用square @ A.T,效果一样。记住一个原则:行向量右乘矩阵,列向量左乘矩阵。

5.3 画图时坐标轴比例失真:几何直觉被扭曲

现象:画出来的平行四边形看起来不像平行四边形,角度和长度都不对。原因:Matplotlib 默认的坐标轴比例不是 1:1,x 轴和 y 轴的单位长度不同。解决:加ax.set_aspect('equal')。这一步在画任何几何变换图时都必须做,否则你的视觉直觉会被误导。我见过有人因为没设这个参数,误以为某个矩阵是保角的。

5.4 SVD 的 full_matrices 参数:内存和形状的权衡

现象:对一个 1000×10 的矩阵做 SVD,U的形状是 1000×1000,内存直接爆掉。原因:np.linalg.svd默认full_matrices=True,返回完整的方阵 U 和 Vt。解决:设full_matrices=False,U 变成 1000×10,Vt 变成 10×10,节省大量内存。只有在需要完整正交基时才用默认值,工程里几乎总是用紧凑形式。

5.5 数值精度问题:为什么重构误差不是零

现象:A - U @ diag(S) @ Vt的范数不是精确的 0,而是 1e-15 量级。原因:浮点数运算有舍入误差,这是正常的。解决:用np.allclose而不是==来判断相等。如果误差到了 1e-5 以上,那可能是矩阵条件数太大,或者算法选择有问题。对于病态矩阵,考虑用np.linalg.lstsq而不是直接求逆。

6. 把图解 PDF 变成自己的直觉:一个我坚持了三年的习惯

我自己的做法是,每学一个新的线性代数概念,就强迫自己用 Matplotlib 画一张图。不追求好看,只追求把“输入是什么、变换是什么、输出是什么”这三件事画清楚。这个习惯坚持了三年,最大的收获不是绘图技巧,而是遇到问题时脑子里能自动浮现几何图像。比如调试一个卡尔曼滤波器,状态协方差矩阵的正定性问题,我第一反应是去看它的特征值是不是有负的,而不是去翻公式。

如果你也想建立这种直觉,我建议从这份 PDF 里挑三张你最看不懂的图,用 Python 复现一遍。不用全部复现,三张就够。复现的过程中你会被迫回答很多看的时候忽略的问题:这个箭头为什么指向这里?这个角度为什么是 90 度?这个矩阵为什么是对称的?这些问题才是真正让你从“知道”变成“理解”的开关。

最后一个技巧:把复现的图存成 PNG,和 PDF 原图放在同一个文件夹里,隔一周再打开对比。如果你能一眼看出自己画的图和原图的差异,并且说出差异的原因,那这个概念你就拿下了。如果看不出差异,说明你还没真正理解,回去再画一遍。这个验证方法比做习题有效得多,因为它不给你蒙对的机会。

希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进