1. 最小二乘法:从数学原理到生活应用

最小二乘法听起来像是个高深莫测的数学概念,但其实它的核心思想非常简单:找到一条曲线,让这条曲线到所有数据点的"距离"之和最小。想象一下,你手里有一把散落的豆子(数据点),现在要用一根橡皮筋(拟合曲线)把这些豆子串起来,怎么调整橡皮筋的形状才能让它最贴近所有豆子?这就是最小二乘法在做的事情。

在实际应用中,最小二乘法最常见的场景就是曲线拟合。比如你记录了过去一年每个月的电费数据,想要预测下个月的电费;或者测量了不同温度下金属棒的伸长量,想找出温度与长度之间的关系。这些情况下,我们都需要找到一个数学函数来描述变量之间的关系。

最小二乘法的数学本质是求解一个最优化问题。假设我们有n个数据点(x₁,y₁), (x₂,y₂), ..., (xₙ,yₙ),要拟合的函数形式为f(x;a₁,a₂,...,aₘ),其中a₁到aₘ是待定参数。最小二乘法就是要找到这些参数,使得所有数据点的残差平方和最小:

J = Σ[yᵢ - f(xᵢ)]²

这个J就是我们常说的损失函数。当J达到最小值时,我们就得到了最优的拟合参数。这个优化过程可以通过求导等于零来解析求解(线性情况),或者使用数值优化方法(非线性情况)。

2. MATLAB中的最小二乘实现:polyfit详解

MATLAB中的polyfit函数是进行多项式拟合的利器。它的基本语法非常简单:

p = polyfit(x, y, n)

其中x和y是数据向量,n是多项式的阶数,p返回的是多项式系数。

让我们通过一个实际例子来理解它的用法。假设我们有一组实验数据,记录了某化学反应中物质浓度随时间的变化:

time = [0, 1, 2, 3, 4, 5]; % 时间(小时)
concentration = [0.82, 0.72, 0.63, 0.60, 0.55, 0.50]; % 浓度(mol/L)

我们想用二次多项式来拟合这组数据:

p = polyfit(time, concentration, 2)

运行后会得到三个系数,分别对应x²、x和常数项。为了评估拟合效果,我们可以计算拟合值并与原始数据比较:

fit_values = polyval(p, time);
plot(time, concentration, 'o', time, fit_values, '-')
legend('原始数据','拟合曲线')

polyfit虽然简单,但有几个实用技巧值得注意:

  1. 数据标准化:当x值很大时(如年份2020,2021...),直接拟合可能导致数值问题。可以先减去均值:
x_normalized = x - mean(x);
  1. 阶数选择:不是越高越好。可以通过观察残差来选择合适的阶数:
[p, S] = polyfit(x, y, n);
residual = S.normr^2; % 残差平方和
  1. 加权拟合:如果某些数据点更可靠,可以赋予它们更大权重:
w = [1, 1, 1, 0.5, 0.5, 0.5]; % 权重向量
p = polyfit(x, y, n, [], w);

3. 处理非线性关系:lsqcurvefit实战

当数据关系不是多项式时,polyfit就力不从心了。这时lsqcurvefit就派上用场了。这个函数可以拟合任意形式的非线性函数,只要你能写出它的数学表达式。

假设我们有一组数据,疑似遵循指数衰减规律: y = a * exp(-b * x) + c

首先,我们需要定义这个函数:

model = @(p, x) p(1)*exp(-p(2)*x) + p(3);

然后准备初始参数猜测(这很关键!):

initial_guess = [1, 0.1, 0.5]; % [a, b, c]的初始猜测

接着调用lsqcurvefit:

options = optimoptions('lsqcurvefit', 'Display', 'iter');
[p, resnorm] = lsqcurvefit(model, initial_guess, xdata, ydata, [], [], options);

我曾经在一个药物代谢动力学项目中遇到过这样的问题:需要拟合血药浓度随时间变化的曲线。数据呈现明显的先快速下降后缓慢下降的趋势。最初尝试用polyfit,但效果很差。后来改用lsqcurvefit拟合双指数模型: y = Aexp(-αt) + Bexp(-βt)

