1. 课程导入与排障思维建立

数值计算异常与收敛问题,是科学计算、工程仿真、优化求解和机器学习训练中最常见、也最容易被误判的一类故障。它们往往不表现为程序崩溃,而是以“结果不对”“跑不完”“越算越离谱”的形式出现,排查难度远高于语法错误。本课程用45个真实场景化的案例,帮助你建立一套可复用的排障思维:先判断异常属于哪一类,再定位它发生在计算链的哪一环,最后用最小代价的试验验证假设。

一、典型表现:异常到底长什么样

数值异常的表象五花八门,但归纳起来主要有四类。第一类是数值溢出与非法值:迭代中出现 NaN、Inf,或矩阵出现 1e308 量级的元素,随后整个计算被污染。第二类是不收敛或收敛过慢:迭代残差在某个量级反复震荡,或残差下降速度远低于预期,迭代次数触顶仍未达到容差。第三类是结果失真:程序正常结束,但解与物理直觉或解析解偏差很大,常见于病态矩阵、步长过大、离散格式不当。第四类是性能与稳定性异常:计算时间突然暴涨、内存溢出、并行结果与串行不一致。

这四类表现对应的根因层次不同:非法值多来自表达式定义域越界或除零;不收敛多来自算法选择、初值、松弛因子或雅可比条件;结果失真多来自模型离散化与条件数;性能异常多来自稀疏结构破坏或线程竞争。排障的第一步不是改代码,而是给异常归类,因为不同类别的排查路径完全不同。

二、排障流程:从现象到根因的收敛路径

一个高效的排障流程应当遵循“先复现、再缩小、后验证”的原则。具体来说:先构造一个最小可复现用例,把问题从大模型中剥离出来;再沿计算链逐段插桩,观察中间量的量级、符号、条件数是否突变;然后针对可疑环节做对照试验,例如把迭代法换成直接法、把步长减半、把初值改为解析近似解,看现象是否消失;最后回归验证,确认修复后原用例通过且未引入新问题。

这里要特别强调“二分定位”的思想:不要一上来就怀疑最复杂的模块。数值问题的传播具有放大性,一个早期的微小误差可能在下游被放大成灾难。因此插桩点应优先放在数据入口、矩阵组装、迭代更新和输出四个关键位置,先确认哪一段开始“变坏”,再深入该段内部。

数值异常排障主线:现象归类 → 插桩定位 → 对照验证 ① 现象归类(先判断属于哪一类) NaN / Inf 非法值 残差震荡 / 不收敛 结果失真 / 偏差大 性能 / 并行不一致 ② 计算链插桩(找“从哪一段开始变坏”) 数据入口 量级 / 缺失 / 单位 矩阵组装 对称性 / 条件数 迭代更新 残差 / 步长 / 松弛 输出结果 守恒 / 边界 / 量纲 ③ 对照试验(用最小改动验证假设) 换算法:迭代 → 直接法 判断是否算法适用性问题 改参数:步长减半 / 初值 判断是否离散或初值敏感 缩规模:小算例复现 判断是否规模/结构导致 定位根因 → 修复 → 回归验证

三、工具链概览:让中间量“看得见”

排障的效率很大程度上取决于你能否低成本地观察中间量。常用工具可分为四层。第一层是语言级调试与断言:在 Python 中用 numpy.seterr(all='raise') 让除零和溢出直接抛异常,而不是静默产生 NaN;在 C/C++ 中用 assert(std::isfinite(x)) 守住关键变量。第二层是数值诊断函数:numpy.linalg.cond 查看条件数,numpy.isnan/isinf 扫描数组,scipy.sparse.linalg 的残差监控。第三层是日志与可视化:把每步残差、步长、目标函数值写入日志并画收敛曲线,震荡与发散一眼可辨。第四层是性能与并行工具:cProfile、perf、线程检查工具,用于定位性能异常和并行不一致。

一个实用习惯是:在迭代循环里固定打印 iter, residual, step, x_norm 四个量。残差单调下降说明收敛正常;残差先降后升往往意味着步长过大或矩阵条件恶化;残差在 1e-3 附近平台化,可能是容差设置不合理或存在舍入误差下限。

四、45例学习路径:按“异常类型”而非“算法名称”组织

本课程不按算法罗列,而是按异常类型组织案例,因为排障时你面对的是现象,不是算法名。整体路径分为五个阶段:基础诊断(非法值、条件数、容差设置)、线性系统(病态矩阵、预条件、稀疏结构)、非线性与优化(雅可比奇异、线搜索失败、局部极小)、常微分与时间推进(刚性、步长控制、稳定性)、综合复盘(并行一致性、精度与性能权衡)。每个案例都遵循“现象描述—最小复现—插桩观察—对照验证—修复与回归”的闭环,你可以把它当作排障模板直接套用。

建议在学习每个案例时,先只看“现象描述”,自己尝试判断属于哪一类异常、该在哪个环节插桩,再对照案例的排查路径。这种“先猜后看”的方式能显著提升排障直觉。

五、本节小结与行动建议

数值排障的核心不是记住某个算法的坑,而是建立“归类—插桩—对照—回归”的稳定思维链。面对一个异常,先问三个问题:它是非法值、不收敛、结果失真还是性能异常?它从计算链的哪一段开始出现?用哪个最小改动能验证我的假设?把这三个问题问清楚,大多数看似玄学的数值问题都会收敛到明确的根因。接下来的44个案例,就是这条思维链在不同场景下的反复演练。