获取更多资讯,赶快关注上面的公众号吧!
社会蜘蛛算法(Social Spider Algorithm ,SSA)
受社会蜘蛛的启发,香港大学的James J.Q. Yua和Victor O.K. Lia于2015年提出了一种新的全局优化算法——社会蜘蛛算法(Social Spider Algorithm ,SSA),该算法主要基于社会蜘蛛的觅食策略,利用蜘蛛网的振动来确定猎物位置。本文首先了解社会蜘蛛的觅食行为,然后给出详细的算法流程,最后再Matlbab、Python、C++和Java实现,完整代码请关注公众号并后台回复"社会蜘蛛"获取。
灵感
虽然我们平时生活中,经常见到的蜘蛛好像都是独居的,但实际上还有一些是社会的,即群居的,它们互相之间会以某种方式进行信息交互。它们会利用各种不同的捕食策略,但大多数都是通过感知振动来侦测猎物,蜘蛛对振动刺激非常敏感,如果振动在规定的频率范围内,蜘蛛就会像振动源发起攻击。同时蜘蛛还能区分出振动是由猎物还是其他蜘蛛发出的。
社会蜘蛛的觅食行为可以描述为蜘蛛向食物源位置进发的协同移动,蜘蛛接收并分析蜘蛛网上传播的振动,以确定食物来源的潜在方向,就是基于这种思想才提出了社会蜘蛛算法。
Social Spider Algorithm
在SSA中,将优化问题的搜索空间形式化为超空间蜘蛛网,蜘蛛网上的每个位置表示优化问题的一个可行解,所有可行解在蜘蛛网上都有对应的位置,蜘蛛网还是振动的传播媒介。每只蜘蛛在蜘蛛网上都有自己的位置,解的质量或适应度是基于目标函数的,并表示为在当前位置找到食物源的可能性。蜘蛛可以在蜘蛛网上自由移动,但是不能离开蜘蛛网,因为蜘蛛网以外的区域为优化问题的不可行解。当蜘蛛移动到一个新的位置,它就会产生振动并在蜘蛛网上传播,每一个振动都持有一只蜘蛛的信息,而其他蜘蛛在接收到振动后就可以获得该信息。
蜘蛛
蜘蛛就是SSA中执行优化的代理,在算法初始阶段,先将预定义数量的蜘蛛放置在蜘蛛网上,每只蜘蛛 都有一个存储器,用于存储以下个体信息:
- 在蜘蛛网上的位置。
- 当前位置的适应度。
- 上一次迭代时 的目标振动。
- 自 上一次改变其目标振动之后的迭代次数。
- 上一次迭代时 的移动。
- 上一次迭代中 用于导引移动的维度掩码。
前两类信息描述了 的个体情况,而其他所有信息都涉及引导 到新的位置。
根据观察发现,蜘蛛对振动有非常准确的感觉。此外,它们可以区分在同一蜘蛛网上传播的不同振动,并感知它们各自的强度。在SSA中,当蜘蛛到达一个与前一个不同的新位置时,它会产生振动。振动的强度与位置的适应度有关。振动会在蛛网上传播,其他蜘蛛可以感觉到它。这样,蜘蛛在同一个网络上与他蜘蛛分享自己的个体信息,就形成了集体的社会知识。
振动
在SSA中振动是一个非常重要的概念,是区分SSA与其他元启发式算法的主要特征之一。在SSA中,使用振动的源位置和源强度这两个属性定义振动。源位置由优化问题的搜索空间进行定义,而振动强度的取值范围为 。当蜘蛛移动到新的位置时,它就就会在新的位置上产生振动,将蜘蛛 在使劲儿 地位置定义为 ,或者时间参数为 时直接简化为 ,进一步使用 来表达当振动源在位置 时,在时刻 由位置 上的蜘蛛感知到的振动强度。那么 就表示蜘蛛 在源位置产生的振动强度。源位置的振动强度与其位置 的适应度值有关,定义如下:
其中
为一足够小的数,所有可能的适应度值都大于
值(考虑最小化问题),上式的设计主要考虑了以下几个问题:
- 优化问题中所有可能的振动强度都是正的。
- 具有较好的适应度值的位置,即最小化问题的较小值,比适应度值较差的位置具有更大的振动强度。
- 当解逼近全局最优时,振动强度不应过度增加,从而避免振动衰减机制失效。
作为能量的一种形式,振动也会随距离而衰减,这一物理现象在SSA的设计中有所体现。蜘蛛
和蜘蛛
之间的距离为
,并根据1-范数进行距离计算:
所有蜘蛛位置在每个维度上的标准差为 ,那么振动随距离的衰减可以定义为:
参数 控制振动强度随距离的衰减率, 越大,振动衰减越弱。
搜索模式
在SSA中存在三个顺序执行的阶段:初始化、迭代和结束。
在初始化阶段,算法定义了目标函数和求解空间,同时还指定算法参数的值,然后就创建初始蜘蛛种群。在仿真过程中,种群大小不变,所以分配固定大小的内存来存储这些蜘蛛的信息,蜘蛛的位置在搜索空间中随机生成,设置每只蜘蛛在种群中的初始目标振动在当前位置,且振动强度为0。蜘蛛存储的其他属性也都初始化为0。
在迭代阶段,每次迭代中蜘蛛网上的蜘蛛都会移动到新的位置,并评估适应度值,每次迭代又可以分为以下几步:适应度评估、振动生成、掩码改变、随机行走和约束处理。
-
适应度评估。算法首先计算所有蜘蛛在不同位置的适应度值,更新全局最优解,每只蜘蛛在每次迭代中只评估一次。
-
振动生成。根据式(1)这些蜘蛛在它们的位置上产生振动,然后使用式(3)模拟振动的传播过程,在该过程中,每只蜘蛛 都将接收 个来自其他蜘蛛产生的不同振动,其中 为蜘蛛种群大小。这些被接收的振动信息包括振动源位置和衰减强度。使用 表达这 个振动,在接收到 后, 会选择 中最强的振动 ,然后比较该强度与内存中存储的目标振动 的强度,如果 更大,则 将 存储为 ,同时设置自上次改变目标振动后的迭代次数 重置为0,否则 保持不变, 加1。使用 和 分别表示 和 的源位置,其中 。
-
掩码改变。接下来对 向 执行随机行走,这里利用了维度掩码引导移动。每只蜘蛛都有一个长度为 的0-1二进制向量 ,其中 为优化问题的维度。初始时,掩码中所有的值为0,但是在每次迭代中蜘蛛 可以以 的概率改变掩码,其中 为用户自定义的用于目标掩码改变的概率,如果决定更改掩码,那么向量中的每一位都有 的概率被赋值为1, 也是由用户自定义的控制参数.掩码的每个位都是独立改变的,与前一个掩码没有任何关联,如果所有的位都是0,掩码的一个随机值就会变成1。类似地,如果所有值都为1,则将一个随机位赋值为0。维度掩码确定后,基于该掩码生成新的位置 ,其第 个维度值计算如下:
其中 为 内的随机整数, 代表蜘蛛 维度掩码 的第 个纬度值。对 的两个不同维度的随机数 是独立生成的。 -
随机行走。根据生成的 , 向该位置执行随机行走:
其中 表示元素相乘, 为[0,1]之间均匀分布的随机浮点型向量。在遵循 之前, 首先沿着上一次迭代的方向进行移动,而移动的距离是上一次移动的随机一部分,然后才是 根据(0,1)的随机因子在每个维度上向 逼近,不同维度上的随机因子也是独立生成的。随机行走之后, 将该移动存储在当前迭代中以备下次迭代使用。
- 约束处理。蜘蛛在行走过程中可能会移出蜘蛛网,从而导致违反约束,SSA中采用下面的方法保证边界约束:
其中 和 分别为搜索空间第 维的上界和下界。
结束阶段,重复执行上述迭代过程,直到满足终止条件。终止条件可以是最大迭代次数、最大CPU运行时间、误差率、最优适应度值不再改进最大迭代次数或者任意其他合适的准则。
通过以上三个阶段,就构成了完整的SSA算法:
1:分配SSA参数值。
2:创建蜘蛛种群 并分配内存。
3:为每只蜘蛛初始化 。
4:while 不满足终止条件 do
5: for 中的每只蜘蛛 do
6: 评估 的适应度值。
7: 根据式(1)生成 在当前位置的振动。
8: end for
9: for 中的每只蜘蛛 do
10: 根据式(3)计算所有蜘蛛生成的振动 的强度。
11: 从 中选择最强的振动 。
12: if 大于 then
13: 将 存储为 。
14: end if
15: 更新 。
16: 生成 上的随机数 。
17: if then
18: 更新维度掩码 。
19: end if
20: 根据式(4)生成
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();
}
程序运行结果如下: