☰
复现蝴蝶优化算法改进:Circle混沌初始化+非线性因子w,p,r+正余弦融合
2026/10/5 8:29:04 网站建设 项目流程

复现一篇蝴蝶优化算法(BOA)的改进论文,标题写着“Circle混沌初始化种群+非线性因子w、p、r+融合正余弦算法”,原以为照着摘要把公式抄下来就能跑出曲线,结果断断续续折腾了一个多星期才真正把整套流程走通。这类组合型改进在智能优化领域非常典型:基础算法优化能力不足,就用混沌映射提升初始种群质量,用动态参数调节探索与开发节奏,再引入正余弦算法(SCA)增强局部搜索能力。单拎出来每一步都不难,难的是让它们协同工作。这篇文章把我在复现过程中的拆解思路、数学推导补充、关键代码和踩坑记录都整理出来,给正在做BOA改进或类似元启发式算法复现的朋友一个参考。

1. 复现前必须拆清楚:这篇BOA改进论文到底改了什么

拿到一个标题就急着写代码,是复现工作中最影响效率的做法。标题里几个词看起来各自独立,实际上只有先把原始BOA的机制、三个改进点针对的缺陷、以及它们之间的联动关系摸清楚,后面写代码才不会返工。

1.1 原始BOA的三段式机制

蝴蝶优化算法是Arora和Singh在2019年提出的元启发式算法,核心思想是模拟蝴蝶觅食和求偶时对花蜜香味的感知。算法里每只蝴蝶是一个解,每个解对应的适应度值被当作“刺激强度”I,经过转换后成为该蝴蝶散发的“香味强度”f:

f = c * I^a

其中c是感觉因子,a是模态指数。习惯上a取0.1,c在迭代过程中从0.01逐渐增长到0.25。之所以让c递增,是因为算法后期需要更大的香味吸引力来加快收敛。

香味强度算出来后,算法通过一个切换概率p决定当前蝴蝶执行哪个搜索分支:

  • 全局搜索(探索阶段):x_i^(t+1) = x_i^t + (r^2 * g* - x_i^t) * f_i
  • 局部搜索(开发阶段):x_i^(t+1) = x_i^t + (r^2 * x_j^t - x_k^t) * f_i

其中g*是当前全局最优解,x_j和x_k是从种群中随机挑出的两个不同个体,r是[0,1]均匀随机数。p在原始论文中固定为0.8,也就是说每轮迭代中约80%的蝴蝶走全局搜索,20%走局部搜索。

从这套机制能看出原始BOA的设计逻辑:蝴蝶通过香味感知全局最优位置,并朝它靠拢,这保证了全局搜索能力;而随机挑选两个个体做线性组合,则提供了一种简单的邻域扰动。但缺陷也很明显,后面三个改进点几乎都是冲着这些缺陷去的。

1.2 论文的三个改进方向分别补什么

第一,原始BOA的种群是纯随机初始化生成,在解空间里分布不均匀,容易导致初始种群离真实最优区域太远。标题里的“Circle混沌初始化种群”就是针对这一点,用Circle混沌序列生成初始位置,让种群在搜索空间里铺得更均匀。

第二,原始BOA的p固定为0.8、c固定增长、r使用均匀随机数,都属于“静态”或“半静态”参数。标题里的“非线性因子w、p、r”是把三个关键参数都改成随迭代次数非线性变化的动态值,让算法前期重探索、后期重开发。

第三,原始BOA的局部搜索结构过于简单,只是两个随机解的差作为扰动方向,精度有限。标题里的“融合正余弦算法”就是引入SCA的正弦余弦位置更新机制,增强局部阶段的开采能力。

1.3 编码前必须确认的四个问题

在动手写代码之前,建议先把下面四个问题落实清楚,否则后续很容易推倒重来:

  1. 这篇论文用的测试函数是什么。CEC2017、CEC2019还是一般的Sphere、Rastrigin、Ackley,决定了函数实现方式和结果统计口径。
  2. 三个改进点各自的精确公式。特别是w和r,不同论文有不同定义,有的是惯性权重,有的是随机系数方差,要对照原文确认,不要凭标题猜测。
  3. 改进点之间是否有参数联动。比如p下降后,进入局部搜索分支的概率变小,此时SCA的混合比例是否也要跟着调整,必须在写主循环前设计好。
  4. 边界处理方式。混沌映射出来的值是否会被clip截断,SCA的绝对值项是否会导致越界,这些细节直接影响收敛行为。

这四个问题都没有标准答案,但提前想清楚能让复现过程更有针对性。论文复现不是抄答案,而是把作者的思路转成可运行的代码,过程中必然要补充原作者没有写明的细节。

2. Circle混沌映射初始化:公式与实现的数学细节

混沌初始化在群智能优化里很常见,但很多人只是随便套个公式,没有理解映射的数学性质和实现时的参数选择问题,导致初始化效果甚至不如随机生成。

2.1 Circle映射公式与推荐参数

Circle映射的迭代公式如下:

x_(n+1) = mod(x_n + b - (a / 2π) * sin(2π * x_n), 1)

其中a和b是控制参数。复现中最常用的组合是a=0.5、b=0.2,这个组合下序列在[0,1]区间内具有较好的遍历性和低相关性。mod运算的作用是把结果拉回[0,1]区间,保证混沌序列不发散。

为什么选Circle而不是更常见的Logistic映射?因为Logistic映射在参数接近4时虽然混沌,但序列在0和1附近分布过密,种群初始化后容易在边界堆积。Circle映射的均匀性相对更好,尤其在维度较高时,边界堆积造成的负面影响会更小。

下面用一个表对比几种常用于初始化的混沌映射:

映射名称迭代公式均匀性实现复杂度常用参数
Logisticx_(n+1)=μx(1-x)中等,边界偏密最低μ=4
Tentx_(n+1)=min(x,1-x)*μ较好,但易落入不动点低μ=2
Circlex_(n+1)=mod(x+b-(a/2π)sin(2πx),1)较好,序列均匀低a=0.5, b=0.2
Chebyshevx_(n+1)=cos(k*arccos(x))中等,波动大中k∈[2,6]

2.2 从一维混沌序列到多维种群

Circle映射生成的是[0,1]区间的一维序列,但种群初始化需要的是一个pop_size×dim的矩阵。这里有两种映射方式:

  • 方式一:对每个维度独立生成一条混沌序列,分别映射到搜索空间;
  • 方式二:只生成一条长序列,然后按顺序切成多段填充给每个维度。

实际复现时更推荐方式一,因为方式二会让不同维度之间保持序列的连续性,导致维度间相关性过强,种群个体在搜索空间中对角线方向聚集。每个维度用不同的随机初值独立迭代,得到的多维分布更接近均匀。

实现代码如下:

import numpy as np def circle_chaos_sequence(x0, length, a=0.5, b=0.2, burn=500): seq = np.zeros(length) x = x0 # 先迭代 burn 次,跳过混沌映射的瞬态阶段 for _ in range(burn): x = (x + b - a / (2*np.pi) * np.sin(2*np.pi*x)) % 1 for i in range(length): x = (x + b - a / (2*np.pi) * np.sin(2*np.pi*x)) % 1 seq[i] = x return seq def circle_initialization(pop_size, dim, lb, ub, a=0.5, b=0.2): X = np.zeros((pop_size, dim)) for d in range(dim): x0 = np.random.uniform(0, 1) seq = circle_chaos_sequence(x0, pop_size, a, b) X[:, d] = lb + (ub - lb) * seq return X

上面代码里有一个细节:生成序列之前先迭代500次。因为混沌映射的初始段受初值影响较大,分布不够稳定,跳过这段瞬态能让进入种群的序列更均匀。这个操作在多数论文里不会写,却是保证复现效果的重要一步。

2.3 如何验证初始化真的变好了

