简介:面向智能仿生算法优化研究的Matlab实现资源,围绕 Tent 映射种群初始化与自适应权重因子平衡全局探索和局部开发这一核心改进思路,提供完整的天鹰优化算法(IAO)改进代码。资源共11个文件,包含10个.m脚本与1个.xlsx说明文档,压缩包仅16KB,按初始化、边界约束、主程序等模块划分,结构清晰,易于在MATLAB中直接运行和对比实验。算法内置23个标准测试函数,仿真图中将改进后IAO与原始AO在收敛速度与精度上进行直观对比,有助于理解两种策略对算法性能的影响。附带说明文档详细阐述所用策略原理,降低上手门槛。已有278人学习下载,适合从事群智能算法改进、函数优化实验及相关课题研究的算法工程师和研究生参考使用。
1. 天鹰优化算法为什么要做 Tent 映射和自适应因子改进
天鹰优化算法(AO)是智能仿生算法里出现较晚、复现成本低的一个代表,2021 年提出后,在路径规划、参数辨识、调度排程这些问题上被反复套用。它的问题也很典型:初始种群完全靠rand均匀随机,碰到某些函数容易在迭代初期就挤在局部峰附近;中后期从探索切换到开发只依赖迭代次数阈值,碰到形态复杂的代价面,切换时机判断经常不灵。Tent 映射把随机初始化换成混沌初始化,改善个体在解空间里的分布质量;自适应因子再拿一个随迭代递减的权重去调节最优个体的牵引力,让探索与开发的过渡更平滑。下面给出一套可直接运行的 MATLAB 实现,并讲清楚每一步的参数为什么这么设,以及对比实验里哪些坑会导致你的改进看起来“没效果”。
2. 改进天鹰优化算法的两个核心:Tent 映射初始化与自适应因子设计
先说明一个容易混淆的术语:标题里的“Tent 映射”指的是用帐篷映射生成混沌序列,再用序列值映射到搜索空间完成种群初始化;“自适应因子”则是一个随迭代进度变化的系数,嵌进天鹰优化算法的四个位置更新策略里。这两处改动都不改变 AO 的算法骨架,属于典型的结构内改进,适合做控制变量对比。
2.1 为什么把初始化换成 Tent 混沌映射而不是 Logistic 映射
Tent 映射的标准形式如下:当r < 0.5时取2 * r,否则取2 * (1 - r)。这个分段函数画出来像一个帐篷,所以叫帐篷映射。与 Logistic 映射比起来,Tent 映射在[0,1]区间上遍历性更均匀,序列不会在两端附近堆积。用真实场景解释就是:Logistic 初始化在高维情况下容易让某些维度上的个体扎堆在边界,而 Tent 映射产生的初始种群整体分布更散开,后续迭代不容易因为初始覆盖不足而漏掉最优区域。
MATLAB 里一个足够健壮的 Tent 子函数可以写成这样:
function r_new = tent_map(r) % 输入 r 在 (0,1) 内,输出下一次迭代的混沌值 if r <= 0 || r >= 1 r = 0.1; % 防止落在不动点 end if abs(r - 0.5) < 1e-6 r = r + 1e-6; % 0.5 会映射到 1,再下一步变成 0,序列锁死 end if r < 0.5 r_new = 2 * r; else r_new = 2 * (1 - r); end if r_new >= 1 r_new = 1 - 1e-6; % 同样避免出现 1 end end这段代码的逻辑说明:先排除0和1这种会导致迭代停滞的初值;再处理0.5这个特殊点,因为0.5下一步变成1,再下一步变成0,整个序列就死掉了。最后的r_new >= 1判断是为了防止浮点运算产生精确的1。实际调用时,初值r = rand,然后循环调用这个子函数生成一整条混沌序列。
选 Tent 不是因为它比 Logistic 更“高级”,而是因为同样的代码量下,Tent 的均匀遍历特性更适合种群初始化。如果你的问题维度在 30 以内,Tent 和随机初始化的差距可能不明显;但到了dim >= 50或者目标函数有大量局部极值时,Tent 初始化的个体更容易覆盖多个吸引域,后续对比实验里多峰函数的表现差距会拉开。
2.2 自适应因子 ω 的数学形式与设计动机
自适应因子我用余弦递减形式:
omega = cos(0.5 * pi * t / Tmax);t是当前迭代数,Tmax是最大迭代数。当t = 1时omega接近1,当t = Tmax时omega接近0。选择余弦而不是线性递减,是因为余弦曲线前期的衰减比较平缓,后期衰减加快,这样算法在最开始能保持较强的全局搜索能力,到了最后阶段再快速收敛到当前最优附近。这个特性在有很多局部峰的函数上比线性递减更稳。
这个omega具体嵌在位置更新里:探索阶段用它控制种群均值对当前个体的牵引强度,开发阶段用它控制最优解对个体的吸引力度。直观理解就是,前期每个个体更愿意往远离最优的区域跑,避免大家都挤到一个局部峰里;后期个体逐步收拢到最优解附近做精细搜索。
2.3 改进后的四阶段更新公式与参数含义
天鹰优化算法的四个阶段在改进后保留原结构,只替换系数。四个策略的简化复现形式如下:
x1(扩大探索):X_new = X_best * (1 - t/Tmax) + omega * (X_mean - X_best * rand)
x2(缩小探索):X_new = X_best * levy_flight(dim) + X_rand + 0.01 * omega * (X_mean - X(i,:))
x3(扩大开发):X_new = omega * (X_best - X_mean) - rand + 0.01 * (ub - lb) * rand
x4(缩小开发):X_new = omega * X_best - G1 * X(i,:) * rand - (1 - omega) * G2 .* levy_flight(dim) + rand * G1
其中G1 = 2 * rand - 1,G2 = 2 * (1 - t/Tmax)。与原版 AO 相比,改动点集中在X_best和X_mean前面的系数上:原版是固定系数或只依赖 t 的线性系数,现在统一换成omega,让整个搜索过程在同一个自适应框架下推进。
下面这张表是这套改进里最需要关注的参数,也是你调参时的主要对象:
| 参数 | 代码对应 | 常用取值 | 影响 |
|---|---|---|---|
| 自适应因子 ω | cos(0.5*pi*t/Tmax) | 1 到 0 递减 | 前期探索强度,后期收敛速度 |
| Tent 映射参数 | tent_map内 | 固定 0.5 | 改变混沌序列分布形态 |
| Levy 飞行 β | levy_flight内 | 1.5 | 小于 1.5 重尾更明显,越界概率增加 |
| x2 扰动系数 | 0.01 * omega | 0.001 到 0.1 | 控制探索阶段的随机游走幅度 |
| x3 扰动系数 | 0.01 * (ub - lb) | 0.001 到 0.05 | 控制开发阶段的局部扰动步长 |
这些参数里,ω和 x2 的扰动系数对结果影响最大。0.01 是我在多数基准函数上的经验值,如果你发现收敛曲线全程平稳但最终精度差,优先减小这个扰动系数;如果收敛曲线下降太慢,优先调大它。
3. MATLAB 完整代码:从 Tent 初始化到迭代收尾
代码采用单文件结构:把主函数、tent_map、levy_flight按照主函数在前、子函数在后的顺序放在同一个IAO.m文件里。这样不需要额外管理多个文件,运行时不会出现路径问题,也方便你直接复制到自己的工作目录。
3.1 主函数签名和参数约定
function [Best_pos, Best_score, Convergence_curve] = IAO(N, Tmax, lb, ub, dim, fobj) % 改进天鹰优化算法 % N 种群规模 % Tmax 最大迭代次数 % lb 下界,标量或 1 x dim 向量 % ub 上界,标量或 1 x dim 向量 % dim 决策变量维度 % fobj 目标函数句柄,输入 1 x dim,输出标量 if isscalar(lb) lb = repmat(lb, 1, dim); end if isscalar(ub) ub = repmat(ub, 1, dim); end这段主要做输入标准化。很多人写代码时默认lb和ub是标量,一旦遇到每维边界不同的问题就要改内部逻辑。我这里提前转成行向量,后面所有(ub - lb)的向量运算都能正常工作。
3.2 Tent 初始化:每维独立生成一条混沌序列
% Tent 混沌映射初始化种群 X = zeros(N, dim); for d = 1:dim r = rand; for i = 1:N r = tent_map(r); X(i, d) = lb(d) + r * (ub(d) - lb(d)); end end这里的关键决策是“每维一条序列”。如果只生成一条长度为N * dim的混沌序列再整体 reshape,维度之间会出现序列相关性,等于人为破坏了搜索空间的均匀覆盖。每维重新从随机初值开始迭代,用N个连续混沌值填充这一维,种群在这个维度上的投影是近似均匀的。
初始化之后要马上评估一次适应度:
fitness = zeros(N, 1); for i = 1:N fitness(i) = fobj(X(i, :)); end注意fobj接收的是行向量。如果你自己的目标函数写成接受列向量,这里会直接报错,后面第 5 章会再强调接口问题。
3.3 主循环:四阶段位置更新与自适应因子嵌入
主循环是整个改进的核心,代码比原版 AO 多了omega的计算和嵌入:
for t = 1:Tmax omega = cos(0.5 * pi * t / Tmax); X_mean = mean(X, 1); [~, idx] = min(fitness); X_best = X(idx, :); for i = 1:N X_rand = X(randi(N), :); if t <= (2/3) * Tmax % 探索阶段:保留原 x1、x2 策略 if rand < 0.5 X_new = X_best * (1 - t / Tmax) + omega * (X_mean - X_best * rand); else X_new = X_best * levy_flight(dim) + X_rand + 0.01 * omega * (X_mean - X(i, :)); end else % 开发阶段:x3、x4 策略 if rand < 0.5 X_new = omega * (X_best - X_mean) - rand + 0.01 * (ub - lb) * rand; else G1 = 2 * rand - 1; G2 = 2 * (1 - t / Tmax); X_new = omega * X_best - G1 * X(i, :) * rand - (1 - omega) * G2 .* levy_flight(dim) + rand * G1; end end这段代码的逻辑说明:omega在主循环最外层计算,因此同一个迭代步里的所有个体使用同一个自适应因子,保证策略切换的同步性。X_mean是整个种群的平均位置,它代表当前群体的整体中心;X_best是当前最优个体。探索阶段中omega乘在均值差分项上,让前期均值牵引更强;开发阶段中omega直接控制最优解的权重,后期最优解对个体的吸引越来越强。
x2里的 Levy 飞行函数定义如下:
function L = levy_flight(d) beta = 1.5; sigma = (gamma(1 + beta) * sin(pi * beta / 2) / ... (gamma((1 + beta) / 2) * beta * 2^((beta - 1) / 2)))^(1 / beta); u = randn(1, d) * sigma; v = randn(1, d); L = u ./ (abs(v).^(1 / beta)); endLevy 飞行是一种重尾随机游走,偶尔会出现很大的步长,这正是探索阶段需要的行为。beta = 1.5是经验常用值,它的作用是控制重尾程度:beta越小,大步长出现的概率越高,容易越过边界但也更容易跳出局部极值。
3.4 边界修复与贪心选择:别让改进效果毁在最后一步
位置更新完成后,要做边界修复、评估、贪心选择:
% 边界修复 X_new = min(max(X_new, lb), ub); % 贪心选择 fnew = fobj(X_new); if fnew < fitness(i) X(i, :) = X_new; fitness(i) = fnew; end end [Best_score, idx] = min(fitness); Best_pos = X(idx, :); Convergence_curve(t) = Best_score; end边界修复我直接用了最省事的夹取,也就是min(max(X_new, lb), ub)。这个做法的副作用是:当种群已经收敛到边界附近时,很多个体会被压到同一条边界上,多样性快速下降。如果遇到这个问题,可以把夹取改成反射边界或随机重建,但更简单有效的办法是在夹取后对“恰好等于边界”的个体加一个小幅高斯扰动。这个方法我只在有边界解的问题上使用,普通基准函数夹取就够用了。
贪心选择保证了每个个体只会接受更优的位置,所以收敛曲线一定是单调不增的。如果跑出来的曲线出现明显上升,检查一下是不是fnew和目标函数里用了全局变量或随机种子,这种问题在工程嵌入时经常出现。
4. 在基准函数上做对比:AO 与改进 AO 的验证流程与参数修正
改进算法写完后,第一件事不是直接拿到工程问题上跑,而是先用基准函数验证改进是否真的有效。基准函数选择的经验是:先一个单峰函数确认代码逻辑,再一个多峰函数确认不会早熟。Sphere 适合当第一个验证函数,Rastrigin 和 Ackley 适合当多峰压力测试。
4.1 用同一个随机种子做控制变量对比
对比实验里最常见的错误是两行代码分别跑 AO 和 IAO,中间没有重置随机数流,结果初始种群完全不同,无法判断提升来自算法还是随机运气。我一般不用网上现成的 AO 包,而是直接在IAO.m上做两处修改得到对照版:把初始化改回均匀随机,并把omega固定为 1。
% 对照 AO 版需要修改的两处: % 1. 初始化替换成 X = lb + rand(N, dim) .* (ub - lb); % 2. 主循环里 omega 固定 omega = 1;这样对照组与改进组共享完全相同的代码路径,唯一的变量就是 Tent 初始化和自适应因子,结论才站得住。测试脚本结构如下:
num_runs = 30; scores_AO = zeros(num_runs, 1); scores_IAO = zeros(num_runs, 1); for r = 1:num_runs rng(r, 'twister'); % 每次运行前锁定随机数 [~, fmin_AO, ~] = AO(N, Tmax, lb, ub, dim, fobj); rng(r, 'twister'); % 用同一个种子跑改进版 [~, fmin_IAO, ~] = IAO(N, Tmax, lb, ub, dim, fobj); scores_AO(r) = fmin_AO; scores_IAO(r) = fmin_IAO; end注意rng(r, 'twister')要写在每次调用算法之前。不要用rand('twister', r),这是旧接口,在 MATLAB 2023b 及更新版本里虽然还能兼容,但官方推荐统一用rng。如果你在循环里跑并行,记得把rng放进每个 worker 内部,否则并行池默认随机流可能导致结果不可复现。
4.2 结果表要关注 Best、Mean、Std 和 p 值
基准测试的输出不能只贴一条最优收敛曲线,因为曲线只代表运气最好的一次。我建议至少记录 30 次独立运行的三个统计量,外加显著性检验:
| 指标 | 说明 | AO 对照 | 改进 IAO |
|---|---|---|---|
| Best | 30 次里最优的一次 | 运行后填入 | 运行后填入 |
| Mean | 30 次平均值,反映平均效果 | 运行后填入 | 运行后填入 |
| Std | 标准差,反映稳定性 | 运行后填入 | 运行后填入 |
| p value | Wilcoxon 秩和检验结果 | 参考值 | 参考值 |
判读逻辑:单峰函数上重点看 Mean 是否提升;多峰函数上重点看 Std 是否减小,因为多峰函数解决的是“稳定找到好解”而不是“偶尔找到好解”。如果 IAO 在某个函数上的 Mean 比 AO 差但 Std 小很多,说明改进虽然让搜索更保守,但可能压制了跳出局部极值的能力,这时候需要回调omega的衰减速度。
4.3 三个影响结果的参数与两个常见翻车点
最影响结果的参数有三个。第一个是omega的衰减形式,换成线性递减后,前期探索时间变短,后期开发时间变长,适用于光滑的单峰函数;余弦递减更适合多峰函数。第二个是x2里的扰动系数0.01,这个系数太小会导致探索阶段几乎完全围绕最优解转,改进直接退化成原版 AO;太大则会让大量个体频繁越界,边界夹取会浪费掉很多有效搜索步数。第三个是种群规模与迭代次数的比例,我见过很多人在N=10, Tmax=100下测试,这种参数下 Tent 初始化的优势还没发挥出来就结束了,至少要N=30, Tmax=500。
常见翻车点有两个。一是 Tent 映射的子函数返回了0或1,导致某个维度固定在边界上,整个种群在该维度失去多样性,表现为收敛曲线出现平台期。二是在记录收敛曲线时,Convergence_curve的数组在每次贪心选择前就记录,导致曲线偶尔出现非单调。正确的做法是每次迭代结束后,用当前种群的最优适应度记录,而不是记录所有个体更新前的临时值。
5. 把改进 AO 接到你自己的优化任务:接口、约束与显著性验证
工程上把 IAO 嵌入真实问题的关键点只有一个:把目标函数封装成“输入一个行向量,返回一个标量”。它不是仿真脚本,不是绘图函数,更不是一个读取全局变量的脚本。所有需要传递的参数,要么通过函数参数传进去,要么用嵌套函数捕获环境变量。
5.1 把代价函数换成你的工程目标
假设你要优化的目标函数是两个变量的组合,其中带一个正弦扰动项,可以写成:
function J = my_cost(x) % x 是 1 x dim 行向量 J = (x(1) - 2)^2 + 3 * (x(2) + 1)^2 + ... 0.5 * sin(5 * x(1)) * cos(3 * x(2)); end调用 IAO 的写法:
[Best_pos, Best_score, Convergence_curve] = ... IAO(30, 1000, -5 * ones(1, 2), 5 * ones(1, 2), 2, @my_cost);注意-5 * ones(1, 2)和5 * ones(1, 2)明确给出了每维边界,避免之前提到的标量边界歧义。如果你的目标函数来自 Simulink 或外部程序,把一个x输入进去再返回结果即可;如果每次评估需要 1 秒以上,先考虑减少种群规模,然后考虑用parfor替换贪心选择循环,但要注意并行下fitness的更新时机。
5.2 约束和离散变量处理
工程问题几乎都有约束。最省事的做法是罚函数法,把违反约束的程度乘一个大系数加到目标函数上:
function J = constrained_cost(x) raw = (x(1) - 2)^2 + 3 * (x(2) + 1)^2; g = abs(sum(x) - 1); % 等式约束例子 J = raw + 1e3 * (g > 1e-6) * g; end这里1e3是罚系数,取值太小约束不满足,取值太大会让算法的搜索方向完全被罚项主导,忽略原始目标。离散变量更直接:在目标函数内部对决策变量取整再评估,但算法位置更新还是用连续值,这样既保持了搜索连续性,又满足了离散要求。
5.3 显著性检验:ranksum 命令怎么读
对比实验最后一步是显著性检验。把 30 次 AO 的结果放进scores_AO,30 次 IAO 的结果放进scores_IAO,执行:
[p, h] = ranksum(scores_AO, scores_IAO);h = 1表示在 95% 置信水平下拒绝“两组结果无差异”的原假设,说明改进在统计意义上显著;p小于 0.05 是论文里常用的显著性标准。如果两组数据在某函数上多次得到完全相同的最优值,ranksum会因为数据无变异而报警,这时候直接看 Mean 和 Std,用ttest2或者干脆放弃对这个函数的统计判断,只报告分布结果。报告时把Mean ± Std和p一起放进表格,比只贴一条收敛曲线有说服力得多。
本文还有配套的精品资源,点击获取