获取更多资讯,赶快关注上面的公众号吧!
受社会蜘蛛的启发,香港大学的James J.Q. Yua和Victor O.K. Lia于2015年提出了一种新的全局优化算法——社会蜘蛛算法(Social Spider Algorithm ,SSA),该算法主要基于社会蜘蛛的觅食策略,利用蜘蛛网的振动来确定猎物位置。本文首先了解社会蜘蛛的觅食行为,然后给出详细的算法流程,最后再Matlbab、Python、C++和Java实现,完整代码请关注公众号并后台回复"社会蜘蛛"获取。
虽然我们平时生活中,经常见到的蜘蛛好像都是独居的,但实际上还有一些是社会的,即群居的,它们互相之间会以某种方式进行信息交互。它们会利用各种不同的捕食策略,但大多数都是通过感知振动来侦测猎物,蜘蛛对振动刺激非常敏感,如果振动在规定的频率范围内,蜘蛛就会像振动源发起攻击。同时蜘蛛还能区分出振动是由猎物还是其他蜘蛛发出的。
社会蜘蛛的觅食行为可以描述为蜘蛛向食物源位置进发的协同移动,蜘蛛接收并分析蜘蛛网上传播的振动,以确定食物来源的潜在方向,就是基于这种思想才提出了社会蜘蛛算法。
在SSA中,将优化问题的搜索空间形式化为超空间蜘蛛网,蜘蛛网上的每个位置表示优化问题的一个可行解,所有可行解在蜘蛛网上都有对应的位置,蜘蛛网还是振动的传播媒介。每只蜘蛛在蜘蛛网上都有自己的位置,解的质量或适应度是基于目标函数的,并表示为在当前位置找到食物源的可能性。蜘蛛可以在蜘蛛网上自由移动,但是不能离开蜘蛛网,因为蜘蛛网以外的区域为优化问题的不可行解。当蜘蛛移动到一个新的位置,它就会产生振动并在蜘蛛网上传播,每一个振动都持有一只蜘蛛的信息,而其他蜘蛛在接收到振动后就可以获得该信息。
蜘蛛就是SSA中执行优化的代理,在算法初始阶段,先将预定义数量的蜘蛛放置在蜘蛛网上,每只蜘蛛 s s s都有一个存储器,用于存储以下个体信息:
前两类信息描述了 s s s的个体情况,而其他所有信息都涉及引导 s s s到新的位置。
根据观察发现,蜘蛛对振动有非常准确的感觉。此外,它们可以区分在同一蜘蛛网上传播的不同振动,并感知它们各自的强度。在SSA中,当蜘蛛到达一个与前一个不同的新位置时,它会产生振动。振动的强度与位置的适应度有关。振动会在蛛网上传播,其他蜘蛛可以感觉到它。这样,蜘蛛在同一个网络上与他蜘蛛分享自己的个体信息,就形成了集体的社会知识。
在SSA中振动是一个非常重要的概念,是区分SSA与其他元启发式算法的主要特征之一。在SSA中,使用振动的源位置和源强度这两个属性定义振动。源位置由优化问题的搜索空间进行定义,而振动强度的取值范围为 [ 0 , + ∞ ) [0,+\infty) [0,+∞)。当蜘蛛移动到新的位置时,它就就会在新的位置上产生振动,将蜘蛛 a a a在使劲儿 t t t地位置定义为 P a ( t ) \boldsymbol{P}_{a}(t) Pa(t),或者时间参数为 t t t时直接简化为 P a \boldsymbol{P}_{a} Pa,进一步使用 I ( P a , P b , t ) I\left(\boldsymbol{P}_{a}, \boldsymbol{P}_{b}, t\right) I(Pa,Pb,t)来表达当振动源在位置 P a \boldsymbol{P}_{a} Pa时,在时刻 t t t由位置 P b \boldsymbol{P}_{b} Pb上的蜘蛛感知到的振动强度。那么 I ( P s , P s , t ) I\left(\boldsymbol{P}_{s}, \boldsymbol{P}_{s}, t\right) I(Ps,Ps,t)就表示蜘蛛 s s s在源位置产生的振动强度。源位置的振动强度与其位置 f ( P s ) f\left(\boldsymbol{P}_{s}\right) f(Ps)的适应度值有关,定义如下:
I ( P s , P s , t ) = log ( 1 f ( P s ) − C + 1 ) (1) I\left(\boldsymbol{P}_{s}, \boldsymbol{P}_{s}, t\right)=\log \left(\frac{1}{f\left(\boldsymbol{P}_{s}\right)-C}+1\right)\tag{1} I(Ps,Ps,t)=log(f(Ps)−C1+1)(1)
其中 C C C为一足够小的数,所有可能的适应度值都大于 C C C值(考虑最小化问题),上式的设计主要考虑了以下几个问题:
作为能量的一种形式,振动也会随距离而衰减,这一物理现象在SSA的设计中有所体现。蜘蛛 a a a和蜘蛛 b b b之间的距离为 D ( P a , P b ) D(\boldsymbol{P}_{a},\boldsymbol{P}_{b}) D(Pa,Pb),并根据1-范数进行距离计算:
D ( P a , P b ) = ∥ P a − P b ∥ 1 (2) D\left(\boldsymbol{P}_{a}, \boldsymbol{P}_{b}\right)=\left\|\boldsymbol{P}_{a}-\boldsymbol{P}_{b}\right\|_{1}\tag{2} D(Pa,Pb)=∥Pa−Pb∥1(2)
所有蜘蛛位置在每个维度上的标准差为 σ \boldsymbol{\sigma} σ,那么振动随距离的衰减可以定义为:
I ( P a , P b , t ) = I ( P a , P a , t ) × exp ( − D ( P a , P b ) σ ‾ × r a ) (3) I\left(\boldsymbol{P}_{a}, \boldsymbol{P}_{b}, t\right)=I\left(\boldsymbol{P}_{a}, \boldsymbol{P}_{a}, t\right) \times \exp \left(-\frac{D\left(\boldsymbol{P}_{a}, \boldsymbol{P}_{b}\right)}{\overline{\boldsymbol{\sigma}} \times r_{a}}\right)\tag{3} I(Pa,Pb,t)=I(Pa,Pa,t)×exp(−σ×raD(Pa,Pb))(3)
参数 r a ∈ ( 0 , ∞ ) r_a\in(0,\infty) ra∈(0,∞)控制振动强度随距离的衰减率, r a r_a ra越大,振动衰减越弱。
在SSA中存在三个顺序执行的阶段:初始化、迭代和结束。
在初始化阶段,算法定义了目标函数和求解空间,同时还指定算法参数的值,然后就创建初始蜘蛛种群。在仿真过程中,种群大小不变,所以分配固定大小的内存来存储这些蜘蛛的信息,蜘蛛的位置在搜索空间中随机生成,设置每只蜘蛛在种群中的初始目标振动在当前位置,且振动强度为0。蜘蛛存储的其他属性也都初始化为0。
在迭代阶段,每次迭代中蜘蛛网上的蜘蛛都会移动到新的位置,并评估适应度值,每次迭代又可以分为以下几步:适应度评估、振动生成、掩码改变、随机行走和约束处理。
适应度评估。算法首先计算所有蜘蛛在不同位置的适应度值,更新全局最优解,每只蜘蛛在每次迭代中只评估一次。
振动生成。根据式(1)这些蜘蛛在它们的位置上产生振动,然后使用式(3)模拟振动的传播过程,在该过程中,每只蜘蛛 s s s都将接收 ∣ p o p ∣ |pop| ∣pop∣个来自其他蜘蛛产生的不同振动,其中 ∣ p o p ∣ |pop| ∣pop∣为蜘蛛种群大小。这些被接收的振动信息包括振动源位置和衰减强度。使用 V V V表达这 ∣ p o p ∣ |pop| ∣pop∣个振动,在接收到 V V V后, s s s会选择 V V V中最强的振动 v s b e s t v_s^{best} vsbest,然后比较该强度与内存中存储的目标振动 v s t a r v_s^{tar} vstar的强度,如果 v s b e s t v_s^{best} vsbest更大,则 s s s将 v s b e s t v_s^{best} vsbest存储为 v s t a r v_s^{tar} vstar,同时设置自上次改变目标振动后的迭代次数 c s c_s cs重置为0,否则 v s t a r v_s^{tar} vstar保持不变, c s c_s cs加1。使用 P s i \boldsymbol{P}_s^i Psi和 P s s t a r \boldsymbol{P}_s^{star} Psstar分别表示 V V V和 v t a r v_{tar} vtar的源位置,其中 i = { 1 , 2 , ⋯ , ∣ p o p ∣ } i=\{1,2, \cdots,|p o p|\} i={1,2,⋯,∣pop∣}。
掩码改变。接下来对 s s s向 v s t a r v_s^{tar} vstar执行随机行走,这里利用了维度掩码引导移动。每只蜘蛛都有一个长度为 D D D的0-1二进制向量 m \boldsymbol{m} m,其中 D D D为优化问题的维度。初始时,掩码中所有的值为0,但是在每次迭代中蜘蛛 s s s可以以 1 − p c c s 1-p_c^{c_s} 1−pccs的概率改变掩码,其中 p c ∈ ( 0 , 1 ) p_c\in(0,1) pc∈(0,1)为用户自定义的用于目标掩码改变的概率,如果决定更改掩码,那么向量中的每一位都有 p m p_m pm的概率被赋值为1, p m ∈ ( 0 , 1 ) p_m\in(0,1) pm∈(0,1)也是由用户自定义的控制参数.掩码的每个位都是独立改变的,与前一个掩码没有任何关联,如果所有的位都是0,掩码的一个随机值就会变成1。类似地,如果所有值都为1,则将一个随机位赋值为0。维度掩码确定后,基于该掩码生成新的位置 P s f o \boldsymbol{P}_s^{fo} Psfo,其第 i i i个维度值计算如下:
P s , i f o = { P s , i t a r m s , i = 0 P s , i r m s , i = 1 (4) P_{s, i}^{f o}=\left\{\begin{array}{ll} P_{s, i}^{t a r} & m_{s, i}=0 \\ P_{s, i}^{r} & m_{s, i}=1 \end{array}\right.\tag{4} Ps,ifo={Ps,itarPs,irms,i=0ms,i=1(4)
其中 r r r为 [ 1 , ∣ p o p ∣ ] [1,|pop|] [1,∣pop∣]内的随机整数, m s , i m_{s, i} ms,i代表蜘蛛 s s s维度掩码 m \boldsymbol{m} m的第 i i i个纬度值。对 m s , i = 1 m_{s,i}=1 ms,i=1的两个不同维度的随机数 r r r是独立生成的。
随机行走。根据生成的 P s f o \boldsymbol{P}_s^{fo} Psfo, s s s向该位置执行随机行走:
P s ( t + 1 ) = P s + ( P s − P s ( t − 1 ) ) × r + ( P s f o − P s ) ⊙ R (5) \boldsymbol{P}_{s}(t+1)=\boldsymbol{P}_{s}+\left(\boldsymbol{P}_{s}-\boldsymbol{P}_{s}(t-1)\right) \times r+\left(\boldsymbol{P}_{s}^{f o}-\boldsymbol{P}_{s}\right) \odot \boldsymbol{R}\tag{5} Ps(t+1)=Ps+(Ps−Ps(t−1))×r+(Psfo−Ps)⊙R(5)
其中 ⊙ \odot ⊙表示元素相乘, R \boldsymbol{R} R为[0,1]之间均匀分布的随机浮点型向量。在遵循 P s f o \boldsymbol{P}_{s}^{f o} Psfo之前, s s s首先沿着上一次迭代的方向进行移动,而移动的距离是上一次移动的随机一部分,然后才是 s s s根据(0,1)的随机因子在每个维度上向 P s f o \boldsymbol{P}_s^{fo} Psfo逼近,不同维度上的随机因子也是独立生成的。随机行走之后, s s s将该移动存储在当前迭代中以备下次迭代使用。
P s , i ( t + 1 ) = { ( x i ‾ − P s , i ) × r if P s , i ( t + 1 ) > x i ‾ ( P s , i − x i ‾ ) × r if P s , i ( t + 1 ) < x i ‾ (6) P_{s, i}(t+1)=\left\{\begin{array}{ll} \left(\overline{x_{i}}-P_{s, i}\right) \times r & \text { if } P_{s, i}(t+1)>\overline{x_{i}} \\ \left(P_{s, i}-\underline{x_{i}}\right) \times r & \text { if } P_{s, i}(t+1)<\underline{x_{i}} \end{array}\right.\tag{6} Ps,i(t+1)={(xi−Ps,i)×r(Ps,i−xi)×r if Ps,i(t+1)>xi if Ps,i(t+1)<xi(6)
其中 x i ‾ \overline{x_{i}} xi和 x i ‾ \underline{x_{i}} xi分别为搜索空间第 i i i维的上界和下界。
结束阶段,重复执行上述迭代过程,直到满足终止条件。终止条件可以是最大迭代次数、最大CPU运行时间、误差率、最优适应度值不再改进最大迭代次数或者任意其他合适的准则。
通过以上三个阶段,就构成了完整的SSA算法:
1:分配SSA参数值。
2:创建蜘蛛种群 p o p pop pop并分配内存。
3:为每只蜘蛛初始化 v s t a r v_s^{tar} vstar。
4:while 不满足终止条件 do
5: for p o p pop pop中的每只蜘蛛 s s s do
6: 评估 s s s的适应度值。
7: 根据式(1)生成 s s s在当前位置的振动。
8: end for
9: for p o p pop pop中的每只蜘蛛 s s s do
10: 根据式(3)计算所有蜘蛛生成的振动 V V V的强度。
11: 从 V V V中选择最强的振动 v s b e s t v_s^{best} vsbest。
12: if v s b e s t v_s^{best} vsbest大于 v s s t r v_s^{str} vsstr then
13: 将 v s b e s t v_s^{best} vsbest存储为 v s s t r v_s^{str} vsstr。
14: end if
15: 更新 c s c_s cs。
16: 生成 [ 0 , 1 ) [0,1) [0,1)上的随机数 r r r。
17: if r > p c c s r>p_c^{c_s} r>pccs then
18: 更新维度掩码 m s \boldsymbol{m}_s ms。
19: end if
20: 根据式(4)生成 P s f o \boldsymbol{P}_s^{fo} Psfo
21: 执行随机行走(式(5))。
22: 处理违反的约束,
23: end for
24:end while
这部分提供了SSA的MATLAB、C++、Python和Java代码,完整代码请关注公众号并后台回复"社会蜘蛛"获取,其中部分Java代码如下。
bestSpider = new Spider(new Position(this.dim, this.objectiveFun.getRange()));
bestSpider.setFitness(Double.MAX_VALUE);
initPop();
while (this.iterator < maxGen) {
// 计算适应度值和振动
calcFitnessAndVibration();
// 各个维度标准差的平均值
double[] averageArray = IntStream.range(0, this.dim).mapToDouble(dim -> {
return spiders.stream().mapToDouble(spider -> spider.getPosition().getPositionCode()[dim]).average()
.getAsDouble();
}).toArray();
double meanDeviation = IntStream.range(0, this.dim).mapToDouble(dim -> {
return Math.pow(spiders.stream()
.mapToDouble(
spider -> Math.pow(spider.getPosition().getPositionCode()[dim] - averageArray[dim], 2))
.sum() / this.spiderAmount, 0.5);
}).average().getAsDouble();
for (Spider spidera : spiders) {
// 计算传播后的振动V
List<Double> V = new ArrayList<Double>();
for (Spider spiderb : spiders) {
double distance = 0;
if (spidera != spiderb) {
distance = MathUtil.calcManhattanDistance(spidera.getPosition().getPositionCode(),
spiderb.getPosition().getPositionCode());
}
double newIntensity = spiderb.getVibration() * Math.exp(-distance / (meanDeviation * ra));
V.add(newIntensity);
}
// 从V中选择最大的振动
double vbest = V.stream().mapToDouble(v -> v).max().getAsDouble();
int bestInd = V.indexOf(vbest);
// 重新设置目标振动
if (vbest > spidera.getTargetVibration()) {
spidera.setTargetVibration(vbest);
spidera.setTargetPosition(spiders.get(bestInd).getPosition().getPositionCode());
// 重置Cs
spidera.setCs(0);
} else {
// 更新Cs
spidera.setCs(spidera.getCs() + 1);
}
double r = random.nextDouble();
// 很多代cs没有更新时,就有更大可能更新掩码
if (r > Math.pow(this.pc, spidera.getCs())) {
// 如果更改掩码,则以pm的概率变为1。
// 对于全部为1或为0时,随机选择某一维度进行更改
// long zeroLen = Arrays.stream(spidera.getMask()).filter(i -> i == 0).count();
// long oneLen = Arrays.stream(spidera.getMask()).filter(i -> i == 1).count();
// // 全为1
// if (zeroLen == 0) {
// spidera.getMask()[random.nextInt(this.dim)] = 0;
// }
// // 全为0
// if (oneLen == 0) {
// spidera.getMask()[random.nextInt(this.dim)] = 1;
// }
for (int i = 0; i < this.dim; i++) {
double rr = random.nextDouble();
// 以概率pm设置为1
if (rr <= this.pm) {
spidera.getMask()[i] = 1;
} else {
spidera.getMask()[i] = 0;
}
}
}
// 计算pfollow
double[] pfollow = new double[this.dim];
for (int maskInd = 0; maskInd < spidera.getMask().length; maskInd++) {
// 从目标位置中选择
if (spidera.getMask()[maskInd] == 0) {
pfollow[maskInd] = spidera.getTargetPosition()[maskInd];
} else {
// 从随机选择的蜘蛛中选择
pfollow[maskInd] = spiders.get(random.nextInt(this.spiderAmount)).getPosition()
.getPositionCode()[maskInd];
}
}
// 随机行走,计算下一次迭代的位置
double[] currPos = spidera.getPosition().getPositionCode();
double[] movement = IntStream.range(0, this.dim)
.mapToDouble(dim -> spidera.getMovenment()[dim] * random.nextDouble()
+ (pfollow[dim] - currPos[dim]) * random.nextDouble())
.toArray();
double[] newPos = IntStream.range(0, this.dim).mapToDouble(dim -> currPos[dim] + movement[dim])
.toArray();
spidera.getPosition().setPositionCode(newPos);
spidera.setMovenment(movement);
}
System.out.println("**********第" + iterator + "代最优解:" + bestSpider + "**********");
incrementIter();
}
程序运行结果如下: