ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Matlab遗传算法与fmincon组合优化:全局搜索到局部精修的完整实践

Matlab遗传算法与fmincon组合优化:全局搜索到局部精修的完整实践 简介基于Matlab的遗传算法与非线性规划函数寻优算法资源内含45个.m源码文件和2个.asv自动保存副本压缩包仅29KB。资源面向计算机、电子信息工程、数学等专业的大学生适用于课程设计、期末大作业或毕业设计中的函数优化与算法实现环节也可作为科研入门参考。代码按案例1、案例2、案例3划分并额外提供每个案例的非线性扩展版本其中选择、遗传、交叉、变异等核心操作均由独立函数实现并配有目标函数定义与辅助测试脚本整体目录结构清晰便于按需调用和二次开发。学习者可对照多个案例理解遗传算法从初始种群生成到收敛判定的完整过程并进一步观察引入非线性规划后对局部搜索精度的改善。目前已有684人学习下载建议具备Matlab基础、能独立调试并扩展代码的读者作为参考使用。1. 函数寻优里遗传算法和非线性规划为什么总被放在一起函数寻优这个题目最棘手的地方不是算法跑不起来而是单一算法总有一个绕不过去的短板。遗传算法能在多峰搜索空间里把大致区域捞出来但靠交叉和变异去逼近极值点精度经常卡在 10^-3 到 10^-4 这个量级就上不去了。非线性规划的 fmincon 收敛又快又准可它是个局部求解器初值给不好就直接掉进最近的局部极值点。把两者串起来先用遗传算法在全局范围撒网再把捞到的最优点作为 fmincon 的初值做局部精修这套组合在 Matlab 里用全局优化工具箱的ga配合优化工具箱Optimization Toolbox的fmincon就能搭好也是这类源码包里最常见的算法结构。适合刚接触 Matlab 优化工具箱、想把全局搜索和局部精化拼起来的人。2. Matlab里GA的全局搜索和fmincon的局部精化怎么分工2.1 遗传算法擅长全局撒网收敛精度却卡在量化步长上遗传算法在连续函数寻优中用的是浮点编码交叉算子和变异算子负责在解空间里制造新个体。这种机制对“跳出局部极值”非常有效只要种群规模够大多个局部最优的盆地会被同时覆盖即使某一个体陷进去其他个体仍保留着别的区域的基因信息。但遗传算法没有梯度信息它判断“该不该继续优化”靠的是适应度函数的变化量而不是解的变化方向。因此到了后期所有个体都挤进同一个盆地时交叉产生的子代几乎在同一个位置附近变异步长又不敢设太大否则会跳出好区域最终收敛过程就变成在最优值附近反复试探。精度天花板由此而来。Matlab 的ga默认用FunctionTolerance判断适应度是否还在明显改进默认值 1e-6 意味着适应度变化小于这个阈值时算法会认为收敛。对 Rastrigin 这类表面剧烈震荡的函数遗传算法能稳定找到全局最优所在的盆地但要把解精确到小数点后六位靠变异算子去碰是低效的。MaxStallGenerations默认 50 代适应度不改进也会触发停止所以经常看到ga跑完fval停在 10^-2 或 10^-3 量级。2.2 fmincon依赖初值但梯度信息能把残差压到接近机器精度fmincon 是 Matlab 优化工具箱里的约束非线性求解器核心思路是基于梯度的迭代从给定初值出发用目标函数的导数信息构造搜索方向沿方向前进直到满足一阶最优性条件。它支持interior-point、sqp、trust-region-reflective几种算法对连续可微的函数收敛速度远快于遗传算法精度由OptimalityTolerance和ConstraintTolerance控制设到 1e-12 很常见。fmincon 的代价是初值敏感性。初值落在哪个极值点的吸引域里最终就大概率收敛到哪个极值点。这就是为什么直接拿随机初值跑 fmincon多峰函数的结果一次一个样。但它和遗传算法形成完美互补ga负责回答“哪里值得精修”fmincon 负责回答“这个区域里真正的极值点在哪”。两者的分工在代码层面也很清晰x_ga传给 fmincon 做x0其余约束条件完全复用。2.3 串行混合和代际嵌入两种混合模式的选型对比把遗传算法和非线性规划结合起来常见的有两种做法工程上按目标和计算成本取舍。混合模式核心流程精度表现计算成本适用场景串行混合ga完整跑完取x_ga作为 fmincon 初值fmincon 二次精修常能从 10^-3 压到 10^-10 以下低ga只跑一轮大多数源码包的标准做法实现简单结果稳定代际嵌入每代遗传算法选出精英个体后立刻对精英做 fmincon 修正再把结果放回种群成功率更高避免遗传算法在中间代丢失好解高每代都要调用多次 fmincon目标函数计算便宜、对成功率要求苛刻的场景多起点 fmincon不用遗传算法随机生成多组初值分别跑 fmincon取决于初值覆盖程度多峰函数容易漏掉全局最优中只作为对照组用来验证遗传算法的价值串行混合是绝大多数“函数寻优算法”源码包采用的结构。它够用且不容易出问题调试时只需要关心ga有没有落入错误盆地以及 fmincon 精修时的约束处理。代际嵌入的优点是每代都在修正精英个体遗传算法的搜索方向会更快集中到真正的极值附近但计算量成倍增加。3. 用Matlab遗传算法和fmincon跑通函数寻优最小示例3.1 先定义测试函数和约束函数让两种算法共用同一份目标用经典的二维 Rastrigin 函数作为寻优对象它在原点有全局最小值搜索空间内还有大量按周期分布的局部极值能让遗传算法展示全局搜索能力也能让 fmincon 展示局部精修能力。目标函数单独放在一个 m 文件里后续ga和fmincon都通过函数句柄调用它。function y rastrigin(x) % 二维 Rastrigin 函数全局最优在 x[0,0]f0 y sum(x.^2 - 10 * cos(2 * pi * x) 10); end非线性约束函数单独写一个c是不等式约束约定c 0为可行ceq是等式约束约定ceq 0为可行。这里用一个半径为 3 的圆盘约束做例子限制搜索点不能离原点太远。function [c, ceq] circle_constraint(x) % 非线性不等式约束: x1^2 x2^2 - 3^2 0 c x(1)^2 x(2)^2 - 9; ceq []; endga和 fmincon 的约束参数签名完全一致都是A, b, Aeq, beq, lb, ub, nonlcon这个顺序所以约束函数可以原样复用这是 Matlab 这套组合最顺手的地方。3.2 调ga()做全局搜索核心参数放在optimoptions里先验证当前环境有没有可用的ga求解器再设置遗传算法参数。以下是完整的最小调用代码。% 检查全局优化工具箱是否可用 if exist(ga, file) ~ 2 error(需要安装 Global Optimization Toolbox); end nvars 2; lb [-5, -5]; ub [5, 5]; % 遗传算法参数设置 opts_ga optimoptions(ga, ... PopulationSize, 120, ... MaxGenerations, 200, ... MaxStallGenerations, 30, ... FunctionTolerance, 1e-6, ... Display, iter, ... PlotFcn, gaplotbestf); % ga 求解: 无线性约束A/b/Aeq/beq 传空先不加非线性约束 [x_ga, fval_ga, exitflag_ga] ga(rastrigin, nvars, ... [], [], [], [], lb, ub, [], opts_ga);这段代码里nvars是决策变量维度二维函数传 2。lb和ub把搜索空间压缩到[-5,5]对 Rastrigin 函数来说这个范围已经覆盖了足够多的局部极值再放大范围只会让遗传算法的早熟概率上升并不会带来收益。PopulationSize取 120 是为了保证种群能同时覆盖多个局部极小点所在的盆地太小容易让所有个体在早期就被某个局部最优吸引。MaxGenerations和MaxStallGenerations共同控制迭代边界一个管总代数上限一个管适应度停滞判断。FunctionTolerance保留默认量级即可因为后续 fmincon 会接手精修遗传算法不需要在这里把精度磨得很高。3.3 把ga的返回点作为fmincon初值完成精修fmincon 的关键参数是算法选择和终止容差。这里选sqp而不是interior-point原因是sqp在带边界约束的非线性问题上步进更直接从遗传算法给出的可行解出发收敛更快而interior-point往往会在边界附近额外绕一些迭代。% fmincon 参数设置: sqp 算法 高精度终止条件 opts_sqp optimoptions(fmincon, ... Algorithm, sqp, ... Display, final, ... MaxIterations, 500, ... OptimalityTolerance, 1e-12, ... ConstraintTolerance, 1e-10); % 用 ga 的结果作为初值进行局部精修 [x_opt, fval_opt, exitflag_sqp] fmincon(rastrigin, x_ga, ... [], [], [], [], lb, ub, [], opts_sqp); fprintf(GA 终止: x [%.8f, %.8f], f %.8e\n, ... x_ga(1), x_ga(2), fval_ga); fprintf(GASQP 结果: x [%.12f, %.12f], f %.12e\n, ... x_opt(1), x_opt(2), fval_opt); fprintf(exitflag: ga%d, fmincon%d\n, exitflag_ga, exitflag_sqp);OptimalityTolerance设到 1e-12fmincon 会把解一路压到接近机器精度这是遗传算法靠变异算子无法触及的区域。x_ga作为初值是整个组合的核心fmincon 不需要知道全局长什么样只需要知道从哪个点开始爬坡。3.4 串行混合主程序连同停止条件的判断把前面的代码整理成一个可运行的主函数。遗传算法先跑完fmincon 再精修两个阶段的exitflag都要看。ga的 exitflag 为 1 表示满足收敛条件5 表示达到最大代数fmincon 的 exitflag 为 1 表示满足一阶最优性条件对应着“找到一个局部最优解”而不代表全局最优这也是要做多起点验证的原因。function run_ga_nlp_serial() % 串行混合: ga 全局搜索 fmincon 局部精修 lb [-5, -5]; ub [5, 5]; % 阶段一: 遗传算法 opts_ga optimoptions(ga, ... PopulationSize, 120, ... MaxGenerations, 200, ... MaxStallGenerations, 30, ... FunctionTolerance, 1e-6, ... Display, off); [x_ga, fval_ga, exitflag_ga] ga(rastrigin, 2, ... [], [], [], [], lb, ub, [], opts_ga); % 阶段二: fmincon 精修 opts_sqp optimoptions(fmincon, ... Algorithm, sqp, ... Display, off, ... OptimalityTolerance, 1e-12); [x_opt, fval_opt, exitflag_sqp] fmincon(rastrigin, x_ga, ... [], [], [], [], lb, ub, [], opts_sqp); % 输出与验收 fprintf(x_ga [%.8f, %.8f], f %.8e\n, ... x_ga(1), x_ga(2), fval_ga); fprintf(x_opt [%.12f, %.12f], f %.12e\n, ... x_opt(1), x_opt(2), fval_opt); if abs(fval_opt) 1e-9 exitflag_sqp 1 fprintf(寻优成功: 收敛到全局最优附近\n); else fprintf(检查 exitflag: ga%d, fmincon%d\n, ... exitflag_ga, exitflag_sqp); end end运行这段代码常见的结果是x_ga离原点还有 0.1 到 0.3 的偏差fval_ga停在 1e-2 或 1e-3而x_opt的每个分量都能到 1e-10 以内fval_opt接近浮点误差级别。这种量级差距正是两种算法分工的直观验证。4. 带非线性约束的函数寻优实战与Matlab参数调整4.1 把约束写进nonlcon罚函数几乎是最后手段很多旧源码包还在用罚函数处理约束做法是把约束违反量乘以一个大系数加进目标函数。罚因子太小约束形同虚设罚因子太大则会在约束边界形成陡峭的深谷遗传算法的变异算子很容易被撕裂的地形带偏。现代 Matlab 的ga和 fmincon 都原生支持nonlcon参数优先直接传约束函数不要自己实现罚函数。function y himmelblau_constrained(x) % Himmelblau 函数有多个等值局部最优便于观察约束的影响 y (x(1)^2 x(2) - 11)^2 (x(1) x(2)^2 - 7)^2; end % 调用: ga 阶段直接传入非空 nonlcon opts_ga optimoptions(ga, PopulationSize, 100, ... Display, final); [x_ga, fval_ga] ga(himmelblau_constrained, 2, ... [], [], [], [], -4*ones(1,2), 4*ones(1,2), ... circle_constraint, opts_ga); % 精修阶段复用同一个 nonlcon opts_sqp optimoptions(fmincon, Algorithm, sqp, ... OptimalityTolerance, 1e-10); [x_opt, fval_opt] fmincon(himmelblau_constrained, x_ga, ... [], [], [], [], -4*ones(1,2), 4*ones(1,2), ... circle_constraint, opts_sqp);ga在处理带nonlcon的问题时会先用初始种群筛选可行解如果初始种群整体不可行它会倾向于往可行域方向搜索这个过程比较慢。所以带约束问题时lb和ub不要留太多无效空间尽量贴近实际约束边界能显著减少ga寻找可行域的时间。4.2 一张参数对照表覆盖ga和fmincon的常用调整实际调参时不需要逐个试探先看现象再对上表的参数。求解器参数名作用常用区间调整方向gaPopulationSize种群大小决定全局覆盖密度50 ~ 200早熟时往上调gaMaxGenerations总迭代上限100 ~ 400配合 FunctionTolerance 使用gaFunctionTolerance适应度变化的收敛阈值1e-4 ~ 1e-8只想粗搜就放宽省时间gaCrossoverFraction交叉算子概率0.7 ~ 0.9解太离散时往低调gaMigrationInterval子种群迁移间隔10 ~ 30多岛种群才生效fminconAlgorithm局部迭代算法sqp / interior-point边界约束为主选 sqpfminconMaxIterations局部搜索迭代上限300 ~ 1000收敛慢时调大fminconOptimalityTolerance一阶最优性判定阈值1e-8 ~ 1e-12要求高精度就调小fminconConstraintTolerance约束违反允许量1e-6 ~ 1e-10约束边界抖动时放宽一个常见误区是把MaxGenerations从 200 调到 2000 去追求精度。遗传算法的后期收敛是线性的多跑几百代可能只把fval从 1e-3 压到 5e-4性价比远低于交给 fmincon 精修。正确的做法是让ga尽早触发停止条件把省下来的时间花在多次重复实验上。4.3 遗传算法早熟和边界振荡的排查早熟是遗传算法最典型的现象gaplotbestf曲线前几十代快速下降然后变得平直而fval离已知最优还很远。这时先检查PopulationSize是否过小再检查lb和ub是否给得太大。Rastrigin 这类多峰函数二维搜索空间放到[-10,10]时局部极值密度会显著增加120 的种群并不算宽裕。边界振荡多发生在带约束问题中。表现是 fmincon 的迭代过程一直卡在约束边界附近来回试探exitflag返回 0 或 -2但看起来输出值“差不多”。优先排查ConstraintTolerance是否设得过于严格比如 1e-12 这种量级会让求解器在边界上无限细分步长。另一个排查点是ceq等式约束遗传算法对等式约束的处理能力本来就弱如果等式约束严格且非线性先考虑是否能把等式约束消元而不是硬塞给ga。4.4 数据文件组织 把测试集存成.mat这类源码包通常附带数据目录存放测试函数和预先算好的基准结果。可以把多个测试函数的函数句柄、维度和边界打包成一个结构体数组存成.mat文件后续做算法对比时直接加载不必每次修改主程序。function make_benchmark() % 测试集元数据: 函数句柄、维度、变量边界 bench(1).fun rastrigin; bench(1).dim 2; bench(1).lb [-5, -5]; bench(1).ub [5, 5]; bench(2).fun himmelblau_constrained; bench(2).dim 2; bench(2).lb [-4, -4]; bench(2).ub [4, 4]; save(benchmark_data.mat, bench); end % 使用时直接加载 data load(benchmark_data.mat); f (x) data.bench(1).fun(x); [x_opt, f_opt] fmincon(f, x_ga, [], [], [], [], ... data.bench(1).lb, data.bench(1).ub, [], opts_sqp);把函数句柄放进结构体再保存到.mat文件时Matlab 会把句柄关联的函数一并打包加载后可以直接调用。这样组织和“源码数据”的课题包结构是一致的换测试函数时只需要在make_benchmark里加一行不需要动寻优主程序。5. 验证算法效果的方法以及一个嵌入式进阶变体5.1 用已知全局最优值做一次干净验收Rastrigin 的全局最优在[0,0]理论最小值 0这是验证算法最方便的地方。跑完混合寻优后检查 fmincon 的exitflag是否为 1再判断fval_opt的绝对值是否小于 1e-9。这个阈值不能设得太小浮点运算本身会带来 1e-14 量级的误差abs(fval_opt) 1e-12已经属于接近机器精度的结果。如果只验证到 1e-6实际上无法区分算法是找到了全局最优还是在某个局部极值点停下。5.2 重复实验统计成功率别信单次结果遗传算法带随机性单次运行证明不了什么。用 20 次重复实验每次换随机种子统计成功率才是判断混合算法是否可靠的正确姿势。rng(default); success false(20, 1); for t 1 : 20 rng(t * 100, twister); [x_ga, ~] ga(rastrigin, 2, [], [], [], [], lb, ub, [], opts_ga); [x_opt, f_opt, exitflag] fmincon(rastrigin, x_ga, ... [], [], [], [], lb, ub, [], opts_sqp); success(t) (exitflag 0) (abs(f_opt) 1e-9); end fprintf(成功率: %.0f%%\n, 100 * sum(success) / 20);单独拿随机初值跑 fmincon 对照组会发现成功率明显低于串行混合。这正是遗传算法阶段的价值把 fmincon 的起点限制在正确的盆地里而不是在整个搜索空间随机撒点碰运气。5.3 每代对精英做fmincon修正的变体代码如果目标函数计算便宜且对成功率要求苛刻可以用代际嵌入模式。核心是给ga设置OutputFcn在每次迭代时把种群前 10% 的精英个体用 fmincon 修正后写回种群。function run_ga_embedded() lb [-5, -5]; ub [5, 5]; opts optimoptions(ga, ... PopulationSize, 100, ... MaxGenerations, 50, ... Display, iter, ... OutputFcn, outfun); [x_opt, f_opt] ga(rastrigin, 2, [], [], [], [], lb, ub, [], opts); function [state, options, optchanged] outfun(options, state, flag) optchanged false; if strcmp(flag, iter) state.Generation 0 pop state.Population; elite_num max(1, round(0.1 * size(pop, 1))); localOpts optimoptions(fmincon, ... Algorithm, sqp, Display, off); for i 1 : elite_num x_new fmincon(rastrigin, pop(i, :), ... [], [], [], [], lb, ub, [], localOpts); state.Population(i, :) x_new; state.Score(i) rastrigin(x_new); end optchanged true; end end endoutfun里直接修改state.Population并置optchanged为 true遗传算法会把这些局部优化过的个体纳入下一代演化。这个变体的优势是每代都在改善精英质量劣势是每代都要跑若干次 fmincon总计算量上升一个量级适合目标函数解析式简单、计算一次只要几毫秒的场合。本文还有配套的精品资源点击获取
返回列表