ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

牛顿迭代法详解:原理、失效分析、Python实现与工程优化

牛顿迭代法详解:原理、失效分析、Python实现与工程优化 1. 从一次数值发散事故说起去年年底我在处理一个光学系统设计项目需要反解一个包含菲涅耳系数的非线性方程。理论上有多种迭代方案可选我随手写了牛顿迭代初值拍了个大概。结果迭代直接原地起飞前几步还算正常到第四轮残差反而增长了三个数量级最后干脆NaN。当时第一反应是代码写错了但查来查去逻辑没问题最后才发现是初值处在函数凹凸性反转的区间牛顿法直接走进了死胡同。那是我第一次真正觉得牛顿迭代这个看起来无非就是切线逼近但用不好切线会把你带到沟里去。也是从那以后我花了不少时间系统梳理这个基础算法的原理、边界和救火手段。这篇文章不是教科书复读是我在实际工程里反复打磨过后的一点心得重点放在为什么它会失效和失效了该怎么救上顺便给出一套我日常用着很顺手的套路化方案。文章适合这几类读者数值计算刚入门、只会照着公式敲代码但没仔细想过背后机制的人写过几次牛顿迭代但被发散布警告搞过头的人以及想把牛顿迭代从能用调到稳稳能用的工程技术人员。后文会讲到原理、代码、失效分析和实操技巧不涉及复杂数学推导但会把该说的话说透。2. 切线逼近的直观逻辑与隐藏假设2.1 从猜答案开始牛顿迭代的几何含义先回到最朴素的场景你要求解方程f(x) 0。直接解不出来不要紧可以先猜一个解x₀然后在(x₀, f(x₀))处画一条切线让这条切线与横轴相交交点的横坐标就是新的猜测值x₁。再从(x₁, f(x₁))开始画切线、求交点……如此循环只要运气不太差这个数列会一路逼近真正的解。听起来非常直观对吧我给学生讲的时候也喜欢用这个版本因为它避开了公式的吓人感。但工程里光有几何直觉是不够的你还需要清楚这条切线到底承载了哪些信息。切线的斜率是函数在该点的导数切线与横轴的交点等于在 用线性函数局部替身原函数 之后求这个替身的零点。换句话说牛顿迭代的每一步都在做一件事在局部用一个简单函数近似原函数然后精确求解这个近似版本。这种用简单模型替代复杂模型的思路后来我发现在机器学习里也复用得很多本质上就是局部线性化的思想。你每一轮不是在追求一步到位而是带着上一次的信息重新审视当前位置并对之前的偏差做出修正。这也是它和不动点迭代、二分法在气质上最不一样的地方牛顿法带着很强的方向感。2.2 为什么二次收敛泰勒展开给出的线索要说清楚牛顿迭代为什么收敛那么快得借助泰勒公式。假设目标解是x*在当前猜测值xₙ附近做一阶泰勒展开忽略高阶项可以写成f(x) ≈ f(xₙ) f(xₙ)(x - xₙ)。令右边等于零求出的x就是xₙ₊₁ xₙ - f(xₙ) / f(xₙ)。如果把上一轮的真实误差设为eₙ xₙ - x*同样对f(x*)做泰勒展开并把牛顿迭代公式代进去在经过一番化简后你会得到一个重要结论eₙ₊₁ ≈ [f(x*) / (2f(x*))] * eₙ²。直觉看就是这一轮的误差大致等于上一轮误差的平方。平方这个事最有意思如果误差已经小于1那么平方之后误差会急剧缩小。比如上一轮误差是 0.01下一轮大约变成 0.001 甚至更小误差位数近似翻倍。这就是二次收敛这个名字的由来也是为什么牛顿法一通百通之后速度会快得离谱。我以前在求解三维重建里的重投影误差方程时通常五六次迭代就能从初值误差 0.1 压到 1e-12换成割线法可能需要二十多次。不过这里必须强调这个收敛速度有一个前提就是你的初始猜测在解的某个邻域之内而且二阶导数在这个邻域里不会过于嚣张。如果初始猜测离解太远那上面的推导就完全不适用了后续我会专门讲这个坑。2.3 公式推导中用到的条件回过头来看牛顿迭代每一步能走通依赖三个隐藏条件缺一个都可能导致整个流程跑飞这三点平时教科书很少单独拎出来讲。第一函数在当前点必须可导而且导数不能为零。如果导数刚好是零切线是水平的它跟横轴要么不相交要么重叠迭代公式直接就是除以零的节奏。第二目标解必须是单根。如果是重根比如f(x) (x - a)²那么解的位置不仅函数值为零导数也为零前面的二次收敛会退化成一阶收敛虽然依旧能收敛但速度骤减。第三函数在解附近的行为要足够规矩一阶和二阶导数不能变化得太剧烈否则你用切线代表整段函数近似效果会很差。我习惯每接一个新方程先把函数曲线画出来肉眼观察根附近有没有极值点、突变点、渐近线再做牛顿迭代。这个习惯帮我省掉了大量查 bug 的时间。3. Python 实现牛顿迭代一套顺手的最小骨架3.1 基础版函数、导数、循环直接给一个我平时用得最多、也推荐给同事的最小实现。它接收目标函数、导函数、初值、容差和最大迭代次数返回解和迭代历史方便出问题时排查。def newton(f, df, x0, tol1e-10, max_iter100): 标量牛顿迭代 f: 目标函数 df: 导函数 x0: 初值 tol: 变量变化的容差 max_iter: 最大迭代次数 返回: (解, 迭代历史列表) x x0 history [x] for _ in range(max_iter): fx f(x) dfx df(x) if abs(dfx) 1e-14: raise ValueError(f导数接近零无法继续迭代当前 x {x}) x_new x - fx / dfx history.append(x_new) if abs(x_new - x) tol: return x_new, history x x_new raise RuntimeError(f超过最大迭代次数仍未收敛最后值为 {x})这个版本做了两件比公式本身更工程化的事一是对导数接近零的情况主动抛异常而不是让 Python 给你一个ZeroDivisionError或者给你一个飘到天边的数二是用变量变化量小于容差作为终止条件我实测下来对绝大多数场景比用函数绝对值小于容差更稳定原因后面会说。3.2 一个计算根号的栗子举一个处处能验证的例子用牛顿迭代计算sqrt(2)。求解的方程是f(x) x² - 2 0导数是f(x) 2x。迭代公式可以手写为xₙ₊₁ (xₙ 2 / xₙ) / 2——这就是著名的巴比伦方法很多没有学过数值分析的人其实也用过它。f lambda x: x**2 - 2 df lambda x: 2*x root, hist newton(f, df, x01.5, tol1e-12, max_iter50) print(root) # 1.4142135623730951 print(len(hist), 次迭代)输出结果是 1.4142135623730951只迭代了 5 次就到了双精度浮点数的极限。如果从 1.5 出发各步误差大致是 0.0858 → 0.0026 → 0.0000023 → 1.2e-12每一步误差的位数差不多都在翻倍这就是二次收敛的直观体现。3.3 数值微分怎么选步长现实中很多函数的导数解析式不好求甚至根本没法求这时候就要用数值微分。我平时的经验法则是用中心差分(f(x h) - f(x - h)) / 2h步长选h eps^(1/3) * max(1, |x|)其中eps是机器精度大约 2.2e-16。这样算下来h大约在 1e-5 量级。选步长的逻辑是太大则截断误差大太小则浮点舍入误差大存在一个最优平衡点。中心差分比前向差分更准因为它的截断误差是O(h²)而非O(h)代价只是多一次函数求值工程上完全值得。需要注意如果你的函数本身计算量很大比如求解 PDE 离散化后的残差那么每次迭代多一次求值可能就很疼这时可以考虑用前向差分配合自动微分的库或者直接用下面的割线法替代。写到这里想起一个常见错误有人用数值微分直接嵌入牛顿迭代步长始终取 1e-8这在 x 很小的时候误差巨大更合理的做法是结合当前 x 的尺度调整 h。这一点在我后来处理跨越多个数量级的物理量时尤其明显因为物理量动不动就是 1e-12 到 1e8 的跨度。3.4 终止条件到底该怎么选这是个特别值得展开的细节。很多入门教程会用abs(f(x)) tol作为退出条件但实际工程里这个条件有很大的隐患。假设函数值本身很大比如f(x) 1e6 * (x - 3)那么f(x) 1e-10可能需要 x 精确到 1e-16 才能达到这在浮点数里几乎不可能反过来如果函数本身被缩放得很小比如f(x) 1e-10 * (x - 3)你随便猜一个 x2.5函数值就是 5e-11 小于容差迭代直接假收敛退出。所以我的默认做法是用变量步长|x_new - x|作为主终止条件用残差|f(x)|作为辅助参考。如果两者都小那基本可以放心结果有效。如果变量步长已经小了但残差还很大那可能是函数本身很陡、或者导数值非常大这时候要检查一下是初值太差还是目标方程本身条件不好。另外有一种场合必须用残差作为终止条件当你要求的不是高精度解而是函数值足够小就够用时。比如嵌入在更大仿真循环里的内部迭代不需要压到 1e-12 那么狠只要残差压到 1e-4 就能继续后续计算。这种时候再死磕变量步长纯属浪费算力。4. 收敛性分析什么情况会翻车什么情况能救4.1 初值选择的危险区间前面说牛顿迭代在解的附近收敛快但附近两个字是魔鬼。我拿经典的f(x) x³ - x做过系统性测试这个函数有三个根-1、0、1。从不同初值出发你会看到完全不同的结局。当初值取 0.5 时迭代一路收敛到 1取 -0.6 时收敛到 -1但当取 0.3 时它会先去到某个较小的数然后在 -1 和 1 之间反复横跳最后震荡甚至直接逼近 0。很多人会以为f(x) x³ - x这种光滑函数随便给个初值都能收敛到某个根但实际上一小片区域会让牛顿法陷入周期震荡根本停不下来。我把几个代表性初值的行为整理成了下面这张表方便直观感受初值x₀迭代行为最终结果3.0快速单调下降收敛到 10.5逐步逼近收敛到 10.3在 -1 和 1 之间震荡周期循环不收敛-0.5逐步逼近收敛到 -1-0.3在 -1 和 1 之间震荡周期循环不收敛1.0 / -1.0直接落在根上立即收敛0.0导数恰好为零崩溃所以说给牛顿法选初值不能拍脑袋。我现在的习惯是简单函数先画个图复杂函数先用一个全局法比如网格搜索跑几轮找到大概区域再用牛顿法精确落根。多花这几分钟时间比之后排查发散省太多事。4.2 导数零点附近的灾难现场这是一个例子。考虑f(x) x³根是x 0但导数f(x) 3x²在根处也是 0。代入牛顿公式xₙ₊₁ xₙ - xₙ³ / (3xₙ²) xₙ - xₙ/3 (2/3)xₙ。你会发现每次迭代以后数值缩小到原来的 2/3收敛是收敛但是线性收敛每轮只能缩小三分之一和二次收敛差着十万八千里。更危险的情况是导数在迭代过程中接近零但不在根上比如f(x) x² 1在实数域没有根初值取 0.1 时第一次迭代就会跳到0.1 - (0.011)/(0.2) -4.95。再迭代一次直接变成 48 附近然后数值一路狂飙很快溢出。针对这种情况我的建议很简单检测abs(dfx)小于某个阈值时立即切换策略。可以退一步用二分法或割线法或者给当前迭代步加一个阻尼系数也就是用部分步长而非完整牛顿步。后面会专门讲阻尼怎么做。4.3 复数域内的分形行为如果你有成像或者信号处理背景很可能把牛顿迭代直接放到复数域去用会遇到一种看起来很漂亮但很烦人的现象。著名的牛顿分形就是在复平面上给方程z³ - 1 0做牛顿迭代时不同初始点收敛到三个不同根的边界是无限复杂的分形结构。两个初值相差微小最终的收敛目标可能天差地别。这个现象给工程带来的实际教训是如果你的自变量本质上是复的或者你的问题域存在多个根的竞争牛顿迭代的结果可能会对初值高度敏感。我在做数字全息重建的时候需要对每个像素点的复数场相位求解一个非线性方程有些像素相位跳变剧烈用同一套初值策略就会出现奇怪的孤立坏点——后来排查发现就是落到了分形边界附近。处理办法是把初值选得离已知物理解更近或者减少每一步的步长让迭代路径更慢但更稳。这比上什么高级技巧都管用。4.4 重根与病态函数工程里容易碰到一整类病态问题目标解是重根。典型例子就是f(x) (x - 1)⁵根在 1。这个函数在根附近极其平坦牛顿迭代会走得很慢而且由于浮点误差接近根时函数值和导数值都几乎为零你很难判断当前值到底有多接近解。处理重根有两个常用套路一是改用求重根的修正格式把迭代公式改为xₙ₊₁ xₙ - m * f(xₙ) / f(xₙ)其中m是根的重数。这样可以把退化的一阶收敛拉回二阶。但是重数m往往不事先知道你可以用m ≈ f(xₙ)^2 / (f(xₙ))^2做一个滑动估计另一个套路是直接对函数做变换转而求解u(x) f(x) / f(x)的零点因为u(x)的根是单根即使原来f的重根处理起来会顺畅不少。5. 把牛顿迭代用到极限多维牛顿法5.1 从标量到向量雅可比矩阵的角色多维情形其实很自然。你要解一组非线性方程组F(x) 0其中x是 n 维向量F是 n 维向量值函数。对应的迭代公式是xₙ₊₁ xₙ - J(xₙ)⁻¹ F(xₙ)其中J是雅可比矩阵它包含了每一个方程对每一个变量的偏导数。初看这个公式你可能会觉得这跟标量情形长得几乎一样就是做个矩阵求逆而已。但工程实现里千万不要真的去求逆那样又慢又不稳。正确做法是把它改写成线性方程组J(xₙ) Δxₙ -F(xₙ)然后用高斯消元或者 LU 分解解出Δxₙ再更新xₙ₊₁ xₙ Δxₙ。这和你手算线性方程组是一个道理但避免了显式求逆带来的数值灾难。5.2 数值雅可比的构造技巧多维情况下解析雅可比通常很难求尤其当你的方程组来自一个大规模仿真器时基本上只能靠数值差分。标准做法是对每一列分别做中心差分J[:, j] ≈ (F(x h eⱼ) - F(x - h eⱼ)) / (2h)其中eⱼ是第 j 个标准基向量。这样做会带来一个矛盾计算雅可比矩阵需要 2n 次函数求值当 n 很大时成本很高。所以实际工程中如果解同一个问题很多次或者雅可比变化不剧烈通常不会每一轮都重新计算雅可比而是几轮之后重算一次中间用近似更新公式这个方法就是后面会提到的拟牛顿。我最早写多维牛顿代码的时候老老实实每轮都求一遍雅可比结果 80% 的时间都花在求导上后来改成每 5 轮更新一次雅可比总耗时直接砍掉一半还多收敛次数略有增加但完全可接受。5.3 线路搜索与阻尼牛顿法多维牛顿法比标量情况更容易发散因为每一步的方向是 n 维的稍有不对就会窜到未知区域。我强烈建议所有多维场景都配上线路搜索也就是阻尼牛顿法在每一轮求出的方向Δxₙ上不要全量走而是找个步长α ∈ (0,1]使得||F(xₙ αΔxₙ)||比上一步有所下降。可以用简单的回溯法从α 1开始如果不满足下降条件就把α减半直到满足为止或者衰减到某个下限值。这个思路其实跟深度学习里的学习率调度很像。常说牛顿法是二阶方法具有二次收敛的潜力但进入更深的非线性区域时你不确定前方是什么地形先试探性走一小步比大步闯进去稳得多。我处理一个三维匹配场定位问题的时候加了线路搜索之后收敛率从经常失败变成每次必成代价仅是每轮多算几次残差。6. 实战工程场景与替代方案抉择6.1 求解隐含波动率一个金融工程里的经典场景期权定价模型里给定市场价反推隐含波动率本质上就是求BlackScholes(σ) - 市场价 0的根。这个函数对σ单调而且导数可以直接求出来是牛顿迭代的绝佳应用场景。我写出过这个函数和它的导数vega初值取 0.2通常三四轮就能收敛到 1e-10 以内。但有一个非常隐蔽的坑当期权深度虚值或者深度实值时vega 可能小到 1e-8 甚至更小这时牛顿迭代会算出非常夸张的Δσ甚至直接把波动率打穿到负数。我的解决方案是给迭代步加一个上限比如每步最大变化不超过 0.5如果 vega 太小就改用二分法在[0.01, 5]区间搜索反而又快又稳。这个规律可以推广到任何导数在有效区间内出现过小值的场景与其在牛顿法里死磕不如划定一个合理上下界用二分法做保底。混合策略是最稳的。6.2 视觉 SLAM 和机器人领域的牛顿法在视觉 SLAM 里捆绑调整优化通常用高斯-牛顿法或其变种 Levenberg-Marquardt。很多搞深度学习的人第一次听到高斯-牛顿会以为是什么新东西其实它就是在牛顿法的框架里把海森矩阵用雅可比的外积来近似专门用来处理最小二乘问题。如果你手里是min Σ ||rᵢ(x)||²这类目标函数那高斯-牛顿是首选因为你的问题是天然的最小二乘结构你不需要求二阶导数用一阶雅可比就能搭出近似的二阶信息。这里有一段我之前写的高斯牛顿最小二乘骨架供参考import numpy as np def gauss_newton(residual, jacobian, x0, tol1e-10, max_iter50): x x0.copy() for _ in range(max_iter): r residual(x) J jacobian(x) # 求解正规方程 (J^T J) delta -J^T r A J.T J b -J.T r try: delta np.linalg.solve(A, b) except np.linalg.LinAlgError: # 奇异时加正则化 A_reg A 1e-8 * np.eye(A.shape[0]) delta np.linalg.solve(A_reg, b) x_new x delta if np.linalg.norm(delta) tol: return x_new x x_new return x代码里注意一点当J^T J奇异或接近奇异时直接求解会报错或者得到大得离谱的步长。这时候加一小块单位阵做正则化实际就是在朝 Levenberg-Marquardt 的方向靠。在机器人和三维视觉里这个正则化参数就是所谓的阻尼因子它决定了你是更信任高斯-牛顿的快速收敛还是更信任梯度下降的稳定性。6.3 割线法与二分法什么时候更值得用总有人来问我既然牛顿法收敛快是不是所有求根问题都应该用它答案是大大的否定。割线法不需要求导数用最近两轮的函数值来近似导数收敛阶是黄金比例约 1.618。它的好处是每轮成本只有一次函数求值牛顿法至少一次函数求值加一次导数求值所以总效率在某些场景反而更高。如果函数每次求值都很贵比如来自大型仿真器那么割线法通常优于牛顿法。二分法就更朴素了只需要知道函数在区间两端异号然后不断折半。它保证收敛只是线性速度慢每一轮只能把误差缩半。可它的稳定性是所有方法里最好的永远不担心发散。在我处理那些极度不光滑、甚至带噪声的测量数据时我反而最常用带区间约束的二分法。究其原因是确定性的保证比很快但不保证更有工程价值。6.4 牛顿迭代和不动点迭代的联系顺带一提牛顿迭代本身也是不动点迭代的一种特例。把迭代公式写成xₙ₊₁ g(xₙ)其中g(x) x - f(x)/f(x)那么解x*恰好是g的不动点。从不动点理论的视角看收敛条件要求|g(x*)| 1。你可以直接算出g(x*) 0这正好再次解释了为什么牛顿法能有二次收敛的潜力——它的迭代函数在根处的导数等于零这是不动点迭代中极高阶的体现。有时候把问题放到不动点框架里想能带来不少启发。比如你在设计一个等效的迭代格式时如果发现|g(x)|在解附近接近甚至超过 1就可以预判这个格式会发散而不必真的跑一轮看结果。7. 调试牛顿迭代的实操清单7.1 从发散到定位问题的五步流程我每次牛顿迭代出了问题会按一套固定流程排查省掉了大量反复试错的时间这里直接共享出来。第一步把迭代全过程的(xₙ, f(xₙ), f(xₙ))打印或者存下来先看趋势。如果函数值在前几轮还能下降但突然反向暴涨说明已经进入危险区域。第二步检查导数序列导数是否出现过小值、零值或者符号剧烈振荡。第三步把函数曲线画出来标注迭代轨迹看看每一步是不是在跨过极值点或者渐近线。第四步换个初值如果行为完全不可复现那基本就是初值敏感问题。第五步用二分法或网格搜索验证目标区间里确实存在根并且根的性态和你设想的一致——这一步看着蠢但特别能排除方程本身建模出了问题这种情况远比怀疑算法值得先确认。7.2 为什么函数缩放会影响迭代行为一个容易被忽视的细节是函数本身的量纲。假设你要求解的方程来源于某种工程设计变量x是温度量级 300K残差f(x)的量级却是 1e-8那么当你用abs(f(x)) 1e-10作为终止条件时可能迭代还没开始就满足了。反过来残差量级是 1e8那即使变量已经非常接近真实解残差仍然大得惊人循环只能空转死在最大迭代数上。我处理过仿真数据里一个 ev 量级的物理量和另一个 1e-20 量级的物理量耦合在一起的方程组当时花了整整一下午在反直觉的不收敛上最终才发现是残差量级不一致导致的。规范和统一的做法是在建模阶段就做好量纲归一化把变量和残差都缩放到O(1)附近。这不仅让数据更稳定也让迭代终止条件的判断靠谱很多。7.3 是否需要每次重新求雅可比多维场景还有个小实践不用每轮更新雅可比。如果问题比较顺滑可以每 3-5 轮重算一次雅可比中间轮次继续沿用旧矩阵做线性求解。这种停顿式更新虽然会让收敛阶从二次降到超线性但每次迭代的成本直线下降整体算下来通常更快而且对噪声的容忍度也更好。这种方式和 Broyden 类的拟牛顿方法是同一个思路只是在更新频率上做了更朴素的取舍。我的建议是先用每轮完整更新的牛顿法跑通问题确认收敛性和精度然后如果性能是瓶颈再实验性地降低雅可比更新频率观察迭代次数的变化找到一个性价比最高的节奏。8. 从牛顿迭代延伸出去的几个技术方向8.1 拟牛顿法BFGS 和 L-BFGS 的核心思想当问题规模变大计算雅可比或者海森矩阵的代价高到难以接受时拟牛顿法才真正显示威力。以 BFGS 为例它不直接计算海森矩阵而是利用迭代过程中梯度差和信息差来逐步逼近海森矩阵的逆。也就是说它把求导的昂贵开销变成了一次便宜的递推更新。L-BFGS 是 BFGS 的内存友好版适合变量维度很高、没法显式存储矩阵的场景机器学习里大量用到。第一次接触时有一个点容易想不通既然矩阵是近似的凭什么还能收敛答案是只要近似的矩阵足够保持目标函数的下降方向迭代并不需要精确的二阶信息就能稳步前进。这就像你不需要看清楚每一步路只需要大方向对照样能走到终点。8.2 自动微分与牛顿迭代的结合传统求导方式有解析、数值和符号三种各有短板。解析求导很多时候函数太复杂数值求导有步长选择烦恼符号求导会遇到表达式爆炸。自动微分则是把复杂函数拆成基本算子利用链式法则精确得到导数值计算精度高速度也快。如果你用的是 JAX、PyTorch 这类框架求雅可比就是顺手的事。我自己在做一个场反演问题时残差函数光解析推导就花了三个小时表达式快有三米长结果算出来还有错后来改用自动微分一分钟搞定还不会出错。牛顿迭代之所以在现代编程环境中变得更香很大程度上归功于自动微分把求导成本降了一个数量级。8.3 多解问题与全局收敛策略默认的牛顿迭代是局部收敛它自己不知道全域还有几个解。如果你的问题有多解并且解之间有实际意义区别建议先做粗粒度的扫描把所有可能存在解的大致区域找出来然后分别用牛顿法定位。这个方法听起来简单却是我在工程上最常用的套路。还有一种策略是同伦延拓把原方程逐渐从简单问题过渡到目标问题比如构造F(t, x) t f(x) (1-t) g(x)其中g(x)是你知道根的简单函数。从t0出发逐步增加t到 1每一步都用上一步的解做初值。这个方法在处理强非线性问题时表现非常出色虽然代价是要解一串子问题但每一段都相对温和总体风险比从远初值直接轰牛顿法小得多。9. 对初值和参数调优的个人习惯我踩过太多初值相关的坑现在已经总结出一套固定的初值策略分享给读者参考。先看问题维度一维简单问题先画函数曲线确认解在哪个区间多维问题先做几十个随机初值的预迭代挑一个残差最低的作为正式初值。如果物理背景给得出合理估计直接用它比如透镜焦距就在几十毫米量级、期权隐含波动率大概率在 0.1 到 0.5 之间直接给区间中值比乱猜稳得多。另外我习惯在正式迭代之前先打印几个关键对象初始残差、初始导数的模长、函数值绝对值是否大于 1e6通过这些特征快速判断入手的难度。如果初始残差就有 1e100那一遍遍调牛顿参数大多没有意义基本可以确定方程建模或者量纲出了问题回去检查模型更靠谱。这个习惯帮我避免了大量无效的调参循环。还有一个我认为所有数值计算的人都应该养成的好习惯所有迭代终止后的结果一定要回代原方程检验残差。不要因为迭代收敛了就默认结果是可靠的。我曾经碰到过一次残差收敛到 1e-12 但结果是另一支根的情况虽然数学上没有错但对我的工程需求来说它就是错的。回代检验是最后一道防线几行代码的代价却可能避免一次严重的后续失误。
返回列表