ARTICLE DETAIL

资讯详情

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

插值还是曲线拟合?从拉格朗日到梯度下降的选型指南

插值还是曲线拟合?从拉格朗日到梯度下降的选型指南 拿到一组横纵坐标想补中间值或者想从一堆乱糟糟的点里找趋势到底该用插值还是曲线拟合这个问题几乎每个做数据分析、数值计算的人都纠结过也是我这套系列文章里容易被问到的点。今天这篇就专门把“插值”和“曲线拟合”放在一起拆开讲包括手工可算的拉格朗日插值、基于最小二乘和梯度下降的拟合过程以及我在实际项目里踩过的一些坑。适合刚入门数值分析的同学也适合那些已经在用代码做拟合但说不出所以然的开发者。先说结论插值解决的是“必须经过所有已知点”的问题拟合解决的是“误差尽量小、但不一定经过任何已知点”的问题。很多人在这两者之间反复横跳是因为没想明白自己的数据到底从哪来、要拿曲线干什么。这篇文章会沿着判据、原理、实操、避坑四条线走希望能让你下次看到数据时第一眼就知道该上插值还是拟合。1. 先分清插值和拟合别一头扎进公式里1.1 插值是“精确穿过每一个样本点”的确定性恢复插值的中文说全一点是“插值法”英文叫 interpolation。它的使用场景很经典手里只有一张稀疏的数据表中间位置也有值但表里没写。比如实验测了室温随时间的五个采样点现在想知道第 3.5 分钟是多少度这就是插值问题。插值的基本假设是这些数据点高度可信并且点之间确实存在某个连续函数。于是我们构造一个函数让它严格通过所有已知点。最常见的是多项式插值n1 个点可以唯一确定一个不超过 n 次的多项式。因为次数可控、计算也成熟所以教科书几乎都从多项式插值讲起。但插值有个“隐含代价”它是确定性恢复不是统计推断。一旦某个点带有测量误差插值就会把这个误差当成真实规律保留下来相邻区间会受到感染。所以插值更适合表格化数据、标定数据、几何建模这类“已知点本身可信”的场景而不是传感器采集后直接扔过来的原始数据。1.2 拟合的目标是最小化误差不是通过所有点曲线拟合英文叫 curve fitting。它关注的不是“过不过点”而是“能不能代表整体规律”。数据通常来源于测量本身就携带噪声如果强行让一条高次多项式穿过每个带噪声的点得到的往往是一条剧烈扭动的曲线工程上完全没有预测价值。拟合的做法是从某个模型族中选一个函数让它与所有样本点的整体误差最小。最常见指标是均方误差也就是传说中的“最小二乘”。比如用一条直线 y wx b 去描述一百个点求一组 w 和 b让所有点到直线的垂直距离平方之和最小。拟合的结果是模型加参数的组合。模型可以是一条直线也可以是一条带有二次项、三次项的曲线甚至可以是一个神经网络。不管模型多复杂核心没变误差最小化。这也决定了后续所有的更新算法包括正规方程、梯度下降全部围绕着“最小化损失”展开。1.3 选型判断看这三个关键词实操里怎么选我通常看三个关键词噪声、点数、外推需求。如果已知点非常少、精度也非常高中间值必须严格落在某条连续规律上插值是首选。比如机械加工的标准曲线数据点是从图纸上精确读出来的插值出来再顺着取值完全没问题。如果数据量多、来源是测量或者统计中间有抖动拟合就更合适。比如商品价格趋势、传感器校准曲线、用户增长曲线谁也无法保证每个点都精确我们要的是“趋势”。第三个关键词是外推。所谓外推就是在已知范围之外预测值。插值从数学定义上就禁止外推超出采样区间多项式会以你想不到的速度发散。拟合虽然能外推但也只能依据模型假设出了样本范围同样不可靠。所以无论选哪个都要对“采样区间之外”保持足够的谨慎。2. 拉格朗日插值原理、手算与容易踩的坑2.1 为什么用拉格朗日而不是解线性方程组每个学过高等数学的人都知道多项式插值可以列线性方程组去解系数。比如用三个点求二次多项式 ax² bx c把三个点代进去得到包含 a、b、c 的三个方程解出来就行。这个方法抽象上没问题但实际写代码时矩阵条件数差、求解不稳定尤其点数一多手算几乎不可能。拉格朗日插值就是绕开方程组的一种显式构造法。它的思路很聪明为每个点构造一个“只在这一点上取 1在其它所有点上取 0”的基函数然后把所有基函数加权求和。权重就是对应点的纵坐标。基函数的公式长这样L_i(x) ∏_{j≠i} (x - x_j) / (x_i - x_j)不要怕这个连乘符号。它实际就是在算x 到其它所有点的距离的乘积除以目标点到其它所有点的距离的乘积。分子保证当 x 取到任意一个其它点 x_j 时连乘积中有 0 项整个 L_i 变为 0分母保证在 x_i 处整体恰好等于 1。最后插值多项式就是p(x) Σ y_i · L_i(x)这种构造的好处是逻辑非常直观代码实现也简单只需两层循环第一层选哪个点作为“当前重点”第二层做连乘。不需要解矩阵也不容易出现大规模线性方程组的数值问题。2.2 一个能自己算完的例子三点插值我讲个可以完全手算的经典例子数据点取 (0, 2)、(1, 3)、(2, 6)。先构造三个基函数。第一个点的基函数需要满足在 x0 时等于 1在 x1 和 x2 时等于 0。于是L_0(x) ((x-1)(x-2)) / ((0-1)(0-2)) ((x-1)(x-2)) / 2第二个点的基函数是L_1(x) ((x-0)(x-2)) / ((1-0)(1-2)) -x(x-2)第三个点的基函数是L_2(x) ((x-0)(x-1)) / ((2-0)(2-1)) x(x-1)/2把它们组合起来p(x) 2L_0(x) 3L_1(x) 6L_2(x)展开合并同类项p(x) (x² - 3x 2) (-3x² 6x) (3x² - 3x)整理后得到p(x) x² 2你可以验证一下x0 时等于 2x1 时等于 3x2 时等于 6完全吻合。这个例子说明拉格朗日公式虽然看起来是一个一个分式项但合并后会变成非常普通的多项式后续取值只需计算这个多项式即可不需要再动那些分式。这里有一个我特别想强调的点拉格朗日插值的结果是唯一的不会因为构造方式不同而改变。只要数据点相同高斯插值、牛顿插值、拉格朗日插值最终得到的是同一个多项式只是表达式形式不同。这意味着你用任何方法手算最后都应该归到同一个答案。如果结果对不上要不就是点代错要不就是函数写错。2.3 高阶插值不是越多越好Runge 现象我见过不少刚接触数值分析的人听到“多项式次数越高越精确”于是拿着几十个数据点去构造高次多项式。结果是中间或许正常可两端像发了疯一样上下乱跳越接近边界振荡越离谱。这个现象在数值分析里叫 Runge 现象最典型的例子是函数 f(x) 1/(125x²)在区间 [-1, 1] 上用等距节点做高次插值。节点数越多、次数越高插值结果在边界附近不但不收敛反而剧烈振荡。原因不复杂多项式虽然光滑但本质上是“全局函数”任何一个数据点的变化都会影响整条曲线的形状而等距节点在两端的信息不足边界误差就一路飙升。所以我对所有新人的建议都是能不用高次多项式插值就不用。如果只是要补中间值优先考虑分段插值或三次样条。拉格朗日插值适合用来理解插值思想适合在点很少、点数不超过十个左右时手算或在程序里做一个快速实现但玻璃后面要同时装着数据表一旦点数变多就改用其它插值方案。2.4 更稳的替代方案分段插值和三次样条日常项目里我更常用分段线性插值和三次样条插值。分段线性插值最朴实每两个相邻点之间用一条直线连接。它的优点是不会振荡、计算量小、代码一行就能写完缺点是整体曲线不光滑在已知点的位置会出现折角。如果对光滑性要求不高这足够了。三次样条插值则更精细在每个区间使用一个三次多项式同时保证节点处数值、一阶导数、二阶导数连续。最终得到一条光滑、连续、局部可控的曲线。它不会像全局高次多项式那样一点变动拖累全身也不会像分段直线那样留一堆棱角。实际工程里如果你确定要做插值我建议默认先选三次样条不建议直接上手一个 n 次多项式。3. 曲线拟合从最小二乘到梯度下降拟合3.1 为什么损失函数用平方误差拟合的第一步是定义一个损失函数。把平方误差和作为优化目标数学上写为J (1/m) Σ (y_i - f(x_i, θ))²其中 f(x_i, θ) 是模型在 x_i 处的预测值θ 是模型参数。为什么用平方而不是绝对值因为平方误差处处可导能直接套梯度下降等工具而且它对大误差的惩罚比小误差更重这符合“尽量压低明显偏离样本的预测”这一直觉。当然平方误差对离群点敏感如果数据里混了几个严重异常的点它们会主导整个拟合过程。这是代价不是 bug。数据清洗不干净就做最小二乘结果很容易被离群点带走。3.2 正规方程小规模线性拟合最快的解法如果模型是关于参数线性的最小二乘损失是二次函数有全局唯一最优解。这种情况下可以不解也可以用解析解。以直线拟合为例设模型 y wx b要最小化J (1/m) Σ (y_i - wx_i - b)²最优参数为w (m·Σx_iy_i - Σx_i·Σy_i) / (m·Σx_i² - (Σx_i)²)b (Σy_i - w·Σx_i) / m我第一次看这个公式觉得记不住后来发现它和“协方差比方差”是同一个意思。分子是 x 和 y 的协方差分母是 x 的方差w 就是两个变量之间的线性关联强度。这样理解之后再也不需要死背。用一组数据来试已知点为 (1, 2.3)、(2, 1.8)、(3, 3.5)、(4, 4.2)。先算出几个关键求和Σx 10Σy 11.8Σxy 33.2Σx² 30m 4。代入公式w (4×33.2 - 10×11.8) / (4×30 - 10²) (132.8 - 118) / (120 - 100) 14.8 / 20 0.74b (11.8 - 0.74×10) / 4 (11.8 - 7.4) / 4 1.1所以最优拟合直线是y 0.74x 1.1验证一下x1 时预测 1.84实际 2.3误差 0.46x4 时预测 4.06实际 4.2误差只有 0.14。这条直线诚实地反映了整体线性趋势但并没有强制经过任何已知点。3.3 梯度下降拟合从零开始更新参数正规方程虽然快但数据特征一多、样本量一上来求矩阵逆的成本和稳定性都会出问题。这时候迭代式优化就成了主要手段其中最基础也最通用的是梯度下降拟合。目标还是最小化损失 J。对直线模型求偏导∂J/∂w -(2/m) Σ x_i(y_i - wx_i - b)∂J/∂b -(2/m) Σ (y_i - wx_i - b)梯度告诉了我们参数该往哪个方向调整。梯度是负向误差增大的方向所以更新时减去梯度w w - α·∂J/∂wb b - α·∂J/∂bα 是学习率控制每一步迈多大。还是用刚才那组数据验证。初始设 w 0b 0学习率 α 0.01。第一轮迭代∂J/∂w -(2/4)×33.2 -16.6∂J/∂b -(2/4)×11.8 -5.9更新后w 0 - 0.01×(-16.6) 0.166b 0 - 0.01×(-5.9) 0.059一步之后预测值是 0.166x 0.059。初始损失大约是 9.605一步之后损失降到约 6.76明显下降。继续迭代参数会慢慢逼近正规方程得到的 0.74 和 1.1。如果你习惯写代码一个最基本的梯度下降拟合逻辑是这样的def fit_gradient_descent(x, y, lr0.01, epochs200): w 0.0 b 0.0 n len(x) for epoch in range(epochs): pred [w * xi b for xi in x] dw -2.0 / n * sum(xi * (yi - pred_i) for xi, yi, pred_i in zip(x, y, pred)) db -2.0 / n * sum(yi - pred_i for yi, pred_i in zip(y, pred)) w - lr * dw b - lr * db if epoch % 40 0: loss sum((yi - pred_i) ** 2 for yi, pred_i in zip(y, pred)) / n print(epoch, w , w, b , b, loss , loss) return w, b这个代码里没有用到任何第三方库纯粹展示梯度下降的更新过程。实际项目中你可以用 numpy 向量化实现但迭代原理是完全一样的。3.4 什么时候必须用梯度下降而不是解公式有人会问既然直线拟合有解析解为什么还要学梯度下降这个问题我年轻时候也想不通。直到后来开始做稍微复杂的模型才明白只要模型不是参数线性的比如拟合一个带指数项、对数项甚至神经网络激活函数的模型偏导方程解不出一个闭合公式正规方程根本没法写。这时唯一普适的路就是梯度下降。另外就算模型是线性可解析的特征维度高到几十万甚至百万级别构造和求逆一个巨大的矩阵也极不划算。迭代式优化可以配合随机样本在线更新每轮只要一小批数据内存压力小训练进度能看得见。梯度下降最大的变量是学习率 α。太大会让参数来回弹跳损失不降反升太小则收敛慢到让人失去耐心。我的习惯是先设 0.01看前几十轮损失是不是稳步下降。如果抖得厉害明显发散就把学习率降到 0.001如果下降太慢再适当调大。数据量级差异大的时候先把 x 归一化到差不多的范围可以避免梯度更新被某一维特征带偏。4. 同一份数据两种方式会如何4.1 带噪声的两点对比假设你有一组真实数据围绕二次曲线 y 0.8x² 0.3x 2 产生每个点叠加了一点随机噪声。用插值做会得到一条必须经过每个噪声点的光滑曲线用二次多项式拟合会得到一条贴近真实曲线的平滑抛物线。前者完全尊重数据却把噪声当成了信号后者不尊重单个点却逼近了真实规律。我做实验时经常拿这种对照来提醒自己和同事如果项目目标是从数据里提炼规律插值就是在画蛇添足。反之如果项目目标是补全一张精确的密钥表那个值需要按标准精确复现你却用拟合把标称值噪声化那是自找麻烦。4.2 拟合效果评估残差与 R²拟合不等于“算完参数就收工”。算完直线或多项式后停一下画残差图。残差是每个点的实际值减去预测值。一个合格的拟合残差应该围绕 0 随机分布看不出一眼可识别的规律。还可以用决定系数 R² 来量化拟合质量R² 1 - Σ(y_i - ŷ_i)² / Σ(y_i - ȳ)²其中 ȳ 是样本均值。R² 等于 1说明预测完美等于 0说明模型和“直接用均值预测”差不多。但 R² 高并不能说明模型正确。数据本来非线性你硬塞直线R² 可能很低数据恰好形态对称直线拟合的 R² 也可能不错可残差图明显弯曲。所以我在项目里始终把残差图放在 R² 前面数值是辅助图形才是证据。4.3 一个容易被忽略的边界不要外推无论是插值还是拟合都要小心“边界之外”。插值多项式在采样区间之外会大幅振荡这不是程序 bug而是多项式本身的特性。拟合模型即使内部误差很小到了数据范围之外也只能纯粹依赖模型假设假设一变预测全错。有次我看到一个同学用三次多项式对某变量做预测数据范围是 1 到 10他直接预测 x50 时的值得到的结果大得离谱还以为是公式写错了。其实公式没问题问题在于他把“采样区间内部的拟合能力”误解成了“任意范围都成立”。这一点值得单独写进任何一份数据工作规范里外推可以探讨趋势但绝不能当确定结论用。5. 常见问题与避坑清单5.1 这个表格我建议你存在项目文档里我把自己这些年总结的高频问题整理成了一张速查表每次建模前翻一眼能省不少试错时间。现象常见原因优先处理方式插值曲线在两端剧烈振荡使用高阶多项式插值出现 Runge 现象改用分段插值或三次样条插值曲线形状扭曲毫无物理意义数据噪声较大被插值保留了放弃插值改做曲线拟合梯度下降误差越来越大学习率太大参数在极值附近震荡降低学习率或对特征做归一化梯度下降收敛极慢学习率太小或数据尺度差异大适当调大学习率先标准化再训练拟合残差图有明显弯曲模型形式太简单遗漏非线性项增加二次多项式、对数项或交叉项R² 很高但预测值严重偏离实际数据范围外外推或模型过拟合禁止外推用交叉验证检查模型复杂度注意这里我在最后一项写了“模型过拟合”。过拟合和“R² 高”经常同时出现。多项式次数越高越容易把训练样本磨得很完美但换一份新数据就露馅。所以做多项式拟合时先试二次或三次再往上升千万不要一上来就用十次多项式。5.2 我在实操中的一套固定流程拿到拟合任务我基本按照这个流程走先画散点图看大致形态。是直线、曲线还是指数型直接决定模型族。用低阶多项式开始拟合低阶不是“简单”是“可解释”。先看低阶残差图。如果残差存在明显弯曲增加模型复杂度比如从一次升到二次每升一阶都检查 R² 和残差图。用交叉验证或留出验证集确认不是过拟合。最后一步才是输出结论并明确标注预测范围。这套流程看起来非常简单但我在无数项目里靠它躲掉了大坑。尤其是第三步很多人会直接选一个高次多项式让训练集 R² 逼近 1结果验证集惨不忍睹。低阶模型加上残差诊断反而能看见数据最需要的形态。5.3 关于拉格朗日插值代码实现我多说一句拉格朗日插值实现起来不像拟合那样有现成梯度但代码逻辑并不复杂。写的时候核心是两层循环def lagrange_poly(x_data, y_data, x): total 0.0 for i in range(len(x_data)): term y_data[i] for j in range(len(x_data)): if i j: continue term * (x - x_data[j]) / (x_data[i] - x_data[j]) total term return total这段代码直接对应公式没有额外优化适合小规模数据。我在教学和快速验证时经常写它帮助理解公式非常有效。真到工程里点一多我就会换样条插值所以我上面专门强调过“应用前先确认点数”。写到这里我脑子里转了一圈这些年处理数据的场景。一个小习惯我很想分享无论你是要补中间值还是从噪声里提炼趋势先把“数据点到底可不可信”这个问题搞清楚。标定表、标准曲线、几何离散点插值就够了测量值、采集值、带有波动的业务数据拟合更合适。拉格朗日让我理解插值的本质梯度下降让我理解拟合的本质而两者之间的选择最终取决于数据本身而不是某个公式更好看。
返回列表