粒子群优化(Particle Swarm Optimization, PSO),又称微粒群算法,是由J. Kennedy和R. C. Eberhart等于1995年开发的一种演化计算技术,来源于对一个简化社会模型的模拟。
粒子群优化算法的基本思想是通过群体中个体之间的协作和信息共享来寻找最优解.
PSO的优势:在于简单容易实现并且没有许多参数的调节。目前已被广泛应用于函数优化、神经网络训练、模糊系统控制以及其他遗传算法的应用领域。
对于粒子群优化算法,如果我们仅仅分析其基本思想,并不清楚它到底是一个什么原理,那我们该如何通俗的理解它的思想呢?
我们假设有一片森林,森林中有一群鸟,它们在初始化的时候随机的分布在这个森林中,它们正在寻找一个它们需要的东西,而它们手中拥有一个仪器,可以使得它们能够知道自己距离它们要找的东西距离自己有多远,但无法知道那个东西在哪个方向。同时,它们还有一个微信群,可以随时交流。
探索开始。
所有鸟个体随机的在森林中飞行,经过一个单位时间后,它们开始进行第一次探测,每个人都得出自己距离那个物品的距离,并在微信群中共享一次,经过大家的讨论,发现鸟A距离那个物品更近,因此所有鸟则必然会向着A刚刚所在的位置靠近。
再飞行一个单位时间后,重复上述操作。
同时各只鸟在位置不停变化时候离食物的距离也不断变化,所以一定有过离食物最近的位置,这也是它们的一个参考。
通过鸟群在不断搜索的过程中,它们会不断发现距离物品更近的地方,这样,经过足够多的次数的搜索后,必然会足够靠近这个物品。
综上,影响鸟的运动状态变化有下面两个因素:
这几个因素就是我们算法的关键点,好了,我们来看原理描述
PSO算法是基于群体的,根据对环境的适应度将群体中的个体移动到好的区域。然而它不对个体使用演化算子,而是将每个个体看作是D维搜索空间中的一个没有体积的微粒(点),在搜索空间中以一定的速度飞行,这个速度根据它本身的飞行经验和同伴的飞行经验来动态调整。第i个微粒表示为Xi = (xi1, xi2, …, xiD),它经历过的最好位置(有最好的适应值)记为Pi = (pi1, pi2, …, piD),也称为pbest。在群体所有微粒经历过的最好位置的索引号用符号g表示,即Pg,也称为gbest。微粒i的速度用Vi = (vi1, vi2, …, viD)表示。对每一代,它的第d+1维(1 ≤ d+1 ≤ D)根据如下方程进行变化:
vid+1 = w∙vid+c1∙rand()∙(pid-xid)+c2∙Rand()∙(pgd-xid) (1a)
xid+1 = xid+vid (1b)
其中,w为惯性权重(inertia weight),c1和c2为加速常数(acceleration constants),rand()和Rand()为两个在[0,1]范围里变化的随机值。
此外,微粒的速度Vi被一个最大速度Vmax所限制。如果当前对微粒的加速导致它的在某维的速度vid超过该维的最大速度vmax,d,则该维的速度被限制为该维最大速度vmax,d。
对公式(1a),第一部分为微粒先前行为的惯性,第二部分为“认知(cognition)”部分,表示微粒本身的思考;第三部分为“社会(social)”部分,表示微粒间的信息共享与相互合作。
“认知”部分可以由Thorndike的效应法则(law of effect)所解释,即一个得到加强的随机行为在将来更有可能出现。这里的行为即“认知”,并假设获得正确的知识是得到加强的,这样的一个模型假定微粒被激励着去减小误差。
“社会”部分可以由Bandura的替代强化(vicarious reinforcement)所解释。根据该理论的预期,当观察者观察到一个模型在加强某一行为时,将增加它实行该行为的几率。即微粒本身的认知将被其它微粒所模仿。
PSO算法使用如下心理学假设:在寻求一致的认知过程中,个体往往记住自身的信念,并同时考虑同事们的信念。当其察觉同事的信念较好的时候,将进行适应性地调整。
标准PSO的算法流程如下:
PSO参数包括:群体规模N,惯性权重w,加速常数c1和c2,最大速度Vmax,最大代数Gmax。
Vmax决定在当前位置与最好位置之间的区域的分辨率(或精度)。如果Vmax太高,微粒可能会飞过好解,如果Vmax太小,微粒不能进行足够的探索,导致陷入局部优值。该限制有三个目的:防止计算溢出;实现人工学习和态度转变;决定问题空间搜索的粒度。
惯性权重w使微粒保持运动的惯性,使其有扩展搜索空间的趋势,有能力探索新的区域。
加速常数c1和c2代表将每个微粒推向pbest(个体最优位置)和gbest(群体最优位置)位置的统计加速项的权重。低的值允许微粒在被拉回来之前可以在目标区域外徘徊,而高的值导致微粒突然的冲向或者越过目标区域。
如果没有第一部分,即w = 0,则速度只取决于微粒当前的位置和它们历史最好位置pbest和gbest,速度本身没有记忆性。假设一个微粒位于全局最好位置,它将保持静止。而其它微粒则飞向它本身最好位置pbest和全局最好位置gbest的加权中心。在这种条件下,微粒群将统计的收缩到当前的全局最好位置,更象一个局部算法。
在加上第一部分后,微粒有扩展搜索空间的趋势,即第一部分有全局搜索的能力。这也使得w的作用为针对不同的搜索问题,调整算法全局和局部搜索能力的平衡。
如果没有第二部分,即c1 = 0,则微粒没有认知能力,也就是“只有社会(social-only)”的模型。在微粒的相互作用下,有能力到达新的搜索空间。它的收敛速度比标准版本更快,但是对复杂问题,比标准版本更容易陷入局部优值点。
如果没有第三部分,即c2 = 0,则微粒之间没有社会信息共享,也就是“只有认知(cognition-only)”的模型。因为个体间没有交互,一个规模为m的群体等价于m个单个微粒的运行。因而得到解的几率非常小。
如果没有后两部分,即c1 = c2 = 0,微粒将一直以当前的速度飞行,直到到达边界。由于它只能搜索有限的区域,将很难找到好的解。
c1、c2:根据研究表明,通常情况下,c1、c2在取2的情况下,能更好、更快的接近我们希望的答案。
群体规模N:一般取20~40,对较难或特定类别的问题可以取到100~200。
w:对于w,我们需要综合考虑。但由于我们常常无法准确确定问题适合的w,便有人提出了动态的确定w的方法。即线性递减权值策略。
w = wend + ((wstart - wend) * (1.0 - ((i + 1.0) / maxstep)));
wstart和wend为w的最大值和最小值。经过证明,wstart和wend通常取0.9和0.4时算法有较好的性能。i为当前的迭代轮数,maxstep为最大迭代轮数。
通过这种策略,在迭代之初,w会以较大的值对全局进行搜索,然后逐渐减小,这和机器学习中的梯度下降类似。
在本部分,我们对该算法进行实现。
我们尝试使用该算法对如图所示函数求最大值。使用Java实现。
代码如下:
/* 点类,用于保存个体的位置信息以及速度信息 */
class Posiotion{
private double x; //X轴方向
private double y; //Y轴方向
private double f; //适应度
public Posiotion() {
}
public Posiotion(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getF() {
return f;
}
public void setF(double f) {
this.f = f;
}
public String toString(){
return " x: "+x+" y: "+y+" f: "+f;
}
}
public class POS {
private int n = 100; //微粒个数
private Posiotion[] p; //位置数组
private Posiotion[] v; //速度数组
private Posiotion[] pbest; //保存自己到目前为止发现的最好的位置
private Posiotion gbest; //phest中的最好位置
private double vmax = 0.1; //最大速度
private double w = 0.9; //惯性w,初始化
private double wstart = 0.9;
private double wend = 0.4;
private double c1 = 2;
private double c2 = 2;
private long maxstep = 300; //迭代次数
private double xmax = 60; //边界
private double xmin = -60;
private double ymax = 60;
private double ymin = -60;
public POS() {
}
public void fitnessFunction(){ //适应度函数,评估当前微粒
for(int i=0;i<n;i++){
double x=p[i].getX();
double y=p[i].getY();
if (x<30&&y<30){
p[i].setF(30*x-y);
}else if (x<30&&y>=30){
p[i].setF(30*y-x);
}else if (x>=30&&y<30){
p[i].setF(x*x-y/2);
}else if (x>=30&&y>=30){
p[i].setF(20*y*y-500*x);
}
}
}
//初始化参数
public void init() {
p = new Posiotion[n];
v = new Posiotion[n];
pbest = new Posiotion[n];
gbest = new Posiotion(0.0, 0.0);
//对微粒的位置、速度进行随机初始化,math.random能生成[0,1.0)间的随机数
for (int i = 0; i < n; i++) {
p[i] = new Posiotion(Math.random() * 60, Math.random() * 60);
v[i] = new Posiotion(Math.random() * vmax, Math.random() * vmax);
pbest[i] = new Posiotion(0.0, 0.0);
//System.out.println("======"+(i+1)+"======p[i]:"+p[i]);
//System.out.println("======"+(i+1)+"======v[i]:"+v[i]);
}
fitnessFunction(); //对每一个个体进行适应度评估,初始化它们的参数
//对个体最好位置以及群体最好位置进行初始化
gbest.setF(Integer.MIN_VALUE);
for (int i = 0; i < n; i++) {
pbest[i].setX(p[i].getX());
pbest[i].setY(p[i].getY());
pbest[i].setF(p[i].getF());
if (gbest.getF() < pbest[i].getF()){
gbest.setX(pbest[i].getX());
gbest.setY(pbest[i].getY());
gbest.setF(pbest[i].getF());
}
System.out.println("start gbest:"+gbest);
}
}
//迭代函数
public void Pos(){
//一共迭代maxstep次
for (long i = 0; i < maxstep; i++){
//确定本次迭代中w的值
w = wend + ((wstart - wend) * (1.0 - ((i + 1.0) / maxstep)));
System.out.print("w in " + (i + 1) + " is:" + w + "\t");
//每次迭代对所有的微粒进行更新
for (int j = 0; j < n; j++){
//更新微粒的速度,对速度采用正交分解,两个方向
//v = (w * v) + (c1 * rand() * (pbset - x)) + (c2 * rand() * (gbset - x))
v[j].setX((v[j].getX() * w) + (c1 * Math.random() * (pbest[j].getX() - p[j].getX())) + (c2 * Math.random() * (gbest.getX() - p[j].getX())));
v[j].setY((v[j].getY() * w) + (c1 * Math.random() * (pbest[j].getY() - p[j].getY())) + (c2 * Math.random() * (gbest.getY() - p[j].getY())));
//判断速度是否超过最大值,如果超过,则修改为最大值
if (v[j].getX() > vmax)
v[j].setX(vmax);
if (v[j].getY() > vmax)
v[j].setY(vmax);
//更新位置 x = x + v
p[j].setX(p[j].getX() + v[j].getX());
p[j].setY(p[j].getY() + v[j].getY());
//对位置进行判断,如果超出边界,则修改为边界值
if (p[j].getX() > xmax)
p[j].setX(xmax);
if (p[j].getY() > ymax)
p[j].setY(ymax);
if (p[j].getX() < xmin)
p[j].setX(xmin);
if (p[j].getY() < ymin)
p[j].setY(ymin);
}
//对每一个个体适应度进行评估
fitnessFunction();
//更新个体极值和群体极值
for (int j=0; j<n; j++){
if (pbest[j].getF() < p[j].getF()){
pbest[j].setX(p[j].getX());
pbest[j].setY(p[j].getY());
pbest[j].setF(p[j].getF());
}
if(pbest[j].getF() > gbest.getF()){
gbest.setX(pbest[j].getX());
gbest.setY(pbest[j].getY());
gbest.setF(pbest[j].getF());
}
}
System.out.println("======"+(i+1)+"======gbest:"+gbest.toString());
}
}
//开始训练
public static void main(String[] args) {
POS pos = new POS();
pos.init();
pos.Pos();
}
}
运行结果如下: