2020.06.24:修改錯誤的Schwefel
本篇主要是紀錄Particle swarm optimization (PSO). A tutorial的閱讀心得。
最近腦子動到想用PSO來訓練神經網路,所以蒐集了一些相關資料,預計將分享5篇文章
第一篇是敲門磚,主要是描述基本算法框架,並且進一步的提供超參數的設定指南
第二篇是我碩班用的演算法,我只能說這個真的很猛
第三篇的作者群和第二篇相似,趁這次機會強迫自己看一下
第四篇是很早期的PSO+NN,雖然內容簡單但勝在可讀性絕佳
第五篇我認為是近代PSO+NN的聖經,但我今天測試的結果是不太好,找機會在分享
完全沒碰過的請直接看PSO 粒子群演算法
簡單來說,一群個被稱為粒子的潛在解在多維度解空間中找尋最佳解位置,粒子每次移動都會參考自身過往曾找到的的最佳解位置、所有例子的過往最佳解位置,然後再決定移動方向還有距離。
假設解空間總共有D個維度,則粒子i的結構應設計為
因為這個演算法明子叫作粒子'群',所以我們會將N個粒子灑在解空間中,所以
編程上,我習慣用row代表粒子,而column代表維度,用Sphere Function作為範例:
這邊我直接上結果,若想進一步了解請看原文
從前文2.2.2(b)可知,x(t+1)=x(t)+v(t+1),若v(t+1)過大會導致:
U是指uniform distribution(均勻分布)
為防止速度爆炸(velocity explosion)因此初始速度V(0)建議極小值或者直接設0
k是超參數
這個就是目前PSO相關文獻最常看到的速率更新公式,透過w來縮小v(t),以防止速度爆炸
原文建議50個粒子,因為當粒子數大於50後對於求解幫助明顯下降
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
class PSO():
def __init__(self, fit_func, num_dim, num_particle=20, max_iter=500,
x_max=1, x_min=-1, w_max=0.9, w_min=0.4, c1=2.0, c2=2.0, k=0.2):
self.fit_func = fit_func
self.num_dim = num_dim
self.num_particle = num_particle
self.max_iter = max_iter
self.x_max = x_max
self.x_min = x_min
self.w = w_max
self.w_max = w_max
self.w_min = w_min
self.c1 = c1
self.c2 = c2
self.k = k
self._iter = 1
self.gBest_curve = np.zeros(self.max_iter)
self.X = np.random.uniform(low=self.x_min, high=self.x_max,
size=[self.num_particle, self.num_dim])
self.V = np.zeros(shape=[self.num_particle, self.num_dim])
self.v_max = self.k*(self.x_max-self.x_min)
self.pBest_X = self.X.copy()
self.pBest_score = self.fit_func(self.X)
self.gBest_X = self.pBest_X[self.pBest_score.argmin()]
self.gBest_score = self.pBest_score.min()
self.gBest_curve[0] = self.gBest_score.copy()
def opt(self):
while(self._iter<self.max_iter):
R1 = np.random.uniform(size=(self.num_particle, self.num_dim))
R2 = np.random.uniform(size=(self.num_particle, self.num_dim))
w = self.w_max - self._iter*(self.w_max-self.w_min)/self.max_iter
for i in range(self.num_particle):
self.V[i, :] = w * self.V[i, :] \
+ self.c1 * (self.pBest_X[i, :] - self.X[i, :]) * R1[i, :] \
+ self.c2 * (self.gBest_X - self.X[i, :]) * R2[i, :]
self.V[i, self.v_max < self.V[i, :]] = self.v_max[self.v_max < self.V[i, :]]
self.V[i, -self.v_max > self.V[i, :]] = -self.v_max[-self.v_max > self.V[i, :]]
self.X[i, :] = self.X[i, :] + self.V[i, :]
self.X[i, self.x_max < self.X[i, :]] = self.x_max[self.x_max < self.X[i, :]]
self.X[i, self.x_min > self.X[i, :]] = self.x_min[self.x_min > self.X[i, :]]
score = self.fit_func(self.X[i, :])
if score<self.pBest_score[i]:
self.pBest_score[i] = score.copy()
self.pBest_X[i, :] = self.X[i, :].copy()
if score<self.gBest_score:
self.gBest_score = score.copy()
self.gBest_X = self.X[i, :].copy()
self.gBest_curve[i] = self.gBest_score.copy()
self._iter = self._iter + 1
def plot_curve(self):
plt.figure()
plt.title('loss curve ['+str(round(self.gBest_curve[-1], 3))+']')
plt.plot(self.gBest_curve, label='loss')
plt.grid()
plt.legend()
plt.show()
測試代碼
from PSO import PSO
import numpy as np
import time
import pandas as pd
np.random.seed(42)
def Sphere(x):
if x.ndim==1:
x = x.reshape(1, -1)
return np.sum(x**2, axis=1)
def Schwefel_P222(x):
if x.ndim==1:
x = x.reshape(1, -1)
return np.sum(np.abs(x), axis=1) + np.prod(np.abs(x), axis=1)
def Quadric(x):
if x.ndim==1:
x = x.reshape(1, -1)
outer = 0
for i in range(x.shape[1]):
inner = np.sum(x[:, :i+1], axis=1)**2
outer = outer + inner
return outer
def Rosenbrock(x):
if x.ndim==1:
x = x.reshape(1, -1)
left = x[:, :-1].copy()
right = x[:, 1:].copy()
return np.sum(100*(right - left**2)**2 + (left-1)**2, axis=1)
def Step(x):
if x.ndim==1:
x = x.reshape(1, -1)
return np.sum(np.round((x+0.5), 0)**2, axis=1)
def Quadric_Noise(x):
if x.ndim==1:
x = x.reshape(1, -1)
matrix = np.arange(x.shape[1])+1
return np.sum((x**4)*matrix, axis=1)+np.random.rand(x.shape[0])
def Schwefel(x):
if x.ndim==1:
x = x.reshape(1, -1)
return -1*np.sum(x*np.sin(np.abs(x)**.5), axis=1)
def Rastrigin(x):
if x.ndim==1:
x = x.reshape(1, -1)
return np.sum(x**2 - 10*np.cos(2*np.pi*x) + 10, axis=1)
def Noncontinuous_Rastrigin(x):
if x.ndim==1:
x = x.reshape(1, -1)
outlier = np.abs(x)>=0.5
x[outlier] = np.round(2*x[outlier])/2
return np.sum(x**2 - 10*np.cos(2*np.pi*x) + 10, axis=1)
def Ackley(x):
if x.ndim==1:
x = x.reshape(1, -1)
left = 20*np.exp(-0.2*(np.sum(x**2, axis=1)/x.shape[1])**.5)
right = np.exp(np.sum(np.cos(2*np.pi*x), axis=1)/x.shape[1])
return -left - right + 20 + np.e
def Griewank(x):
if x.ndim==1:
x = x.reshape(1, -1)
left = np.sum(x**2, axis=1)/4000
right = np.prod( np.cos(x/((np.arange(x.shape[1])+1)**.5)), axis=1)
return left - right + 1
d = 30
g = 500
p = 20
times = 1
table = np.zeros((2, 11))
for i in range(times):
x_max = 100*np.ones(d)
x_min = -100*np.ones(d)
optimizer = PSO(fit_func=Sphere, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 0] += optimizer.gBest_score
table[1, 0] += end - start
x_max = 10*np.ones(d)
x_min = -10*np.ones(d)
optimizer = PSO(fit_func=Schwefel_P222, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 1] += optimizer.gBest_score
table[1, 1] += end - start
x_max = 100*np.ones(d)
x_min = -100*np.ones(d)
optimizer = PSO(fit_func=Quadric, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 2] += optimizer.gBest_score
table[1, 2] += end - start
x_max = 10*np.ones(d)
x_min = -10*np.ones(d)
optimizer = PSO(fit_func=Rosenbrock, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 3] += optimizer.gBest_score
table[1, 3] += end - start
x_max = 100*np.ones(d)
x_min = -100*np.ones(d)
optimizer = PSO(fit_func=Step, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 4] += optimizer.gBest_score
table[1, 4] += end - start
x_max = 1.28*np.ones(d)
x_min = -1.28*np.ones(d)
optimizer = PSO(fit_func=Quadric_Noise, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 5] += optimizer.gBest_score
table[1, 5] += end - start
x_max = 500*np.ones(d)
x_min = -500*np.ones(d)
optimizer = PSO(fit_func=Schwefel, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 6] += optimizer.gBest_score
table[1, 6] += end - start
x_max = 5.12*np.ones(d)
x_min = -5.12*np.ones(d)
optimizer = PSO(fit_func=Rastrigin, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 7] += optimizer.gBest_score
table[1, 7] += end - start
x_max = 5.12*np.ones(d)
x_min = -5.12*np.ones(d)
optimizer = PSO(fit_func=Noncontinuous_Rastrigin, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 8] += optimizer.gBest_score
table[1, 8] += end - start
x_max = 32*np.ones(d)
x_min = -32*np.ones(d)
optimizer = PSO(fit_func=Ackley, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 9] += optimizer.gBest_score
table[1, 9] += end - start
x_max = 600*np.ones(d)
x_min = -600*np.ones(d)
optimizer = PSO(fit_func=Griewank, num_dim=d, num_particle=p, max_iter=g, x_max=x_max, x_min=x_min)
start = time.time()
optimizer.opt()
end = time.time()
table[0, 10] += optimizer.gBest_score
table[1, 10] += end - start
table = table / times
table = pd.DataFrame(table)
table.columns=['Sphere', 'Schwefel_P222', 'Quadric', 'Rosenbrock', 'Step', 'Quadric_Noise', 'Schwefel',
'Rastrigin', 'Noncontinuous_Rastrigin', 'Ackley', 'Griewank']
table.index = ['mean score', 'mean time']
同APSO那篇作者的測試條件,設D=30;P=20;迭代10,000次;個別運作30次,PSO結果如下