牛顿法 · 切线的力量,与它翻车的时候

牛顿法顺着切线滑向根,每步让正确位数翻倍——二次收敛,快得惊人。这一章手写它、打印「位数翻倍」的收敛表,也诚实展示它翻车的三种方式:导数为零、跑飞、来回震荡。快与险,是它的一体两面。

作者 David更新于 第 6 篇(共 21 篇)

牛顿法 · 切线的力量,与它翻车的时候

二分法慢,因为它只看 ff 的符号。牛顿法(Newton's method)快,因为它还用了 ff 的斜率——顺着切线滑,一步到位得多。

思想:沿切线滑到 x 轴

在当前猜测 xnx_n 处,画一条 ff 的切线,看这条切线和 x 轴的交点,把它当作下一个、更好的猜测 xn+1x_{n+1}。切线方程一解,就得到牛顿法的迭代公式:

xn+1=xn−f(xn)f′(xn)x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}

直觉很美:如果 ff 在根附近接近一条直线,那么「沿切线滑到 x 轴」几乎就直接命中根了。

手写 + 「位数翻倍」的收敛表

还是求 2\sqrt{2}(f(x)=x2−2f(x)=x^2-2,f′(x)=2xf'(x)=2x):

def newton(f, df, x0, tol=1e-14, maxiter=50):
    x = x0
    for _ in range(maxiter):
        fx = f(x)
        if abs(fx) < tol:
            return x
        x = x - fx / df(x)          # 沿切线滑
    return x

import math
root = newton(lambda x: x*x - 2, lambda x: 2*x, x0=1.0)
print(root, abs(root - math.sqrt(2)))

打印每步的误差,和二分法对比,差距惊人:

step   x_n                       误差 |x_n - √2|
0      1.00000000000000          4.1e-01
1      1.50000000000000          8.6e-02
2      1.41666666666667          2.5e-03
3      1.41421568627451          2.1e-06
4      1.41421356237469          1.6e-12
5      1.41421356237310          0.0

看误差:0.41 → 0.086 → 0.0025 → 2e-6 → 1.6e-12——每一步大致是上一步的平方。 正确位数 1→2→5→111\to2\to5\to11 大致翻倍。这叫二次收敛(quadratic convergence):

en+1≈C en2e_{n+1} \approx C\, e_n^{2}

二分法砍 40 刀才到的精度,牛顿法5 步就到了。一旦进入根附近,它快得没道理。

但它会翻车

牛顿法的快,是有代价的——它不像二分法那样保证收敛。三种经典翻车方式:

① 导数为零:除以 0。 公式里除以 f′(xn)f'(x_n),一旦迭代点撞上 f′(x)=0f'(x)=0(函数的水平切线),就除零崩溃,或切线几乎水平、把下一点甩到十万八千里。

② 起点不好:跑飞或跑到别的根。 牛顿法对初值敏感。起点离根太远、或函数有多个根 / 拐点,切线可能把你导向发散,或收敛到一个你没想要的根。

③ 来回震荡:陷入循环。 某些函数(如 f(x)=x3−2x+2f(x)=x^3-2x+2 从 x0=0x_0=0 出发)会让牛顿迭代在两个点之间无限来回跳,永远不收敛。

用代码亲眼看一次发散——对 f(x)=arctan⁡(x)f(x)=\arctan(x) 从一个稍远的起点出发:

# arctan 的牛顿迭代,起点稍远就发散
f  = lambda x: math.atan(x)
df = lambda x: 1 / (1 + x*x)
x = 2.0
for i in range(6):
    x = x - f(x) / df(x)
    print(f"step {i+1}: x = {x:.4f}")
# step 1: x =  -3.5357
# step 2: x =   13.95
# step 3: x = -279.34    ← 越跑越远,发散

切线把 xx 一步步甩得越来越远——牛顿法飞了。同样的方法,起点 x0=1x_0=1 时几步收敛,x0=2x_0=2 时直接发散。快和险,是一体两面。

实用建议:防翻车

工程上很少「裸用」牛顿法,而是给它加保险:

  • 限制步长 / 加阻尼:如果一步跳得太远(比如 ∣xn+1−xn∣|x_{n+1}-x_n| 反而变大),就把步长砍半,别让它飞。
  • 和二分法混用(safeguarded Newton):牛顿步若跳出了「夹住根的区间」,就退回去做一步稳的二分。这样既有牛顿的快、又有二分的绝不失手——鱼和熊掌兼得。成熟的库(scipy.optimize.brentq 等)内部就是这类混合方法。
  • 不想手算导数:用下一章的割线法——它用差商近似导数,不需要 f′f',收敛也几乎和牛顿一样快。

口诀:牛顿法快,但要「牵着绳子放」——给它兜底,别让它裸奔。

小结与下一章

牛顿法沿切线滑向根,xn+1=xn−f(xn)/f′(xn)x_{n+1}=x_n-f(x_n)/f'(x_n),二次收敛——每步正确位数翻倍,快得惊人。但它不保证收敛:导数为零会崩、起点不好会跑飞、某些函数会震荡。工程上要加保险(限步长、和二分混用、或用割线法免导数)。

牛顿是「二次收敛」,二分是「线性收敛」——这个「收敛阶」到底怎么定义、怎么测?还有一个「不用导数、却快过二分」的割线法,收敛阶是有趣的黄金比 1.6181.618。下一章把收敛阶这把尺子讲清。👉 不动点与割线法:收敛阶怎么量。

参考文献

本页目录