简介:这份资源面向学习MATLAB优化算法的学生与工程技术人员,以遗传算法、粒子群优化等现代优化技术为主线,帮助读者从基础概念过渡到算法实现与实际问题求解。压缩包共282个文件,以199个m脚本为主体,配合35个mat数据、31个bmp图像及少量txt说明、fig图形与dat数据文件,整体约1.29MB,脚本可直接运行复现。内容覆盖优化工具箱中fmincon、fminunc等函数的使用,遗传算法中种群规模、交叉与变异概率的参数设置,以及粒子群算法中速度位置更新、惯性权重与学习因子的调整,并延伸至多目标优化、混合优化策略及工程设计、模型参数调整等应用案例。已有399人学习,适合按章节顺序研读代码、对照实验,逐步掌握算法调参与问题建模思路。
1. 从一份 MATLAB 优化算法案例包说起:它到底能省掉多少重复劳动
做优化算法落地的人大概都有过这种体验:理论课上的遗传算法、粒子群、模拟退火讲得头头是道,真到要跑一个带约束的工程问题时,光是搭框架、调参数、画收敛曲线就能耗掉两三天。这份 MATLAB 优化算法案例分析代码,本质上就是把这些重复劳动打包了——它按算法分目录,每个案例给出可直接运行的脚本、目标函数、约束处理方式和结果可视化,覆盖遗传算法、粒子群、模拟退火、差分进化等常见启发式方法,也包含线性规划、非线性规划这类传统求解器的调用示例。适合两类人:一是刚接触优化、想对照可运行代码理解算法流程的新手;二是手头有实际问题、需要快速拿一个基线结果做对比的从业者。它不解决“算法选型对不对”这种判断问题,但能让你在半小时内把一条完整的求解链路跑通,把精力留给建模和调参。
2. 案例包的目录结构与运行环境:先跑通再谈改
拿到一个 MATLAB 案例包,最忌讳上来就逐个打开脚本读代码。正确顺序是先确认环境、再跑通一个最小案例、最后才按需拆解。这一章把目录组织、版本兼容和第一次运行的关键动作讲清楚,避免你在Undefined function和Not enough input arguments之间反复翻车。
2.1 目录组织与文件职责
这类案例包通常按算法名分文件夹,每个文件夹里至少包含三类文件:主运行脚本(如main_ga.m)、目标函数(如obj_fun.m)、约束或适应度计算函数。部分案例还会带plot_result.m做收敛曲线和种群分布图。常见结构如下:
| 文件/目录 | 职责 | 是否需改动 |
|---|---|---|
main_*.m | 参数设置、调用求解器、输出结果 | 首次运行不改 |
obj_fun.m | 目标函数,输入决策变量返回标量 | 换问题时要改 |
constraint*.m | 非线性约束,返回[c, ceq] | 有约束时启用 |
plot_*.m | 收敛曲线、种群散点、帕累托前沿 | 一般不改 |
data/*.mat | 测试数据或初始种群 | 按需替换 |
判断一个案例能不能直接跑,看主脚本开头有没有clear; clc; close all;和完整的参数赋值段。如果主脚本里出现load('xxx.mat')而目录里没有对应文件,这个案例就是残缺的,先跳过。
2.2 版本兼容与工具箱依赖
MATLAB 的优化函数在不同版本间有行为差异,尤其是ga、particleswarm、simulannealbnd这几个来自 Global Optimization Toolbox 的函数。运行前先执行下面这段检查,确认工具箱在位、版本够用:
% 检查优化相关工具箱与版本 v = ver; toolboxNames = {v.Name}; hasGlobalOpt = any(contains(toolboxNames, 'Global Optimization')); hasOptToolbox = any(contains(toolboxNames, 'Optimization Toolbox')); fprintf('MATLAB 版本: %s\n', version); fprintf('Global Optimization Toolbox: %d\n', hasGlobalOpt); fprintf('Optimization Toolbox: %d\n', hasOptToolbox);逻辑说明:ver返回已安装工具箱结构体数组,用contains做模糊匹配比精确匹配更稳,因为不同版本工具箱全称略有差异。参数上没什么可调的,重点看两个逻辑值是否都为 1。如果hasGlobalOpt为 0,遗传算法和粒子群的案例全部跑不了,需要先补装工具箱;如果只有传统规划案例能跑,说明包里的启发式部分对你当前环境不可用,别硬试。
提示:R2016b 之前的版本对脚本内局部函数支持有限,部分案例把子函数写在脚本末尾,老版本会报解析错误。遇到这种情况,把子函数拆成独立
.m文件即可,不需要改逻辑。
2.3 第一次运行:选一个无约束案例打底
不要拿带约束、多目标的案例做首次运行。挑一个main_ga.m里目标函数是简单二次型、没有非线性约束的案例,直接 F5 运行。正常表现是:命令行打印若干代的最优值,弹出收敛曲线图,最后输出最优解向量。如果卡在ga调用处报Too many output arguments,多半是目标函数签名写成了function [y] = obj_fun(x)而求解器按单输出调用,改成function y = obj_fun(x)即可。这一步的目的是建立“这个包在我机器上是活的”这个基线,后面改任何参数都有对照。
3. 遗传算法案例拆解:编码、适应度与参数怎么改
遗传算法是这类案例包里出现频率最高的算法,也是最容易被“能跑就行”糊弄过去的部分。真正要把它用到自己的问题上,得搞清楚三件事:决策变量怎么编码、适应度函数怎么写、种群规模和迭代次数怎么定。这一章按这三个点拆,每个点都给可抄的改法和判断依据。
3.1 编码方式与种群初始化
案例包里常见的编码是实数编码,直接以决策变量向量作为个体,配合ga的默认交叉变异算子。少数案例用二进制编码,通过CreationFcn和MutationFcn自定义。实数编码的好处是不用做编解码转换,边界处理直接靠lb、ub约束,适合连续变量问题;二进制编码适合离散或组合问题,但案例包里往往只给了框架,实际用起来还得自己补解码函数。
改编码方式时,重点看主脚本里这段:
% 遗传算法参数设置(实数编码) nvars = 5; % 决策变量个数 lb = zeros(1, nvars); % 下界 ub = 10 * ones(1, nvars); % 上界 options = optimoptions('ga', ... 'PopulationSize', 80, ... % 种群规模 'MaxGenerations', 300, ... % 最大迭代代数 'CrossoverFraction', 0.8, ... % 交叉比例 'FunctionTolerance', 1e-6, ... % 函数值收敛容差 'Display', 'iter'); % 逐代打印 [xBest, fBest] = ga(@obj_fun, nvars, [], [], [], [], lb, ub, [], options);逻辑说明:ga的前几个空矩阵[]分别对应线性不等式、线性等式约束,没有就留空。nvars必须和目标函数接受的向量长度一致,这是最常见的报错来源。参数上,PopulationSize取 50 到 100 是常规区间,变量维度高时往上加;MaxGenerations不要一上来设几千,先 200 到 300 看收敛曲线是否已经走平;CrossoverFraction默认 0.8,问题越连续越可以保持高位,离散问题适当降到 0.6 左右。FunctionTolerance设太小会让迭代白跑,1e-6 对多数工程问题够用。
3.2 适应度函数与约束处理
目标函数和适应度函数在无约束问题里是一回事,有约束时就得决定用罚函数还是用ga自带的非线性约束接口。案例包里两种写法都有,判断标准很简单:约束是显式不等式且容易计算违反量,用罚函数改适应度;约束来自仿真或黑箱,用nonlcon接口更省事。
罚函数写法的典型结构:
function fitness = obj_fun_penalty(x) % 原始目标 f = sum(x.^2); % 约束 g(x) <= 0,违反量平方惩罚 g1 = x(1) + x(2) - 5; % 示例约束 penalty = 1e4 * max(0, g1)^2; fitness = f + penalty; end逻辑说明:max(0, g1)保证只有违反约束时才加惩罚,满足约束时惩罚项为 0。惩罚系数1e4不是随便取的,太小约束压不住,太大会让适应度曲面变得陡峭、早熟收敛。经验做法是先取目标函数量级的 1e3 到 1e5 倍,跑一次看最优解是否满足约束,不满足就往上加一个数量级。用nonlcon接口时,约束函数返回[c, ceq],c <= 0、ceq = 0,别把符号搞反,这是血泪经验里排前三的坑。
3.3 收敛判断与结果可信度
跑完一次 GA 不能只看最后那个最优值。至少要看三样东西:收敛曲线是否走平、多次运行的解是否稳定、最优解是否贴着边界。收敛曲线在最后几十代还在明显下降,说明代数不够;同一组参数跑五次,最优值波动超过 5%,说明种群规模偏小或问题本身多峰严重;最优解某个分量正好等于lb或ub,要警惕是边界把解“顶”住了而不是真的最优。
案例包里的plot_result.m一般会画收敛曲线,但不会帮你做多次运行统计。补一段简单的重复运行统计很有必要:
% 多次运行统计,评估结果稳定性 nRuns = 5; bestVals = zeros(nRuns, 1); for k = 1:nRuns [~, fval] = ga(@obj_fun, nvars, [], [], [], [], lb, ub, [], options); bestVals(k) = fval; end fprintf('均值 %.4f,标准差 %.4f,最优 %.4f\n', ... mean(bestVals), std(bestVals), min(bestVals));逻辑说明:ga内部有随机数,不设rng每次结果不同,这正好用来评估稳定性。标准差相对均值超过 5% 就值得回头调PopulationSize。这段代码可以直接套到粒子群案例上,把ga换成particleswarm、参数名换成对应字段即可。
4. 粒子群与模拟退火:参数敏感性和常见误用
粒子群和模拟退火在案例包里往往和遗传算法并列出现,代码结构相似但参数含义差别很大。很多人把 GA 的调参习惯直接搬过来,结果 PSO 早熟、SA 不收敛。这一章分别讲两者的关键参数和典型误用,给出可对照的改法。
4.1 粒子群的速度更新与惯性权重
PSO 的核心参数是惯性权重w、个体学习因子c1、社会学习因子c2。案例包里常见固定w = 0.7、c1 = c2 = 1.5的写法,能跑但不够稳。更常见的做法是让w随迭代线性递减,前期偏探索、后期偏开发。
% 粒子群参数(线性递减惯性权重) options = optimoptions('particleswarm', ... 'SwarmSize', 60, ... % 粒子数 'MaxIterations', 400, ... % 最大迭代 'InertiaRange', [0.4 0.9], ... % 惯性权重范围,线性递减 'SelfAdjustmentWeight', 1.5, ...% c1 'SocialAdjustmentWeight', 1.8, ...% c2 'Display', 'iter'); [xBest, fBest] = particleswarm(@obj_fun, nvars, lb, ub, options);逻辑说明:InertiaRange给的是区间,求解器在迭代中从上限往下降,比固定值更适应多峰问题。SwarmSize一般取 40 到 80,维度高时加到 100 以上。c1偏大鼓励个体探索,c2偏大加快向群体最优靠拢,两者都取 2 左右是经典值,但c2略大于c1在多数工程问题上收敛更稳。注意particleswarm不接受非线性约束,有约束问题要么用罚函数,要么换ga或fmincon,这是选型时先要确认的边界。
4.2 模拟退火的温度调度与终止条件
模拟退火的关键在降温策略。案例包里常见指数降温T = T0 * alpha^k,alpha取 0.95 到 0.99。alpha越接近 1 降温越慢、搜索越充分但耗时越长;取 0.9 以下往往还没搜到好解温度就掉没了。
% 模拟退火参数 options = optimoptions('simulannealbnd', ... 'InitialTemperature', 100, ... % 初始温度 'TemperatureFcn', @temperatureexp, ... % 指数降温 'MaxIterations', 1000, ... % 最大迭代 'MaxFunctionEvaluations', 5000, ... % 函数评估上限 'ReannealInterval', 100, ... % 每 100 次评估回温一次 'Display', 'iter'); [xBest, fBest] = simulannealbnd(@obj_fun, x0, lb, ub, options);逻辑说明:InitialTemperature要跟目标函数量级匹配,目标值在 1e3 量级时初始温度取 100 左右合理,取 1 基本等于没退火。ReannealInterval是很多人忽略的参数,定期回温能跳出局部最优,代价是总评估次数上升。MaxFunctionEvaluations是真正的硬约束,MaxIterations到了但评估次数没到,求解器还会继续,两个都要设。模拟退火对初始解x0敏感,案例包里如果x0是随机生成的,记得固定随机种子做对比实验。
4.3 三种启发式算法的选型对照
案例包把几种算法摆在一起,最容易犯的错是“哪个跑得快用哪个”。实际选型要看问题特征,下面这张对照表可以作为快速判断依据:
| 维度 | 遗传算法 | 粒子群 | 模拟退火 |
|---|---|---|---|
| 适合变量类型 | 连续、离散均可 | 偏连续 | 连续为主 |
| 约束支持 | 线性+非线性 | 仅边界 | 仅边界 |
| 参数敏感度 | 中 | 高 | 高 |
| 多峰问题表现 | 较好 | 易早熟 | 较好但慢 |
| 单次运行耗时 | 中 | 低 | 高 |
判断逻辑:有非线性约束直接排除 PSO 和 SA;变量维度超过 20 且目标函数评估便宜,优先 PSO;问题多峰明显、对时间不敏感,SA 的回温机制有优势。这张表不是绝对结论,但能帮你少走“用 PSO 硬套带约束问题然后发现根本不支持”的弯路。
5. 避坑与排查:跑不通、结果差、复现难的三类问题
案例包用起来最耗时的不是写代码,是排查。下面五条是按“现象 → 原因 → 解决”整理的踩坑记录,覆盖环境、语法、结果和复现四个层面,基本能覆盖新手前两周会遇到的大部分问题。
5.1 报错Undefined function 'ga'
现象:运行主脚本直接报找不到ga或particleswarm。原因:Global Optimization Toolbox 未安装或未授权。解决:先跑 2.2 节的工具箱检查脚本,确认逻辑值为 0 后补装工具箱;如果环境不允许装,把该案例换成传统规划部分(linprog、fmincon),这两类函数在 Optimization Toolbox 里,依赖更基础。
5.2 目标函数维度不匹配
现象:报Index exceeds matrix dimensions或Not enough input arguments。原因:nvars与目标函数内部索引的变量个数不一致,或者目标函数签名写成了多输入。解决:打开目标函数数一下x(1)到x(n)的最大下标,把主脚本nvars改成一致;目标函数统一写成单输入function y = obj_fun(x),需要额外参数用匿名函数包装,如ga(@(x) obj_fun(x, p1), nvars)。
5.3 结果每次都不一样,无法复现
现象:同一份代码跑两次最优值差很多。原因:启发式算法内部用随机数,未固定种子。解决:在主脚本开头加rng(42),42可换成任意整数,关键是同一批对比实验用同一个种子。注意固定种子只保证可复现,不保证结果好,稳定性评估仍要按 3.3 节做多次运行。
5.4 收敛曲线正常但解不可用
现象:算法报告收敛,最优解却违反约束或落在无意义区域。原因:罚函数系数过小、约束符号写反、或者边界设置过宽导致搜索空间里大量无效解。解决:先检查nonlcon返回的c是否满足c <= 0;罚函数系数按 3.2 节往上调一个数量级;边界lb、ub按物理意义收紧,别图省事设成-inf、inf。
5.5 换了自己的目标函数后速度骤降
现象:原案例几秒跑完,换成自己的函数后几分钟一代。原因:目标函数里有load、文件读写或重复计算,被每代每个个体调用一次,开销被放大几百倍。解决:把不变的数据在调用前算好存成变量,用匿名函数传入;目标函数内部避免clear、load;必要时用tic/toc在目标函数里打点,确认耗时到底在哪一行。
6. 把案例改成自己的问题:一条可复用的改造路径
案例包的价值不在原样运行,而在把它当成模板改。改造路径可以固定成四步:先定决策变量和边界,再写目标函数并用单点测试,然后选约束处理方式,最后做参数扫描找稳定区间。这套流程我每次接新问题都会走一遍,比直接改现成脚本少很多返工。
第一步,把决策变量列成表,写清每个变量的物理含义、量纲和合理范围,直接映射到lb、ub。量纲差异大的变量先做归一化,否则 PSO 的速度更新会被大量纲变量主导。第二步,目标函数先用一个已知点验证返回值是否符合手算预期,这一步能挡掉大部分索引错位和符号错误。第三步,约束能写成线性的就用A、b、Aeq、beq传进去,比罚函数稳;非线性约束优先用nonlcon,实在不行再上罚函数。第四步,对PopulationSize或SwarmSize做一次小范围扫描,比如取 40、60、80 各跑五次,看均值和标准差,选稳定区间而不是选单次最优。
下面这段参数扫描脚本可以直接套用,改算法名和参数名即可:
% 种群规模扫描,评估稳定性 sizes = [40 60 80]; results = zeros(numel(sizes), 5); for i = 1:numel(sizes) opts = optimoptions('ga', 'PopulationSize', sizes(i), ... 'MaxGenerations', 200, 'Display', 'off'); for k = 1:5 rng(k); % 每个规模用相同种子序列,保证可比 [~, fval] = ga(@obj_fun, nvars, [], [], [], [], lb, ub, [], opts); results(i, k) = fval; end end meanVal = mean(results, 2); stdVal = std(results, 0, 2); table(sizes', meanVal, stdVal, 'VariableNames', {'PopSize','Mean','Std'})逻辑说明:外层循环扫种群规模,内层固定种子序列跑五次,保证不同规模之间可比。std(results, 0, 2)的第二个参数 0 表示按样本标准差(除以 n-1),第三个参数 2 表示沿列方向求标准差,得到每个规模对应的波动。输出表格里均值低且标准差小的规模就是相对稳定的选择。这套方法同样适用于 PSO 的SwarmSize和 SA 的InitialTemperature,只是把optimoptions里的字段换掉。
最后说一个习惯:每次改完参数或目标函数,我都会先把rng固定、跑三次、把最优值和耗时记在一张表里,再决定要不要继续调。优化算法本身带随机性,不记录就没法判断改动是有效还是运气。从那以后我每次换问题都强制走一遍“单点验证 → 约束确认 → 参数扫描 → 多次记录”这四步,翻车次数明显少了。希望帮到你。
本文还有配套的精品资源,点击获取