经过多次调试初始值后,最终得到了非常理想的拟合结果(R²>0.99)。这个案例让我深刻体会到:

  1. 初始猜测很重要:可以从物理意义或对数变换后的线性拟合获得合理初始值
  2. 参数约束很有用:比如代谢速率不可能是负数,可以通过lb参数设置下限
  3. 可视化是必须的:每次拟合后都要绘图检查,不能只看R²

4. 模型评估与选择:从拟合优度到过拟合防范

得到拟合结果后,如何判断模型的好坏?常见的评估指标有:

  1. 残差平方和(RSS):越小越好
  2. 确定系数(R²):0到1之间,越接近1越好
  3. 调整后的R²:考虑了参数个数,防止过拟合
  4. AIC/BIC:信息准则,平衡拟合优度和模型复杂度

在MATLAB中,可以这样计算这些指标:

% 计算R²
y_mean = mean(y);
SStot = sum((y - y_mean).^2);
SSres = sum((y - fit_y).^2);
R2 = 1 - SSres/SStot;

% 计算调整R²
n = length(y); % 样本数
k = length(p); % 参数个数
adj_R2 = 1 - (SSres/(n-k))/(SStot/(n-1));

过拟合是曲线拟合中的常见陷阱。我曾用9阶多项式拟合只有10个数据点的数据集,R²达到了完美的1.0,但新数据预测效果极差。防范过拟合的方法包括:

  1. 交叉验证:将数据分为训练集和测试集
  2. 正则化:在损失函数中加入参数惩罚项
  3. 模型简化:选择更简单的模型形式
  4. 增加数据量:这是最有效的方法

在MATLAB中,可以使用交叉验证来评估模型泛化能力:

cv_partition = cvpartition(length(y), 'HoldOut', 0.3);
train_idx = training(cv_partition);
test_idx = test(cv_partition);

% 训练模型
p_train = polyfit(x(train_idx), y(train_idx), 3);

% 测试模型
y_test_pred = polyval(p_train, x(test_idx));
test_error = mean((y(test_idx) - y_test_pred).^2);

5. 高级技巧与实战经验分享

经过多个项目的实践,我总结出一些有价值的经验:

数据预处理方面:

  1. 异常值处理:使用isoutlier函数检测并处理
outliers = isoutlier(y);
y_clean = y(~outliers);
x_clean = x(~outliers);
  1. 数据变换:对数变换常能改善非线性拟合
logy = log(y);
p = polyfit(x, logy, 1);
a = exp(p(2));
b = p(1);

模型选择方面:

  1. 分段拟合:当数据明显分阶段时很有效
break_point = 3; % 通过观察数据确定
mask = x < break_point;
p1 = polyfit(x(mask), y(mask), 1);
p2 = polyfit(x(~mask), y(~mask), 1);
  1. 混合模型:结合多种简单模型
model = @(p, x) p(1)*sin(p(2)*x) + p(3)*exp(-p(4)*x);

调试技巧:

  1. 参数边界设置:防止出现不合理的参数值
lb = [0, 0, -Inf]; % a>0, b>0, c无限制
ub = [Inf, Inf, Inf];
p = lsqcurvefit(model, initial_guess, x, y, lb, ub);
  1. 全局优化:避免局部最优解
problem = createOptimProblem('lsqcurvefit', 'objective', model, ...
    'x0', initial_guess, 'xdata', x, 'ydata', y);
ms = MultiStart;
[p, fval] = run(ms, problem, 20); % 尝试20个初始点

可视化技巧:

  1. 绘制置信区间:
[ypred, delta] = polyconf(p, x, S);
plot(x, y, 'o', x, ypred, '-', x, ypred+delta, 'r--', x, ypred-delta, 'r--');
  1. 残差分析:
residuals = y - polyval(p, x);
plot(x, residuals, 'o');
hline = refline(0, 0);
hline.Color = 'r';

最后提醒一点:任何拟合模型都应该有物理解释。我曾见过一个完美的五次多项式拟合,但每个参数都无法对应实际物理意义,这样的模型虽然拟合效果好,但预测能力和解释性都很差。

Logo

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

更多推荐