1. 项目概述:从“差不多”到“最精确”的数学桥梁

做数据分析、工程建模或者搞科研的朋友,对“拟合”这个词一定不陌生。我们手里有一堆实验数据点,横七竖八地散落在坐标图上,心里却总想找到一条光滑的曲线,能最好地描述这些点背后的规律。这个寻找最佳曲线的过程,就是拟合。而“最小二乘法”,就是实现这个目标最经典、最强大的数学工具,没有之一。它的核心思想直白又深刻:找到一条曲线,让所有数据点到这条曲线的垂直距离(即误差)的平方和最小。这个“平方和最小”就是“最小二乘”名字的由来,它巧妙地避免了正负误差相互抵消,让最终的拟合结果在数学上最优、最稳定。

你可能会问,拟合函数不就是一次函数(直线)吗?那可就小看它了。实际工作中,数据背后的关系千变万化。可能是简单的线性增长(一次函数),可能是存在加速或减速趋势(多项式函数),也可能是先快速增长后趋于平缓(指数函数),甚至是周期性波动(三角函数)。 最小二乘法 就像一个万能适配器,它能根据你的数据和选择的函数形式,自动计算出最优的参数,让这条预设的曲线以“最亲密”的姿态穿过你的数据点云。

最近在技术社区里,关于最小二乘法的讨论热度一直不减,尤其是结合Python、Origin等工具的具体实现。大家关心的不再是抽象理论,而是“我这个非线性方程该怎么拟合?”、“Origin里自定义复杂函数老报错怎么办?”、“用递推法处理实时数据流稳不稳?”。这说明,最小二乘法早已从教科书里的数学公式,变成了工程师和科学家手中解决实际问题的“瑞士军刀”。今天,我就结合自己多年折腾数据的经验,抛开复杂的数学推导,重点聊聊几种最常用拟合函数(线性、多项式、非线性)在用最小二乘法实现时的核心思路、实操要点,以及那些容易踩坑的细节。无论你是用Python的NumPy/SciPy,还是用Origin、MATLAB,甚至是自己手搓算法,这里的原理和注意事项都是相通的。

2. 核心思路解析:为什么是“二乘”以及如何“最小”

在深入具体函数之前,我们必须先吃透最小二乘法的“灵魂”。它不是魔法,其背后是一套严谨的数学优化逻辑。

2.1 误差衡量:平方和的智慧

假设我们有n个数据点 (x_i, y_i) ,我们用一个函数 f(x, β) 来拟合它,其中 β 代表所有待求参数(比如直线 y = ax + b 里的 a b )。每个点的误差(或称残差)就是 e_i = y_i - f(x_i, β)

为什么要把误差平方后再求和(即最小化 Σ(e_i)² ),而不是直接最小化误差的代数和 Σe_i 或者绝对值和 Σ|e_i|

  1. 数学处理友好 :平方函数处处可导,光滑连续,这为我们使用强大的微积分工具求极值提供了可能。而绝对值函数在零点不可导,处理起来麻烦得多。
  2. 惩罚大误差 :平方操作会放大较大误差的影响。这意味着拟合曲线会极力避免远离数据群的“离群点”,从而使曲线更贴合大多数数据点所在的主流趋势。这通常符合我们对“最佳”的直观感受。
  3. 统计基础 :在误差服从正态分布的假设下,最小二乘估计等价于最大似然估计,这意味着它在统计意义上是最优的。

注意 :正是平方操作对离群点敏感这一特性,是一把双刃剑。当你的数据中存在明显的、非正常的“坏点”时,最小二乘法的拟合结果可能会被严重拉偏。这时可能需要考虑更稳健的拟合方法,如最小绝对值法。

2.2 求解本质:解一个方程组

最小二乘法的求解过程,可以归结为寻找一组参数 β ,使得误差平方和 S(β) = Σ[y_i - f(x_i, β)]² 达到最小。根据微积分,函数取极值的必要条件是它对各个参数的偏导数等于零。

这就导出了一组方程,称为 正规方程 。对于不同的拟合函数 f ,正规方程的形式不同。

  • 对于线性函数 f(x) = ax + b 。正规方程是一个关于 a b 二元一次线性方程组 ,可以直接用消元法求解,这也是最简单的情况。
  • 对于多项式函数 f(x) = a0 + a1*x + a2*x² + ... + am*x^m 。其正规方程是一个关于系数 a0, a1, ..., am 线性方程组 。虽然未知数多了,但方程仍然是线性的,可以通过求解线性代数方程组(例如使用高斯消元法或矩阵运算)得到唯一解。
  • 对于非线性函数 :例如 f(x) = a * exp(b*x) f(x) = a / (1 + b*x) 。这时误差平方和 S(β) 对参数 β 的偏导数方程不再是线性方程。正规方程变成了一个 非线性方程组 ,通常无法直接求出解析解。

