文献翻译 (3):粒子群优化算法

本文详细介绍了粒子群优化(PSO)算法的工作原理,包括如何模拟鸟类的社会行为,如何在超维空间中寻找函数最小值,以及关键参数的设定和迭代过程。通过实例演示展示了算法的实施步骤和应用效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 缘起

  粒子群优化 (Particle swarm optimization, PSO) 算法是一种基于种群的搜索算法,其模拟了鸟群中鸟类的社会行为。粒子群概念的最初意图是用图形模拟鸟群优美但不可预测的编排,发现控制鸟类同步飞行能力的模式,并通过重新组合成最佳编队来改变航向,后衍生出简单却高效的PSO优化算法。
  在PSO中,个体,亦称粒子通过超维搜索空间“飞行”。搜索空间内粒子位置的变化是基于个体模仿其他个体的成功社会心理倾向。因此,群体中粒子的变化受到其邻居的经验或知识的影响。换言之,一个粒子的搜索行为会受到群体内其他粒子同样行为的影响,所以PSO可以称为是一种共生协作算法。对这种社会行为建模的结果是使得粒子随机返回搜索空间中先前成功区域的搜索过程。

2 示意

  PSO擅长于超维空间中寻找预定义函数的最大或最小值。令 f ( X ) f(X) f(X)表示从向量参数 X X X生成实值的函数,而 X X X可以是空间中的任意取值。例如 X X X是平面坐标 ( x , y ) (x,y) (x,y),PSO将返回以下函数的最小值:
f ( x , y ) = ( x − 3.14 ) 2 + ( y − 2.72 ) 2 + sin ⁡ ( 3 x + 1.41 ) + sin ⁡ ( 4 y − 1.73 ) f(x,y)=(x-3.14)^2+(y-2.72)^2+\sin(3x+1.41)+\sin(4y-1.73) f(x,y)=(x3.14)2+(y2.72)2+sin(3x+1.41)+sin(4y1.73)  绘制如下:

  代码如下:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

raw = np.random.rand(100, 3)
fig = plt.figure()

ax = plt.axes(projection="3d")
x = y = np.arange(start=0, stop=5, step=0.02)
X, Y = np.meshgrid(x, y)
Z = (X - 3.14) ** 2 + (Y - 2.72) ** 2 + np.sin(3 * X + 1.41) + np.sin(4 * Y + 1.73)
ax.plot_surface(X, Y, Z, alpha=0.9, cstride=1, rstride=1, cmap='rainbow')

rotate = lambda angle: ax.view_init(azim=angle)
rot_animation = animation.FuncAnimation(fig, rotate, frames=np.arange(0, 362, 2), interval=100)
rot_animation.save('rotation.gif', dpi=80, writer='imagemagick')

  如图,该函数像一个弯曲的鸡蛋盒,并不属于凸优化,因此可能找到局部最小值而非全局最小值。那如何来找到该函数的最小值呢?
  1)计算每一个点?似乎代价昂贵了些;
  2)随机选择样本点?在找到的最小值点附近,可能找到更小值的点。这一事实也是PSO想要做的。
  与鸟儿捕食相似,首先在空间中找到一组随机点,也就是粒子,然后令它们朝着随机的方向寻找最小值点。在每一次迭代中,每个粒子围绕它找到的最小点以及整个粒子群找到的最小点进行搜索。在确定的多次迭代后,这群粒子曾经探索过的最小值便视作函数的最小值

3 细节

  假设有 P P P个粒子,在第 t t t次迭代中的第 i i i个粒子记作 X i ( t ) X^i(t) Xi(t)。对应于节2中的问题,有 X i ( t ) = ( x i ( t ) , y i ( t ) ) X^i(t)=(x^i(t),y^i(t)) Xi(t)=(xi(t),yi(t)),对应了其在空间中的坐标。进一步,每个粒子的速度定义为 V i ( t ) = ( v x i ( t ) , v y i ( t ) ) V^i(t)=(v^i_x(t),v_y^i(t)) Vi(t)=(vxi(t),vyi(t))。在下一次迭代中,当前粒子的坐标更新为:
