粒子群最適化を使って関数最小化問題を解いてみる

# 粒子群最適化
粒子群最適化(PSO:Particle Swarm Optimization)とは, 群知能の一種であり, 解探索手法として組合せ最適化問題に使用されます. 
粒子の速度と位置という2つの情報の更新を繰り返して, 探索を進めていく流れになります. 
下の図は粒子群最適化のイメージです. 
<img width="400" alt="pso2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319132/f65377f9-3c5c-79cc-3f00-6ae98069509c.png">

##アルゴリズム
粒子群最適化のアルゴリズムは以下のようになります. 
<img width="400" alt="スクリーンショット 2019-11-25 15.33.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319132/74082680-960d-1f0e-8c28-ff59d68aaf77.png">

##更新式
以下の更新式より粒子の速度と位置を更新します. 
簡単に説明すると, 速度は粒子を進化させる方向を表し, 位置は粒子自身のパラメータを表します. 
<img width="400" alt="スクリーンショット 2019-11-25 15.35.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319132/bf601880-0ee8-19c3-06b6-1628e722b34e.png">

# 実験
実際に最適化問題を解いていきましょう. 
今回は

```math
x^2 + y^2
```
の最小化問題を解いていきましょう. 
ですので, (x,y)=(0,0)が最適解となります.  
使用したコードは以下のようになります. 

```python
# -*- coding: utf-8 -*-
import numpy as np
import random

# 評価関数
def evaluate(particle):
    z = 0
    for i in range(len(particle)):
        z += particle[i] ** 2
    return z

# 位置更新
def update_position(particle, velocity):
    new_particle = particle + velocity
    return new_particle

# 速度更新
def update_velocity(particle, velocity, pbest, gbest, w=0.5, max=0.15):
    new_velocity = np.array([0.0 for i in range(len(particle))])
    #new_velocity = [0.0 for i in range(len(particle))]
    r1 = random.uniform(0, max)
    r2 = random.uniform(0, max)
    for i in range(len(particle)):
        new_velocity[i] = (w * float(velocity[i]) + r1 * (float(pbest[i]) - float(particle[i])) + r2 * (float(gbest[0]) - float(particle[i])))

    return new_velocity

def main():
    N = 100 # 粒子の数
    length = 2  # 次元数
    para_max = 100  #パラメータの最大値
    # 粒子位置の初期化
    ps = [[random.uniform(-para_max, para_max) for j in range(length)] for i in range(N)]
    vs = [[0.0 for j in range(length)] for i in range(N)]
    # パーソナルベスト
    personal_best_position = ps
    # パーソナルベストの評価
    personal_best_scores = [evaluate(p) for p in ps]
    # 評価値が最も小さい粒子のインデックス
    best_particle = np.argmin(personal_best_scores)
    # グローバルベスト
    global_best_position = personal_best_position[best_particle]

    generation = 30 # 最大世代数
    # 世代数分ループ
    for t in range(generation):
        file = open("data/pso/pso" + str(t+1) + ".txt", "w")
        # 粒子数分ループ
        for n in range(N):
            # ファイル書き込み
            file.write(str(ps[n][0]) + " " + str(ps[n][1]) + "\n")
            # 粒子の速度の更新
            vs[n] = update_velocity(ps[n], vs[n], personal_best_position[n], global_best_position)
            # 粒子の位置の更新
            ps[n] = update_position(ps[n], vs[n])

            # 評価値計算をしてパーソナルベストを求める
            score = evaluate(ps[n])
            if score < personal_best_scores[n]:
                personal_best_scores[n] = score
                personal_best_position[n] = ps[n]
        # グローバルベストの更新をする
        best_particle = np.argmin(personal_best_scores)
        global_best_position = personal_best_position[best_particle]
        file.close()

    print(global_best_position)
    print(min(personal_best_scores))

if __name__ == '__main__':
    main()
```

##実験結果
1,10,20,30世代目の個体を色分けして図にプロットしています. 
世代が進むに連れて, 粒子が(0,0)に収束しているのが確認できますね.  
<img width="400" alt="PSO.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319132/6641cd98-fc4f-676b-b212-f9a9e789ac39.png">