这里是一个关键分水岭 :线性和多项式拟合(参数以线性形式出现在函数中)可以通过解线性方程组精确、一次性地求解,称为 线性最小二乘 。而非线性拟合则必须依赖迭代优化算法(如梯度下降法、高斯-牛顿法、Levenberg-Marquardt算法)来数值逼近最优解,称为 非线性最小二乘 。后者计算更复杂,对初始值敏感,且不一定能找到全局最优解。

2.3 模型选择:没有最好,只有最合适

选择哪种拟合函数,是应用最小二乘法前的首要决策,这取决于:

  1. 数据散点图形态 :这是最直观的依据。先画图,观察数据点的分布趋势。
  2. 物理/业务背景 :数据背后是否有已知的理论模型?例如,衰减过程可能对应指数函数,生长过程可能符合逻辑函数。
  3. 奥卡姆剃刀原则 :在拟合效果相近的情况下,优先选择形式更简单、参数更少的模型。过度复杂的模型(如过高次多项式)虽然对现有数据拟合得“天衣无缝”(过拟合),但预测新数据的能力往往很差。

3. 线性与多项式拟合:解方程的艺术

线性拟合是入门,多项式拟合是其自然延伸,它们共享“线性最小二乘”的核心求解框架。

3.1 一元线性拟合:一切的基础

模型: y = a*x + b 目标:求参数 a (斜率) 和 b (截距)。

推导与求解 : 误差平方和 S = Σ(y_i - a*x_i - b)² 。 分别对 a b 求偏导并令为零,得到正规方程:

Σ(y_i - a*x_i - b) * x_i = 0
Σ(y_i - a*x_i - b) = 0

整理后,可以得到 a b 的解析解:

a = (n*Σ(x_i*y_i) - Σx_i * Σy_i) / (n*Σ(x_i²) - (Σx_i)²)
b = (Σy_i - a*Σx_i) / n

其中 n 为数据点个数。

实操心得(以Python为例) : 虽然可以手算,但用NumPy向量化操作效率极高。

import numpy as np
# 假设 x, y 是数据数组
n = len(x)
sum_x = np.sum(x)
sum_y = np.sum(y)
sum_xy = np.sum(x * y)
sum_x2 = np.sum(x ** 2)

a = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2)
b = (sum_y - a * sum_x) / n

更简单直接的方法是使用 np.polyfit(x, y, 1) ,其中 1 代表1次多项式(即直线)。

注意 :计算分母 (n*Σ(x_i²) - (Σx_i)²) 时,如果x值非常集中或量级很大,可能导致数值计算上的“病态”问题,即结果对数据微小变化异常敏感。对于重要应用,可以考虑对x数据进行中心化(减去均值)处理。

3.2 多元线性拟合:从线到面

模型: y = β0 + β1*x1 + β2*x2 + ... + βp*xp 这用于研究一个因变量y与多个自变量x1, x2...之间的关系。

矩阵形式求解 : 这是理解线性最小二乘的关键。将模型写成矩阵形式 Y = Xβ + ε 。 其中,

  • Y 是 n×1 的观测值向量。
  • X 是 n×(p+1) 的设计矩阵,第一列常为1(对应截距β0),后面各列是自变量值。
  • β 是 (p+1)×1 的待求参数向量。
  • ε 是误差向量。

最小二乘的解为: β = (XᵀX)⁻¹ XᵀY 这个公式非常优美,它通过一次矩阵运算就能得到所有参数的最优估计。

实操要点

import numpy as np
# 假设有自变量x1, x2和因变量y
X = np.column_stack((np.ones_like(x1), x1, x2)) # 构造设计矩阵
beta = np.linalg.inv(X.T @ X) @ (X.T @ y) # @ 表示矩阵乘法
# 或者使用更数值稳定的np.linalg.lstsq
beta, residuals, rank, s = np.linalg.lstsq(X, y, rcond=None)

np.linalg.lstsq 是更推荐的方法,它使用奇异值分解等更稳定的数值算法,能有效处理 XᵀX 接近奇异矩阵的情况。