X i ( t + 1 ) = X i ( t ) + V i ( t + 1 ) X^i(t+1)=X^i(t)+V^i(t+1) Xi(t+1)=Xi(t)+Vi(t+1)其等价于
x i ( t + 1 ) = x i ( t ) + v x i ( t + 1 ) y i ( t + 1 ) = y i ( t ) + v y i ( t + 1 ) x^i(t+1)=x^i(t)+v^i_x(t+1)\\ y^i(t+1)=y^i(t)+v_y^i(t+1) xi(t+1)=xi(t)+vxi(t+1)yi(t+1)=yi(t)+vyi(t+1)于此同时,速度更新为:
V i ( t + 1 ) = w V i ( t ) + c 1 r 1 ( p b e s t i − X i ( t ) ) + c 2 r 2 ( g b e s t − X i ( t ) ) V^i(t+1)=wV^i(t)+c_1r_1(pbest^i-X^i(t))+c_2r_2(gbest-X^i(t)) Vi(t+1)=wVi(t)+c1r1(pbestiXi(t))+c2r2(gbestXi(t))其中 r 1 r_1 r1 r 2 r_2 r2是0和1之间的随机数; w , c 1 , c 2 w,c_1,c_2 w,c1,c2是PSO的常量参数; p b e s t i pbest^i pbesti粒子 X i ( t ) X^i(t) Xi(t)取得最小值的坐标;以及 g b e s t gbest gbest是所有粒子取得的最小值的坐标。该更新的好处是通过将该粒子找到的最佳点与当前坐标的差值附加在当前速度 V i ( t ) V^i(t) Vi(t)上,可以推动粒子朝着 p b e s t i pbest^i pbesti移动。 g b e s t gbest gbest也是同样的道理。
  我们将 w ∈ ( 0..1 ] w\in(0..1] w(0..1]称为惯性权重参数,其决定了粒子保持原有速度的程度。 c 1 c_1 c1 c 2 c_2 c2分别称为认知和社交系数,它们用于细化粒子本身与群体的搜索结果之间的权衡。
  PSO区别于其他优化算法的一个很重要的属性是不依赖于目标函数的梯度,这使得其可以适应于 f ( X ) f(X) f(X)求导困难的情况。另一个属性是支持并行化,为了找到最优解,多个粒子可以同时行动,收集它们所找到的最小值,我们需要做的则是更新 g b e s t gbest gbest。.这使得map-reduce架构完美匹配PSO。

4 实现

  1)定义优化目标且展示如下:

  代码如下:

import numpy as np
import matplotlib.pyplot as plt


def f(x, y):
    """优化目标"""
    return (x - 3.14) ** 2 + (y - 2.72) ** 2 + np.sin(3 * x + 1.41) + np.sin(4 * y - 1.73)


if __name__ == '__main__':
    x, y = np.array(np.meshgrid(np.linspace(0, 5, 100), np.linspace(0, 5, 100)))
    z = f(x, y)
    x_min = x.ravel()[z.argmin()]
    y_min = y.ravel()[z.argmin()]
    plt.figure(figsize=(8, 6))
    plt.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
    plt.colorbar()
    plt.plot([x_min], [y_min], marker='x', markersize=5, color="white")
    contours = plt.contour(x, y, z, 10, colors='black', alpha=0.4)
    plt.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
    plt.show()

  2)随机初始化粒子及其速度:

# 初始化粒子
    np.random.seed(1)
    n_particles = 20
    X = np.random.rand(2, n_particles) * 5
    V = np.random.randn(2, n_particles) * 0.1

  看看它们在哪里:

  代码如下:

import numpy as np
import matplotlib.pyplot as plt


def f(x, y):
    """优化目标"""
    return (x - 3.14) ** 2 + (y - 2.72) ** 2 + np.sin(3 * x + 1.41) + np.sin(4 * y - 1.73)


if __name__ == '__main__':
    # 初始化粒子
    np.random.seed(1)
    n_particles = 20
    X = np.random.rand(2, n_particles) * 5
    V = np.random.randn(2, n_particles) * 0.1

    x, y = np.array(np.meshgrid(np.linspace(0, 5, 100), np.linspace(0, 5, 100)))
    z = f(x, y)
    x_min = x.ravel()[z.argmin()]
    y_min = y.ravel()[z.argmin()]
    plt.figure(figsize=(8, 6))
    plt.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
    plt.colorbar()
    plt.scatter(X[0], X[1])
    plt.plot([x_min], [y_min], marker='x', markersize=5, color="white")
    contours = plt.contour(x, y, z, 10, colors='black', alpha=0.4)
    plt.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
    plt.show()

  3)计算当前的最小值点:

pbest = X
pbest_obj = f(X[0], X[1])
gbest = pbest[:, pbest_obj.argmin()]
gbest_obj = pbest_obj.min()

  加上方向看看:

  代码如下:

import numpy as np
import matplotlib.pyplot as plt


def f(x, y):
    "Objective function"
    return (x - 3.14) ** 2 + (y - 2.72) ** 2 + np.sin(3 * x + 1.41) + np.sin(4 * y - 1.73)


x, y = np.array(np.meshgrid(np.linspace(0, 5, 100), np.linspace(0, 5, 100)))
z = f(x, y)

x_min = x.ravel()[z.argmin()]
y_min = y.ravel()[z.argmin()]

n_particles = 20
np.random.seed(1)
X = np.random.rand(2, n_particles) * 5
V = np.random.randn(2, n_particles) * 0.1

pbest = X
pbest_obj = f(X[0], X[1])
gbest = pbest[:, pbest_obj.argmin()]
gbest_obj = pbest_obj.min()

fig, ax = plt.subplots(figsize=(8, 6))
fig.set_tight_layout(True)
img = ax.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
fig.colorbar(img, ax=ax)
ax.plot([x_min], [y_min], marker='x', markersize=5, color="white")
contours = ax.contour(x, y, z, 10, colors='black', alpha=0.4)
ax.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
pbest_plot = ax.scatter(pbest[0], pbest[1], marker='o', color='black', alpha=0.5)
p_plot = ax.scatter(X[0], X[1], marker='o', color='blue', alpha=0.5)
p_arrow = ax.quiver(X[0], X[1], V[0], V[1], color='blue', width=0.005, angles='xy', scale_units='xy', scale=1)
gbest_plot = plt.scatter([gbest[0]], [gbest[1]], marker='*', s=100, color='black', alpha=0.4)
ax.set_xlim([0, 5])
ax.set_ylim([0, 5])
plt.show()

  4)初始化相关参数:

c1 = c2 = 0.1
w = 0.8

  5)设置更新函数:

def update():
    global V, X, pbest, pbest_obj, gbest, gbest_obj

    r1, r2 = np.random.rand(2)
    V = w * V + c1 * r1 * (pbest - X) + c2 * r2 * (gbest.reshape(-1, 1) - X)
    X = X + V
    obj = f(X[0], X[1])
    pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
    pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
    gbest = pbest[:, pbest_obj.argmin()]
    gbest_obj = pbest_obj.min()

  6)更新一次看看:

  更新两次:

  更新5次:

  更新10次:

  更新20次:

  更新30次:

  代码如下:

import numpy as np
import matplotlib.pyplot as plt


def f(x, y):
    return (x - 3.14) ** 2 + (y - 2.72) ** 2 + np.sin(3 * x + 1.41) + np.sin(4 * y - 1.73)


x, y = np.array(np.meshgrid(np.linspace(0, 5, 100), np.linspace(0, 5, 100)))
z = f(x, y)

x_min = x.ravel()[z.argmin()]
y_min = y.ravel()[z.argmin()]

c1 = c2 = 0.1
w = 0.8

n_particles = 20
np.random.seed(1)
X = np.random.rand(2, n_particles) * 5
V = np.random.randn(2, n_particles) * 0.1

pbest = X
pbest_obj = f(X[0], X[1])
gbest = pbest[:, pbest_obj.argmin()]
gbest_obj = pbest_obj.min()


def update():
    global V, X, pbest, pbest_obj, gbest, gbest_obj

    r1, r2 = np.random.rand(2)
    V = w * V + c1 * r1 * (pbest - X) + c2 * r2 * (gbest.reshape(-1, 1) - X)
    X = X + V
    obj = f(X[0], X[1])
    pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
    pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
    gbest = pbest[:, pbest_obj.argmin()]
    gbest_obj = pbest_obj.min()


# 这里设置更新次数
for i in range(1):
    update()

