蝙蝠算法(BA)是由 Xin-She Yang 于 2010 年开发的一种高效生物启发式(bio-inspired)算法。
蝙蝠是一种神奇的动物。它们是唯一一种有翅膀的哺乳动物,它们也具有先进的回声定位能力。据估计,大约有 1000 种不同的蝙蝠种类,占所有哺乳动物种类的 20%。它们的大小从微小的凹脸蝠(bumblebee bats)(约 1.5 至 2 克)到翼展约 2 米,重量高达约 1 公斤的巨型蝙蝠。小蝙蝠(microbats)通常具有约 2.2 至 11 厘米的前臂长度。大多数蝙蝠在一定程度上都使用回声定位,在所有的这些蝙蝠中,小蝙蝠因广泛使用回声定位而著名,而狐蝠(megabats)则不。
大多数小蝙蝠是食虫动物。小蝙蝠使用一种称为回声定位的声纳来检测猎物,避开障碍物,并在黑暗中找到栖息的裂缝。这些蝙蝠发出非常响亮的声波脉冲,并监听从周围物体反射回来的回声。发出脉冲的特性与狩猎战略相关,依据猎物种类不同而改变。大多数蝙蝠使用短调频信号扫过一个八度音阶;其他的则经常使用恒定频信号进行回声定位。它们的信号带宽随着物种而变化,通常通过使用更多的谐波来增加。
研究表明,小蝙蝠使用了回声发射与检测的时延,两耳之间的时间差和回声响度的变化来建立周围环境的三维场景。它们可以侦测到目标的距离和方向,猎物的类型,甚至猎物的移动速度,如小昆虫。事实上,研究表明,蝙蝠似乎能够通过目标昆虫的翅膀颤动率引起的多普勒效应的变化来区分目标。
尽管每个脉冲的持续时间只有千分之几秒(最长可达 8 到 10 ms),但它的频率通常在 25 kHz 到 150 kHz 之间。尽管有些种类的蝙蝠可以发出高达 150 kHz 的高频声波,但大多数蝙蝠的典型频率范围在 25 kHz 到 100 kHz 之间。每个超声波脉冲可以(典型地)持续 5 到 20ms,小蝙蝠每秒可以发出大约 10 到 20 个这样的声波脉冲。当它们捕猎靠近猎物时,脉冲发射速率可以被加速到每秒约 200 个脉冲。这样的短时声音突发意味着蝙蝠具有奇妙的信号处理能力。事实上,研究表明,蝙蝠耳朵的等效积分时间通常约为 300 到 400 微秒。
由于空气中室温下声速的典型值为 v=340m/s ,所以以恒定频率 f 发出的超声波,其波长 λ 由 λ=v/f 给出,其中,典型波长为 2 mm 到 14 mm,典型频率为 25 kHZ 到 150 kHZ。这些波长显然与猎物尺寸在同一数量级[2,27]。
令人惊讶的是,蝙蝠发出的脉冲可能高达 110 分贝(dB),幸运的是,脉冲处于超声波区。响度也随着目的不同而改变,从最响的搜寻猎物到相对安静飞向猎物。这种短脉冲的行程范围通常是几米,其取决于实际的频率。小蝙蝠可以设法避免如人头发丝一样薄的障碍物。
很显然,有些蝙蝠视力好,大多数蝙蝠也有非常敏感的嗅觉。在实际中,他们将所有的感官结合在一起,最大限度地发现猎物,平稳导航。但是,这里我们只关心回声定位以及相关的行为。
小蝙蝠的回声定位行为可以通过与待优化的目标函数相关联的方式来表达,这使得有可能制定新的优化算法。这里我们首先概述 BA 的基本表述,然后讨论它的实现。
如果理想化小蝙蝠回声定位的一些特征,我们就可以开发出各种蝙蝠启发(bat-inspired)或蝙蝠算法了[32]。为了简单起见,我们使用以下近似或理想的规则:
另一个明显的简化是,估计时延和三维地形时不使用射线追踪(ray tracing)。尽管在计算几何应用中这可能是一个很好的特性,但在这里不使用这种简化,因为在多维时增加了计算量。
除了这些简化以外,我们还使用了以下近似值。通常,频率 f 在范围 [fmin,fmax] 内,相应的波长在范围 [λmin,λmax] 内。例如,频率在 [20 kHz, 500 kHz] 范围内,则相应的的波长在 [0.7 mm, 17 mm] 范围内。
对于给定的问题,我们可以使用任意波长来实现。在实际应用中,我们可以通过调整频率(波长)来调整范围。选择的范围(或最大波长)应与感兴趣区域的大小相当,然后再逐渐调整至较小的范围。而且,我们不一定必须调节波长本身。相反,可以通过改变频率来实现。这是因为 λ 和 f 相关, λf 的值是常数。稍后我们给出使用这一方法的实现。
为简单起见,可以假设 f∈[0,fmax] 。我们知道较高的频率具有短的波长和较短的距离。蝙蝠的典型范围是几米。冲发射率可以表示为 [0,1] ,其中 0 表示完全没有脉冲,1 表示最大的脉冲发射率。
基于以上近似和理想化规则,BA 的基本步骤可以总结为以下伪代码:
初始化蝙蝠种群 xi 和 vi (i=1,2,...,n)
初始化频率 fi,脉冲发射率 ri ,和响度 Ai
while (t < Max 迭代数)
通过调整频率产生新解
更新速度与位置/解 [公式(1) 到 (3)]
if (rand > ri)
从最佳解中选择一个
围绕选择的解产生一个局部解
end if
通过随机飞行产生新解
if (rand < Ai & f(xi) < f(x*))
接受新解
增加 ri 减少 Ai
end if
排列蝙蝠,找出当前最佳解 x*
end while
Pseudo code of the bat algorithm
在模拟中,我们使用虚拟蝙蝠。需要定义d-维搜索空间虚拟蝙蝠位置 xi 速度 vi 值更新的规则,在时间步长 t 新解 xti 和速度 vti 给出如下
其中 β∈[0,1] 是服从均匀分布的随机向量。这里 x∗ 是当前全局最佳位置(解),它是在比较了所有 n 个蝙蝠的所有解后找到的。由于乘积 lambdaifi 是一个常数,我们可以使用 fi (或 λi )来调整速度大小,同时固定另一个因子 λi (或 fi ),这取决于具体的问题类型。对于我们的实现,使用 fmin=0 和 fmax=O(1) ,其取决于感兴趣问题域的大小。开始时,每个蝙蝠被随机分配一个服从均匀分布 [fmin,fmax] 的频率。
对于局部搜索部分,一旦从当前最佳解中选择了一个解,则使用随机游走对每个蝙蝠产生一个新解
蝙蝠速度和位置的更新可能与标准粒子群优化(PSO)程序有些相似,因为 fi 本质上控制了粒子群运动的速度和范围。然而,BA 更有效,因为它使用频率调节和参数控制来影响种群的探索发现。
此外,响度 Ai 和脉冲发射率 ri 也随着迭代的进行而相应地更新。因为一旦发现猎物,响度通常会降低,而脉冲发射率会增加。响度可以为了方便而设置成任意值。为简单起见,可以设 A0=1,Amin=0 ,假设 Amin=0 意味着一只蝙蝠刚刚了找到猎物,暂时停止发声。那么现在我们有
参数的选择需要试验。开始时,每只蝙蝠应具有不同的响度和脉冲发射率,这可以通过随机化来实现。例如,初始响度 A0i 通常可以取为 1,初始 发射率 r0i 如果使用(6)式,可以是 0 或任何 r0i∈(0,1] ,只有当新解得到改进时,响度和发射率才会更新,这便意味着这些蝙蝠正朝着最优解努力。
仔细分析 BA,我们可以发现它可以捕捉到许多其他算法的特征。如果我们用随机参数代替频率 fi 并设 Ai=0 , ri=1 ,BA 基本上成为了标准 PSO。同样,如果不使用速度,而使用固定的响度和发射率: Ai 和 ri 。例如, Ai=ri=0.7 ,该算法则简化为简单和声搜索(harmony search, HS),因为频率/波长的变化本质上是音调的调整,而脉冲发射率则类似于 HS 中的谐波接受率。换句话说,HS 和 PSO 可以被认为是 BA 的特例。因此,BA 高效并不奇怪。
从伪代码来看,用编程语言实现 BA 都是相对简单的。 为了便于可视化,我们用 Matlab 实现了它。
有许多用于验证新算法的标准测试函数。我们来看看一个简单的基准函数 egg crate 函数
eggcrate 函数(左)和最近 10 次迭代 40 只蝙蝠的位置
为了演示,我们将 A 和 r 设为常数以简化蝙蝠算法。
% -------------------------------------------------------- %
% Bat-inspired algorithm for continuous optimization (demo)%
% Programmed by Xin-She Yang @Cambridge University 2010 %
% -------------------------------------------------------- %
function [best,fmin,N_iter]=bat_algorithm(para)
% Default parameters
if nargin<1, para=[10 0.25 0.5]; end
n=para(1); % Population size, typically 10 to 25
A=para(2); % Loudness (constant or decreasing)
r=para(3); % Pulse rate (constant or decreasing)
% This frequency range determines the scalings
Qmin=0; % Frequency minimum
Qmax=2; % Frequency maximum
% Iteration parameters
tol=10^(-5); % Stop tolerance
N_iter=0; % Total number of function evaluations
% Dimension of the search variables
d=3;
% Initial arrays
Q=zeros(n,1); % Frequency
v=zeros(n,d); % Velocities
% Initialize the population/solutions
for i=1:n
Sol(i,:)=randn(1,d);
Fitness(i)=Fun(Sol(i,:));
end
% Find the current best
[fmin,I]=min(Fitness);
best=Sol(I,:);
% Start the iterations -- Bat Algorithm
while (fmin>tol)
% Loop over all bats/solutions
for i=1:n
Q(i)=Qmin+(Qmin-Qmax)*rand;
v(i,:)=v(i,:)+(Sol(i,:)-best)*Q(i);
S(i,:)=Sol(i,:)+v(i,:);
% Pulse rate
if rand>r
S(i,:)=best+0.01*randn(1,d);
end
% Evaluate new solutions
Fnew=Fun(S(i,:));
% If the solution improves or not too loudness
if (Fnew<=Fitness(i)) & (randi,:)=S(i,:);
Fitness(i)=Fnew;
end
% Update the current best
if Fnew<=fmin
best=S(i,:);
fmin=Fnew;
end
end
N_iter=N_iter+n;
end
% Output/display
disp(['Number of evaluations: ',num2str(N_iter)]);
disp(['Best = ',num2str(best),' fmin = ',num2str(fmin)]);
% Objective function -- Rosenbrock's 3D function
function z=Fun(u)
z=(1-u(1))^2+100*(u(2)-u(1)^2)^2+(1-u(3))^2;
BA 在许多测试函数上的精度和效率远优于其他算法,因为通常它提供了一个更快的收敛速度。
python version
#!/usr/bin/env python3
"""An implementation of Bat Algorithm
"""
import numpy as np
from numpy.random import random as rand
__author__ = "GreatX"
__copyright__ = "Copyright 2017"
__credits___ = ["GreatX"]
__license__ = "MIT"
__email__ = "[email protected]"
# Parameters setting
# objfun: objective function
# N_pop: population size, typically 10 to 40
# N_gen: number of generation
# A: loudness (constant or decreasing)
# r: pulse rate (constant or decreasing)
# This frequency range determines the scalings
# You should change these values if necessary
# Qmin: frequency minmum
# Qmax: frequency maxmum
# d: number of dimensions
# lower: lower bound
# upper: upper bound
def bat_algorithm(objfun, N_pop=20, N_gen=1000, A=0.5, r=0.5,
Qmin=0, Qmax=2, d=10, lower=-2, upper=2):
N_iter = 0 # Total number of function evaluations
# Limit bounds
Lower_bound = lower * np.ones((1,d))
Upper_bound = upper * np.ones((1,d))
Q = np.zeros((N_pop, 1)) # Frequency
v = np.zeros((N_pop, d)) # Velocities
S = np.zeros((N_pop, d))
# Initialize the population/soutions
# Sol = np.random.uniform(Lower_bound, Upper_bound, (N_pop, d))
# Fitness = objfun(Sol)
Sol = np.zeros((N_pop, d))
Fitness = np.zeros((N_pop, 1))
for i in range(N_pop):
Sol[i] = np.random.uniform(Lower_bound, Upper_bound, (1, d))
Fitness[i] = objfun(Sol[i])
# Find the initial best solution
fmin = min(Fitness)
Index = list(Fitness).index(fmin)
best = Sol[Index]
# Start the iterations
for t in range(N_gen):
# Loop over all bats/solutions
for i in range(N_pop):
# Q[i] = Qmin + (Qmin - Qmax) * np.random.rand
Q[i] = np.random.uniform(Qmin, Qmax)
v[i] = v[i] + (Sol[i] - best) * Q[i]
S[i] = Sol[i] + v[i]
# Apply simple bounds/limits
Sol[i] = simplebounds(Sol[i], Lower_bound, Upper_bound)
# Pulse rate
if rand() > r:
# The factor 0.001 limits the step sizes of random walks
S[i] = best + 0.001*np.random.randn(1, d)
# Evaluate new solutions
# print(i)
Fnew = objfun(S[i])
# Update if the solution improves, or not too loud
if (Fnew <= Fitness[i]) and (rand() < A):
Sol[i] = S[i]
Fitness[i] = Fnew
# update the current best solution
if Fnew <= fmin:
best = S[i]
fmin = Fnew
N_iter = N_iter + N_pop
print('Number of evaluations: ', N_iter)
print("Best = ", best, '\n fmin = ', fmin)
return best
def simplebounds(s, Lower_bound, Upper_bound):
Index = s > Lower_bound
s = Index * s + ~Index * Lower_bound
Index = s < Upper_bound
s = Index * s + ~Index * Upper_bound
return s
# u: array-like
def test_function(u):
a = u ** 2
return a.sum(axis=0)
if __name__ == '__main__':
# print(bat_algorithm(test_function))
bat_algorithm(test_function)
# l = [1, 2, 3, 4, 5]
# u = np.array(l)
# print(test_function(u))
# lb = np.array([1, 1, 1, 1])
# ub = np.array([2, 2, 2, 2])
# s = np.array([0.5, 3, 1.5, 1.5])
# print(simplebounds(s, lb, ub))