3.3 多项式拟合:万能逼近器

模型: y = a0 + a1*x + a2*x² + ... + am*x^m 多项式拟合可以看作是一种特殊的多元线性拟合,只不过自变量是 x, x², x³, ... 。因此,它完全可以用多元线性拟合的矩阵方法求解。

关键参数:阶数m的选择 这是多项式拟合的核心挑战。阶数太低,拟合不足,无法捕捉数据趋势;阶数太高,过拟合,曲线剧烈波动以穿过每一个点,丧失预测能力。

选择策略

  1. 可视化观察 :从低阶(如2,3)开始尝试,画图看曲线与数据的贴合程度。
  2. 交叉验证 :将数据分为训练集和验证集。用训练集拟合不同阶数的模型,在验证集上计算误差(如均方误差MSE),选择验证误差最小的阶数。
  3. 信息准则 :如AIC(赤池信息准则)或BIC(贝叶斯信息准则),它们在拟合优度和模型复杂度之间进行权衡。

Python实现

import numpy as np
import matplotlib.pyplot as plt

# 生成示例数据
x = np.linspace(-3, 3, 20)
y = np.exp(-x**2) + np.random.normal(0, 0.05, x.shape) # 加噪声的钟形曲线

# 尝试不同阶数
degrees = [2, 4, 8, 12]
plt.figure(figsize=(12, 8))
for i, deg in enumerate(degrees):
    coeffs = np.polyfit(x, y, deg) # 拟合多项式系数
    p = np.poly1d(coeffs) # 构造多项式函数
    y_pred = p(x) # 预测值

    plt.subplot(2, 2, i+1)
    plt.scatter(x, y, s=20, label='Data')
    x_fine = np.linspace(x.min(), x.max(), 200)
    plt.plot(x_fine, p(x_fine), 'r-', label=f'Degree {deg} Fit')
    plt.legend()
    plt.title(f'Polynomial Fit (Degree {deg})')
    # 计算训练集上的R²,仅作参考(不能用于最终阶数选择!)
    ss_res = np.sum((y - y_pred) ** 2)
    ss_tot = np.sum((y - np.mean(y)) ** 2)
    r2 = 1 - (ss_res / ss_tot)
    plt.text(0.05, 0.9, f'$R^2$ = {r2:.4f}', transform=plt.gca().transAxes)
plt.tight_layout()
plt.show()

从结果图可以清晰看到,阶数4的拟合已经很好,阶数8开始出现不必要的波动,阶数12则完全过拟合。

警告 np.polyfit 在高阶(如>15)时,直接使用幂函数基底 [1, x, x², ...] 可能导致设计矩阵 X 的条件数极高,产生严重的数值误差。对于高阶多项式拟合,应考虑使用正交多项式基底(如勒让德多项式、切比雪夫多项式),或使用 np.polynomial 模块下的类(如 np.polynomial.Polynomial.fit ),它们数值稳定性更好。

4. 非线性拟合:迭代寻优的挑战

当模型参数不以线性形式出现时,我们就进入了非线性最小二乘的领域。这是实际科研和工程中最常遇到,也最容易出问题的部分。

4.1 常见非线性模型举例

  1. 指数衰减/增长 y = a * exp(b*x) y = a * exp(-b*x) + c
  2. 幂律关系 y = a * x^b
  3. 饱和增长曲线(如米氏方程) y = (a*x) / (b + x)
  4. 高斯峰 y = a * exp(-((x-b)/c)²)
  5. 正弦波 y = a * sin(b*x + c) + d

4.2 核心算法:从梯度下降到L-M算法

非线性最小二乘问题没有通解,必须迭代。目标是找到参数 β ,最小化目标函数 S(β) = Σ[r_i(β)]² ,其中 r_i(β) = y_i - f(x_i, β) 是残差。

1. 梯度下降法 : 最直观的优化方法。参数沿着目标函数 S(β) 梯度(即下降最快)的方向更新: β_new = β_old - γ * ∇S(β_old) ,其中 γ 是学习率。

  • 优点 :概念简单,易于实现。
  • 缺点 :收敛速度慢,对学习率敏感,容易陷入局部极小值。在最小二乘问题中很少直接使用。

