最小二乘拟合10种案例详解:从直线到非线性回归的Python实现

📅 2026/8/27 3:15:49
最小二乘拟合10种案例详解:从直线到非线性回归的Python实现
简介最小二乘法是数据分析与科学计算中最基础也最常用的参数估计方法通过最小化误差平方和来寻找数据背后的规律。它的原理简洁却衍生出丰富的模型形态从普通线性回归到多项式、指数、高斯、正弦等非线性拟合都能在统一框架下实现。理解斜率与截距的求解本质、掌握模型选择与参数初始化策略是工程实践中避免过拟合和收敛失败的关键。加权最小二乘、迭代优化与残差分析等技巧也让该方法在处理异方差数据、信号提取与传感器标定等场景中更加稳健。借助Python中numpy与scipy工具库可以快速完成从数据清洗、模型拟合到效果评估的全流程。本文围绕最小二乘拟合在数据回归与曲线拟合中的10种典型应用展开结合代码示例帮助你在真实项目中少走弯路。 直接把最小二乘拟合玩明白其实不需要啃完一本数值分析教材。我这些年做数据分析、信号处理、实验数据处理前前后后把最小二乘的各个变种用了个遍从最朴素的直线拟合到非线性拟合、加权拟合、鲁棒拟合踩过不少坑也攒下了一些能直接抄作业的经验。这篇文章不打算讲高深推导就围绕“最小二乘拟合的10种案例”展开把每个案例的适用场景、模型设计、Python实现、注意事项拆开揉碎讲清楚。无论你是刚接触数据分析的新手还是已经在处理实验数据的工程师、科研狗这10个案例应该都能对应到你的实际需求上。1. 模型设计思路先搞清楚你要拟合什么1.1 最小二乘到底在干什么最小二乘拟合的核心逻辑很朴素给定一组数据点找一条曲线让曲线和实际数据点之间的垂直距离的平方和最小。为什么用“平方”而不是简单地用距离之和因为平方放大了大误差的惩罚力度同时在数学上可以使目标函数处处可导方便用求导等于零来解最优参数。你把它理解成“找一条最折中的数据曲线”本质上就是在做优化。实际使用中我把最小二乘看成一个三部分组成的流程模型假设、参数求解、结果验证。模型假设决定你用什么样的函数去拟合是直线还是指数还是高斯参数求解是让目标函数的偏导为零或者迭代到收敛结果验证则是看残差分布、判断系数R²、做交叉验证。很多人只盯着第二步觉得调个函数就完事事实上第一步和第三步才是真正的分水岭。1.2 怎么根据数据形状选拟合模型选模型本质上是在猜数据背后的物理规律或业务逻辑。数据的散点图如果是单调递增并且增速放缓多半考虑对数或者幂律如果是先增后减、单峰形状高斯或洛伦兹峰是首选如果是带有周期性那就绕不开正弦或傅里叶拟合如果只是简单的线性趋势直接上最小二乘直线拟合就够了。我自己的选型习惯是先画图再看三点趋势方向、弯曲方向、波动模式。如果肉眼都看不出规律不要急着上复杂模型先检查数据质量和采样范围。另外一个很重要的原则模型复杂度要和数据量匹配。只有20个点就别硬上五阶多项式那叫过拟合不叫拟合。1.3 手算最小二乘直线理解斜率与截距的来处这里快速过一遍最简单的情况。假设你有n组数据(x_i, y_i)要用 y ax b 拟合最小二乘的目标是让 ∑(y_i - ax_i - b)² 最小。对a和b分别求偏导并令其等于0整理后得到a [n∑(x_i y_i) - ∑x_i∑y_i] / [n∑(x_i²) - (∑x_i)²]b [∑y_i - a∑x_i] / n这两条公式看起来有点绕其实就是协方差除以方差斜率的本质是x和y的相关强度标准化截距则是让直线穿过数据点的质心。理解了这层关系后面再学加权拟合、多元拟合时会顺畅得多。Python里直接用 numpy.polyfit(x, y, 1) 就能得到a和b但公式自己推一遍有助于你理解更复杂的非线性场景。2. 十种拟合案例逐个拆解下面这10个案例是我从实际工作中筛选出来的覆盖了最常见的模型形态和数据处理场景。每个案例都给出模型形式、适用场景、Python代码和关键参数说明。2.1 直线拟合最简单的趋势判断与预测直线拟合是最基础但绝对不过时的案例。我经常用它做传感器标定或者判断某个指标是否符合线性增长预期。比如车间里测了一批电机的转速-电压数据想确认线性度是否合格直接用一次多项式拟合看残差是否随机分布在零附近。import numpy as np import matplotlib.pyplot as plt x np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) y np.array([1.2, 2.1, 2.8, 4.3, 5.1, 5.8, 7.0, 7.8, 8.9, 10.2]) # 一次多项式拟合返回系数从高次到低次 coeffs np.polyfit(x, y, 1) a, b coeffs y_fit a * x b print(f斜率: {a:.4f}, 截距: {b:.4f}) # 计算R² ss_res np.sum((y - y_fit) ** 2) ss_tot np.sum((y - np.mean(y)) ** 2) r_squared 1 - (ss_res / ss_tot) print(fR²: {r_squared:.4f}) # 可视化 plt.scatter(x, y, label原始数据) plt.plot(x, y_fit, colorred, label拟合直线) plt.legend() plt.show()这里 R² 接近1说明拟合优度高但是要注意R²对异常值非常敏感。我有一次拟合结果R²0.95看似很好后来发现末端一个离群点把斜率拉偏了剔除后R²到了0.99斜率变化超过10%。所以直线拟合做完一定要看残差图残差如果呈现弯曲的形态说明这个关系本质上不是线性的你只是在强行画一条线。注意R²不是越高越好更不是应用最小二乘的唯一指标。一定要结合残差分布判断模型是否合适尤其是数据点较少的时候高阶拟合很容易在R²上“作弊”。2.2 多项式拟合标定曲线与系统校正多项式拟合是直线拟合的自然扩展我用得最多的场景是传感器标定。热电偶的电压-温度关系、压力传感器的输出-负载关系经常不是纯粹线性的这时候三阶多项式比直线拟合准得多。但多项式可不是阶数越高越好。我见过有人用九阶多项式去拟合十几个点曲线穿过每一个点R²等于1看起来完美实际上预测能力几乎为零。x np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) y np.array([0.8, 2.5, 4.2, 7.1, 9.3, 12.0, 14.5, 18.2, 21.0, 25.1, 28.6]) # 三阶多项式拟合 coeffs_3 np.polyfit(x, y, 3) y_fit_3 np.polyval(coeffs_3, x) # 五阶多项式拟合做对比 coeffs_5 np.polyfit(x, y, 5) y_fit_5 np.polyval(coeffs_5, x) print(f三阶系数: {coeffs_3}) print(f五阶系数: {coeffs_5})选择多项式阶数的一个实用经验是先用低阶开始逐次增加阶数观察每个阶数下参数的置信区间和预测误差。如果某次增加阶数后R²提升很小但曲线出现了明显的震荡说明已经过拟合了。常用的做法是把数据集分成训练集和验证集用训练集拟合用验证集检验真实误差这样比单纯看R²可靠得多。标定曲线这种东西讲究的是插值区间内的最小误差而不是光滑到失控的曲线。2.3 指数衰减拟合RC电路与信号衰减分析指数衰减在物理和工程领域太常见了电容放电、放射性衰变、噪声衰减。这类数据的特点是开始下降快后面越来越慢。模型形式是 y A·exp(-t/τ) C其中τ是时间常数、C是基线偏移。最小二乘拟合这类模型时如果直接用非线性拟合容易收敛失败所以我通常先取对数转化为直线拟合来获得初始值。from scipy.optimize import curve_fit t np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) y np.array([10.2, 6.8, 4.5, 3.1, 2.1, 1.5, 1.1, 0.8, 0.6, 0.45, 0.35]) def exp_decay(t, A, tau, C): return A * np.exp(-t / tau) C # 先通过对数转换得到初始猜测 # 对 y-C 取对数需要先估计C这里用最后一个点的值 C_init y[-1] y_log np.log(y - C_init 1e-9) slope, intercept np.polyfit(t, y_log, 1) A_init np.exp(intercept) tau_init -1.0 / slope popt, pcov curve_fit(exp_decay, t, y, p0[A_init, tau_init, C_init]) A, tau, C popt print(fA{A:.4f}, tau{tau:.4f}, C{C:.4f})初始化参数为什么重要因为非线性拟合本质上是在一个平滑但可能有多个极小值的曲面上搜索最小值如果初始值离全局最小值太远迭代很容易掉进局部陷阱。先取对数做线性化相当于先粗略地确定参数的大致范围再交给非线性优化去精细调整。那些直接上 curve_fit 不给 p0 然后报错的情况多半就是初始值给得太随意。2.4 高斯拟合峰形识别与光谱分析高斯拟合是光谱分析、质谱分析、色谱分析里绕不开的工具。一个单峰可以用高斯函数 y A·exp(-(x-μ)²/(2σ²)) b 来描述其中μ是峰位、σ是峰宽、A是峰高、b是基线。最小二乘拟合出来的参数不仅是画一条漂亮的曲线更重要的是从参数中提取物理意义μ告诉你峰值位置σ告诉你峰的展宽程度面积则与物质的含量成正比。x np.linspace(-5, 15, 100) y_true 8.0 * np.exp(-(x - 5.0)**2 / (2 * 1.5**2)) 0.5 y_noise y_true np.random.normal(0, 0.2, sizex.shape) def gaussian(x, A, mu, sigma, b): return A * np.exp(-(x - mu)**2 / (2 * sigma**2)) b # 初始值峰高用最大值减基线峰位用最大值位置峰宽用半高宽估 b_init np.percentile(y_noise, 5) A_init np.max(y_noise) - b_init mu_init x[np.argmax(y_noise)] # 半高宽初估找到大于一半峰高的x范围 half_max b_init A_init / 2 indices np.where(y_noise half_max)[0] if len(indices) 1: sigma_init (x[indices[-1]] - x[indices[0]]) / 2.355 # FWHM / 2.355 else: sigma_init 1.0 popt, _ curve_fit(gaussian, x, y_noise, p0[A_init, mu_init, sigma_init, b_init]) print(f峰位{popt[1]:.4f}, 峰高{popt[0]:.4f}, 峰宽σ{popt[2]:.4f}, 基线{popt[3]:.4f})高斯拟合常见的问题是双峰混叠两个距离很近的峰叠加在一起单高斯拟合的残差会呈现“高-低-高”的波浪形。这时候用双高斯模型才能分开。还有一点高斯拟合时如果基线b没拟合好峰高和峰面都会受影响所以数据预处理阶段做基线扣除是值得的。我习惯是先线性拟合一个大概的基线然后再做全参数拟合这样结果更稳定。2.5 正弦拟合周期信号的参数提取正弦拟合在处理振动数据、交流信号分析时非常好用。模型是 y A·sin(2πft φ) C需要拟合出振幅A、频率f、相位φ和直流偏置C。其中频率是最难拟合的参数因为目标函数在频率维度上有很多局部极小值给个不靠谱的初始频率很容易收敛到错误结果。from scipy.optimize import curve_fit t np.linspace(0, 2 * np.pi, 200) y_true 3.0 * np.sin(2 * np.pi * 1.2 * t 0.6) 0.5 y_noise y_true np.random.normal(0, 0.15, sizet.shape) def sine_wave(t, A, freq, phase, C): return A * np.sin(2 * np.pi * freq * t phase) C # 用FFT粗略估计频率作为初始值 from numpy.fft import fft, fftfreq y_fft fft(y_noise - np.mean(y_noise)) freqs fftfreq(len(t), t[1] - t[0]) magnitude np.abs(y_fft) positive_idx np.where(freqs 0)[0] peak_freq freqs[positive_idx[np.argmax(magnitude[positive_idx])]] print(fFFT估计频率: {peak_freq:.4f}) popt, _ curve_fit(sine_wave, t, y_noise, p0[2.0, peak_freq, 0.0, 0.0]) print(f振幅{popt[0]:.4f}, 频率{popt[1]:.4f}, 相位{popt[2]:.4f}, 偏置{popt[3]:.4f})先用FFT粗估频率再拟合这是常规操作能极大降低拟合的失败率。还有一个容易踩的坑是频率的单位要和采样时间匹配。如果t的单位是秒频率f就是Hz如果t是离散索引频率就是每样本的周期数需要格外注意尺度。正弦拟合的残差分析也很重要如果拟合后残差里仍然有周期性说明原始数据包含了多个频率分量单正弦模型不够用。2.6 幂律拟合物理定律与尺度关系的验证幂律关系 y a·x^b 在物理中很常见牛顿引力、开普勒定律、断裂力学裂纹扩展速率等都会出现。拟合时最简单的办法是对两边取对数ln y ln a b·ln x这样就变成了一次线性拟合b就是斜率a是恒量的指数幂。不过取对数会改变噪声的权重的分布如果原始噪声服从等方差高斯分布取对数后大值区间的噪声会被压缩、小值区间噪声相对放大这是对数变换的副作用。x np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) y np.array([1.9, 4.1, 7.8, 13.2, 20.1, 29.5, 40.8, 53.9, 69.2, 86.7]) log_x np.log(x) log_y np.log(y) # 对数域线性拟合 coeffs_log np.polyfit(log_x, log_y, 1) b coeffs_log[0] a np.exp(coeffs_log[1]) print(f幂指数b{b:.4f}, 系数a{a:.4f}) # 直接用非线性拟合对比 def power_law(x, a, b): return a * np.power(x, b) popt, _ curve_fit(power_law, x, y, p0[a, b]) print(f非线性拟合: a{popt[0]:.4f}, b{popt[1]:.4f})两种方法得到的结果通常有差异对数变换得到的参数更关注相对误差而非线性拟合更关注绝对误差。如果你关心的是小数值区域的拟合精度用非线性拟合如果你关心的是整个数量级范围内的趋势用对数变换更稳。幂律拟合最后一个坑当x变量包含0或负数时不能取对数要处理数据本身的范围或者改用带偏移的形式 y a·(xx0)^b。2.7 对数拟合收益递减与饱和趋势刻画对数拟合的形式是 y a·ln(x) b适合描述收益递减、学习曲线、饱和过程等常见场景。比如营销投入和转化率的关系、肥料用量与农作物产量的关系往往呈现先快速增长、后增速放缓的形态对数函数就能很好地刻画这种饱和趋势。拟合这种模型可以用 linearize 的思路直接令 X ln(x)然后做普通直线拟合。x np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20]) y np.array([2.1, 3.4, 4.2, 4.8, 5.3, 5.7, 6.0, 6.3, 6.5, 6.8, 7.2, 7.6, 8.0]) X np.log(x) a, b np.polyfit(X, y, 1) print(f对数拟合: y {a:.4f} * ln(x) {b:.4f}) y_fit a * np.log(x) b # 残差检查 residual y - y_fit print(f残差标准差: {np.std(residual):.4f})对数拟合的优势在于参数解释直观a表示x每增加一个因子e时y的变化量。但要注意对数拟合在x很小时的变化率极大如果数据范围包含很小的x值拟合结果容易被这些点牵着鼻子走。我的经验是如果数据在x较小区间的噪声较大最好对比一下去掉小x点后的拟合结果看看参数是否发生了显著变化以此判断拟合稳定性。2.8 多变量线性拟合多因素共同作用解析现实中很少有单一因素决定结果的情况更多时候是多变量共同影响。比如某个产品的销量可能取决于价格、广告投放量、季节因素等多个变量。多变量线性拟合的模型是 y β0 β1x1 β2x2 ... βpxp使用最小二乘求解系数向量β。代码上可以直接用 numpy.linalg.lstsq或者更常用的 sklearn.linear_model.LinearRegression。import numpy as np from sklearn.linear_model import LinearRegression # X的每一列是一个自变量X的第一列是常数项的占位若用lstsq X np.array([ [1, 2.0, 5.0], [1, 3.0, 6.0], [1, 4.0, 6.5], [1, 5.0, 7.0], [1, 6.0, 8.0], [1, 7.0, 9.5], ]) y np.array([10.5, 12.8, 14.0, 16.2, 19.0, 22.5]) # 使用 lstsq beta, res, rank, s np.linalg.lstsq(X, y, rcondNone) print(flstsq系数: {beta}) # 使用 sklearn注意它会自动加截距项 X_multi X[:, 1:] # 去掉常数项列 model LinearRegression().fit(X_multi, y) print(fsklearn系数: {model.coef_}, 截距: {model.intercept_})多变量拟合最容易被忽视的问题是多重共线性就是两个自变量之间存在较强的线性相关。比如温度和湿度往往负相关同时放进模型会导致系数的估计方差变大系数值很不稳定。判断共线性可以看各变量的相关系数矩阵或者计算方差膨胀因子VIF。如果VIF大于10就得考虑剔除变量、合并变量或者使用正则化方法。多元线性拟合做预测前也一定要检查残差是否随某个自变量呈现模式化变化否则说明漏掉了重要的交互项或非线性项。2.9 加权最小二乘处理异方差数据的正确姿势真实数据很少满足“每个点的噪声方差相同”这个假设。测量仪器在不同量程下的精度不同重复实验的次数不同导致每个数据点的可信度不同。这时候普通最小二乘把所有点一视同仁结果是高噪声的点也会拉着拟合曲线跑。加权最小二乘通过给每个数据点不同的权重来修正这个问题权重通常取1/σ²也就是噪声大的点权重低。x np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) y np.array([0.5, 1.8, 3.1, 4.0, 5.8, 6.9, 8.2, 9.5, 10.6, 12.1]) # 假设前5个点精度高后5个点噪声大 sigma np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.8, 0.8, 0.8, 0.8, 0.8]) w 1 / sigma**2 A np.vstack([x, np.ones_like(x)]).T # 加权最小二乘求解 W np.diag(w) beta_w np.linalg.inv(A.T W A) (A.T W y) print(f加权拟合斜率{beta_w[0]:.4f}, 截距{beta_w[1]:.4f}) # 对比普通最小二乘 beta_ols np.polyfit(x, y, 1) print(f普通拟合斜率{beta_ols[0]:.4f}, 截距{beta_ols[1]:.4f})加权最小二乘的一个关键前提是噪声方差已知或可以估计。如果不知道σ可以采用迭代的方式先用普通最小二乘拟合计算残差再用残差的平方作为方差的近似重新做加权拟合反复几次直到收敛。这种方法叫迭代加权最小二乘。在科学实验中如果你能记录每个数据点的测量标准差务必用加权版本因为结果会更可靠置信区间也更有意义。2.10 非线性最小二乘通用小钢炮前面几个案例其实都绕到了非线性拟合这里系统地讲一下它的核心逻辑。非线性最小二乘的目标同样是让残差平方和最小但参数和自变量之间的关系不是线性的所以不能直接求解析解需要迭代优化。curve_fit封装了Levenberg-Marquardt算法这是非线性最小二乘最常用的实现之一。它的核心思想是结合梯度下降和高斯-牛顿法的优点既能稳定收敛又能在接近最优解时加速。from scipy.optimize import curve_fit xdata np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) ydata np.array([0.8, 2.3, 3.8, 4.6, 5.1, 5.5, 5.7, 5.9]) def logistic(x, L, k, x0): Logistic生长曲线 return L / (1 np.exp(-k * (x - x0))) popt, pcov curve_fit(logistic, xdata, ydata, p0[6, 1, 2]) perr np.sqrt(np.diag(pcov)) print(fL{popt[0]:.4f}±{perr[0]:.4f}, k{popt[1]:.4f}±{perr[1]:.4f}, x0{popt[2]:.4f}±{perr[2]:.4f})使用 curve_fit 时p0参数是最关键的变量。除了凭经验猜测更系统的方法包括网格搜索初始值、全局优化算法配合局部优化、或者用差分进化算法先找大致区域再用精炼拟合。pcov返回的是参数协方差矩阵对角线开方就是参数的标准误差这个在论文和报告中非常有用能告诉读者你的拟合参数有多可信。实际项目中我至少会用两到三组不同的初始值做拟合确认收敛到同一个结果这样可以避免掉进局部最优。3. 拟合好坏的判断与坑位排查3.1 残差分析拟合质量的第一道检验拟合完之后第一件事不是看R²而是看残差图。残差是指原始数据y和拟合值y_fit之间的差值理想情况下残差应该随机分布在零附近没有明显的模式。如果残差呈现U形或倒U形说明你的模型缺了二次项如果残差在一段范围内持续为正、另一段持续为负说明模型的结构有问题或者存在漂移。我见过最典型的错判就是只看R²R²达到0.99但残差随自变量呈明显的正弦波动说明周期项没被捕捉。residuals y - y_fit plt.scatter(x, residuals) plt.axhline(y0, colorred, linestyle--) plt.xlabel(x) plt.ylabel(残差) plt.show()残差分析还可以用来发现异常值。如果某个点的残差明显大于其他点三个标准差以上优先检查这个数据点本身是否可靠而不是直接剔除。直接剔除异常值会让拟合结果看起来很好但也可能掩盖了真实的物理机制异常。比较安全的做法是对含异常值和不含异常值的两组结果做对比如果参数差异不大说明模型稳健如果差异很大说明这个点对拟合结果影响太大需要更谨慎地决定去留。3.2 过拟合与欠拟合的平衡技巧过拟合的本质是模型参数过多导致拟合曲线把噪声当成了信号。欠拟合则是模型太简单捕捉不到数据的真实趋势。我之前做过一组实验数据真实关系是三阶多项式但是用一阶拟合时残差里明显弯弯绕绕用二阶拟合时好了一些到三阶拟合时残差才变得随机。这里的关键技术是F检验或信息准则AIC/BIC来量化评价模型复杂度的增加是否值得。# 计算不同阶数多项式拟合的AIC def compute_aic(y_true, y_pred, num_params): n len(y_true) rss np.sum((y_true - y_pred) ** 2) return n * np.log(rss / n) 2 * num_params x np.linspace(0, 10, 50) y_true 0.5 * x**3 - 2 * x**2 x 1 y y_true np.random.normal(0, 5, sizex.shape) for order in range(1, 6): coeffs np.polyfit(x, y, order) y_pred np.polyval(coeffs, x) aic compute_aic(y, y_pred, order 1) print(f阶数{order}: AIC{aic:.2f})AIC越低表示在拟合优度和复杂度之间取得了更好的平衡。从1阶到5阶通常AIC会先下降再上升拐点对应最佳阶数。正则化是另一种抑制过拟合的常用手段比如岭回归在目标函数中加上参数平方和惩罚对处理多变量共线性场景很有效。实际项目中我还会用交叉验证来辅助判断把数据分成五份轮流取四份拟合、一份验证看平均预测误差。这种方法比AIC更直观也更贴合预测场景。3.3 初始值设置与收敛失败时的应对思路非线性拟合的收敛失败率并不低典型表现是curve_fit返回的协方差矩阵是inf或者拟合出的参数明显不合理。这时候我有一套标准的排查流程先检查数据本身有没有NaN或inf然后打印每次迭代的残差变化必要时画出拟合函数叠加在原始数据上看看是不是完全偏离。如果曲线与数据完全不沾边问题大概率出在初始值上。改善初始值的技巧包括全局搜索初始化、约束参数范围、问题分解。全局搜索可以用 scipy.optimize.differential_evolution 搭配 curve_fit约束参数范围用bounds参数问题分解则是先把模型拆成几个简单部分分别估计参数再合并。比如双指数衰减模型可以先拟合慢衰减部分获得慢速率再拟合快衰减部分获得快速率最后统一做全参数拟合。这样得到的初始值质量很高收敛成功率也会大幅提升。4. 实操工具箱这些代码值得放进自己的工具库4.1 封装一个通用拟合函数减少重复劳动实际做项目时会发现不同数据的拟合流程高度相似加载数据、选函数、初始化、拟合、画图、评估。与其每次写一遍流程不如封装成一个通用的函数。下面是我常写的fit_wrapper支持传入任意Python函数、数据、初始值和权重返回参数、误差和指标。from scipy.optimize import curve_fit import numpy as np def fit_curve(func, xdata, ydata, p0None, sigmaNone, bounds(-np.inf, np.inf)): 通用非线性最小二乘拟合封装 popt, pcov curve_fit(func, xdata, ydata, p0p0, sigmasigma, boundsbounds) perr np.sqrt(np.diag(pcov)) y_pred func(xdata, *popt) rss np.sum((ydata - y_pred) ** 2) n len(ydata) p len(popt) aic n * np.log(rss / n) 2 * p return { params: popt, errors: perr, y_pred: y_pred, rss: rss, aic: aic, }这个封装够用了项目中不用每次重新写标准差的代码。调用时只需要定义拟合函数然后传入数据即可。要扩展也能加入分位数残差、置信带计算等功能。我个人习惯把数据预处理、拟合、后处理分开写成三个模块这样改起来快排查问题也方便。4.2 数据预处理的几个关键习惯拟合前做数据清洗经常被忽略但恰恰是最影响拟合质量的一环。我总结了下常用的几个预处理步骤检查缺失值numpy数组中有NaN时曲线拟合会直接失败或者返回垃圾结果。去除明显异常值绘制箱线图或使用IQR规则筛选剔除非物理极值。数据标准化当自变量量级差异悬殊时将每列标准化到相同尺度能提升数值稳定性和收敛性。确保网格数据没有重复点重复点会导致某些权重算法不稳定可以取均值合并。数据量过少时谨慎使用复杂模型低于10个点不建议拟合超过3个参数的非线性模型。这些习惯看起来琐碎但至少有一半的拟合失败可以用“数据没洗干净”一个原因解释。省这些时间最后都会花在调试莫名其妙的结果上。5. 往深一层这些拟合案例之间的内在联系把10个案例放在一起看其实有一条隐形的线索最小二乘是一个统一框架区别只在于“模型函数的形式”和“求解方式”。线性直线、多项式、对数、幂律都可以通过变量变换或构造新变量转成线性最小二乘问题高斯、指数、正弦、多峰模型则需要非线性最小二乘求解。两者之间的桥梁是初始值估计——通过变换或FFT等手段先拿到一个不错的初解再进入非线性迭代精修。用矩阵视角看最小二乘本质上是一个投影问题把观测向量投影到模型函数张成的空间中使残差向量与模型空间正交。每条拟合曲线都是在一个特定函数空间中的投影。理解了这一点很多问题都能联系起来。比如为什么多项式拟合过拟合因为多项式空间在高阶时自由度太大为什么加权最小二乘因为不同维度的投影距离度量要按精度加权。这种统一的视角有助于在日常工作中快速判断某个问题能不能用最小二乘解决。如果你能把问题写成“预测值 f(x1, x2, ..., xn, θ1, θ2, ..., θk)”的形式且定义出可微的损失函数最小二乘的框架就可以套上去。6. 从最小二乘到更广阔的拟合世界搞定了最小二乘这套基本功之后有几个方向值得继续深挖。第一个是正则化方法比如岭回归、Lasso它们能在参数过多或共线性严重时帮你稳住系数估计代价是引入一点点偏差。第二个是鲁棒回归当数据存在大量离群点时普通最小二乘会被带偏Huber回归或RANSAC等鲁棒方法更可靠。第三个是贝叶斯拟合把参数当成随机变量拟合出一个概率分布而不仅仅是点估计在物理实验和金融风控领域特别有用。我自己在做过几次海量数据拟合后最大的体会是工具会不断迭代但“残差分析合理的模型假设稳健的参数估计”这个思想是不变的。无论将来碰到什么拟合需求回到这三条基本都能找到突破口。我再分享一个小技巧可能大多数人没注意到在做完拟合后把拟合参数和原始数据画在同一张图上同时画上置信带能一目了然地看到模型的不确定性区间。很多读者只看点线图觉得拟合很好一旦有置信带就会发现参数其实很不稳定。做汇报或写报告时这个图非常有说服力。最小二乘拟合这个工具箱你用熟练之后会觉得它像一把瑞士军刀。遇到线性趋势用直线遇到衰减用指数遇到单峰用高斯遇到周期用正弦。重要的不是知道这些函数而是知道在什么场景下选什么、怎么初始化、怎么判断结果。希望这10个案例能帮你省去一些弯路直接在真实数据上跑起来。本文还有配套的精品资源点击获取