2. MATLAB优化环境与编程基础
在MATLAB中做参数求解,真正决定效率的往往不是优化算法本身,而是“目标函数怎么写、怎么传、怎么算”。同一个fmincon调用,如果目标函数写成循环逐点计算,可能比向量化版本慢几十倍;如果约束函数用字符串或文件名字符串传递,在新版本中会直接报错。本节围绕四个基础能力展开:函数句柄、匿名函数、向量化表达,以及脚本与实时编辑器在参数求解中的分工。
函数句柄:把“函数”当作参数传递
函数句柄是一种可以像变量一样被赋值、传递和调用的MATLAB数据类型,用 @ 创建。优化函数(fminsearch、fminunc、fmincon、lsqnonlin、ga等)的第一个输入几乎都是函数句柄,而不是函数名字符串。例如定义一个Rosenbrock函数:
function f = rosen(x)
f = 100*(x(2) - x(1)^2)^2 + (1 - x(1))^2;
end
% 在脚本中创建句柄并求解
fun = @rosen;
[x, fval] = fminsearch(fun, [-1.2, 1]);
这里 fun 就是句柄,它把函数“打包”成一个可传递对象。若目标函数需要额外参数,例如 f(x, a, b),可用匿名函数包装:fun = @(x) f(x, a, b),这样优化器只看到单变量 x,而 a、b 被固定。这是工程参数求解中最常用的模式:把实验数据、权重、几何尺寸等作为固定参数,把待求参数作为优化变量。
匿名函数:一行表达目标与约束
匿名函数适合表达简单、单行、无需复用的目标或约束。例如拟合模型 y = a*exp(-b*t),残差平方和可写成:
t = [0 1 2 3 4 5]';
y = [5.0 3.1 1.9 1.2 0.7 0.4]';
res = @(p) y - p(1)*exp(-p(2)*t);
p0 = [5, 0.5];
p = lsqnonlin(res, p0);
匿名函数捕获创建时刻的 t、y,因此数据变化后需重新创建句柄。对于多行逻辑、需要中间变量或需要调试的目标函数,应改用局部函数或独立函数文件,避免把复杂逻辑塞进一行匿名函数导致可读性下降。
向量化表达:让目标函数一次算完
优化过程会反复调用目标函数,循环次数可能达到数百甚至数千次。若函数内部用 for 逐点计算,开销会显著放大。向量化就是用矩阵/数组运算替代显式循环。例如计算 sum(sin(x).*x.^2),向量化写法为:
% 非向量化
s = 0;
for i = 1:numel(x)
s = s + sin(x(i))*x(i)^2;
end
% 向量化
s = sum(sin(x).*x.^2);
注意点乘 .*、点除 ./、点幂 .^ 的使用,以及 sum、mean、max 等聚合函数的方向参数。对于多参数目标,可把每个候选点按行排列,一次计算整批残差,再按列求和,这样既减少函数调用次数,也便于后续并行或GPU加速。
脚本与实时编辑器:参数求解的工作流
脚本适合固定流程、需要反复运行的求解任务;实时编辑器(Live Editor)适合边调参、边看结果、边记录结论。在实时编辑器中,可以把数据加载、目标函数定义、优化调用、结果绘图分成多个节(Section),逐节运行。例如先运行数据节得到 t、y,再运行模型节定义 res,最后运行求解节得到 p 并绘制拟合曲线。这样每次只改初值或权重,不必重跑全部代码。
实时编辑器的另一优势是结果内联显示:优化退出标志 exitflag、迭代次数 output.iterations、一阶最优性 output.firstorderopt 可直接显示在输出区,便于判断求解是否收敛。若 exitflag 为负,通常说明目标函数或约束在初值附近不可行、梯度异常或迭代超限,需要回到函数定义检查。
一个可复用的参数求解模板
把上述能力组合起来,可以得到一个结构清晰的求解脚本。以下示例用于拟合 y = a*exp(-b*t),其中 a、b 为待求参数,初值取 [5, 0.5],下界取 [0, 0],上界取 [10, 2]。数据为教学示例,非实测结果。
% 1. 数据(示例)
t = [0 1 2 3 4 5]';
y = [5.0 3.1 1.9 1.2 0.7 0.4]';
% 2. 目标函数:匿名函数 + 向量化
res = @(p) y - p(1)*exp(-p(2)*t);
% 3. 初值与边界
p0 = [5, 0.5];
lb = [0, 0];
ub = [10, 2];
% 4. 求解
opts = optimoptions('lsqnonlin', 'Display', 'iter');
[p, resnorm, residual, exitflag, output] = lsqnonlin(res, p0, lb, ub, opts);
% 5. 结果判断
fprintf('a = %.4f, b = %.4f\n', p(1), p(2));
fprintf('exitflag = %d, iterations = %d\n', exitflag, output.iterations);
% 6. 验证:绘制拟合曲线
tt = linspace(0, 5, 200)';
yy = p(1)*exp(-p(2)*tt);
plot(t, y, 'o', tt, yy, '-'); legend('数据', '拟合');
运行后应重点观察三件事:exitflag 是否为正;residual 是否呈现随机分布而非系统性偏差;p 是否落在边界内部。若 p 顶在边界上,说明边界可能设得过紧,或模型形式与数据不匹配,需要回到模型节调整,而不是直接接受结果。