2. 高斯-牛顿法 : 专门为最小二乘问题设计的更高效方法。它利用目标函数的特殊结构,对残差函数 r(β) 进行一阶泰勒展开,将非线性问题在每一步迭代中近似为线性最小二乘问题来求解。 参数更新公式: Δβ = -(JᵀJ)⁻¹ Jᵀ r(β) ,其中 J 是残差 r 对参数 β 的雅可比矩阵(即一阶偏导数矩阵)。

  • 优点 :在初始值接近真值且问题接近线性时,收敛速度非常快(二阶收敛速度)。
  • 缺点 :需要计算雅可比矩阵。当 JᵀJ 接近奇异时,迭代会不稳定甚至发散。

3. Levenberg-Marquardt算法 : 可以说是非线性最小二乘拟合的“工业标准”。它本质上是高斯-牛顿法和梯度下降法的融合。通过引入一个阻尼因子 λ ,参数更新公式变为: Δβ = -(JᵀJ + λI)⁻¹ Jᵀ r(β)

  • λ 很大时, λI 占主导,算法退化为梯度下降法,步长小但稳定,保证能下降。
  • λ 很小时,算法接近高斯-牛顿法,步长大,收敛快。
  • 算法在每次迭代中根据拟合效果的改善情况动态调整 λ :如果误差减小,则减小 λ (更信任高斯-牛顿方向);如果误差增大,则增大 λ (更信任梯度下降方向)。
  • 优点 :兼具鲁棒性和收敛速度,是大多数科学计算软件(如SciPy, Origin)非线性拟合的默认或核心算法。

4.3 实操流程与关键陷阱(以SciPy为例)

使用 scipy.optimize.curve_fit 函数,它默认使用L-M算法。

import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt

# 1. 定义待拟合的非线性模型函数
def exp_decay(x, a, b, c):
    """指数衰减模型:y = a * exp(-b*x) + c"""
    return a * np.exp(-b * x) + c

# 2. 准备数据
x_data = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
y_data = np.array([10.2, 7.1, 4.8, 3.5, 2.6, 2.1, 1.7, 1.4, 1.2, 1.1])

# 3. 提供初始参数猜测 (p0) - 这是关键!
# 目测数据:起始值约10,衰减到约1,衰减速率中等。
p0 = [9, 0.5, 1]

# 4. 执行拟合
params, params_covariance = curve_fit(exp_decay, x_data, y_data, p0=p0)
a_fit, b_fit, c_fit = params
print(f"拟合参数: a = {a_fit:.3f}, b = {b_fit:.3f}, c = {c_fit:.3f}")

# 5. 计算拟合值并绘图
y_fit = exp_decay(x_data, a_fit, b_fit, c_fit)
plt.scatter(x_data, y_data, label='Original Data')
plt.plot(x_data, y_fit, 'r-', label=f'Fit: {a_fit:.2f}*exp(-{b_fit:.2f}*x)+{c_fit:.2f}')
plt.legend()
plt.show()

非线性拟合成功的关键——初始值 p0 : 这是新手最容易失败的地方。L-M算法是局部优化算法,如果初始值离全局最优解太远,它很可能收敛到一个错误的局部最优解,甚至无法收敛。

如何设置一个好的初始值?

  1. 物理意义法 :如果模型参数有物理意义,根据经验或数据粗略估计。例如,指数衰减的 c 通常是基线值,可以取y数据的最小值或尾部平均值。
  2. 图形估算法 :将模型函数画出来,手动调整参数,使曲线形状大致贴合数据点,此时的参数可作为初始值。
  3. 线性化法(谨慎使用) :对一些可线性化的模型,先通过线性回归得到粗略参数。例如,对 y = a*exp(b*x) 取对数得 ln(y) = ln(a) + b*x ,用线性拟合求 ln(a) b ,再转换回来作为初始值。 但要注意 ,这相当于对原始数据做了对数加权,得到的初始值可能有偏,且对于加常数项的模型(如 y=a*exp(b*x)+c )不适用。
  4. 网格搜索法 :如果参数范围大致可知,可以在一个粗糙的网格上计算误差,选择误差最小的点作为初始值。

4.4 Origin等图形化软件中的非线性拟合

像Origin这样的软件极大简化了操作,但原理相同,陷阱也相同。

操作流程

  1. 将数据录入工作表并绘图。
  2. 选择“分析”->“拟合”->“非线性曲线拟合”,打开NLFit对话框。
  3. 在“函数选择”页面,从内置类别(如指数、峰函数、生长模型)中选择,或进入“高级模式”自行定义函数。
  4. 关键步骤 :在“参数”页面,为每个参数输入初始值。软件通常会提供“自动参数初始化”的按钮,点击后它会根据数据范围给出一个猜测, 但这个猜测经常不准确,必须手动检查和调整
  5. 点击“拟合”按钮执行。

