粒子群算法的优缺点(粒子群算法可以解决什么问题)

简介

粒子群算法(Particle Swarm Optimization,PSO)是解决优化问题的经典智能算法之一。

1995年,Eberhart和Kennedy从鸟群等群体社会模型中获得启发,抽象出粒子群算法,其特点是强调群体的信息共享。PSO概念简单,很容易与其他优秀的算法和技巧结合,被广泛应用于工程、计算机科学、行为管理科学等领域。

核心思想

粒子群可能是最简单的元启发式算法,我从一个简化的粒子群算法开始说明。

一开始,粒子在搜索空间中任意位置随机出生,每个粒子坐标都能计算出一个解,其中最优解的粒子坐标就是这个群体当前认为的最佳位置。

如果把这个最优解理解成食物最多,粒子群算法强调群体的直接信息共享,因此,所有粒子都立马知道了这里食物最多,全聚集过来了。

最简单的元启发式算法 - 粒子群算法

基础粒子群算法

在我看来,上述过程就是粒子群算法的核心。当然,作为一种目标是全局优化的算法,为了提高群体对搜索空间的探索能力,基础的粒子群算法除了记忆群体搜索时历史最优位置,还会记忆个体在搜索时的历史最优位置。

这两个最优位置就像某种引力源,使得粒子受到群体最佳和个体最佳的吸引。形象地说,就是个体既会考虑集体的观点,也会相信自己的经验。而全局优化算法必不可少的随机因素,也主要体现在每次搜索时这两个引力源的引力权重

最简单的元启发式算法 - 粒子群算法

这种直接牵引特点使得粒子群算法能够快速收敛,但也容易过早陷入局部最优。在粒子群算法的发展过程中,也引入了遗传算法中的进化等概念,以提高全局搜索能力。

说到这里,大家可能已经发现了元启发式算法的特点,确定简单但核心的启发思想后,开始加料,如增加随机因素,结合优秀的算法和技巧。元启发式算法的目标几乎都是找到全局最优,因此改进的方向大都是提高探索能力,避免过早陷入局部最优。

公式重构

我们接着聊基础的粒子群算法。依据前文所述的群体最佳和个体最佳两个要素,Eberhart和Kennedy给出的粒子坐标更新公式为:

其中速度更新公式为:

看得出来,他们俩写这个公式的时候就是原汁原味地把他们脑子想的搬出来了。在我眼里,这个公式其实与数值优化中的基本迭代格式非常相似:

这么一比,数值优化迭代格式的和粒子群算法更新公式中的直接对应上了。

我们从信息的角度来理解这个问题的本质。不管是粒子群优化还是数值优化,迭代方法都是逐个探索搜索空间中的坐标。我们把自己当做一个粒子,每迭代一次,就需要外界给出新的指引信息,以帮助我们更好地去往下一个点,而不是瞎跑。自然,数值优化中指引方向的就是梯度等信息,而粒子群算法虽然没有高阶信息,但是有兄弟,其他人会告诉自己哪里食物更多。

最简单的元启发式算法 - 粒子群算法

既然两者的逻辑如此相似,我直接考虑对粒子群算法的速度更新公式进行了魔改,使得其更贴近熟悉的数值优化框架,便于理解。注意到速度更新公式中,本质上就是历史速度,个体最佳引力和全局最佳引力在争夺权重。根据概念不同,设:

其中global_weight就是全局最佳引力的权重,根据粒子群算法的设定,默认为0-1的随机值,也可以指定为固定值(如设置global_weight较大使得粒子偏向全局最佳靠近)。或者设计为与当前迭代次数相关,使得前期随机性强,后期确定性强。再有:

其中是搜索步长,和数值优化一样,确定方向后,我们也可以进一步抉择在这个方向上走多远。有了这个概念后,甚至也可以在粒子群中引入线搜索确定步长的方法,或者AdaDelta等步长优化方法。

另外,其中history_weight是全局最佳引力的权重,根据粒子群算法的设定,大概为0.5,并将步长设定为2。对梯度下降法有一些了解的同学可能会发现,好家伙,这就是动量的概念啊。后面这部分就是在权衡考虑多少历史速度,本质上是指数移动平均方法。由于保留了较多的历史速度,这可能是粒子群算法收敛较快,同时会产生震荡现象的原因。

至此,我们完成了对粒子坐标更新公式的重新理解,能够明白每个参数的设定会产生什么影响。

算法实现

理解了基本概念后,就可开始设计代码了。粒子的位置、速度等数据需要高频使用,故不考虑创建粒子个体对象,而是形成粒子群整体的数据表

出于性能的需要,数据表并不是像结构体数组一样的整表,而是另写了一个由数组构成的类ParticleSwarm。ParticleSwarm应当提供粒子群数据的更新方法。通过python的魔法方法 __getitem__,也能很轻松地做到像整表索引一样获取每个粒子的信息。

另外再写一个ParticleSwarmOptimizer类型,用于记录优化器参数,定义优化逻辑,并提供基本的优化问题的接口solve。solve返回最终优化结果,其中callback可以接收优化过程的数据用于分析或可视化。

class ParticleSwarm:
 def __init__():
        self.velocity
        self.x
        self.y
        self.swarm_best
        ...
        
    def how_to_update_data():
        ...

class ParticleSwarmOptimizer:
    def __init__(opt_parameters):
        ...
        
    def solve(func,bound,constraint,callback)->opt_result:
        ...
        
    def step()# how_to_update_data
        update_v()
        update_x()
        update_y()
        update_swarm_best()
        ...

省略亿点细节,我们写完了一个粒子群优化器,找几个函数看看优化过程有没有问题。

测试

branin

最简单的元启发式算法 - 粒子群算法

ackley

最简单的元启发式算法 - 粒子群算法

似乎还挺像那么回事,再用timeit测测速度。

在github上找到一个star比较高的包,比较下性能。

最简单的元启发式算法 - 粒子群算法

使用相同参数优化同一个问题,都设置为达到迭代次数上限,设置粒子数量50个:

最简单的元启发式算法 - 粒子群算法

嗯,也就快了3倍。

再设置粒子数量为500个:

最简单的元启发式算法 - 粒子群算法

快了18倍。差不多得了。

版权声明