工程工具箱:误差诊断 · 算法选型 · 停止准则 · 函数地图
正文回答「为什么」,这一页回答「动手时先看哪一页、用哪个函数」。它是一张索引:拿到一个具体症状,先在本页定位到该算哪个量、该调哪个函数,再回正文读推导。本页不重述正文内容,也不重复复习层的公式卡与对照卡。
| 页 | 管什么 | 什么时候用 |
|---|---|---|
| 先修与自测 | 基础层:线代 / 微积分 / 编程 / 记号的清单与自测 | 开学前、数学卡壳时 |
| 本页 | 操作层:诊断 → 选型 → 停止准则 → 函数与后端 | 每一段数值代码落地时 |
| 公式与结论卡 | 查阅层:第一章 + 补充篇必记公式 | 忘了公式 |
| 易混对照卡 | 辨析层:16 对易混概念 | 判断题、概念题 |
| 术语索引 | 命名层:中英术语与定义位置 | 查名字 |
| 附录 B | 深挖层:浮点算术的完整推导 | 想看清 、Kahan、求和界是怎么来的 |
| 必考点竞技场 | 输出层:限时答题、错题本 | 考前限时训练 |
一、误差诊断:四个量、一张判据表
| 量 | 怎么算 | 它回答什么 | 常见误用 |
|---|---|---|---|
| 残差 | r = b - A @ x | 方程被满足到什么程度(可以算) | 拿它当误差用(例 2.4.4:残差 、误差 ) |
| 后向误差(向后误差) | 范数型: | 算得的解是「哪个邻近问题的精确解」——求解器实际保证的量 | 把后向误差当精度报告(它是算法指标,不是解的准确度) |
| 条件数 | np.linalg.cond(A, 2)(内部走 SVD) | 问题天生的放大倍数 / 要丢几位 | 不注明范数就互相比较(2.4.6) |
| 误差 | 一般算不出(真解未知) | 解离真值多远 | 用「相邻两次迭代之差」冒充它 |
核心判据(把残差换算成误差): (定理 2.4.4)。求解器向后稳定只保证残差小;要谈解准不准,必须乘上条件数。丢掉几位有效数字用 估(2.4.2 换算表)。
「后向误差与条件数无关」的实测证据(同一批 Hilbert 矩阵,np.linalg.solve 求解、真解全为 ):
| 后向误差 | 相对残差 | 前向误差(最大分量) | ||
|---|---|---|---|---|
| 8 | ||||
| 12 |
读法:两行的后向误差都停在 甚至更小——算法一直是向后稳定的;而前向误差从 涨到 ,差了 6 个数量级。病态吃掉的是前向误差,与算法的稳定性无关(1.2.6 的 conditioning / stability 之分在这里落地)。
症状 → 先算哪个量 → 判据 → 处方:
| 症状 | 先算 | 判据 | 处方 |
|---|---|---|---|
| 残差很小,结果仍可疑 | 与 | 后者远大于期望精度 | 换 QR / SVD、迭代精化、提高工作精度、正则化 |
| 解的系数量级离谱(如 而真值 ) | 奇异值谱、行量级 | 有条件数平方迹象(曾算 ) | 先用 QR / SVD 复算,再决定是否正则化(习题 D1 的诊断报告就是这套流程) |
| 换一种解法结果差很多 | 各算法误差都 | 确认病态属于问题本身,改算法没有用 | |
| 行量级悬殊(如相差 ) | 前后对比 | 行均衡化后条件数骤降 | 先均衡化再评估(例 2.4.5:) |
| 数值秩每次统计都不一样 | 奇异值谱 + 噪声水平 | 与噪声同量级 | 按噪声水平定阈值,不照抄库的默认值 |
可复算的两个锚点(本机 numpy 2.4.6,命令可直接跑):8 阶 Hilbert 矩阵 ,solve 最大误差 ;12 阶 ,solve 最大误差 (真值全为 )——第二行说明解已经失去意义,而求解器并不报错。
二、算法选择树:解方程组与特征值
解 (方阵)
| 情形 | 首选 | 函数 | 复杂度 | 为什么 |
|---|---|---|---|---|
| 稠密、一般 | LU + 列主元 | np.linalg.solve;多个右端用 scipy.linalg.lu_factor + lu_solve | 列主元把乘数控制在 以内(第 3 讲) | |
| 稠密、对称正定 | Cholesky | scipy.linalg.cholesky / cho_factor + cho_solve | 省一半工作,且不需要选主元 | |
| 稠密、对称不定 | LDL(Bunch–Kaufman 型) | scipy.linalg.ldl | 量级 | 对称性可用,但主元要 块 |
| 三对角 / 带状 | 追赶法 / 带状求解 | scipy.linalg.solve_banded | 带状结构不该被当成稠密处理 | |
| 稀疏( 大、非零少) | 稀疏 LU(SuperLU)或迭代法 | scipy.sparse.linalg.splu / spsolve;迭代用 cg(对称正定)、gmres(一般) | 见 nnz | 直接法有 fill-in,内存可能爆;迭代法需要预条件子 |
| 病态() | 先问「问题要不要重述」,再选算法 | 见上一节处方列 | — | 任何算法都只给 量级 |
判定顺序(照这个顺序问四个问题): ① 矩阵对称吗?正定吗?→ 决定能否用 Cholesky / LDL。② 稀疏吗(判据见第四节的经验法则)?→ 决定稠密库还是 scipy.sparse。③ 条件数量级多大?→ 决定要不要换路线(QR / SVD / 正则化 / 提高精度)。④ 同一个 要解多少个右端?→ 多个右端先分解一次(lu_factor / cho_factor),再反复 *_solve。
超定最小二乘 ()
| 情形 | 首选 | 函数 | 关键量级 |
|---|---|---|---|
| 列满秩、 | QR(Householder) | np.linalg.lstsq;显式用 np.linalg.qr + 回代亦可 | 误差 |
| 列满秩、 | SVD + 阈值 | np.linalg.lstsq(内部走 SVD)或显式 svd | 同上,且能同时给出数值秩 |
| 秩亏 / 近似秩亏 | 截断伪逆 | np.linalg.pinv(A, rcond=...)、lstsq(..., rcond=...) | 阈值按噪声水平 |
| 正态方程() | 只用于教学与手算 | — | 误差 ,唯一优点是公式短(2.3.6) |
特征值与奇异值问题
| 要求 | 首选 | 函数 | 说明 |
|---|---|---|---|
| 稠密、一般矩阵、全部特征值 | QR 算法(先化 Hessenberg 再迭代) | scipy.linalg.eig | 特征值可为复数,注意返回类型 |
| 稠密、对称矩阵、全部特征值 | 对称 QR / 分治 | scipy.linalg.eigh | 特征值必为实数、特征向量正交,比 eig 又快又稳 |
| 只要主特征值、矩阵–向量乘足够便宜 | 幂法 + Rayleigh 商 | 手写循环(算法 2.5.1) | 速度由 决定,估计值用 Rayleigh 商(二阶精度) |
| 大规模稀疏、要最大的几个(对称) | Lanczos | scipy.sparse.linalg.eigsh | 只做矩阵–向量乘, 每步 |
| 大规模稀疏、非对称 | Arnoldi | scipy.sparse.linalg.eigs | 需要多大的 、ncv 取多少,见函数文档 |
| 全部特征值、还想看中间过程 | QR 迭代 | 手写(2.3 预告) | 相似,对角元趋于特征值;模互异才保证收敛 |
| 只想知道特征值「被困在哪」 | Gerschgorin 圆盘 | 读一遍矩阵即可(2.5.2) | 的围栏;对角占优时圆心就是良好近似 |
| 稀疏 / 只要前几个最大奇异值 | 迭代 SVD | scipy.sparse.linalg.svds | 稠密的完整 SVD 用 np.linalg.svd |
三、收敛判定与停止准则
三条停止准则,缺一不可: ① 相对残差(或相对增量)阈值 ;② 迭代次数上限(阈值没到也要停下来并报告);③ 判据必须能算——用真解做判据等于没有判据。
不成立。迭代矩阵的范数接近 时,相邻差可以很小而离真解很远——这是必考点总表里的一条,也是第 5 讲专门强调的。收敛判据分清两层:充要条件是 ,工程上实用的充分条件是 (2.5.6 还提醒:用 判断在非正规矩阵上会遇到瞬态增长)。
实测收敛阶怎么估。 若误差满足 ,则连续两个误差比给出
可复算的例子(幂法求主特征值,矩阵与数据取自例 2.5.3:):,实测 Rayleigh 商误差每步乘以 。两点读法:
- 比值的读法: 恰好说明「Rayleigh 商的误差按 衰减」——比只用 估计(每步 )快一倍(定理 2.5.3)。
- 阶的读法:把实测误差代进上面的公式,得到 ——它告诉你的信息是「这个序列是几何衰减、比值 」,而不是「一阶收敛」。报「阶」之前先确认序列真的落在 ()的形态里:割线法这类 的序列(第 9 讲)才适合直接用三点公式估阶。
一份可以照抄的停止准则模板:
for k in range(maxit): # ② 上限永远写进循环
x = step(x) # 迭代一步
res = np.linalg.norm(b - A @ x) / bnorm # ① 相对残差
if res < tol: break # 达标即停
else:
print(f"未达标即触顶:k={maxit} 残差={res:.3e} "
f"cond 估计={np.linalg.cond(A, 2):.2e}") # ③ 触顶时报告诊断量
四、工具地图:任务 → 函数 → 后端
| 任务 | NumPy | SciPy | 底层例程(LAPACK) | 复杂度 |
|---|---|---|---|---|
| 解方阵方程 | np.linalg.solve | scipy.linalg.solve(可声明 assume_a='pos'/'sym') | gesv(LU + 列主元) | |
| Cholesky / 复用它 | np.linalg.cholesky | cho_factor + cho_solve | potrf / potrs | |
| LU 分解 + 复用 | — | lu_factor + lu_solve | getrf / getrs | |
| QR(Householder) | np.linalg.qr | scipy.linalg.qr(mode='economic') | geqrf | |
| 列主元 QR(可判秩) | — | scipy.linalg.qr(pivoting=True) | geqp3 | 同上,略高 |
| 最小二乘 | np.linalg.lstsq | scipy.linalg.lstsq(lapack_driver 可切换) | SVD 型最小二乘例程(本节末注) | |
| SVD | np.linalg.svd(默认 gesdd) | scipy.linalg.svd(lapack_driver=...) | gesdd(分治) / gesvd | () |
| 条件数 | np.linalg.cond(A, 2)(SVD);cond(A, 1/inf) 显式求逆再取范数 | scipy.linalg.lapack.dgecon( 估计,需先做 LU) | gecon | / |
| 对称特征值 / 一般特征值 | np.linalg.eigh / np.linalg.eig | scipy.linalg.eigh / eig | syevd 族 / geev | |
| 稀疏直接解 | — | scipy.sparse.linalg.spsolve / splu | SuperLU | 见 nnz 与 fill-in |
| 稀疏迭代解 | — | cg / gmres / minres | — | 每步一次 matvec |
| 稀疏特征值 / SVD | — | eigsh(Lanczos)/ eigs(Arnoldi)/ svds | ARPACK | 每步一次 matvec |
| 伪逆 / 数值秩 | np.linalg.pinv、np.linalg.matrix_rank | — | gesdd + 阈值 |
关于 numpy.linalg 的 LAPACK 后端。 NumPy 与 SciPy 调的是同一批 LAPACK / BLAS 例程(具体实现由发行版的 OpenBLAS、MKL 一类库提供),所以同一个矩阵在不同机器上可能出现末位差异——本教程各表因此都声明「逐位因平台的 BLAS 实现而异」。两处容易踩的差别:
np.linalg.cond(A, 2)走 SVD;而cond(A, 1)/cond(A, np.inf)按「」实现,会显式求逆。大矩阵上想省这一步,用 SciPy 暴露的gecon(先 LU 再做 范数估计),调用签名随版本略变,用前查当前版本文档。np.linalg.svd默认走gesdd(分治),np.linalg.qr走geqrf;要换成别的例程(如gesvd)得用 SciPy 的svd(lapack_driver='gesvd')。- 最小二乘的底层是 LAPACK 的 SVD 型求解器(
gelsd系列)。本机 numpy 2.4.6 的lstsq文档只承诺「用 SVD」,未点名例程——具体例程这一项需核实(要精确核对就查当前版本文档或源码)。
何时该用 scipy.sparse: 判据是「非零元占比」与「能不能只做矩阵–向量乘」两条。经验法则(社区惯例,不是定理): 低于约 、且矩阵来自网格、图或文本这类天然稀疏结构时,换稀疏表示通常值得。三条实测提醒:① 稀疏格式要转对——spsolve 用 CSC(A.tocsc());② 直接法(splu)有 fill-in,分解后非零元可能比原矩阵多几个量级,内存是第一个瓶颈;③ 迭代法(cg / gmres)不收敛时先加预条件子(spilu 做不完全 LU),而不是一味加迭代次数。
五、三条「不要做」的事
1. 不要用显式求逆解方程组。
x = np.linalg.inv(A) @ b # 不要这样写
x = np.linalg.solve(A, b) # 正确的写法
-
代价账:
solve是「LU()+ 两次三角回代()」;inv还要由 LU 显式构造 (再加约 ),总计约 2.5 倍工作量。 -
精度账(可复算, 阶 Hilbert 矩阵、真解全为 ):
solve最大误差inv @ b最大误差8 12 显式求逆多引入一层舍入,误差被条件数再放大一次(1.3.6 的矩阵条件数是这件事的定量依据)。
-
同一 多个右端时,正确做法不是
inv,而是分解一次、回代多次:lu_factor(A)+ 循环lu_solve。
2. 不要把 送给特征值分解。
数学上 的特征值确实给出奇异值的平方,但 ——有效数字直接掉一半。可复算的账:取 的 Vandermonde 设计矩阵( 均匀取自 ,np.vander(x, 10, increasing=True)),实测
所以:算 SVD 用 np.linalg.svd(A);解最小二乘用 lstsq / QR;算条件数用 cond(A, 2)。eigh(A.T @ A) 只适合演示「条件数为什么会平方」(2.6.7 常见错误 2、2.8.2 陷阱 5)。
3. 不要把默认阈值当数据的分辨率。
np.linalg.matrix_rank的默认阈值是 (本机 numpy 2.4.6 文档如此),它只适合「精确矩阵、数值上降秩」的场景。带噪数据的实测:给一个数值秩 3 的矩阵加 量级噪声后,默认阈值判出 ,按噪声水平截断得 (实验三 (d))。- 同一条也适用于伪逆:
np.linalg.pinv的默认rcond是 ,不截断的伪逆会把 放大 倍,与直接求逆同样危险(2.6.4 思考)。正确做法:按数据噪声水平给rcond(或tol),并报告所选阈值。
六、验收清单(提交数值结果前过一遍)
| # | 检查项 | 做法 | 通过标准 |
|---|---|---|---|
| 1 | 残差 | r = b - A @ x; np.linalg.norm(r)/np.linalg.norm(b) |
小于设定的相对阈值 |
| 2 | 条件数与预期精度 | np.linalg.cond(A, 2);算 |
报告的位数与 一致 |
| 3 | 合成算例的前向误差 | 取真解 x_true = ones(n)、b = A @ x_true,再解回来比最大分量误差 |
与 同量级(2.4.2、实验二) |
| 4 | 最小二乘的正交条件 | A.T @ (b - A @ x) |
各分量 (免费自检) |
| 5 | QR / SVD 的回乘 | np.linalg.norm(Q @ R - A)、np.linalg.norm(Q.T @ Q - I) |
分别 与 (Householder / Givens) |
| 6 | 低秩逼近的误差 | np.linalg.norm(A - A_k, 2) 与 S[k] 对照 |
两者相等至机器精度(定理 2.6.4) |
| 7 | 对称 / 正定性 | np.allclose(A, A.T);Cholesky 是否成功 |
声明了对称就必须核对 |
| 8 | 行均衡化后的条件数 | B = A / A.max(axis=1, keepdims=True),再算 |
判断「真病态」还是「单位没选好」 |
| 9 | 两种算法的交叉验证 | lstsq 与 QR 各算一次;pinv(rcond) 与截断 SVD 对照 |
差异落在预期量级内,否则说明某一步写错了 |
| 10 | 可复现性 | 随机种子(default_rng(0))、函数版本(np.__version__、scipy.__version__)、BLAS 类型 |
报告里能写出这三项 |
三条不该出现在报告里的写法: ① 只报残差、不报条件数(读者无从判断解准不准);② 只报迭代次数、不报是否达标(触顶与收敛是两回事);③ 不写种子与版本(别人复现不出你的数字)。
七、症状 → 先看哪一页 / 用哪个函数
| 我遇到的事 | 本页 | 正文 | 先用哪个函数 |
|---|---|---|---|
| 解出来不准,不知道是问题还是算法 | 第一节 | 2.4 | np.linalg.cond |
| 超定系统要不要担心病态 | 第二节 | 2.3.6 | np.linalg.lstsq |
| 秩亏 / 有噪声的拟合 | 第五节 | 2.6.4、习题 D2 | np.linalg.pinv(A, rcond=...) |
| 要四个基本子空间 | 第二节 | 2.2、2.6.3 | np.linalg.svd |
| 矩阵很大、还是稀疏的 | 第四节 | 2.3.5 | scipy.sparse.linalg.spsolve / cg |
| 只要最大的几个特征值 | 第二节 | 2.5.3 | scipy.sparse.linalg.eigsh / eigs |
| 迭代该停在哪里、阶是多少 | 第三节 | 2.5.3、第 5 讲 | — |
| 想压缩 / 降维 / 去噪 | 第二节 | 2.6.5、2.6.6 | np.linalg.svd(截断 SVD) |
| 浮点误差的来龙去脉 | — | 1.2.5、附录 B | np.finfo(float).eps |
依据第一章 + 补充篇正文的公式、量级与实验整理 · 本页所有函数名与默认值在本机 numpy 2.4.6 / scipy 1.18.0 上核对过;各版本默认阈值会变,动手前查当前版本文档 · 与先修与自测的分工:那一页管基础,本页管操作 · 整理日期 2026-10-20。