Origin自定义函数拟合常见问题

  • 语法错误 :在“函数定义”框中,必须使用Origin的脚本语言(类似C语言)正确定义函数。例如,指数衰减应写为 y = a * exp(-b * x) + c;
  • 初始值导致不收敛 :这是最常见的问题。拟合结果报告“未收敛”或卡在某个迭代次数。 解决方案 :回到参数页面,根据数据图手动调整初始值,使其更合理。有时需要将某个参数固定为一个合理的常数值,先拟合其他参数。
  • 拟合边界 :合理设置参数的上下限(如衰减常数b必须大于0),可以防止迭代跑到无意义的区域,增加收敛成功率。
  • 权重 :如果不同数据点的测量精度不同,可以指定权重(如误差棒),进行加权最小二乘拟合。

5. 递推最小二乘法:处理动态数据的利器

前面讨论的都是“批处理”最小二乘法,即一次性使用所有数据进行拟合。但在实时控制、信号处理、在线监测等场景,数据是源源不断到来的流式数据。我们希望在获得新数据点时,能快速更新模型参数,而不是每次都重新计算所有数据。这就是 递推最小二乘法 的用武之地。

5.1 核心思想:增量更新

递推最小二乘的核心公式,是在原有参数估计 β_old 和协方差矩阵 P_old 的基础上,利用新到来的数据点 (x_{new}, y_{new}) ,通过一套数学公式快速计算出更新后的 β_new P_new

基本递推公式(RLS算法)如下:

# 假设已有:β_old, P_old, 新数据点 (x_new, y_new)
# 1. 计算增益向量 K
K = P_old * x_new / (1 + x_new.T * P_old * x_new) # 对于标量y,分母是标量
# 2. 计算预测误差
error = y_new - x_new.T * β_old
# 3. 更新参数估计
β_new = β_old + K * error
# 4. 更新协方差矩阵
P_new = (I - K * x_new.T) * P_old

这里的 x_new 对于线性模型 y = βᵀx 来说是一个向量(包含常数项1)。 P 矩阵大致正比于参数估计误差的协方差矩阵的逆, K 是卡尔曼增益,决定了新数据对旧估计的修正力度。

5.2 应用场景与优势

  1. 自适应滤波 :在通信中实时消除噪声,参数 β 代表滤波器系数。
  2. 系统在线辨识 :实时估计动态系统(如机器人、化工过程)的模型参数。
  3. 金融时间序列预测 :在线更新预测模型的系数。
  4. 优势
    • 计算高效 :每次更新只涉及矩阵向量运算,复杂度远低于重新进行批处理拟合。
    • 内存友好 :无需存储历史数据,只维护当前参数状态。
    • 实时性 :能够立即反映系统的最新变化。

5.3 实现示例与遗忘因子

一个简单的Python实现示例如下(以一维线性模型 y = k*x + b 为例):

import numpy as np

class RecursiveLS:
    def __init__(self, n_params, delta=1000.0, lambda_=1.0):
        """
        n_params: 参数个数 (对于 y = kx + b, n_params=2)
        delta: 初始协方差矩阵 P = delta * I,delta是一个大数,表示初始不确定性大
        lambda_: 遗忘因子 (0 < lambda_ <= 1)。lambda_=1表示不忘却旧数据。
        """
        self.n = n_params
        self.lambda_ = lambda_  # 遗忘因子
        # 初始化参数向量和协方差矩阵
        self.theta = np.zeros(self.n)  # 参数估计,例如 [b, k]
        self.P = delta * np.eye(self.n)  # 协方差矩阵

    def update(self, x_vec, y):
        """
        x_vec: 输入特征向量 (对于 y = kx + b, x_vec = [1, x])
        y: 观测值
        """
        # 计算增益
        K_numerator = self.P @ x_vec
        K_denominator = self.lambda_ + x_vec.T @ self.P @ x_vec
        K = K_numerator / K_denominator

        # 计算先验误差
        error = y - x_vec.T @ self.theta

        # 更新参数估计
        self.theta = self.theta + K * error

        # 更新协方差矩阵 (带遗忘因子)
        self.P = (self.P - np.outer(K, x_vec.T @ self.P)) / self.lambda_

    def predict(self, x_vec):
        """根据当前参数进行预测"""
        return x_vec.T @ self.theta

