2021年2月27日土曜日

Pythonで粒子群最適化(PSO)を試す

粒子群最適化(PSO)用のPythonライブラリPySwarmsを試してみたので使い方をメモ.参考にしたのは本家ページのチュートリアル. https://pyswarms.readthedocs.io/en/latest/tutorials.html

PSOは局所解が多数存在するけど,大雑把にみると傾きがあるような評価関数の最適化に適した手法. 普通の勾配を用いる手法がある点とその近傍のみから勾配が求められるのに対して,PSOは多数の点で勾配に相当する情報から凸凹の底を探す.

In [1]:
import numpy as np
import pyswarms as ps
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%load_ext autoreload
%autoreload 2

テスト用の卵ケース型関数

勾配法だと局所解に陥るような関数でテストしてみたい. このスーパーの卵が入れられているケースみたいな形の関数だと局所解がいっぱい. しかも$(x,y)=(0,0)$のときに-30になることが数式を見れば明らかなので答えも分かってる.

In [2]:
# (0, 0)が最小の関数
def eggcase(x, y):
    return x*x - 15*np.cos(2*x) + y*y - 15*np.cos(2*y)

pyswarms用に引数をベクトル化した関数をつくる.なぜかx[:,0], x[:,1]のような参照方法を使わないといけない.

In [3]:
# pyswarms用の関数
def eggcase_ps(x):
    return eggcase(x[:,0], x[:,1]) #なぜかこの形でアクセスしないといけない
In [4]:
N = 1000
x = np.linspace(-10, 10, N)
y = np.linspace(-10, 10, N)
X, Y = np.meshgrid(x, y)
Z = eggcase(X,Y)
In [5]:
# Figureと3DAxeS
fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.plot_surface(X, Y, Z)
Out[5]:

こんな形の関数.局所解だらけだけど元は2次関数なので(0,0)が一番底.

最適化

オプションの意味はよくわかっていない.

In [6]:
# x_max = 10 * np.ones(2)
# x_min = -1 * x_max
# bounds = (x_min, x_max)
options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 3, 'p': 2}
optimizer = ps.single.LocalBestPSO(n_particles=10, dimensions=2, options=options)
cost, pos = optimizer.optimize(eggcase_ps, 1000)
2021-02-26 20:24:47,198 - pyswarms.single.local_best - INFO - Optimize for 1000 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'k': 3, 'p': 2}
pyswarms.single.local_best: 100%|█████████████████████|1000/1000, best_cost=-30
2021-02-26 20:24:49,624 - pyswarms.single.local_best - INFO - Optimization finished | best cost: -30.0, best pos: [-6.29535169e-09 -2.56245047e-09]

実際に結果が正しいか確認してみる.

In [7]:
eggcase(pos[0], pos[1])
Out[7]:
-30.0