不要只凭经验说“混沌初始化有效果”,要动手验证。一个常用的方法是计算初始种群的多样性度量,比如平均成对欧氏距离或种群方差。以Sphere函数在[-100,100]^30空间、种群大小30为例,随机初始化的种群适应度分布通常集中在1e4到1e5量级,而Circle初始化后的种群适应度分布会更均匀地铺开,最差个体的适应度往往会低一到两个数量级。

另一个更直接的判断方式是看初始全局最优的适应度。多次独立运行后统计平均初始最优值,如果Circle初始化的平均初始最优值明显优于随机初始化,说明种子里确实有更接近最优区域的个体。后面正式跑收敛曲线时,起点更低会让整个收敛曲线的外观更好看。

3. 非线性因子w、p、r:从固定值到动态设计的复现逻辑

这部分是复现工作的重头戏。w、p、r三个字母在标题里并列出现,但作用对象完全不同。很多复现稿把三个参数都简单改成随迭代递减,结果算法后半段几乎停滞,这就是没搞清楚每个参数在BOA搜索方程里的角色。

3.1 固定参数到底限制了什么

先看原始BOA里p固定为0.8的问题:无论迭代早期还是后期,都有大量蝴蝶执行全局搜索。理想情况下,算法前期应该以探索为主,通过大范围搜索捕捉全局信息;后期应该转向开发,对已有最优区域做精细搜索。p固定后,后期仍然有大量蝴蝶跑去做全局大跳,局部精化能力自然不够。

再看感觉因子c。原始BOA中c从0.01线性增到0.25,本质上是一种手动设定的动态策略。但c的作用并非简单越大越好,因为它直接乘在香味强度上,c过大会导致蝴蝶一步跳出几万个单位,越过边界;c过小又会吸附不动。另外随机数r的分布也会影响搜索步长的稳定性。

3.2 惯性权重w的设计选择

w在标题里的定位类似PSO中的惯性权重,直接作用在位置更新式中的当前项上,控制蝴蝶对自身位置的继承程度:

X_new = w * X_current + (r^2 * g* - X_current) * f

w越大,蝴蝶越倾向于保留原位置附近,搜索越保守;w越小,当前位置的惯性越弱,蝴蝶越容易被香味牵引到更远位置。复现中可以用抛物线递减:

w(t) = w_start - (w_start - w_end) * (t / T)^2

也可以使用余弦型衰减:

w(t) = w_end + (w_start - w_end) * cos(π * t / (2T))

两种公式曲线形态不同:抛物线递减在前期衰减慢、后期快,适合那些需要长时间保持探索的测试函数;余弦型衰减在早期下降略快,能更快进入开发阶段。通常取w_start=0.9、w_end=0.4,这两个值主要参考PSO经典参数组合。

3.3 切换概率p的非线性策略

p的改进应该是整篇论文里最容易出效果的修改点。原始p固定为0.8,我复现时把它改为从0.9线性递减到0.3:

p(t) = p_start - (p_start - p_end) * (t / T)

前期p接近0.9,大部分蝴蝶做全局搜索,保证探索范围;后期p降到0.3,大部分蝴蝶转入局部搜索,便于精细开发。这里要注意p的下限不要设置得太低,否则后期几乎全部做局部搜索,种群多样性会迅速耗尽,容易早熟。

也可以用正弦型策略,让p先保持较大值一段时间再下降:

p(t) = p_start - (p_start - p_end) * sin(π * t / (2T))

正弦型策略在前期下降更慢,对多峰函数的探索更友好;线性策略简单直接,对单峰函数收敛更快。具体用哪种,要跟论文原稿保持一致,如果论文没有明确写,建议在代码里做成可配置项,跑几次对比再确定。

3.4 随机因子r的非线性设计

原始BOA中r是[0,1]均匀随机数,它在全局搜索里承担的是随机缩放的职责。改进版通常把r改成一个随迭代非线性递减的控制参数:

r(t) = r_max - (r_max - r_min) * (t / T)^β

这里β控制衰减曲线形态,β=1是线性,β=2是前期快后期平缓的凹曲线,β=0.5则相反。复现习惯上取r_max=1.0、r_min=0.1、β=2。这样前期r较大,允许蝴蝶大步跨越探索;后期r较小,限制步长,只在最优解附近精细调整。

此时全局搜索更新公式变成:

X_new = w(t) * X_current + (r(t)^2 * g* - X_current) * f

把w、p、r三个动态参数放在一起看,w管的是惯性取舍,p管的是全局与局部搜索的分工比例,r管的是单步步长尺度。三者协同后,算法在迭代早期是一个“大步探索且偏全局”的状态,迭代晚期变成“小步开发且偏局部”的状态,这正是组合改进要达成的核心效果。

下面给出一个参数配置表,方便对照实现:

参数原始BOA改进策略推荐取值
w无抛物线递减0.9 → 0.4
p固定0.8线性/正弦递减0.9 → 0.3
rU(0,1)随机幂函数递减1.0 → 0.1, β=2
c0.01→0.25线性保持不变0.01 → 0.25
a0.1保持不变0.1

4. 正余弦算子与BOA的融合:从公式到混合策略选择

融合正余弦算法这一步,最容易踩的坑是把SCA当成一个独立的算法步骤直接拼在BOA后面。正确理解应该是:SCA提供了一种位置更新算子,你可以把这种算子嵌入到BOA的某个分支里,也可以让它以竞争候选的形式参与选择。

4.1 SCA的更新公式与参数含义

SCA的核心思想来自正弦和余弦函数的数学性质,每个个体按下式更新:

X^(t+1) = X^t + r1 * sin(r2) * |r3 * X_best - X^t| (r4 < 0.5) X^(t+1) = X^t + r1 * cos(r2) * |r3 * X_best - X^t| (r4 >= 0.5)

其中:

  • r1 = a - t * a / T,控制搜索幅度,随迭代线性递减;
  • r2 ∈ [0, 2π],控制移动方向;
  • r3 ∈ [0, 2],随机权重,增强位置的随机性;
  • r4 ∈ [0, 1],决定使用正弦还是余弦分支。

表面上看SCA和BOA都是朝最优解移动,但区别在于SCA用正弦/余弦的周期交替特性,可以让个体在靠近最优解时仍然产生振荡,从而跳出局部极值。这个特性与BOA的随机扰动机制有本质差异。

4.2 融合方式一:让SCA接管局部搜索分支

第一种实现方式很直接:当p条件不满足时,原本执行BOA局部搜索的蝴蝶改用SCA更新。这样全局分支保持BOA原有的探索机制,局部分支换成正余弦振荡,既能改善BOA原始局部搜索方向单一的缺陷,又不会过度改变算法主体结构。

if np.random.rand() < p_t: # 全局搜索分支,使用改进后的BOA更新 r_val = r_t X_new = w_t * X[i] + (r_val * r_val * Gbest - X[i]) * f_i else: # 局部搜索分支,使用SCA更新 r1 = a_sca - t * (a_sca / T) r2 = 2 * np.pi * np.random.rand() r3 = 2 * np.random.rand() r4 = np.random.rand() if r4 < 0.5: X_new = X[i] + r1 * np.sin(r2) * np.abs(r3 * Gbest - X[i]) else: X_new = X[i] + r1 * np.cos(r2) * np.abs(r3 * Gbest - X[i])

这种方式实现简单、计算量小,但注意SCA更新完全围绕全局最优Gbest,如果Gbest本身陷入局部极值,局部搜索也会跟着在局部区域震荡,因此SCA的r1衰减速度要和BOA的动态参数保持节奏一致。

4.3 融合方式二:全局搜索阶段的双策略竞争

第二种方式更精细:在全局搜索分支中,同时计算BOA的改进更新结果和SCA的更新结果,然后比较两个候选解以及当前解的适应度,选最优者作为新位置。这种方式不直接替换原有分支,而是增加一个候选池,决策权交给适应度函数。

核心代码如下:

