physboを使って多目的最適化問題をベイズ最適化で解いてみた

* 製造業出身のデータサイエンティストがお送りする記事
* 今回はphysboを使って多目的最適化問題をベイズ最適化で解いてみました。

##はじめに
過去に多目的最適化問題については、記事を何個か書いておりますので参考にして頂けますと幸いです。

* [多目的最適化問題のNSGA-Ⅱを勉強したの整理しました](https://qiita.com/DS27/items/025a52b26a9f2471e67c)
* [多目的最適化問題のNSGA-Ⅱを実装してみた](https://qiita.com/DS27/items/dfedcad7ff48f09933a2)
* [多目的最適化問題のNSGA-Ⅲを勉強したの整理しました](https://qiita.com/DS27/items/8f6e4162dcfeba8bd97b)
* [多目的最適化問題のNSGA-Ⅲを実装してみた](https://qiita.com/DS27/items/03a00b108180342ea97f)
* [pymooを使って多目的最適化のNSGA-Ⅱを実装してみた](https://qiita.com/DS27/items/46b57d1c556ec72aaf7a)

##使用するライブラリー（physbo）
今回は最適化ライブラリ[physbo](https://github.com/issp-center-dev/PHYSBO)を使って実装したいと思います。

- [Officail Page](https://www.pasums.issp.u-tokyo.ac.jp/physbo)
- [MateriApps](https://ma.issp.u-tokyo.ac.jp/app/4994)
- [マニュアル](https://www.pasums.issp.u-tokyo.ac.jp/physbo/doc/manual/)

##physboの実装
今回は実装サンプルを試してみました。

```php
# 必要なライブラリーのインストール
import numpy as np
import matplotlib.pyplot as plt
import physbo
import itertools
%matplotlib inline
```

次にテスト関数を作成します。

```php
def vlmop2_minus(x):
    n = x.shape[1]
    y1 = 1 - np.exp(-1 * np.sum((x - 1/np.sqrt(n)) ** 2, axis = 1))
    y2 = 1 - np.exp(-1 * np.sum((x + 1/np.sqrt(n)) ** 2, axis = 1))

    return np.c_[-y1, -y2]
```

探索範囲を指定します。

```php
a = np.linspace(-2, 2, 101)
test_X = np.array(list(itertools.product(a, a)))
```

シミュレータを定義します。

```php
class simulator(object):
    def __init__(self, X):
        self.t = vlmop2_minus(X)

    def __call__( self, action):
        return self.t[action]

simu = simulator(test_X)
```

今回の設定した関数は、1つは右上にピークがあり、1つは左下にピークがある関数です。
可視化すると以下のような感じです。

```php
# plot objective 1
plt.figure()
plt.imshow(simu.t[:,0].reshape((101,101)),
           vmin=-1.0,
           vmax=0.0,
           origin="lower",
           extent=[-2.0, 2.0, -2.0, 2.0]
          )
plt.title("objective 1")
plt.colorbar()
plt.plot([1.0/np.sqrt(2.0)],
         [1.0/np.sqrt(2.0)],
         '*'
        )
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/7540c71a-1583-6656-3c9b-ce76920d081c.png)

```php
# plot objective 2
plt.figure()
plt.imshow(simu.t[:,1].reshape((101,101)),
           vmin=-1.0,
           vmax=0.0,
           origin="lower",
           extent=[-2.0, 2.0, -2.0, 2.0]
          )
plt.title("objective 2")
plt.colorbar()
plt.plot([-1.0/np.sqrt(2.0)],
         [-1.0/np.sqrt(2.0)],
         '*'
        )
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/9a23c571-ca06-bf94-4eab-c992f230ea42.png)

次に最適化を実行していきます。
* policyのセット
* 多目的最適化用のphysbo.search.discrete_multi.policyを利用
* num_objectivesに目的関数の数を指定

```php
policy = physbo.search.discrete_multi.policy(test_X=test_X,
                                             num_objectives=2
                                            )
policy.set_seed(0)
```

ベイズ最適化は、下記3パターンの方法があるそうです。
* HVPI (Hypervolume-based Probability of Improvement)
* EHVI (Expected Hyper-Volume Improvement)
* TS (Thompson Sampling)

今回はHVPIを使用してみたいと思います。

HVPI (Hypervolume-based Probability of Improvement)は、多次元の目的関数空間における非劣解領域 (non-dominated region) の改善確率をscoreとして算出する手法だそうです。

```php
policy = physbo.search.discrete_multi.policy(test_X=test_X,
                                             num_objectives=2
                                            )
policy.set_seed(0)

policy.random_search(max_num_probes=10,
                     simulator=simu
                    )
res_HVPI = policy.bayes_search(max_num_probes=40,
                               simulator=simu,
                               score='HVPI',
                               interval=10
                              )
```

パレート解の取得は以下のコードでできます。

```php
front, front_num = res_HVPI.export_pareto_front()
front, front_num
```
結果はこんな感じで返ってきます。
各目的変数の値と対象のインデックス番号が得られます。

```
(array([[-9.82978321e-01, -3.32414921e-04],
        [-9.71056940e-01, -3.45637262e-02],
        [-9.50786244e-01, -6.76228653e-02],
        [-9.34458810e-01, -1.15668709e-01],
        [-9.13824628e-01, -1.71906452e-01],
        [-8.88492700e-01, -2.36876025e-01],
        [-8.68576210e-01, -2.82707709e-01],
        [-8.57549341e-01, -3.05692550e-01],
        [-8.33710080e-01, -3.53626320e-01],
        [-8.20907701e-01, -3.78330099e-01],
        [-7.92933832e-01, -4.26778524e-01],
        [-7.77704642e-01, -4.50447880e-01],
        [-7.62117827e-01, -4.74823143e-01],
        [-7.45438101e-01, -4.98117246e-01],
        [-7.28459156e-01, -5.21910482e-01],
        [-7.14031659e-01, -5.50368010e-01],
        [-6.92015440e-01, -5.67552511e-01],
        [-6.72522879e-01, -5.89370083e-01],
        [-6.52909089e-01, -6.11332656e-01],
        [-6.32120559e-01, -6.32120559e-01],
        [-6.11332656e-01, -6.52909089e-01],
        [-5.89370083e-01, -6.72522879e-01],
        [-5.70311337e-01, -6.93980247e-01],
        [-5.44575729e-01, -7.10347737e-01],
        [-5.21910482e-01, -7.28459156e-01],
        [-4.98117246e-01, -7.45438101e-01],
        [-4.74823143e-01, -7.62117827e-01],
        [-4.26778524e-01, -7.92933832e-01],
        [-4.02089723e-01, -8.07119688e-01],
        [-3.90152305e-01, -8.24313473e-01],
        [-3.53626320e-01, -8.33710080e-01],
        [-3.05692550e-01, -8.57549341e-01],
        [-2.58961961e-01, -8.78749507e-01],
        [-2.16642598e-01, -8.97780597e-01],
        [-1.71906452e-01, -9.13824628e-01],
        [-1.15668709e-01, -9.34458810e-01],
        [-6.76228653e-02, -9.50786244e-01],
        [-3.51124618e-02, -9.67608404e-01],
        [-1.46847591e-03, -9.78680443e-01]]),
 array([21, 15, 35, 26, 32, 18, 43, 27, 40, 16, 31, 49, 23, 47, 33, 12, 34,
        45, 22, 44, 28, 41, 13, 42, 29, 48, 19, 30, 46, 10, 37, 24, 38, 17,
        36, 25, 39, 11, 20]))
```

パレート解の可視化は以下のような感じでできます。

```php
def plot_pareto_front(res):
    front, front_num = res.export_pareto_front()
    dominated = [i for i in range(res.num_runs) if i not in front_num]
    points = res.fx[dominated, :]

    plt.figure(figsize=(7, 7))
    plt.scatter(res.fx[dominated,0], res.fx[dominated,1], c = "blue")
    plt.scatter(front[:, 0], front[:, 1], c = "red")
    plt.title('Pareto front')
    plt.xlabel('Objective 1')
    plt.ylabel('Objective 2')
    plt.xlim([-1.0, 0.0])
    plt.ylim([-1.0, 0.0])

plot_pareto_front(res_HVPI)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/b82e4ce7-4475-c93a-e4d7-95684901bf48.png)

ちなみに全探索もできます。

```php
policy = physbo.search.discrete_multi.policy(test_X=test_X,
                                             num_objectives=2
                                            )
policy.set_seed(0)

N = test_X.shape[0]
res_all = policy.random_search(max_num_probes=N,
                               simulator=simu
                              )

plot_pareto_front(res_all)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/743453/645c25b9-7756-3a26-a7cb-820558f49391.png)

##さいごに
最後まで読んで頂き、ありがとうございました。
今回は、physboを使って多目的最適化問題をベイズ最適化を使って解きました。
今回は単純な問題だったので時間はかかりませんでしたが、複雑な問題となった場合、実行時間がどこまでかかるのかは気になりました。
時間がある時に試してみようと思います。

訂正要望がありましたら、ご連絡頂けますと幸いです。