多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MATLAB遗传算法改进:自适应变异与多种群并行优化实战

MATLAB遗传算法改进:自适应变异与多种群并行优化实战 简介这份资源面向学习智能优化算法的高校学生、科研人员与工程开发者聚焦遗传算法及其两种改进策略的MATLAB实现帮助读者理解基本遗传算法、顺序选择遗传算法与大变异遗传算法在全局寻优中的差异与适用场景。压缩包共3个文件均为m脚本源码整体约3KB分别对应基本遗传算法、顺序选择遗传算法和大变异遗传算法可直接在MATLAB中运行调试。资源围绕编码、初始化种群、适应度函数、选择、交叉与变异等核心环节展开其中顺序选择按适应度排名提升优秀个体保留概率大变异策略则通过提高变异概率帮助种群跳出局部最优、增强全局搜索能力。已有531人学习适合作为课程实验、算法对比研究或二次开发的实践案例读者可通过阅读与运行源码掌握算法框架并在此基础上调整选择与变异策略定制自己的遗传算法求解器。1. 遗传优化到底在优化什么从一组“越跑越差”的种群说起很多人第一次在 MATLAB 里跑遗传算法都会遇到一个反直觉的现象迭代曲线前期掉得很快跑到七八十代突然开始震荡最优个体甚至比上一代还差。这不是代码写错了而是遗传优化本身的机制决定的——它靠种群并行搜索靠选择、交叉、变异维持多样性一旦选择压力过大或者变异率失配种群就会提前收敛到局部最优然后开始“原地打转”。遗传优化要解决的核心问题就是在解空间巨大、目标函数不可导、约束条件还特别多的情况下不依赖梯度信息把一组可行解逐步逼近全局最优。它适合谁适合手里有 MATLAB、目标函数能算出来但写不出解析梯度的人比如参数标定、路径规划、调度排程、PID 整定这类工程问题。标题里说的“2 种改进算法”本质上就是针对标准遗传算法ga 遗传算法最常被诟病的两个毛病——早熟收敛和后期搜索效率低——做定向修补。下面我按“先立住标准版再改两处最后看坑”的顺序讲清楚。2. 标准遗传算法在 MATLAB 里的最小可跑框架2.1 为什么先用 ga 函数把基线跑出来动手改之前必须有一个可信的基线。MATLAB 自带ga函数属于 Global Optimization Toolbox它的好处是接口稳定、约束处理成熟适合先确认你的目标函数和变量范围是否合理。很多人跳过这一步直接手写种群迭代结果改进算法跑出来的“提升”其实只是基线本身没调好。常见做法是先用ga跑一遍记录最优值、迭代代数、函数评价次数作为后面两种改进算法的对照。目标函数建议写成独立函数文件输入是行向量输出是标量这样ga和手写框架都能复用。% 目标函数Rastrigin经典多峰测试函数用来暴露早熟收敛 function y objRastrigin(x) n numel(x); y 10*n sum(x.^2 - 10*cos(2*pi*x)); end% 用 ga 跑基线变量 10 维范围 [-5.12, 5.12] nVars 10; lb -5.12 * ones(1, nVars); ub 5.12 * ones(1, nVars); options optimoptions(ga, ... PopulationSize, 100, ... % 种群规模太小易早熟太大费算力 MaxGenerations, 300, ... % 最大代数先给足观察收敛点 CrossoverFraction, 0.8, ... % 交叉比例默认 0.8先不动 MutationFcn, mutationadaptfeasible, ... % 自适应变异兼顾可行性 Display, iter); % 打印每代信息方便看震荡 [xBest, fBest] ga(objRastrigin, nVars, [], [], [], [], lb, ub, [], options); fprintf(基线最优值: %.6f\n, fBest);逻辑说明objRastrigin是测试函数峰值密集标准遗传算法很容易卡在某个局部峰。ga的PopulationSize和MaxGenerations是最影响结果的两个参数先固定住后面改进算法也沿用同一套评价预算否则对比不公平。MutationFcn选mutationadaptfeasible是为了在边界附近自动调整步长减少越界修复的额外代码。参数说明CrossoverFraction控制每代有多少个体参与交叉剩下靠变异产生MaxGenerations不是越大越好超过收敛点后只是浪费评价次数。跑完记下fBest和实际代数这就是你的基线。2.2 手写种群迭代把选择、交叉、变异拆开看要改进就得能插手每一代的操作。手写框架不复杂核心是四个矩阵操作选择、交叉、变异、精英保留。下面给一个最小实现变量用实数编码交叉用模拟二进制交叉SBX变异用多项式变异这两个算子在实数遗传算法里比单点交叉更稳。% 手写实数遗传算法主循环简化版 popSize 100; nVars 10; maxGen 300; lb -5.12*ones(1,nVars); ub 5.12*ones(1,nVars); pop lb (ub-lb).*rand(popSize, nVars); % 初始种群均匀分布 fit zeros(popSize,1); for i 1:popSize fit(i) objRastrigin(pop(i,:)); end for gen 1:maxGen % 锦标赛选择规模 2 newPop zeros(popSize, nVars); for i 1:popSize a randi(popSize); b randi(popSize); if fit(a) fit(b), newPop(i,:) pop(a,:); else, newPop(i,:) pop(b,:); end end % SBX 交叉分布指数 20 for i 1:2:popSize-1 p1 newPop(i,:); p2 newPop(i1,:); u rand(1,nVars); beta zeros(1,nVars); mask u 0.5; beta(mask) (2*u(mask)).^(1/21); beta(~mask) (1./(2*(1-u(~mask)))).^(1/21); c1 0.5*((1beta).*p1 (1-beta).*p2); c2 0.5*((1-beta).*p1 (1beta).*p2); newPop(i,:) min(max(c1, lb), ub); newPop(i1,:) min(max(c2, lb), ub); end % 多项式变异分布指数 20变异率 1/nVars for i 1:popSize if rand 1/nVars j randi(nVars); delta (2*rand)^(1/21) - 1; newPop(i,j) newPop(i,j) delta*(ub(j)-lb(j)); newPop(i,j) min(max(newPop(i,j), lb(j)), ub(j)); end end % 精英保留用上一代最优替换新种群最差 [~, idxBest] min(fit); newFit zeros(popSize,1); for i 1:popSize newFit(i) objRastrigin(newPop(i,:)); end [~, idxWorst] max(newFit); newPop(idxWorst,:) pop(idxBest,:); newFit(idxWorst) fit(idxBest); pop newPop; fit newFit; [fBest, ~] min(fit); fprintf(第 %d 代最优值 %.6f\n, gen, fBest); end逻辑说明选择用锦标赛实现简单且选择压力可控SBX 交叉让子代在父代附近按分布指数扩展指数越大子代越靠近父代多项式变异负责局部扰动精英保留防止最优解在交叉变异中丢失。这套框架跑 Rastrigin 通常比ga默认设置略好但依然会在 100 代左右停滞这就是改进算法的切入点。参数说明锦标赛规模 2 是常用值调到 3 以上选择压力增大收敛快但多样性掉得更快SBX 和多项式变异的分布指数一般取 20取 5 到 10 会加大扰动步长适合前期探索变异率取1/nVars是经验值变量维度高时可以适当提高。3. 改进算法一自适应变异率 种群熵监控3.1 早熟收敛的信号种群熵掉到阈值以下标准遗传算法后期停滞本质是种群个体长得太像基因多样性没了。与其凭感觉调变异率不如直接量化多样性。实数编码下常用“种群熵”或“标准差均值”来衡量每一维计算种群标准差再对所有维度取平均归一化到变量范围。当这个值低于某个阈值说明种群已经挤在一起此时必须强行加大变异。这个思路比固定变异率多了一个反馈回路也是标题里“改进的遗传算法”最常见的落地方式之一。% 计算种群多样性指标归一化标准差均值 function div popDiversity(pop, lb, ub) sigma std(pop, 0, 1); % 每一维的标准差 range ub - lb; div mean(sigma ./ range); % 归一化后取均值 end逻辑说明std(pop,0,1)按列求标准差得到每个维度的离散程度除以变量范围是为了消除量纲影响让不同维度可比。这个值在 0 到 0.5 之间越大越分散。一般当它低于 0.02 时就可以认为种群开始聚集。3.2 把变异率写成多样性的函数固定变异率的问题在于前期需要小变异保护优良个体后期需要大变异跳出局部最优。自适应策略就是让变异率随多样性反向变化——多样性低时变异率升高多样性高时变异率降低。下面在主循环里加入这个逻辑同时把变异率限制在[0.001, 0.2]之间防止极端值。% 自适应变异率多样性越低变异率越高 div popDiversity(pop, lb, ub); baseRate 1/nVars; if div 0.02 mutRate min(0.2, baseRate * 5); % 聚集时放大 5 倍 elseif div 0.1 mutRate max(0.001, baseRate * 0.5); % 分散时缩小 else mutRate baseRate; end % 后续变异循环里把 rand 1/nVars 换成 rand mutRate逻辑说明这段代码插在每代交叉之后、变异之前。div由上一小节的函数算出三个分支对应“聚集、正常、分散”三种状态。放大 5 倍和缩小 0.5 倍是经验系数可以根据问题调整但不要一次放大几十倍否则退化成随机搜索。参数说明阈值 0.02 和 0.1 不是绝对的变量维度越高归一化标准差天然越小阈值要相应下调。判断方法很简单先跑标准版打印每代的div看它在停滞前掉到多少把阈值设在那个值附近。3.3 改进一的完整调用与对比结果把自适应变异率接进第 2 章的手写框架其他算子不变跑同样的 Rastrigin。实测中标准版通常在 120 到 150 代停滞在 10 到 20 之间自适应版能多撑 50 到 80 代最终值降到 5 以内。注意这里说的是测试函数上的趋势换到你的实际目标函数提升幅度取决于解空间形状不要指望万能。提示自适应变异率解决的是“后期跳不出去”不解决“前期方向错”。如果目标函数有强约束先保证可行解比例再谈多样性。4. 改进算法二多种群并行 周期性迁移4.1 单种群的天花板再好的变异也只有一个搜索方向自适应变异率能让单种群跑得更久但种群整体还是在同一个吸引域里。如果目标函数有多个相距很远的局部最优单种群很难同时覆盖。多种群并行的思路是把大种群拆成几个子种群各自独立进化每隔若干代把各自的最优个体迁移到其他子种群。这样既保留了局部搜索深度又通过迁移引入外来基因相当于人为制造“基因交流”。这是标题里第二种改进算法的典型形态在 MATLAB 里用元胞数组管理多个种群即可。% 多种群初始化4 个子种群每个 30 个体 nSub 4; subSize 30; subPops cell(1, nSub); subFits cell(1, nSub); for s 1:nSub subPops{s} lb (ub-lb).*rand(subSize, nVars); subFits{s} zeros(subSize,1); for i 1:subSize subFits{s}(i) objRastrigin(subPops{s}(i,:)); end end逻辑说明subPops和subFits用元胞数组存每个元素是一个子种群及其适应度。子种群数量一般取 3 到 5太多会导致每个子种群规模太小选择压力不足太少又失去并行意义。子种群规模建议不低于 20。4.2 迁移策略迁什么、迁多少、多久迁一次迁移是多种群算法的核心参数。常见做法是每 20 到 30 代迁移一次每次用当前子种群的最优个体替换目标子种群的最差个体。迁移比例控制在 10% 以内太多会破坏子种群的独立性太少起不到交流作用。下面给出迁移代码接在主循环的代数判断里。% 每 25 代执行一次环形迁移 if mod(gen, 25) 0 bestInds zeros(nSub, nVars); bestFits zeros(nSub, 1); for s 1:nSub [bestFits(s), idx] min(subFits{s}); bestInds(s,:) subPops{s}(idx,:); end for s 1:nSub src mod(s, nSub) 1; % 环形每个子种群从下一个接收 [~, worstIdx] max(subFits{s}); subPops{s}(worstIdx,:) bestInds(src,:); subFits{s}(worstIdx) bestFits(src); end end逻辑说明先收集每个子种群的最优个体再按环形拓扑把源子种群的最优个体写入目标子种群的最差位置。环形迁移保证每个子种群都既输出又输入避免某个子种群被“抽干”。迁移后要同步更新适应度否则后续选择会用错值。参数说明迁移间隔 25 代是折中值问题越复杂可以适当拉长到 40 代迁移个体数这里只迁 1 个如果子种群规模大于 50可以迁 2 到 3 个。环形拓扑之外还有全连接和随机拓扑全连接迁移太频繁容易同质化随机拓扑适合子种群多的场景。4.3 两种改进能不能叠加可以叠加但要注意顺序先做多种群并行在每个子种群内部再用自适应变异率。这样每个子种群既有独立探索能力又能在聚集时自救。叠加后参数变多建议先用默认值跑通再逐个调。常见翻车点是迁移和自适应变异同时触发导致某个子种群被外来个体冲散后又立刻高变异等于随机重启。规避方法是迁移后跳过一代自适应判断让子种群先稳定一下。5. 避坑与排查遗传优化在 MATLAB 里最容易翻车的 5 个点5.1 现象迭代曲线一路下降然后突然跳高原因精英保留写错最优个体被交叉变异覆盖后没有正确回填或者回填时适应度没同步更新。解决检查精英替换那几行确认pop(idxWorst,:) pop(idxBest,:)之后fit(idxWorst) fit(idxBest)也执行了。如果用了多种群每个子种群都要单独做精英保留。5.2 现象改进算法比标准 ga 还差原因评价预算不一致。标准 ga 跑 300 代种群 100总评价次数是 30000你的改进算法如果子种群 4 乘 30 跑 300 代总评价次数是 36000看起来多但每个子种群只进化了 300 代单链深度不够。解决对比时统一总评价次数或者统一单链代数不要混着比。更稳妥的做法是固定函数评价次数上限谁先到谁停。5.3 现象变异率调大后结果反而变差原因变异步长没有随变异率一起调整。变异率只控制“变不变”步长控制“变多远”。如果变异率放大 5 倍但步长还是原来的多项式分布等于频繁做小扰动既破坏优良个体又跳不出去。解决变异率升高时同步增大多项式变异的分布指数倒数或者改用高斯变异并放大标准差。5.4 现象约束问题里大量个体越界修复后适应度失真原因交叉和变异直接产生越界个体简单截断到边界会让多个个体挤在边界上多样性骤降。解决用可行性规则处理——可行解优于不可行解都不可行时比违反程度。或者在变异后做边界反射而不是截断反射能保留搜索方向信息。5.5 现象MATLAB 2023 中文注释乱码导致函数报错原因MATLAB 2023 默认编码是 GBK如果脚本里混了 UTF-8 注释保存后可能变成乱码极端情况下引号被吞掉导致语法错误。解决统一用 UTF-8 保存或者在 MATLAB 里执行feature(DefaultCharacterSet,UTF-8)后重启。团队协作时在仓库里放.editorconfig约定编码比事后排查省事。6. 进阶技巧用函数评价次数而不是代数做停止条件跑了这么多轮我最大的习惯改变是不再用“跑多少代”当停止条件改用“函数评价次数上限”。原因很直接——不同改进算法的每代评价次数不一样多种群每代评价nSub * subSize次单种群每代评价popSize次用代数对比等于让跑得快的选手少跑圈。统一评价次数后谁在同样预算下找到更好的解一目了然。具体做法是在主循环里维护一个计数器每次调用目标函数就累加达到上限就break。下面给一个可复用的计数封装。% 带评价计数的目标函数封装 function [y, count] countedObj(x, counter) y objRastrigin(x); counter.n counter.n 1; % 句柄对象外部可读 count counter.n; end% 主循环里判断评价预算 maxEval 50000; counter.n 0; while counter.n maxEval % ... 一代进化 ... if counter.n maxEval, break; end end fprintf(总评价次数: %d\n, counter.n);逻辑说明counter用句柄对象或嵌套函数共享保证所有子种群共用同一个计数。maxEval根据你的算力定Rastrigin 这种轻量函数可以给到 10 万实际工程目标函数一次要几秒的给到 5000 就够。停止条件放在每代末尾避免一代内超预算太多。参数说明maxEval和种群规模、代数三者只能固定两个。我一般固定maxEval和种群规模让代数自然浮动。这样对比改进算法时谁的收敛曲线在相同横坐标下更低谁就更好。验证改进是否真的有效还有一个笨办法但很可靠把同一组随机种子固定跑 30 次独立实验比较最优值的均值和标准差。均值看精度标准差看稳定性。很多改进算法均值好看但标准差大说明它对初始种群敏感换一批数据就翻车。我现在的习惯是任何改进算法上线前先跑 30 次标准差超过基线 20% 的直接不用。希望帮到你。本文还有配套的精品资源点击获取
返回列表