# 计算BOA全局更新候选 X_candidate_boa = w_t * X[i] + (r_t * r_t * Gbest - X[i]) * f_i f_candidate_boa = func(X_candidate_boa) # 计算SCA更新候选 r1 = a_sca - t * (a_sca / T) r2 = 2 * np.pi * np.random.rand() r3 = 2 * np.random.rand() r4 = np.random.rand() if r4 < 0.5: X_candidate_sca = X[i] + r1 * np.sin(r2) * np.abs(r3 * Gbest - X[i]) else: X_candidate_sca = X[i] + r1 * np.cos(r2) * np.abs(r3 * Gbest - X[i]) f_candidate_sca = func(X_candidate_sca) if f_candidate_boa <= f_candidate_sca and f_candidate_boa < fitness[i]: X[i] = X_candidate_boa fitness[i] = f_candidate_boa elif f_candidate_sca < f_candidate_boa and f_candidate_sca < fitness[i]: X[i] = X_candidate_sca fitness[i] = f_candidate_sca

双策略竞争的优点是不会过早丢失其中一种策略的探索能力,理论上搜索能力更强;缺点是每轮每个个体要计算两次目标函数,计算量翻倍,在CEC2017这种高维复杂函数上运行时间会明显增加。

4.4 混合之后才需要关注的参数联动问题

融合SCA后最容易被忽视的是r1的衰减与w、r两个参数的联动。w和r已经被设计成随迭代递减,而SCA的r1也是线性递减,三者叠加会让后期的搜索步长过小,算法可能提前停止有效移动。解决方法是,在SCA分支中不再乘w,只保留r1控制步长;或者在全局分支做双策略竞争时,让SCA候选的r1使用单独的参数a_sca,而不是复用w。

我复现时的经验是:BOA全局分支负责大范围寻优,SCA分支负责局部振荡,两者的步长机制最好保持独立。最简单的做法是给SCA单独设置一个a值,例如a=2,并按原始SCA的线性递减公式计算r1,不与w_t混用。

5. 完整复现代码:从混沌初始化到迭代主循环

前面章节是理论拆解,这一章给出可以直接跑起来的完整代码框架。我以Python为例,测试函数先使用Sphere和Rastrigin,你可以在此基础上扩展CEC系列函数。

5.1 环境准备与测试函数实现

需要安装的库只有numpy和matplotlib,如果要跑Wilcoxon统计检验,还需要scipy。测试函数实现如下:

import numpy as np def sphere(x): return np.sum(x ** 2) def rastrigin(x): A = 10 return A * len(x) + np.sum(x ** 2 - A * np.cos(2 * np.pi * x)) def ackley(x): a, b, c = 20, 0.2, 2*np.pi d = len(x) sum1 = np.sum(x ** 2) sum2 = np.sum(np.cos(c * x)) return -a * np.exp(-b * np.sqrt(sum1 / d)) - np.exp(sum2 / d) + a + np.exp(1)

目标是统一的函数输入一维数组、输出适应度标量,方便后续在主循环中批量计算。

5.2 改进BOA-SCA算法的完整实现

下面是结合前面所有改进点的主循环代码。要注意香味强度f的计算里有一个容易被忽略的归一化步骤,我在代码注释里标了出来。

