PSO(particle swarm optimization) を C++ で実装しました。
1. PSOについて
日本語だと、粒子群最適化と訳されたりする、メタヒューリスティクス手法です。
多点で、勾配情報を用いない探索をすることが特徴に挙げられます。 ちなみに、鳥の群れが餌を探すときの行動を模擬したモデルだそうな。
このアルゴリズムのメリット/デメリットは、以下。
●メリット
- 多点探索を行い、大域的な探索が可能
- 勾配情報を用いない為、微分不可能な関数でも探索が可能
●デメリット
- 複数パラメータとして乱数を用いるため、確率的な収束になる
- 解析的な手法でないため、最適解に収束する保証はない
2. 更新式
その点の今までの過去情報の中で、Bestな(最も関数値が小さい)ものp_bestと、 グループ全体の中での、Bestな(最も関数値が小さい)ものg_bsetを保存しておいて、 イテレーションの移動量に影響を与える。
p は点の数、λ に関しては、5.b.で説明。
3. 諸々の条件と結果
変数の次元 | 2 |
初期点数 | 5 |
ベンチマーク関数 | 2N-minima関数 |
結果の図をどど~んと載せる。
綺麗に、左下の(-3,-3)付近の大域的最適解が求められました。
4. コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <iostream>
#include <fstream>
#include <math.h>
#define N 2 // 次元
#define I 5 // 初期点数
#define K 30 // イテレーション数
#define v_max 3 // 最大の移動量
using namespace std;
double __2n_minima (double *x);
int main()
{
// file出力の為の定義
ofstream fout;
fout.open("pso/pso-example.txt");
// 見出し
fout << "k\tx1_1\tx1_2\tx2_1\tx2_2\tx3_1\tx3_2\tx4_1\tx4_2\tx5_1\tx5_2" << endl;
// 初期点郡
double x[I][N] = {
{-4, 0},
{-1, 3},
{0, -2},
{-4, -3},
{3, -1},
};
// 各点での関数値とBest値
double f_temp, f_pbest[I], f_gbest;
// 各点の実際の値
double x_pbest[I][N], x_gbest[N];
// 移動量
double v[I][N];
// イテレーション0回目の設定
fout << 0 << "\t";
for (int i = 0; i < I; i++) {
f_temp = __2n_minima(x[i]);
f_pbest[i] = f_temp;
for (int n = 0; n < N; n++) {
x_pbest[i][n] = x[i][n];
v[i][n] = 0;
fout << x[i][n] << "\t";
}
if (i == 0 || f_gbest > f_temp) {
f_gbest = f_temp;
for (int n = 0; n < N; n++) {
x_gbest[n] = x[i][n];
}
}
}
fout << endl;
// 各パラメータ
double c = 0.1;
double λ, r1, r2;
double λ_max = 1.0, λ_min = 0.6;
for(int k = 1; k <= K; k++) {
// Inertia Weight Approach
λ = λ_max - (λ_max - λ_min) * k / K;
r1 = ((double)rand() / ((double)RAND_MAX + 1)) * 1.0;
r2 = ((double)rand() / ((double)RAND_MAX + 1)) * 1.0;
// グループ内のBestを計算
for (int i = 0; i < I; i++) {
f_temp = __2n_minima(x[i]);
if (f_pbest[i] > f_temp) {
for (int n = 0; n < N; n++) {
x_pbest[i][n] = x[i][n];
}
f_pbest[i] = f_temp;
if (f_gbest > f_temp) {
f_gbest = f_temp;
for (int n = 0; n < N; n++) {
x_gbest[n] = x[i][n];
}
}
}
}
fout << k << "\t";
// イテレーション毎のgbest を用いるため、for 文は分ける
for (int i = 0; i < I; i++) {
for (int n = 0; n < N; n++) {
v[i][n] = λ * v[i][n] + r1 * (x_pbest[i][n] - x[i][n]) + r2 * (x_gbest[n] - x[i][n]);
// 発散を防ぐ
if (v[i][n] < -1 * v_max) {
v[i][n] = -1 * v_max;
} else if (v[i][n] > v_max) {
v[i][n] = v_max;
}
x[i][n] = x[i][n] + c * v[i][n];
fout << x[i][n] << "\t";
}
}
fout << endl;
}
}
/*
* 目的関数 2N-minima 関数
*/
double __2n_minima (double x[N])
{
double f;
f = pow(x[0], 4) - 16 * pow(x[0], 2) + 5 * x[0] + pow(x[1], 4) - 16 * pow(x[1], 2) + 5 * x[1];
return f;
}
5. 補足
a. イテレーションの移動量の制限を設ける
1
#define v_max 3 // 最大の移動量
あまり必要ないですが、一度のイテレーションでの移動幅のMaxを設定してます。
b. 慣性係数 λ について
1イテレーション前の移動量項の係数 λ の決定を乱数で行うことも出来るのですが、より精度を上げるために、Inertia Weight Approach(IWA)という手法を用います。
これはイテレーション経過とともに、λを小さくしていき、 探索序盤では、大域的探索が期待できる不安定な探索を実行し、 探索終盤は、局所的な安定な探索を行えるようにする手法です。