fig, ax = plt.subplots(figsize=(8, 6))
fig.set_tight_layout(True)
img = ax.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
fig.colorbar(img, ax=ax)
ax.plot([x_min], [y_min], marker='x', markersize=5, color="white")
contours = ax.contour(x, y, z, 10, colors='black', alpha=0.4)
ax.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
pbest_plot = ax.scatter(pbest[0], pbest[1], marker='o', color='black', alpha=0.5)
p_plot = ax.scatter(X[0], X[1], marker='o', color='blue', alpha=0.5)
p_arrow = ax.quiver(X[0], X[1], V[0], V[1], color='blue', width=0.005, angles='xy', scale_units='xy', scale=1)
gbest_plot = plt.scatter([gbest[0]], [gbest[1]], marker='*', s=100, color='black', alpha=0.4)
ax.set_xlim([0, 5])
ax.set_ylim([0, 5])
plt.show()

  7)画一个动图看看:

  完整代码:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


def f(x, y):
    return (x - 3.14) ** 2 + (y - 2.72) ** 2 + np.sin(3 * x + 1.41) + np.sin(4 * y - 1.73)


x, y = np.array(np.meshgrid(np.linspace(0, 5, 100), np.linspace(0, 5, 100)))
z = f(x, y)

x_min = x.ravel()[z.argmin()]
y_min = y.ravel()[z.argmin()]

c1 = c2 = 0.1
w = 0.8

n_particles = 20
np.random.seed(1)
X = np.random.rand(2, n_particles) * 5
V = np.random.randn(2, n_particles) * 0.1

pbest = X
pbest_obj = f(X[0], X[1])
gbest = pbest[:, pbest_obj.argmin()]
gbest_obj = pbest_obj.min()


def update():
    global V, X, pbest, pbest_obj, gbest, gbest_obj

    r1, r2 = np.random.rand(2)
    V = w * V + c1 * r1 * (pbest - X) + c2 * r2 * (gbest.reshape(-1, 1) - X)
    X = X + V
    obj = f(X[0], X[1])
    pbest[:, (pbest_obj >= obj)] = X[:, (pbest_obj >= obj)]
    pbest_obj = np.array([pbest_obj, obj]).min(axis=0)
    gbest = pbest[:, pbest_obj.argmin()]
    gbest_obj = pbest_obj.min()


fig, ax = plt.subplots(figsize=(8, 6))
fig.set_tight_layout(True)
img = ax.imshow(z, extent=[0, 5, 0, 5], origin='lower', cmap='viridis', alpha=0.5)
fig.colorbar(img, ax=ax)
ax.plot([x_min], [y_min], marker='x', markersize=5, color="white")
contours = ax.contour(x, y, z, 10, colors='black', alpha=0.4)
ax.clabel(contours, inline=True, fontsize=8, fmt="%.0f")
pbest_plot = ax.scatter(pbest[0], pbest[1], marker='o', color='black', alpha=0.5)
p_plot = ax.scatter(X[0], X[1], marker='o', color='blue', alpha=0.5)
p_arrow = ax.quiver(X[0], X[1], V[0], V[1], color='blue', width=0.005, angles='xy', scale_units='xy', scale=1)
gbest_plot = plt.scatter([gbest[0]], [gbest[1]], marker='*', s=100, color='black', alpha=0.4)
ax.set_xlim([0, 5])
ax.set_ylim([0, 5])


def animate(i):
    "Steps of PSO: algorithm update and show in plot"
    title = 'Iteration {:02d}'.format(i)
    # Update params
    update()
    # Set picture
    ax.set_title(title)
    pbest_plot.set_offsets(pbest.T)
    p_plot.set_offsets(X.T)
    p_arrow.set_offsets(X.T)
    p_arrow.set_UVC(V[0], V[1])
    gbest_plot.set_offsets(gbest.reshape(1, -1))
    return ax, pbest_plot, p_plot, p_arrow, gbest_plot


anim = FuncAnimation(fig, animate, frames=list(range(1, 30)), interval=500, blit=False, repeat=True)
anim.save("PSO.gif", dpi=120, writer="imagemagick")

print("PSO found best solution at f({})={}".format(gbest, gbest_obj))
print("Global optimal at f({})={}".format([x_min, y_min], f(x_min, y_min)))

参考文献

【1】Complete Step-by-step Particle Swarm Optimization Algorithm from Scratch
【2】https://machinelearningmastery.com/a-gentle-introduction-to-particle-swarm-optimization/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值