def improved_boa_sca(func, dim, pop_size, max_iter, lb, ub): # 参数设置 w_start, w_end = 0.9, 0.4 p_start, p_end = 0.9, 0.3 r_max, r_min = 1.0, 0.1 beta_r = 2.0 c_start, c_end = 0.01, 0.25 epsilon = 1e-12 alpha = 0.1 # 模态指数 a_sca = 2.0 # SCA 控制参数 # Circle混沌初始化种群 X = circle_initialization(pop_size, dim, lb, ub) fitness = np.array([func(x) for x in X]) gbest_idx = np.argmin(fitness) Gbest = X[gbest_idx].copy() Gbest_fit = fitness[gbest_idx] history = [Gbest_fit] for t in range(max_iter): # 动态参数 w_t = w_start - (w_start - w_end) * (t / max_iter) ** 2 p_t = p_start - (p_start - p_end) * (t / max_iter) r_t = r_max - (r_max - r_min) * (t / max_iter) ** beta_r c_t = c_start + (c_end - c_start) * (t / max_iter) # 对当前适应度做归一化,避免香味强度数值爆炸 f_min = fitness.min() f_max = fitness.max() if f_max - f_min < epsilon: norm_fit = np.ones_like(fitness) else: norm_fit = (fitness - f_min) / (f_max - f_min) + epsilon for i in range(pop_size): # 香味强度 f_i = c_t * (norm_fit[i] ** alpha) if np.random.rand() < p_t: # 全局搜索分支:BOA更新 + SCA竞争候选 r = r_t * np.random.rand() X_boa = w_t * X[i] + (r * r * Gbest - X[i]) * f_i X_boa = np.clip(X_boa, lb, ub) f_boa = func(X_boa) r1 = a_sca - t * (a_sca / max_iter) r2 = 2 * np.pi * np.random.rand() r3 = 2 * np.random.rand() r4 = np.random.rand() if r4 < 0.5: X_sca = X[i] + r1 * np.sin(r2) * np.abs(r3 * Gbest - X[i]) else: X_sca = X[i] + r1 * np.cos(r2) * np.abs(r3 * Gbest - X[i]) X_sca = np.clip(X_sca, lb, ub) f_sca = func(X_sca) if f_boa <= f_sca and f_boa < fitness[i]: X[i] = X_boa fitness[i] = f_boa elif f_sca < f_boa and f_sca < fitness[i]: X[i] = X_sca fitness[i] = f_sca else: # 局部搜索分支:使用SCA振荡更新 r1 = a_sca - t * (a_sca / max_iter) r2 = 2 * np.pi * np.random.rand() r3 = 2 * np.random.rand() r4 = np.random.rand() if r4 < 0.5: X_new = X[i] + r1 * np.sin(r2) * np.abs(r3 * Gbest - X[i]) else: X_new = X[i] + r1 * np.cos(r2) * np.abs(r3 * Gbest - X[i]) X_new = np.clip(X_new, lb, ub) f_new = func(X_new) if f_new < fitness[i]: X[i] = X_new fitness[i] = f_new # 更新全局最优 cur_best_idx = np.argmin(fitness) if fitness[cur_best_idx] < Gbest_fit: Gbest = X[cur_best_idx].copy() Gbest_fit = fitness[cur_best_idx] history.append(Gbest_fit) return Gbest, Gbest_fit, history

两个地方要特别说明。第一,r的计算是在r_t基础上再乘一个随机数,目的是保留原始BOA的部分随机性,避免确定性递减导致算法变成纯梯度下降。第二,香味强度计算前做了最大最小归一化,这一步很多复现代码没有做,但如果不做,Ackley这类适应度范围非常大的函数很容易让f_i变成天文数字,一次移动就把解弹到边界外。

5.3 边界处理与精英保留策略

我在这段代码里使用了np.clip直接截断越界个体。相比取模映射,clip的优点是简单并且不会把解重新弹回搜索空间另一侧,缺点是在边界附近会积累个体。对于大多数测试函数,clip带来的影响可以忽略。

还要注意Gbest本身并不会被clip操作触碰,因为每次更新都是直接把X数组里最优个体的引用复制到Gbest,不经过更新公式,所以不存在最优个体被边界修改的问题。

精英保留策略是算法稳定性的保障:每一轮迭代结束时比较当前全局最优和历史最优,只有当历史最优被更优解替代时才更新Gbest。这样能防止个别轮次出现适应度退化。

5.4 统计结果输出格式

复现论文光跑一次不够,通常要运行30次独立实验,统计收敛曲线平均值、最优值均值和标准差。输出示例:

测试函数原始BOA平均最优改进BOA-SCA平均最优改进最优值
Sphere3.21e-051.04e-166.12e-21
Rastrigin7.89e-011.54e-030.00e+00
Ackley1.04e-023.55e-052.11e-07

这张表只是示意,真实的数值取决于维度和迭代次数。重点是要养成记录完整统计结果的习惯,而不是只展示某一次跑出的最好曲线。

6. 复现踩坑实录:验证方法、问题根因与调参建议

