iT邦幫忙

2

[PSO文獻1]Particle swarm optimization (PSO). A tutorial

  • 分享至 

  • xImage
  •  

2020.06.24:修改錯誤的Schwefel


0. 前言:

本篇主要是紀錄Particle swarm optimization (PSO). A tutorial的閱讀心得。

  • 2020/06/20 提供較完整的實驗數據;修改錯誤的PSO代碼;提供測試代碼(含測試函數)

1. 動機:

最近腦子動到想用PSO來訓練神經網路,所以蒐集了一些相關資料,預計將分享5篇文章

1.1. 關於PSO:

第一篇是敲門磚,主要是描述基本算法框架,並且進一步的提供超參數的設定指南
第二篇是我碩班用的演算法,我只能說這個真的很猛
第三篇的作者群和第二篇相似,趁這次機會強迫自己看一下

1.2. 關於PSO+NN:

第四篇是很早期的PSO+NN,雖然內容簡單但勝在可讀性絕佳
第五篇我認為是近代PSO+NN的聖經,但我今天測試的結果是不太好,找機會在分享

  • 其實PSO+NN的文獻大多是[變種PSO]+[FFNN]這種組合,主要篇幅都在介紹變種PSO(狼群算法、蜜蜂算法)架構,很少提到怎麼與NN結合(如何餵值等)

2. 正文

2.1. 什麼是PSO

完全沒碰過的請直接看PSO 粒子群演算法
簡單來說,一群個被稱為粒子的潛在解在多維度解空間中找尋最佳解位置,粒子每次移動都會參考自身過往曾找到的的最佳解位置、所有例子的過往最佳解位置,然後再決定移動方向還有距離。
https://ithelp.ithome.com.tw/upload/images/20200603/20124766VcJyOFT7Hv.png

2.2. PSO的計算流程與算式

假設解空間總共有D個維度,則粒子i的結構應設計為
https://ithelp.ithome.com.tw/upload/images/20200604/20124766hNoQQOcwxe.png
因為這個演算法明子叫作粒子'群',所以我們會將N個粒子灑在解空間中,所以
https://ithelp.ithome.com.tw/upload/images/20200604/20124766pG33uocm4r.png
編程上,我習慣用row代表粒子,而column代表維度,用Sphere Function作為範例:
https://ithelp.ithome.com.tw/upload/images/20200604/20124766MttBmhJTzB.png

  • 因為需要求解的自變數有D=3個,用N=5個粒子下去求解,因此X矩陣的維度會是5x3,每一個粒子都代表一組可能解
  • 所以每一次迭代都會有N=5組可能解彼此競爭

2.2.1 對N個粒子作初始化

  • a. 初始化位置
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766fO1F2SYVIu.jpg
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766Hj7Dxiq8kV.png
  • b. 取得每一粒子的,因為當前t=0,所以
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766oWh1igvlY2.jpg
  • c. 計算所有粒子的適應值,若條件成立
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766D45iB9KQ1T.jpg
    代表粒子j的適應值為群體中最佳的(假設適應值越大越好),所以
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766tHzzpjpgnx.jpg

2.2.2 除非滿足停止條件(loss曲線連續停滯n代、loss已滿足門檻、迭代次數滿足),否則持續執行下列迴圈(a~e=>a~e=>...=>a~e)

  • a. 更新每一粒子的速率
    https://ithelp.ithome.com.tw/upload/images/20200604/201247661VGTTDxQgf.jpg
    這邊還會限制速率範圍,約束條件我放在2.3.5
  • b. 更新每一粒子的位置
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766XUYHaEOhtW.jpg
    並且確認沒有超出解空間
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766WQ2MfNQ658.png
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766UPy1x7Omh9.png
  • c. 計算每一粒子的適應值
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766B3jKghP3a7.jpg
  • d. 如果
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766fIVXTE3qBZ.jpg
    也就是指本次適應值刷新個人過往最佳紀錄,則
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766ULxUU7gls1.jpg
  • e. 如果
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766DAgINzQqVc.jpg
    也就是指本次適應值刷新群體過往最佳紀錄,則
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766z4uemaRyr5.jpg

2.2.3 最終輸出等於g

2.3 PSO的參數指南

這邊我直接上結果,若想進一步了解請看原文

2.3.1. 速度爆炸(velocity explosion)

從前文2.2.2(b)可知,x(t+1)=x(t)+v(t+1),若v(t+1)過大會導致:

  • x(t+1)容易衝出解空間,最終粒子求得的解會是不能使用(超出範圍),這個靠約束條件可以避免
  • 一直貼在牆壁上(因為約束條件),最終粒子求得的解與實際最佳解差距甚遠(因為一直在解空間邊緣徘徊)
  • 移動幅度過大導致無法收斂,最終粒子求得的解與實際最佳解差距甚遠(因為一直大幅度移動的在解空間中)

2.3.2. 加速度常數c

https://ithelp.ithome.com.tw/upload/images/20200604/201247669G0HucKnqr.png

2.3.3 亂數R

https://ithelp.ithome.com.tw/upload/images/20200604/20124766pZJJHNK6qA.png
numpy.random.uniform

2.3.4 初始位置X(0)與初始速度V(0)

U是指uniform distribution(均勻分布)
https://ithelp.ithome.com.tw/upload/images/20200604/201247667DbvENHU2l.png

為防止速度爆炸(velocity explosion)因此初始速度V(0)建議極小值或者直接設0
https://ithelp.ithome.com.tw/upload/images/20200604/20124766xvGm6QCWNP.png

2.3.5 速度v的約束條件-避免速度爆炸

https://ithelp.ithome.com.tw/upload/images/20200604/20124766w61UC6h4Ez.png
https://ithelp.ithome.com.tw/upload/images/20200604/20124766V1VdFJfS0l.png
https://ithelp.ithome.com.tw/upload/images/20200604/201247667rFYku3qcc.png
k是超參數

2.3.6 另一個速率更新公式-避免速度爆炸

這個就是目前PSO相關文獻最常看到的速率更新公式,透過w來縮小v(t),以防止速度爆炸
https://ithelp.ithome.com.tw/upload/images/20200604/20124766iVBisNTZiz.png

  • 當w>1代表全域探勘(exploration)
  • 當w<1代表區域開發(exploitation)
    原文整理出常見的6個w制定策略,我只列出其中三個
  • 定值
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766pAnlGak8HL.png
  • 亂數
    https://ithelp.ithome.com.tw/upload/images/20200604/201247661FezBBUIu0.png
  • 線性遞減
    https://ithelp.ithome.com.tw/upload/images/20200604/20124766MrIUcb4ndz.png

2.3.7 群大小

原文建議50個粒子,因為當粒子數大於50後對於求解幫助明顯下降

2.4 實現

2.4.1 PSO代碼

  • 參考於kuhess/pso-ann
  • 文獻上並沒有明確註明R1和R2的維度,但可以從內文推測是1xD或者Nx1(前者感覺比較合理);然而GITHUB上的寫法通常都是NxD
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()    

2.4.2 測試代碼

測試代碼

  • Benchmark可以在這個網站找或者這個網站,兩個網站對於同一個測試函數的命名會不太一樣!!
  • 測試函數是引用於第二篇(APSO),我們這系列文章都會用該篇結果作為baseline
    https://ithelp.ithome.com.tw/upload/images/20200620/20124766hrebx7QQgJ.png
    https://ithelp.ithome.com.tw/upload/images/20200620/201247663PJbFO7nVM.png
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']

2.4.2 結果

同APSO那篇作者的測試條件,設D=30;P=20;迭代10,000次;個別運作30次,PSO結果如下
https://ithelp.ithome.com.tw/upload/images/20200724/20124766RMl8oWqvmZ.png

3. 小結

  • 跟APSO提供的解相比,明顯看得出有許多地方需要改進

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言