# 使用示例
# 模拟在线数据流:真实模型 y = 2.5 * x + 1.0
true_k, true_b = 2.5, 1.0
rls = RecursiveLS(n_params=2, delta=1000, lambda_=0.98) # 使用遗忘因子

np.random.seed(42)
for i in range(100):
    x = np.random.rand() * 10
    y_true = true_k * x + true_b
    y_noisy = y_true + np.random.randn() * 0.5 # 加噪声

    # 构造特征向量
    x_vec = np.array([1.0, x])
    # 在线更新
    rls.update(x_vec, y_noisy)

    if i % 20 == 0:
        print(f"Step {i}: theta = {rls.theta}")

print(f"Final parameters: b={rls.theta[0]:.3f}, k={rls.theta[1]:.3f}")
print(f"True parameters:  b={true_b:.3f}, k={true_k:.3f}")

遗忘因子 lambda_ 的作用

  • lambda_ = 1 :标准RLS,所有历史数据被平等对待。适用于静态系统。
  • 0 < lambda_ < 1 :旧数据的权重会随时间指数衰减。这适用于时变系统,能让算法“忘记”过去,更快地跟踪系统当前的变化。 lambda_ 越接近0,遗忘越快,跟踪能力越强,但对噪声也越敏感。

实操心得 :递推最小二乘在理论上是无偏的,但实际应用中,初始值 theta delta 的选择、遗忘因子的设定都会影响收敛速度和稳态性能。通常需要一段“预热”数据后,参数估计才会稳定。对于时变剧烈的系统,可能需要结合更复杂的自适应算法。

6. 评估、陷阱与高级话题

拟合完成不是终点,评估拟合质量、理解局限性至关重要。

6.1 拟合优度评估指标

  1. 残差分析 :绘制残差 e_i = y_i - y_pred_i 关于 x_i y_pred_i 的散点图。
    • 理想情况 :残差随机、均匀地分布在0线上下,无明显模式。
    • 如果残差呈现曲线趋势 :说明模型函数形式选择不当,未能捕捉数据中的某种非线性关系。
    • 如果残差离散度随x增大而增大(漏斗形) :存在异方差性,可能需要进行变量变换(如取对数)或使用加权最小二乘。
  2. 决定系数 R² R² = 1 - SS_res / SS_tot 。表示模型解释的数据变异性的比例。越接近1越好。
    • 注意 :对于非线性拟合,R²的定义和解释与线性模型不同,有时甚至可能为负。更可靠的指标是看残差平方和 SS_res 的绝对值。
  3. 调整后的R² Adj-R² = 1 - [(1-R²)*(n-1)/(n-p-1)] ,其中p是参数个数。它惩罚了模型复杂度,用于比较不同参数数量的模型,比普通R²更公平。
  4. 均方根误差 RMSE RMSE = sqrt(SS_res / n) 。它与y有相同的量纲,直观表示平均预测误差的大小。
  5. 参数置信区间 :通过协方差矩阵可以估算参数的置信区间。如果区间包含0,则该参数可能不显著。 scipy.optimize.curve_fit 返回的 params_covariance 对角线元素的平方根近似为标准误差。

6.2 常见陷阱与解决方案

陷阱 现象 可能原因 解决方案
过拟合 模型在训练数据上R²极高,但对新数据预测误差大。多项式拟合中曲线“扭动”剧烈。 模型过于复杂(如多项式阶数过高),学习了数据中的噪声。 1. 简化模型(降低阶数)。
2. 增加数据量。
3. 使用正则化(岭回归、LASSO)。
4. 交叉验证选择模型。
欠拟合 模型在训练数据上R²就很低,残差有明显趋势。 模型过于简单,无法捕捉数据内在关系。 1. 尝试更复杂的模型(如从线性到多项式或非线性)。
2. 检查是否遗漏重要自变量。
不收敛 (非线性拟合) 软件报错“未达到收敛标准”或迭代停止。 1. 初始参数值 p0 太差。
2. 模型函数定义有误(如除零)。
3. 数据量太少或噪声太大。
1. 精心设置初始值 (见4.3节)。
2. 检查并修正模型函数。
3. 尝试不同的算法(如 curve_fit 中设置 method='trf' 'dogbox' )。
4. 为参数设置合理的上下界 ( bounds )。
结果对初始值敏感 每次用不同的初始值拟合,得到截然不同的参数。 目标函数存在多个局部极小值。 1. 使用全局优化算法(如差分进化、模拟退火)先粗搜,再用L-M法精修。
2. 多次随机初始值进行拟合,选择残差最小的结果。
异方差性 残差图呈现漏斗形、扇形等离散度不均的形状。 误差方差随x或y变化。 1. 对y进行变换(如对数变换、平方根变换)。
2. 使用加权最小二乘,权重取为误差方差估计的倒数。

