资讯动态

最小二乘曲面拟合原理与MATLAB实现:从法方程到正则化

发布时间:2026/9/11 19:10:29 来源:尧图企业网站定制
简介一套基于MATLAB的曲面拟合程序源码包面向刚开始接触曲面拟合的MATLAB新手也适合需要快速实现拟合功能的开发人员可应用于测量数据插值、三维形貌重建、数学建模等场景。压缩包共6个文件包含5个.m脚本和1个doc说明文档。脚本按功能划分为主程序与子函数main.m负责整体流程调度leftmatrix.m与rightmatrix.m分别构建拟合所需的系数矩阵qiuhe.m与quotient.m处理累加及求商运算每个函数注释详细便于理解曲面拟合的数学实现。doc文档对程序结构和调用关系进行了补充说明零基础也能按文档逐步分析。22KB的体积在MATLAB源码资源中属于小巧类型既便于下载也说明代码实现精炼适合逐行阅读资源内函数命名直观、模块划分清晰学习者能快速定位关键代码。对于课程作业、毕业设计或项目预研这份源码提供了可直接运行的示例和清晰的二次修改基础。目前已有841人学习是掌握曲面拟合MATLAB编程范式、减少重复造轮子的实用素材。1. 曲面拟合为什么拿到了离散点反而更想把它压成一个函数做测绘、传感器标定或者图像背景去除的人手上最不缺的就是 (x, y, z) 离散点。问题是散点图只能告诉你“数据大概长什么样”却回答不了“峰值点在哪、曲率多少、噪声方差多大”这类定量问题。曲面拟合的作用,就是把这些离散点压成一个显式函数 z f(x, y)再用多项式系数去描述曲面的形态与趋势。这套 MATLAB 源码的可贵之处在于它把 MATLAB 自带拟合工具箱的“黑箱”拆成了五个能单独读、单独改的文件主流程 main.m、构造法方程左端 leftmatrix.m、右端 rightmatrix.m、累加求和 qiuhe.m、系数转换 quotient.m。对想搞懂最小二乘拟合本质的工程师来说这种文件划分比直接调用fit命令更有学习价值也更容易移植到 C 或 Python 环境。2. 最小二乘曲面拟合的数学原理与法方程构造2.1 用完全多项式基而不是张量积基曲面拟合最常见的模型是 p 阶完全多项式z c00 c10·x c01·y c20·x² c11·x·y c02·y² … c0p·yᵖ这里的每一项写作 c_ij·xⁱ·yʲ且 i j p。为什么强调“完全”因为很多入门资料喜欢用张量积基也就是把 x 的 0 到 p 次幂和 y 的 0 到 p 次幂两两相乘得到 (p1)² 项。但完全多项式只保留 i j p 的项系数数量从 (p1)² 降到 (p1)(p2)/2。这不仅仅是省几个系数的问题。看一个具体例子p 2 时张量积基包含 x²y² 这一项而完全多项式基只有 6 项1, x, y, x², xy, y²。x²y² 是四阶分量放到二阶模型里会让低阶项系数产生偏移。用完全多项式基模型阶数与项的阶数严格对齐拟合结果的系数才能直接对应曲面的“坡度”和“弯曲”特征。这个程序包从文件命名上就能看出是走这条路的leftmatrix.m 负责构造法方程左端rightmatrix.m 负责右端而基函数的选择决定了这两个矩阵的大小和数值特性。2.2 leftmatrix 与 rightmatrix法方程的两端是怎么来的最小二乘的思想是把拟合问题写成超定方程组 A·c ≈ z。A 是 n×m 设计矩阵n 是数据点个数m 是系数个数c 是待求系数向量z 是观测值。直接解这个超定方程组通常没有精确解于是转为求解法方程(Aᵀ·A)·c Aᵀ·zleftmatrix.m 构造的就是 Aᵀ·Arightmatrix.m 构造的是 Aᵀ·z。下面按这套源码的职责划分给出一个可实际运行的 leftmatrix 实现function M leftmatrix(x, y, p) % 构造 p 阶完全多项式曲面的法方程左端 A * A % x, y: n×1 列向量观测点坐标 % p: 多项式最高次数p 1 n numel(x); m (p 1) * (p 2) / 2; % 完全多项式系数数量 A zeros(n, m); k 0; for i 0 : p for j 0 : p - i k k 1; A(:, k) x.^i .* y.^j; % 基函数 x^i * y^j end end M A * A; % 对称正定矩阵在数据充分时 end这段代码的核心是两层循环外层遍历 x 的次数 i内层遍历 y 的次数 j并限制 i j p。注意x.^i .* y.^j用的是点运算符含义是对向量每个元素分别求幂再相乘得到的还是 n×1 向量这就是设计矩阵的一列。所有列拼起来A 的每一行对应一个观测点每一列对应一个多项式基函数。rightmatrix.m 的构造逻辑类似差别在于它只计算 Aᵀ·zfunction b rightmatrix(x, y, z, p) % 构造法方程右端 A * z % z: n×1 列向量观测值 [x, y] deal(x(:), y(:)); % 强制转为列向量 z z(:); A buildDesignMatrix(x, y, p); % 复用 2.1 中的基函数循环 b A * z; end这里deal的作用是把输入统一成列向量避免行向量与列向量混用导致维度不匹配。把 A 的构造单独抽出来是更好的工程实践但这套源码把它直接内联在 leftmatrix 和 rightmatrix 里功能上等价代价是两份重复代码。如果后续要改基函数比如加入径向基需要同步改这两个文件这是它最值得重构的地方。2.3 法方程的病态问题为什么直接求解可能失败法方程看起来漂亮实际用起来有一个隐蔽的坑Aᵀ·A 的条件数是原设计矩阵条件数的平方。如果观测数据的 x、y 范围很大比如 x 从 1000 到 2000那么 x² 列和 x³ 列之间会出现严重的数值共线性矩阵变得接近奇异求解出来的系数会剧烈震荡。这就是为什么很多曲面拟合源码在构造法方程之前会先对坐标做归一化。常见做法是把 x、y 平移到均值附近再缩放到 [-1, 1] 区间。平移消除大数缩放控制量纲。经过这种处理设计矩阵各列的量级趋近一致法方程的条件数能下降几个数量级。后面 main.m 的流程里这一步几乎必不可少。关于求解法方程MATLAB 里最稳妥的写法不是显式求逆也不是直接用 M \ b而是用 Cholesky 分解。因为 M 是对称正定矩阵chol(M)分解后回代求解比普通左除快大约一倍数值稳定性也更好R chol(M, upper); c R \ (R \ b);但这里必须强调一个工程判断如果你已经构造了设计矩阵 A直接在 MATLAB 里用c A \ z往往比走法方程更稳定因为 MATLAB 左除会针对超定方程组自动选择 QR 分解或 SVD 分解避开 Aᵀ·A 的条件数平方问题。法方程路线的真正价值在于理解原理以及在没有现成线性代数库的嵌入式环境里手写实现。3. main.m 主流程拆解从归一化到系数求解3.1 数据组织与坐标归一化main.m 的第一步通常是读入数据并做预处理。注意原版源码的说明文档是 Word 格式说明文档.doc不是 MATLAB 的 live script所以读数据多半用load或xlsread。我一般会这样组织主流程% main.m 主流程曲面拟合完整链路 % 1. 载入数据假设 data.txt 每列为 x, y, z data load(data.txt); x data(:, 1); y data(:, 2); z data(:, 3); % 2. 坐标归一化到 [-1, 1]改善法方程条件数 mx mean(x); my mean(y); sx max(abs(x - mx)); sy max(abs(y - my)); xn (x - mx) / sx; yn (y - my) / sy; % 3. 构造法方程并求解 p 2; % 多项式阶数可按需调整 M leftmatrix(xn, yn, p); b rightmatrix(xn, yn, z, p); c M \ b;第 2 步里的归一化为什么用 max 而不是 std因为 max 缩放把坐标严格限制在 [-1, 1] 内基函数值域可控std 缩放对离群点更敏感会把正常点压缩到很小的区间。对多项式拟合来说保证坐标在单位区间内比保证方差一致更重要。第 3 步直接对法方程用左除。这里先用普通左除便于理解后面的章节会给出更稳健的替代方案。如果你观测的点数 n 小于系数数量 m法方程是欠定的M 不可逆左除会给出最小范数解但结果不可信。出现这种情况时优先降低阶数 p而不是硬着头皮求。3.2 系数恢复与曲面重建上面求出的 c 是在归一化坐标 (xn, yn) 下的系数。要得到原始坐标下的曲面方程不能直接把 c 代回原始 x、y除非你重新推导坐标变换。更简单的做法是保留归一化参数后续所有预测都用同一套变换% 4. 在规则网格上重建曲面 xq linspace(min(x), max(x), 50); yq linspace(min(y), max(y), 50); [Xq, Yq] meshgrid(xq, yq); % 网格坐标也做相同的归一化 xnq (Xq - mx) / sx; ynq (Yq - my) / sy; % 5. 用设计矩阵计算预测值 Aq zeros(numel(Xq), numel(c)); k 0; for i 0 : p for j 0 : p - i k k 1; Aq(:, k) xnq(:).^i .* ynq(:).^j; end end Zq reshape(Aq * c, size(Xq)); % 6. 可视化 surf(Xq, Yq, Zq, EdgeColor, none); hold on; plot3(x, y, z, r., MarkerSize, 12);这里网格坐标复用 mx、sx、my、sy 做同参数归一化保证预测点落在与拟合点相同的特征空间中。还有个细节容易被忽略Aq * c计算出来的是列向量必须用reshape恢复成与 Xq 相同的网格尺寸surf才认数据。3.3 判断拟合质量的直观手段系数求出来不是终点。先看一眼残差分布比任何统计指标都直接% 7. 残差分析 z_pred Aq * c % 注意此时 Aq 对应原始观测点的设计矩阵 resid z - z_pred(:); figure; scatter3(x, y, resid, 10, resid, filled); colorbar; title(Residual Distribution);如果残差呈现明显的“碗形”或“马鞍形”空间分布说明还有未被提取的低阶趋势应该提高阶数或者引入交叉项。如果残差像白噪声一样杂乱无章说明模型已经抓到了主要趋势继续加阶数的收益不大。这一步在 main.m 里通常被省略建议读者自己补上因为它是判断“算完了”和“算对了”的分界线。4. qiuhe 与 quotient残差求和与拟合质量的量化度量4.1 qiuhe 承担什么计算文件名 qiuhe 是“求和”的拼音它在源码里的角色是代替 MATLAB 内置的sum去做法方程逐项累加。为什么源码作者要自己写求和而不直接用sum一种可能是为了教学演示让学习者看清法方程每一项是从哪累加来的另一种可能是为了后续扩展到加权最小二乘需要在求和循环里插入权值。按前一种思路qiuhe 的典型实现长这样function s qiuhe(v, w) % qiuhe: 加权求和函数默认权值为全 1 % v: 待求和向量w: 与 v 等长的权重向量 if nargin 2 w ones(size(v)); end s 0; for i 1 : numel(v) s s w(i) * v(i); end end这样写自然比内置sum慢但它把“每一步累加什么”暴露出来教学意义大于性能意义。实际使用时如果你把 qiuhe 用在构造法方程的循环里比如把 Aᵀ·A 的每个元素写成qiuhe(A(:,i) .* A(:,j))就会明白法方程本质上是一堆向量的两两内积。这种写法在低阶、小样本场景下没有问题点数超过一万时一定要换回矩阵乘法。4.2 quotient 在系数换算中的应用quotient 文件名的语义是“求商、求比率”在曲面拟合场景中它的任务通常是把最小二乘求解得到的信息转换成评价指标。一个典型用途是计算决定系数 R²function r2 quotient(SSE, SST) % quotient: 计算拟合决定系数 R^2 1 - SSE / SST % SSE: 残差平方和SST: 总离差平方和 r2 1 - SSE / SST; if r2 0 || r2 1 warning(R2 out of [0,1], check input data); end endR² 越接近 1说明模型解释了越多的数据变异性。但只看 R² 会踩坑阶数每升高一档R² 必然上升哪怕增加的项完全没有意义。这时需要自由度校正。加入调整 R² 后惩罚项会抵消多余系数带来的虚假提升% 调整 R²惩罚多余系数 n numel(z); m numel(c); adjR2 1 - (1 - R2) * (n - 1) / (n - m - 1);调整 R² 可能为负此时说明模型还不如直接取均值。按照经验p 3 到 p 4 的调整 R² 提升如果小于 0.005就不值得增加那 5 到 7 个系数。4.3 用表格比较不同阶次的拟合效果为了给“到底选几阶”一个可操作的参考下面是一组典型对比数据样本数 n 200数据包含二次趋势加随机噪声阶数 p系数数量 m条件数归一化前条件数归一化后残差标准差 σ调整 R²264.2e418.30.1020.9313103.1e742.70.0890.9474152.6e1096.20.0840.9515211.9e13231.50.0830.950看表格能得出一条清晰规律p 从 2 升到 4 时残差标准差从 0.102 降到 0.084收益明显p 从 4 升到 5 时残差只降了 0.001调整 R² 反而从 0.951 降到 0.950说明第 5 阶的多个系数在过拟合。再看条件数归一化前 p 5 已经高达 1.9e13早就病态到不可信归一化后虽然降到 231但这个值仍然偏大。这张表也印证了 main.m 里坐标归一化对数值稳定的重要性——没有归一化p 4 以上的法方程在双精度浮点下已经接近奇异。5. 正则化与加权迭代让曲面拟合在脏数据下仍然稳定5.1 岭回归给法方程对角线加一个稳定因子当条件数过大或数据存在强共线性时最有效的收尾手段不是降阶而是岭回归Tikhonov 正则化。做法极简在 leftmatrix 求出的 M 对角线上加一个小常数 λlambda 1e-6; M_reg M lambda * eye(size(M)); c_reg M_reg \ b;λ 的大小决定偏差与方差的权衡。λ 太小起不到稳定作用λ 太大系数被压向零拟合残差变大。一个可操作的定参方式是网格搜索在 [1e-8, 1e-6, 1e-4, 1e-2] 里逐个试选使留一交叉验证误差最小的值。上面的代码用的是单位矩阵但更精细的做法是用diag(1 ./ diag(M))做归一化让 λ 对每个系数的影响相对均匀。5.2 IRLS 加权压制离群点对曲面的拉扯实测数据里难免有飞点。普通最小二乘里一个离群点可以让整个曲面朝它偏转几度。改进办法是迭代加权最小二乘IRLS先做一次普通拟合计算残差然后根据残差大小给每个点分配权重残差大的点权重大幅降低再重新拟合。在 qiuhe 里预留权重参数的原因就在这里。% IRLS 迭代主循环 w ones(n, 1); for iter 1 : 10 % 构造加权法方程 Aw A .* sqrt(w); % 每行乘以权重的平方根 zw z .* sqrt(w); Mw Aw * Aw; bw Aw * zw; c Mw \ bw; res z - A * c; % Huber 权重函数残差小于阈值的点权重为 1大于阈值的按比例衰减 sigma 1.4826 * median(abs(res - median(res))); % 稳健标准差 w min(1, 1.345 * sigma ./ abs(res)); w(w 1e-8) 1e-8; end这段代码的核心在第 7 行Huber 权重让残差在阈值内的点保持满权重超过阈值的点按反比衰减。阈值取 1.345 倍稳健标准差是 Huber 给出的最优效率平衡点。IRLS 一般 5 到 8 次迭代就能收敛设 10 次上限足够。注意每次迭代都要重新构造加权设计矩阵不能复用法方程左边的原 M。5.3 留一交叉验证选定阶数最后给一个实用的定型方法。对 p 2 到 p 5 各执行一遍完整流程用留一法计算预测误差best_p 2; best_err inf; for p 2 : 5 err 0; for i 1 : n trIdx true(n, 1); trIdx(i) false; M leftmatrix(x(trIdx), y(trIdx), p); b rightmatrix(x(trIdx), y(trIdx), z(trIdx), p); c M \ b; z_pred predictPoint(c, x(i), y(i), p, mx, my, sx, sy); err err (z(i) - z_pred).^2; end rmse sqrt(err / n); fprintf(p%d, LOO-RMSE%.4f\n, p, rmse); if rmse best_err best_err rmse; best_p p; end end留一法在 n 200 时计算量可接受n 超过 2000 时改用 k 折交叉验证k 5 或 10 都行。这个方法比单纯看调整 R² 更可靠因为它直接度量的是模型的预测能力而不是拟合能力。实际项目中我见过很多人用调整 R² 选完阶数后拿去预测新数据误差放大两倍就是因为没有做交叉验证。留一法的另一好处是能暴露单点敏感性——如果你发现某一步拟合的预测值对某个观测点特别敏感那么这个点很可能就是需要 IRLS 压制的离群点。把这套流程固化成一个脚本配合前面加了 λ 的岭回归修正曲面拟合就能从“能出图”提升到“可交付”。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价