前回、「群知能バットアルゴリズムの実装」という記事で、Swarm Intelligenceを取り上げました。その後、Pythonのライブラリを探してみたところ、PySwarmsというのパッケージを発見しました。
しかし、PySwarmsの日本語の紹介記事は見つけられませんでした。そこで、今回は、PySwarmsのドキュメントに従って、使い方の初歩を確認したいと思います。
参考文献
pyswarmsA Python-based Particle Swarm Optimization (PSO) library.pypi.org
PySwarmsは、Particle Swarm Optimization(PSO)という手法を実装したパッケージです。PSOは、arXiv.orgを”swarm”で検索してみると、多くの論文が見つかります。研究レベルでは、かなり利用されているアルゴリズムみたいですね。
ライブラリのインストール
何はともあれ、まずはライブラリをインストールしましょう。pipを使う場合、以下のコマンドでインストールできます。pyyamlは、pyswarms内部で利用されているため、別途インストールが必要でした。
pip install pyswarms pip install pyyaml
なお、PySwarmsのバージョンは0.6です。
シンプルな最小値探索
最もシンプルな使い方は、次の4ステップでできます。
- PySwarmsをインポート
- ハイパーパラメータ(最適化アルゴリズムに必要な定数)を設定
- 最適化インスタンスoptimizerを作成
- optimize関数の呼び出し
オブジェクト指向プログラミングの一般的な使い方ですね。オブジェクト志向をごご存知なら、特に違和感はなく使えると思います。
PySwarmsのインポートは次のとおりです。
import pyswarms as ps
from pyswarms.utils.functions import single_obj as fx
2行目は、PySwarmsの中に用意されている目的関数を使うためにインポートしています。
ハイパーパラメータは、次のように辞書型で用意します。
options = {'c1':0.5, 'c2':0.3, 'w':0.9 }
今のところ、パラメータの意味はおまじないですが、後々理解することにします。
最適化クラスは、大域最適化クラス(GlobalBestPSO)と局所最適化クラス(LocalBestPSO)が存在します。最適化の目的に合わせて使い分ける必要がありますが、今回は大域最適化クラスをインスタンス化します。
optimizer = ps.single.GlobalBestPSO( n_particles = 10, dimensions=2, options=options )
n_particlesは粒子数(エージェント数)を、dimensionsは粒子が移動する次元数を指定します。ここで、ハイパーパラメータoptionsも同時に指定します。
最適化の実行には、GlobalBestPSOクラスのoptimize()関数を呼び出します。
cost, pos = optimizer.optimize( fx.sphere, iters=1000 )
この時、目的関数を第一引数に、第二引数itersには最大反復回数を指定します。ここでは、PySwarmsが用意している球関数sphere(下記のn=1の場合)を目的関数にしています。
f(x) = x(0)**2 + x(1)**2 + ... + x(n)**2
この公式から明らかなように、x=(0,0,…,0)のとき、f(x)=0が最小値です。これが、最適化の結果として出力されれば成功です。
返り値は、costにf(x)、posにxがタプル型で返されます。
実際に計算した結果は、次のようになりました。
2020-10-26 07:29:54,098 - pyswarms.single.global_best - INFO - Optimize for 1000 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9} pyswarms.single.global_best: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|1000/1000, best_cost=2.4e-43 2020-10-26 07:29:55,061 - pyswarms.single.global_best - INFO - Optimization finished | best cost: 2.3970598195237476e-43, best pos: [-4.17359343e-22 -2.55963203e-22]
costもposも、極微小な値が得られました。これは、0とみなせるので、正解値と一致したと考えて良さそうです。
最後に、ソースコード全体を載せておきます。
import numpy as np
import pyswarms as ps
from pyswarms.utils.functions import single_obj as fx
# setup hpyerparameters
options = {'c1':0.5, 'c2':0.3, 'w':0.9 }
# PSO instance
optimizer = ps.single.GlobalBestPSO( n_particles = 10, dimensions=2, options=options )
# performe optimization
cost, pos = optimizer.optimize( fx.sphere, iters=1000 )
境界値が必要な場合
前述のシンプルな場合は、全空間を粒子に探索させていましたが、ある特定の範囲の中の最小値を求めたい場合もあります。その時は、GlobalBestPSOクラスにboundsオプションを設定します。
境界値は、タプル型で用意します。
max_bound = 5.12 * np.ones(2)
min_bound = -max_bound
bounds = (min_bound, max_bound)
次元数を合わせるために、ここではmax_boundもmin_boundも2次元ベクトルとして用意しています。また、Rastrigin関数を目的関数にするため、探索範囲を[-5.12, 5.12]としています。
これを、GlobalBestPSOクラスのコンストラクタのboundsオプションに指定します。
optimizer = ps.single.GlobalBestPSO( n_particles = 10, dimensions=2, options=options, bounds=bounds )
Rastrigin関数の最小値も、x=(0,0)のときf(x)=0です。実際に計算させてみると、次のような結果になりました。
2020-10-26 07:43:32,804 - pyswarms.single.global_best - INFO - Optimize for 1000 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9} pyswarms.single.global_best: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|1000/1000, best_cost=0 2020-10-26 07:43:33,279 - pyswarms.single.global_best - INFO - Optimization finished | best cost: 0.0, best pos: [-8.39698785e-10 -2.91214711e-09]
ちゃんと、f(0)=0になっていますね。
最後にソースコード全体を載せておきます。
import numpy as np
import pyswarms as ps
from pyswarms.utils.functions import single_obj as fx
# setup hpyerparameters
options = {'c1':0.5, 'c2':0.3, 'w':0.9 }
# create bounds
max_bound = 5.12 * np.ones(2)
min_bound = -max_bound
bounds = (min_bound, max_bound)
# PSO instance
optimizer = ps.single.GlobalBestPSO( n_particles = 10, dimensions=2, options=options, bounds=bounds )
# performe optimization
cost, pos = optimizer.optimize( fx.rastrigin, iters=1000 )
独自の目的関数を使う場合
ここまでは、PySwarmsに用意された目的関数を使用してきましたが、実際には取り組む問題に合わせて、独自の目的関数が必要になることがほとんでしょう。しかも、独自関数には任意のパラメータが必要になることがほとんどです。
そこで、独自に関数を定義し、任意のパラメータを渡す方法を確認しておきます。
PySwarmsにも用意されていますが、ここでは下記のRosenbrock関数を使用します。
def rosenbrock(x, a, b, c=0):
f = ( a - x[:,0]) **2 + b * (x[:,1] - x[:,0]**2 ) **2 + c
return f
この関数には、定数a,b,cを渡す必要があります。その方法には、2通りのやり方があります。
- optimize関数の名前付きの引数として渡す
- 辞書kwargsを渡す
1つ目の方法は、次のようなコードになります。
cost, pos = optimizer.optimize( rosenbrock, iters=1000, a=1, b=100, c=0 )
2つ目の方法は、kwargs辞書を作成し、**つきで渡します。
kwargs={"a":1.0, "b":100.0, "c":0}
cost, pos = optimizer.optimize( rosenbrock, iters=1000, **kwargs )
最後に、実際のソースコードとその計算結果を載せておきます。
import numpy as np
import pyswarms as ps
from pyswarms.utils.functions import single_obj as fx
# Rosenbrock function
def rosenbrock(x, a, b, c=0):
f = ( a - x[:,0]) **2 + b * (x[:,1] - x[:,0]**2 ) **2 + c
return f
# setup hpyerparameters
options = {'c1':0.5, 'c2':0.3, 'w':0.9 }
# create bounds
max_bound = 10 * np.ones(2)
min_bound = -max_bound
bounds = (min_bound, max_bound)
# PSO instance
optimizer = ps.single.GlobalBestPSO( n_particles = 10, dimensions=2, options=options, bounds=bounds )
# performe optimization
cost, pos = optimizer.optimize( rosenbrock, iters=1000, a=1, b=100, c=0 )
計算結果
2020-10-26 07:55:34,835 - pyswarms.single.global_best - INFO - Optimize for 1000 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9} pyswarms.single.global_best: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████|1000/1000, best_cost=4.51e-16 2020-10-26 07:55:35,303 - pyswarms.single.global_best - INFO - Optimization finished | best cost: 4.51051564791644e-16, best pos: [0.99999998 0.99999996]
まとめ
群知能の1つであるParticle Swarm Optimization (PSO) を実装したPySwarmsライブラリの簡単な使い方を確認しました。
PySwarmsは、目的関数だけでなく、最適化アルゴリズムも独自アルゴリズムに変更することが可能です。また、可視化の方法も用意されています。モチベーションがあれば、これらも紹介したいと思います。