样条与最小二乘 · 分段的智慧与超定拟合

样条用一段段低次多项式拼接,天然免疫 Runge 现象,是插值主力。而当数据带噪声,你压根不该穿过每个点——那是在拟合噪声,该用最小二乘去「近似」。这一章讲清这两件事,以及「插值 vs 拟合」的根本区别。

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

样条与最小二乘 · 分段的智慧与超定拟合

上一章的 Runge 现象告诉我们:一条高次多项式管全局,危险。这一章给出两个更实用的工具:样条(把插值做对)和最小二乘(处理带噪数据)。它们背后是一个关键区分——你的数据干净吗?

样条:一段段低次拼起来

样条(spline) 的思路:不用一条高次多项式,而是每相邻两点之间用一小段低次多项式(通常是三次),在连接点处拼接光滑。最常用的三次样条保证连接处二阶导连续(C2C^2),曲线肉眼看是完全光滑的。

import numpy as np
from scipy.interpolate import CubicSpline

xs = np.array([0., 1., 2., 3., 4., 5.])
ys = np.array([0., 1., 0., 1., 0., 1.])
cs = CubicSpline(xs, ys)

print(cs(2.5))        # 插值:x=2.5 处的值
print(cs(2.5, 1))     # 顺便还能求导数

样条的好处一箩筐:免疫 Runge(每段都是低次,不会疯狂震荡)、光滑、而且局部——改动一个数据点,只影响它附近几段,不像高次多项式牵一发而动全身。所以工程绘图、动画路径、CAD 曲线,插值的默认选择几乎都是样条。

关键区分:数据干净吗

现在问一个决定性的问题:你的数据点,是精确的,还是带噪声的? 这直接决定你该「插值」还是「拟合」:

数据带噪声时,千万别插值——插值会精确地拟合噪声。 想象你测了 50 个点,真实规律是一条直线 y=2x+1y=2x+1,但每个测量都有随机误差。如果你强行让曲线穿过全部 50 个点(插值),得到的会是一条为了迁就每一个噪声点而上蹿下跳的曲线——它完美「记住」了噪声,却完全没抓住「直线」这个真相。这在机器学习里叫过拟合(overfitting)。

正确做法:承认数据有噪声,用一个简单模型去「近似」它,而不是穿过它。 目标不是「零误差」,而是「抓住趋势、忽略噪声」——这就是最小二乘。

最小二乘:近似,而非穿过

最小二乘(least squares) 的目标:找一个(通常低阶的)模型,让它到所有数据点的残差平方和最小——不要求穿过任何点,只要求整体最贴合。

min⁡c ∑i(yi−m(xi;c))2\min_{c}\ \sum_{i} \big(y_i - m(x_i; c)\big)^2

因为数据点(50 个)远多于模型参数(直线只有 2 个),这是个超定方程组(方程比未知数多,无精确解),最小二乘给出「最接近」的解:

rng = np.random.default_rng(0)
x = np.linspace(0, 10, 50)
y = 2*x + 1 + rng.normal(0, 2, 50)        # 真实 y=2x+1,叠加噪声

a, b = np.polyfit(x, y, 1)                # 最小二乘拟合一条直线
print(f"拟合: y = {a:.2f}x + {b:.2f}")    # y ≈ 2.0x + 1.1 —— 从噪声里还原了趋势

50 个乱跳的点,拟合出的直线斜率约 22、截距约 11——准确抓住了藏在噪声底下的规律。这正是回归 / 曲线拟合的日常。

怎么解:别用正规方程

最小二乘在数学上可以化成正规方程 A⊤A c=A⊤yA^\top A\, c = A^\top y。但——

别直接解正规方程 A⊤Ac=A⊤yA^\top A c = A^\top y——A⊤AA^\top A 的条件数是 AA 的平方! 也就是说,如果 AA 的条件数是 10610^6,A⊤AA^\top A 就是 101210^{12},精度直接砍半还多。正确做法是用基于 QR 分解或 SVD 的求解器,它们不构造 A⊤AA^\top A、数值上稳得多:

A = np.vstack([x, np.ones_like(x)]).T
coef, *_ = np.linalg.lstsq(A, y, rcond=None)   # 内部走 SVD,稳

这又是一个「数学等价、数值不等价」的经典案例:正规方程理论上对,但数值上是个坑;np.linalg.lstsq(或 scipy.linalg.lstsq)才是正道。

插值 vs 拟合:一张表

插值(interpolation)拟合 / 回归(least squares)
是否穿过每个点穿过不穿过,只近似
适用数据干净、精确带噪声
模型自由度= 点数(高)远小于点数(低)
典型工具三次样条 CubicSplinenp.polyfit / lstsq
风险高次 → Runge阶太高 → 过拟合

一句话:点是精确的、要光滑穿过,用样条插值;点带噪声、要抓趋势,用最小二乘拟合。 分不清这个,是很多「曲线画得很怪」的根源。

小结与下一站

样条用分段低次多项式拼接(C2C^2 光滑、免疫 Runge、局部),是插值主力。最小二乘处理带噪数据——不穿过每点(那是拟合噪声 / 过拟合),而是最小化残差平方和抓趋势;求解别用正规方程(κ\kappa 平方),用 lstsq(QR/SVD)。核心是分清插值(干净数据、穿过)vs 拟合(带噪、近似)。

逼近这一站讲完。下一站回到微积分——但这次是它的数值版:导数、积分在计算机上怎么算。先看数值微分,以及它一个「天生不稳」的尴尬。👉 数值微分:有限差分,以及它为什么不稳。

参考文献

  • [1] Carl de Boor.《A Practical Guide to Splines》—— 样条权威专著。
  • [2] Trefethen & Bau.《Numerical Linear Algebra》, lec. 11, 18-19 —— 最小二乘与 QR/SVD。
  • [3] SciPy 文档.「interpolate.CubicSpline / linalg.lstsq.」https://docs.scipy.org/doc/scipy/ —— 样条与最小二乘实现。

本页目录