最后这部分是整篇复现过程中最有价值的沉淀。很多坑不是你不知道公式,而是公式实现出来之后结果不对,又找不到原因。这里把几个常见问题按排查难度列出。

6.1 怎么判断复现真的成功了

建议按三步验证。第一步,画出收敛曲线,观察是否前期快速下降、中期平稳过渡、后期逐渐逼近最优,如果曲线在中后期呈现完全水平甚至回升,说明参数调节有问题。第二步,跑30次独立实验,统计最优值均值、标准差和中位数,与原始BOA做对比。第三步,做非参数检验,常用Wilcoxon秩和检验,计算p值是否小于0.05,判断改进是否具有统计显著性。

稳定性永远比单次最优值重要。一组好的复现结果应该是30次运行中几乎没有灾难性失败,而不是某一次运气好跑出极小值。

6.2 香味强度不归一化导致步长爆炸

Ackley函数在[-32,32]区间内的适应度范围可能从0附近到超过20,如果直接用f = c * (fit^alpha)计算,在适应度高的个体上香味强度会非常大。一次更新可能让解移动几十个单位,直接越过边界。这是复现BOA时最容易踩的坑,原始论文的符号推导没有问题,但在数值实现时不做归一化很难复现出理想的收敛曲线。

解决方式就是在主循环里对每一轮的适应度做最大最小归一化,把刺激强度映射到接近[0,1]的区间。注意归一化后要加上一个极小量epsilon,防止适应度差异为0时除零错误。

6.3 Circle序列共用初值导致种群塌缩

我一开始为了让代码简洁,生成一条Circle混沌长序列后直接reshape成pop_size×dim的矩阵。结果发现高维函数上种群多样性反而变差,原因是同一维度的序列与下一维度序列之间高度相关,所有个体集中在解空间的一个狭长区域里,算法收敛速度极慢。

解决办法是对每个维度使用不同的随机初值并独立迭代,也就是第2章代码里的实现方式。这个问题的特点是表象容易误导:初始最优可能比随机初始化更好看,但全局搜索能力被大幅削弱,只有跑完整实验才能发现异常。

6.4 p值衰减过猛导致早熟

如果把p从0.9直接降到0.05,后期几乎每只蝴蝶都在做局部搜索,种群多样性快速耗尽,在多峰函数上几乎必然陷入局部最优。复现时我把p_end设了0.2,跑Rastrigin函数时30次实验有一半以上收敛到非零结果,后来把p_end调回0.3才恢复正常,收敛精度也提升了。

参数下限的设置思路是:确保局部搜索频率不会高到丧失种群多样性,同时早期探索积累的信息足够支持后期开发。p_end在0.3左右通常是一个合理区间,具体取值可以结合测试函数的多峰程度做敏感性实验。

6.5 参数敏感性与推荐组合

对w、p、r这类动态参数,常见的做法是把起止值和衰减指数做小范围网格搜索。我实际运行后的推荐组合如下:

  • w:0.9递减到0.4,抛物线型,对单峰和多峰函数都比较稳;
  • p:0.9递减到0.3,线性即可,不需要过多归一化处理;
  • r:1.0递减到0.1,β=2,前期探索能力充足,后期步长收敛;
  • c:保持0.01到0.25线性增长;
  • a:保持0.1,这是BOA原论文的经典取值;
  • SCA的a值:取2.0,只控制SCA自身的搜索幅度,不与w混用。

这组参数在Sphere、Rastrigin、Ackley等常用函数上整体表现均衡。如果你在复现自己的论文,建议先用这组参数跑通流程,确认改进有效后再针对目标函数做细调,不要一开始就盲目调参。

整套复现走下来,我最大的体会是组合改进最难的不是把三段代码拼在一起,而是让三段改进的调性一致。Circle混沌初始化把种群铺得足够均匀,w、p、r动态参数管的是搜索节奏的递进,SCA加的则是局部阶段的一次模式切换,三者需要协同发力,而不是各自为政。如果你复现时结果始终上不去,先别急着怀疑算法原理,按这章的顺序检查归一化、种群初始化方式和参数衰减范围,问题往往就藏在这几个细节里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询