6.3 加权最小二乘:当误差并不相等

标准最小二乘假设所有数据点的误差方差相同(同方差)。如果某些点测量更精确(误差小),而另一些点测量粗糙(误差大),我们希望对精确的数据点赋予更大的权重。这就是加权最小二乘。

目标函数变为最小化加权误差平方和: S = Σ [w_i * (y_i - f(x_i, β))²] 其中 w_i 是权重,通常取为测量误差方差 σ_i² 的倒数: w_i = 1 / σ_i²

scipy.optimize.curve_fit 中,通过 sigma 参数传入各点的误差标准差,并设置 absolute_sigma=True ,函数内部会自动进行加权。

# 假设每个y_data都有对应的测量误差标准差 y_err
params, params_cov = curve_fit(exp_decay, x_data, y_data, p0=p0, sigma=y_err, absolute_sigma=True)

np.polyfit 中,使用 w 参数指定权重。

weights = 1.0 / (y_err ** 2) # 权重为方差的倒数
coeffs = np.polyfit(x_data, y_data, deg=2, w=weights)

6.4 稳健回归:对抗离群点

最小二乘对离群点非常敏感。一个严重的离群点可能将拟合直线“拉”偏。稳健回归方法通过修改目标函数,降低离群点的影响。

  • 最小绝对值法(L1回归) :最小化 Σ|y_i - f(x_i, β)| 。对离群点不敏感,但求解更复杂(线性规划)。
  • Huber损失、Tukey双权损失等 :这些损失函数在误差小时类似平方损失,误差大时类似绝对值损失,从而兼顾效率和稳健性。

在Python中,可以使用 statsmodels 库或 sklearn.linear_model 中的 RANSACRegressor HuberRegressor 等。

7. 工具链选择与实战建议

最后,聊聊工具选择和个人实战中的一些体会。

Python (NumPy/SciPy/scikit-learn/statsmodels)

  • 优势 :极其灵活,可编程性强,适合自动化、复杂流程和算法定制。库生态系统丰富。
  • 场景 :研究原型、数据分析流水线、需要复杂自定义模型或评估流程时。
  • 推荐组合 :常规拟合用 np.polyfit (线性/多项式) 和 scipy.optimize.curve_fit (非线性)。稳健回归用 sklearn statsmodels 。递推算法需自己实现或找专门库。

Origin / MATLAB / Mathematica

  • 优势 :交互式图形界面强大,拟合过程可视化好,内置模型库非常全面(尤其是Origin),出图美观,适合探索性数据分析。
  • 场景 :实验数据处理、快速验证模型、生成用于报告和论文的图表。
  • 心得 :这类工具在设置初始值和理解错误信息方面对用户更友好,但自动化程度不如代码。Origin的自定义函数功能非常实用。

个人建议

  1. 永远先绘图 :拟合前,务必绘制 y~x 的散点图,这是选择模型形式最直接的依据。
  2. 从简单开始 :先尝试线性模型,不行再尝试低阶多项式,最后考虑非线性模型。复杂的模型不一定更好。
  3. 重视初始值 :对于非线性拟合,花在思考和试验初始值上的时间,可能比运行拟合本身多得多,但这是值得的。
  4. 评估重于拟合 :不要只看R²。仔细分析残差图,检查模型假设是否合理。用预留的测试集或交叉验证来评估模型的泛化能力。
  5. 理解物理意义 :如果参数有物理意义,检查拟合结果是否在合理范围内。一个物理上不可能的参数值(如负的衰变常数),即使数学上拟合再好,模型也可能是错误的。

最小二乘法是一个深不见底的工具箱,从最简单的直线拟合到复杂的非线性系统辨识,其核心思想一以贯之。掌握它,意味着你掌握了从杂乱数据中提取清晰信号的基本功。希望这篇长文能帮你绕过我当年踩过的那些坑,更高效地让数据开口说话。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