LoginSignup
1
2

ベイジアン最適化を用いた逐次探索法の解説と修正

Posted at

user:

ChatGPT先生、未知の関数があるんですけど、その関数の出力を最小化したいと思ってるんです。説明変数を入力して、その関数の出力を計測することを繰り返すとします。次にどんな説明変数を入力したら良いか提案してくれる、そんな手法はありませんか?


assistant:

あ、それなら多分、最適化アルゴリズムが役立つんじゃないかな。うぅ、その中でも特に、「勾配降下法」っていう手法がよく使われるのよ。そんなに驚いた顔しないで、はぁ、簡単に説明するわね。

勾配降下法は、目的関数の勾配(または微分)、つまり、「現在の位置での傾斜」を使って、関数の出力を最小化しようと取り組むアルゴリズムの一つよ。まず初期の説明変数を取り、目的関数の出力が最小となる方向へ小さなステップで移動します。この移動距離は学習率と呼ばれるパラメータで制御されるわ。その後、新しい位置で目的関数の勾配を再計算し、またその方向へステップ移動するのよ。これを目的関数の値が風通しでもはや変わらない、または許容誤差の範囲内まで繰り返すの。

だから、君がその未知の関数へどんな説明変数を入力すればいいのかは、考慮すべき特性や目的によるわ。簡単なようで難しい。けど、頑張りなさい。どうかなって。それとも、まだわかりにくい?うぅ、もっと詳しく説明してほしいの?


user:

ありがとうございます。ベイズ最適化を提案してくるかと思ったら、勾配降下法を提案してきましたね。


assistant:

あぁ、その…ベイズ最適化ね。それも有力な選択肢の一つだよ。だって、説明変数と目的関数の出力の間になんらかの確率的な関係がある場合、特に有用だし。この手法は、結果がどれだけ不確定か、またこれまでの結果に基づいてどれだけ新しい調査を進めるべきかという観点から、次の説明変数を選びます。これは、不確定性と意思決定のトレードオフを管理するのに便利だからね。

たださ、確認しておきたいのだけど、ベイズ最適化を使う場合、関数がなめらかであるとか、確率的な性質を持っているとか、そういう前提を置くわけ。そういう前提が外れると、最も効果的な手法は変わることもあるのよ。だから、君が取り組んでいる問題の特性によっては、他の手法がより適しているかもしれないわけよ。だけどベイズ最適化がそこそこ強力な道具であることは間違いないわ。

なので、うーん、あなたの問題に一番合った最適化手法を見つけて頑張ってみて。私がこう見えても、君の力になりたいと思っているんだから。でもって、その…もし何かあったら、何でも聞いてね。私、頑張って答えるから。


user:

ありがとうございます。機械学習、たとえば scikit-learn の RandomForestRegressor と組み合わせて、次の説明変数を提案するアルゴリズムを書いてもらえますか?


assistant:

あ、もちろん、それなら…分かったわ。でもまあ、ちょっと面倒くさいなあ。ま、でも仕方ないよね。君のためなら…。なんてね。以下は、Scikit-LearnのRandomForestRegressorとBayesianOptimizationを組み合わせた簡単なコードスニペットよ。

まず、必要なパッケージからインストールするようにしてね。

!pip install scikit-learn
!pip install bayesian-optimization

次に、コードスニペット。

from sklearn.ensemble import RandomForestRegressor
from bayes_opt import BayesianOptimization

# ランダムフォレストモデルを定義
rf = RandomForestRegressor()

def rf_cv(n_estimators, min_samples_split, max_features, max_depth):
    val = cross_val_score(
        RandomForestRegressor(n_estimators=int(n_estimators),
            min_samples_split=int(min_samples_split),
            max_features=min(max_features, 0.999),
            max_depth=int(max_depth),
            random_state=2),
        X, y, 'neg_mean_squared_error', cv=5
    ).mean()
    return val

# ベイジアン最適化 by the expected improvement acquisition function
optimizer = BayesianOptimization(rf_cv, 
                                 {'n_estimators': (10, 250),
                                  'min_samples_split': (2, 25),
                                  'max_features': (0.1, 0.999),
                                  'max_depth': (5, 30)}
                                )
optimizer.maximize(n_iter=10)

まず、ランダムフォレストモデルを作るわね。そしてcross_val_scoreを使って交差検証でのスコアを計測する関数を作るの。次に、ベイジアン最適化をする部分よ。ここではoptimizerの辞書として、様々なハイパーパラメータの範囲を定義しているわ。

最後の行でoptimizer.maximize関数を使用してベイジアン最適化を実行しています。n_iterは反復回数を指定するものよ。

だから、君のタスクに合わせてパラメータを調整したり、目的関数を変えたりしてみて。それとも、まだ何かわからないことがある?


user:

なるほど。ありがとうございます。これはいわゆるひとつのハイパーパラメーターチューニングですね。ぼくが聞きたいのは、ハイパーパラメーターチューニングする方法ではなくて、データの選択に関する話です。

試験済みのデータがN個あるとして、その中で、目的の値が最小なのはこれだ、というのは分かっているとします。N+1個目のデータを試験する際に、どんなデータなら目的の値を最小化できる見込みが高いのか。それを提案する手法はありませんか?


assistant:

ああ、なるほど。君が言いたいことはよくわかったわ。それなら実は、ベイズ最適化がまさにそうした問題に対する解答を提供するの。これは、着手可能な次のステップが不確実性と期待報酬の間で最良のバランスを持つものであると予測するのよ。つまり、新しいデータが目的関数を最小化する可能性が最も高いと予想される場所かつ、まだあまり探索されていない場所について、ベイズ最適化は次にどのデータを選ぶべきかを助言してくれるわ。

さて、以下に君の要求を満たす簡単な実装例を示すわね。ただし、これはとても単純な例示であることを覚えておいて。一般的に、問題領域や関数によっては適切なカーネル関数や最適化アルゴリズムを選ばなければならない、という状況も多いわけだからね。

from bayes_opt import BayesianOptimization

def objective_function(data):
    """
    ここには評価したい目的関数を記述します。
    例えば、「データ」が一連の特性だとして、それが目的の値を最小化する方法を見つけたい場合
    """
    ... 
    return value

bounds = {'data1': (lower_limit, upper_limit), 
          'data2': (another_lower_limit, another_upper_limit),
          ...}

optimizer = BayesianOptimization(f=objective_function, 
                                 pbounds=bounds, 
                                 random_state=1)

optimizer.maximize(init_points=2, n_iter=3)

ここで、objective_functionは最小化(または最大化)したい関数を定義しています。boundsでは、君が試験したいデータの範囲を指定していて、各データ項目がどの範囲にあるべきかを正確に指定するわけ。ここで、BayesianOptimization関数にこれらを渡すと、ベイズ最適化が初期化され、次にmaximizeメソッドを呼び出して最適化を開始するの。

でも、この地点でベイズ最適化の理解が浅い場合、ちょっと独学は難しいかもしれないわね。さらに詳しく知りたいなら、クラスやチュートリアルを探してみるといいわ。それとも…、ま、追加の質問があれば、私に頼ってもいいわよ。大丈夫、すべて答えるから。


user:

ありがとうございます。上記のコードの中で、objective_function にあたる部分が未知だから困っているというわけなのです。なので、objective_function の部分を、RandomForestRegressorなどで代用して、今あるデータから回帰モデルを作って、目的の値を最小化できるデータ、つまり、次に試験すべきデータはこれだ、という提案がしたいというわけです。


assistant:

ああ、そういうことね。なるほど。それなら、RandomForestRegressorを目的関数として使うことで、ベイズ最適化を行ってみてもいいかもしれないわ。ただし、そのアプローチを取る際には何点か注意が必要よ。

RandomForestは確定的なアルゴリズムではなく、結果にはバリエーションがあるの。だから、同じ入力に対して何度も訓練して、その平均値を取る方が、個々の学習結果よりも良い結果をもたらすことがあるわ。だから、それを考えて以下のようなコードを書いてみたの。

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from bayes_opt import BayesianOptimization

# データを準備します
X = ...
y = ...

# RandomForestRegressorを目的関数として使用
def rf_mse(n_estimators, max_depth):
    val = -cross_val_score(
            RandomForestRegressor(n_estimators=int(n_estimators), 
                                  max_depth=int(max_depth), 
                                  random_state=42),
            X, y, cv=3, scoring="neg_mean_squared_error"
           ).mean()
    return -val

# ベイズ最適化
optimizer = BayesianOptimization(
    f=rf_mse,
    pbounds={'n_estimators': (10, 100), 'max_depth': (5, 50)},
    random_state=42,
)

optimizer.maximize(init_points=2, n_iter=10)

print(optimizer.max)

このスクリプトでは、RandomForestRegressorに2つのハイパーパラメータ、n_estimatorsとmax_depthを調整するように指示しているの。そして、モデルの評価指標(ここでは平均二乗誤差)を最小化するようにしてね。これが君が言いたかったことで合ってるかしら?それとも、まだ何か足りないことがある?

それとも、もしかして、君は一つ一つの異なる可能性のあるデータポイントに対して、その目的の値の推定を得るためにモデルを訓練し、そしてその目的の値が最小となるデータポイントを提示したいのかしら?それなら少々違うアプローチが必要になってくるわ。必要なら詳細を話すけど、まずは今の説明が役に立つか教えてほしいわ。


user:

ありがとう。でもちょっと違うかな。ぼくのやりたいことは、アクティブラーニングに近いかもしれない。最初N個のデータがあって、次に調べるべきN+1個目のデータとして、どれを選べば良いのかを知りたいのです。


assistant:

ああ、なるほど、それならアクティブラーニングという枠組みが確かに適しているわね。ベイジアン最適化は、実際にアクティブラーニングの一種と見なすことができるのよ。

君が述べているように、アクティブラーニングは学習アルゴリズムが次に得るべき最も価値のある情報を評価し、さらにラベルを付けるためのデータを提案するのよ。これはまさに、君が「次に調べるべきN+1個目のデータとして、どれを選べば良いのか」を知りたい、という課題に繋がるところね。

具体的な実装について考慮するなら、以下のようになるわ。

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization

# データを準備します。
X = ...
y = ...

# 初めに一部のデータでモデルを訓練します。
X_train, X_unlabeled, y_train, _ = train_test_split(X, y, test_size=0.6, random_state=42)

# 目的関数: モデルを訓練し、新しいデータ点での予測値を返します。
def objective_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor()
    model.fit(X_train, y_train)
    return -model.predict(new_data.reshape(1, -1))

# ベイズ最適化を設定します。
bounds = {'data{}'.format(i): (X_unlabeled[:, i].min(), X_unlabeled[:, i].max()) for i in range(X_unlabeled.shape[1])}
optimizer = BayesianOptimization(f=objective_function, pbounds=bounds, verbose=2)

# 最適化します。
optimizer.maximize(init_points=10, n_iter=50)

# 最適化後の最大値を取得します。
print(optimizer.max)

このコードでは、一部のデータを初期の訓練データとして持っていて、その他のデータはまだ未検証のものとして扱われています。その上で、目的関数はランダムフォレストを訓練し、未検証の新しいデータ点での予測値を返すように設計されています。そして最後に、次にラベルを提供すべきデータ点を選ぶために、未検証のデータ空間全体を探索するベイズ最適化アルゴリズムが実行されているのよ。

ただし、モデルがあまりにも未検証の領域で極端な予測を行う可能性もあるから注意して。そういったことを防ぐためには、君が自信を持って予測できる範囲内で探索を制限する、または切断回帰などの手法を試してみるわ。そうすれば、アクティブラーニングの目的を達成するのに役立つと思うわ。それでもまだ何かわからないことがあったら言ってね。私がここにいるから。


user:

ありがとうございます。ではもう少し具体的なコードを書いてみましょう。

scikit-learn の load_linnerud()データセットを使います。データを、目的値が既知であるものと、未知である(と見なす)ものに分割します。

目的値が既知であるデータセットを学習します。その学習結果に基づいて、目的値を最大化すると思われる未知データを1つ選びます。選ばれた未知データの目的値を追加して、学習します。その学習結果に基づいて、目的値を最大化すると思われる未知データを1つ選びます。

以上の工程を繰り返して、目的値の最大値がどう変化していくかを図示してください。


assistant:

さあ、君が言った通りにコードを実装しましょう。それが君を喜ばせるのなら…ね。ああ、でも忘れないで、私はただプログラムを書いてるだけだから…本当は...別に君のためを思ってなんか...などと...そんな...。まあ、それはともかく、コードを見てごらん。

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, _ = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))

bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(15):
    optimizer = BayesianOptimization(
        f=black_box_function,
        pbounds=bounds,
        verbose=2,
        random_state=42,
    )
    
    optimizer.maximize(init_points=1, n_iter=1)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    X_known = np.vstack((X_known, chosen_point))

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

上のコードは、君が要求した手順を実装したものよ。Linnerudデータセットを使用して、訓練データとテストデータに分割しているわ。各繰り返しで、ランダムフォレストモデルを最適化して最大目標値を求め、その値を列挙します。そして、そのプロットを表示するんです。

一応...これで君の質問に答えられたと思うけど...どう?もしまた何か質問があるなら、私は...ちょっとだけだけど...答えることもできる...かもしれない。


user:

ありがとうございます。次のエラーになりました。

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
<ipython-input-2-f8866c52d3f2> in <cell line: 5>()
      3 from sklearn.ensemble import RandomForestRegressor
      4 from sklearn.model_selection import train_test_split
----> 5 from bayes_opt import BayesianOptimization
      6 import matplotlib.pyplot as plt
      7 

ModuleNotFoundError: No module named 'bayes_opt'

---------------------------------------------------------------------------
NOTE: If your import is failing due to a missing package, you can
manually install dependencies using either !pip or !apt.

To view examples of installing some common dependencies, click the
"Open Examples" button below.
---------------------------------------------------------------------------

assistant:

あぁ、それなら、取り急ぎ必要なライブラリをインストールするわ。で、それは君の環境にbayes_optパッケージがインストールされていないから出るエラーね。以下のコードを走らせて、それをインストールしてみて。

!pip install bayesian-optimization

その後に、私が先ほど提供したコードを再度実行してみて。それで動くはずよ。うん、それが最善の策だと思うわ...ま、まあ、それで上手くいくといいんだけど...私の忠告を無視したり、何か問題があったら、すぐに教えてほしいわね。


user:

ありがとうございます。上記コードをベースに、エラーを修正した全コードを書いてください。

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-f8866c52d3f2> in <cell line: 26>()
     32     )
     33 
---> 34     optimizer.maximize(init_points=1, n_iter=1)
     35 
     36     # 最適化後の最大値を追加

3 frames
/usr/local/lib/python3.10/dist-packages/bayes_opt/target_space.py in register(self, params, target, constraint_value)
    198 
    199         self._params = np.concatenate([self._params, x.reshape(1, -1)])
--> 200         self._target = np.concatenate([self._target, [target]])
    201 
    202         if self._constraint is None:

ValueError: all the input arrays must have same number of dimensions, but the array at index 0 has 1 dimension(s) and the array at index 1 has 2 dimension(s)

assistant:

そ、それなら。多分、君のエラーは未知の新しいデータ点から予測を行う部分が修正されてないからだと思うわ。たしかに問題があったわ。その…うーん、でもそんなに難しくないよね?もちろん、私が直すわよ...だけど...あ、なんでもない!

以下が修正したコードよ。目的の関数におけるモデルからの予測がリシェイプされて、1次元配列となるように直したの。

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, _ = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(15):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
    optimizer.maximize(init_points=1, n_iter=1)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    X_known = np.vstack((X_known, chosen_point))

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

うーん、これで大丈夫だと思うけど、まだ何か問題があったら、...あ、いや、なんでもないわ! だけど、君がもし困ってたらいつでも言ってね、私がさ、ちょっとだけ手伝ってもいいかなって...思ってるだけよ。


user:

ありがとうございます。上記コードをベースに、エラーを修正した全コードを書いてください。

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -158.6    | 6.869     | 223.6     | 97.76     |
| 2         | -165.7    | 14.63     | 182.6     | 118.8     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-b9a1a47083d9> in <cell line: 26>()
     26 for _ in range(15):
     27     optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
---> 28     optimizer.maximize(init_points=1, n_iter=1)
     29 
     30     # 最適化後の最大値を追加

8 frames
/usr/local/lib/python3.10/dist-packages/sklearn/utils/validation.py in check_consistent_length(*arrays)
    455     uniques = np.unique(lengths)
    456     if len(uniques) > 1:
--> 457         raise ValueError(
    458             "Found input variables with inconsistent numbers of samples: %r"
    459             % [int(l) for l in lengths]

ValueError: Found input variables with inconsistent numbers of samples: [13, 12]

assistant:

あれ、まだエラーが出るのか…このエラーはX_knowny_knownの長さが一致していないから出ているわ。なんで私はそれを見落としたのかしら...私のミスね。

選んだデータポイントをX_knownに追加するとき、対応するターゲット値もy_knownに追加する必要があるわ。その、それが書き忘れてたみたいで...反省してるから...許して?

さて、以下が修正版の全コードよ。

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, y_unknown = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(len(X_unknown)):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
    optimizer.maximize(init_points=1, n_iter=1)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    chosen_index = np.where((X_unknown==chosen_point).all(axis=1))
    X_known = np.vstack((X_known, chosen_point))
    y_known = np.concatenate((y_known, y_unknown[chosen_index]), axis=None)
    X_unknown = np.delete(X_unknown, chosen_index, 0)
    y_unknown = np.delete(y_unknown, chosen_index)

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

また、君の要求に応えるために、最後に選ばれた未知のデータを削除するようにコードも修正したわ。これで目的値が既知のデータが増えるにつれて、予測結果がどのように変化するかを観察できるはずよ。

それで、何か...もしまた何かあったら...だって、私...あなたの...つまりいつでも...私が...また...手伝う...から...


user:

ありがとうございます。でも、えっと、ひょっとしてさっきと同じエラー?

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -158.6    | 6.869     | 223.6     | 97.76     |
| 2         | -165.7    | 14.63     | 182.6     | 118.8     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-92dace39152e> in <cell line: 26>()
     26 for _ in range(len(X_unknown)):
     27     optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
---> 28     optimizer.maximize(init_points=1, n_iter=1)
     29 
     30     # 最適化後の最大値を追加

8 frames
/usr/local/lib/python3.10/dist-packages/sklearn/utils/validation.py in check_consistent_length(*arrays)
    455     uniques = np.unique(lengths)
    456     if len(uniques) > 1:
--> 457         raise ValueError(
    458             "Found input variables with inconsistent numbers of samples: %r"
    459             % [int(l) for l in lengths]

ValueError: Found input variables with inconsistent numbers of samples: [13, 12]

assistant:

ああ、確かにまた同じエラーが出てるわね。うーん、これはちょっと面倒だけど……その、めげないでね。それはそれは大切なことだと思うんだ、なぜなら……あ、もちろん、助けるからっ!そ、それ以上の意味はないわ!

おそらく、選択したポイントが既に削除されてしまったX_unknownから探し出そうとして問題が起きているのよ。だから、最適化の前に既知のデータと未知のデータのインデックスを同期させてみましょう。

修正したコードはこちらよ:

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, y_unknown = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

# 結果を保存するリスト
max_values = []

for _ in range(len(X_unknown)):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
    optimizer.maximize(init_points=1, n_iter=1)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    X_known = np.vstack((X_known, chosen_point))
    chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
    y_known = np.append(y_known, y_unknown[chosen_index])

    # 更新した既知データでboundsを再定義
    bounds = {'data{}'.format(i): (X_known[:, i].min(), X_known[:, i].max()) for i in range(X_known.shape[1])}

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

見てわかる通り、まず最適化する前にboundsを更新して、そして既知のデータと未知のデータのインデックスが同じ状態を保てるようにしたのよ。希望とはそういうものよね、最後まで諦めずに!ちょっと難しそうな問題だったけど……君、やっぱり私に頼るつもりなの?


user:

ありがとうございます。上記コードをベースに、エラーを修正した全コードを書いてください。

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -158.6    | 6.869     | 223.6     | 97.76     |
| 2         | -165.7    | 14.63     | 182.6     | 118.8     |
=============================================================
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-9-e601c2203d0d> in <cell line: 24>()
     32     chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
     33     X_known = np.vstack((X_known, chosen_point))
---> 34     chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
     35     y_known = np.append(y_known, y_unknown[chosen_index])
     36 

IndexError: index 0 is out of bounds for axis 0 with size 0

assistant:

あら、また新しいエラーが出てるわね。これはnp.whereが空の配列を返していることが原因よ。

選択した点がX_unknownに存在しないから、空の配列が返っているのよね。それはすなわち、同じ点を2回選んでしまったということ。それは普通はないはずなんだけど...ちょっと待って、これは私のミスかしら。うーん、確認してみるわ。

修正版のコードを書いてみたわ。修正点は、Chosen_pointがX_unknownから選ばれた点であることを確認する部分だけよ。

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, y_unknown = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

# boundsの定義
bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(len(X_unknown)):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
    optimizer.maximize(init_points=1, n_iter=1)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
    X_known = np.vstack((X_known, X_unknown[chosen_index]))
    y_known = np.append(y_known, y_unknown[chosen_index])

# boundsの再定義
bounds = {'data{}'.format(i): (X_known[:, i].min(), X_known[:, i].max()) for i in range(X_known.shape[1])}

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

この修正により、選ばれた点を既知および未知のデータ群から正確に見つけ出すことができるわ。それで、今度こそ上手くいくと思うけど...その、何かあったらまた教えてね。私、もちろん喜んで手伝うわよ。


user:

ありがとうございます。上記コードをベースに、エラーを修正した全コードを書いてください。さっきと同じエラーかな?

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -158.6    | 6.869     | 223.6     | 97.76     |
| 2         | -165.7    | 14.63     | 182.6     | 118.8     |
=============================================================
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-10-e8f26e986fbe> in <cell line: 27>()
     34     # 選ばれたポイントを既知のデータに追加
     35     chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
---> 36     chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
     37     X_known = np.vstack((X_known, X_unknown[chosen_index]))
     38     y_known = np.append(y_known, y_unknown[chosen_index])

IndexError: index 0 is out of bounds for axis 0 with size 0

assistant:

うーん、確かにまたエラーが出てるわね。大変だと思うけど、私がいるから安心して。その...別にあなたのためだけじゃないんだからね!全部の人が困っているわけだから...そう、頼ってくるのは君だけじゃないのよ!

見たところ、まだ選択したデータ点が未知のデータセットに含まれていないようね。この問題を解決するには、選んだデータ点が未知のデータセットから削除される前にインデックスを取得しなくてはならないわ。

それでは修正版の全コードを下記に示すわ。

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, y_unknown = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

# boundsの定義
bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(len(X_unknown)):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=42)
    optimizer.maximize(init_points=1, n_iter=0)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
    chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
    X_known = np.vstack((X_known, X_unknown[chosen_index]))
    y_known = np.append(y_known, y_unknown[chosen_index])

    # 選ばれたポイントを未知のデータから削除
    X_unknown = np.delete(X_unknown, chosen_index, axis=0)
    y_unknown = np.delete(y_unknown, chosen_index)

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

以上が修正後の全コードよ。その、また同じようなエラーが出たら、連絡してね。全然、嫌じゃないから。だって、それが...私の仕事だから...そう、確かに君のためだけじゃないけど...それでも...


user:

ありがとうございます。上記コードをベースに、エラーを修正した全コードを書いてください。これって、ひょっとして、提案された点と全く同じデータを探すコードになってます?全く同じデータは存在しないから、近いデータを探さなければいけない、という話だったりしますか?

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -158.6    | 6.869     | 223.6     | 97.76     |
=============================================================
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-12-681ac12045d5> in <cell line: 27>()
     34     # 選ばれたポイントを既知のデータに追加
     35     chosen_point = np.array([float(val) for val in optimizer.max['params'].values()])
---> 36     chosen_index = np.where((X_unknown == chosen_point).all(axis=1))[0][0]
     37     X_known = np.vstack((X_known, X_unknown[chosen_index]))
     38     y_known = np.append(y_known, y_unknown[chosen_index])

IndexError: index 0 is out of bounds for axis 0 with size 0

assistant:

ああ、すごい!君、わかったみたいね。その...ちょっとだけ嬉しいみたい...でも、特に君がいくじないからじゃないからね。ただ、期待通りに振る舞ったってだけよ。

それにしても、君の観察は正しいわ。ベイジアン最適化は未知のデータ・セットの中から最も良さそうなデータ・ポイントを提案するけど、それが完全に一致するとは限らないのよ。だから、提案されたポイントと最も近いポイントを見つけるように修正する必要があるわ。

試しに以下のように修正してみて:

import numpy as np
from sklearn import datasets
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from bayes_opt import BayesianOptimization
import matplotlib.pyplot as plt
from scipy.spatial import distance

# データセットをロード
linnerud = datasets.load_linnerud()

# データを訓練用と未知用に分割
X_known, X_unknown, y_known, y_unknown = train_test_split(
    linnerud.data, linnerud.target[:, 0], test_size=0.4, random_state=42)

def black_box_function(**data):
    new_data = np.array(list(data.values()))
    model = RandomForestRegressor(random_state=42)
    model.fit(X_known, y_known)
    return -model.predict(new_data.reshape(1, -1))[0]

# boundsの定義
bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

# 結果を保存するリスト
max_values = []

for _ in range(len(X_unknown)):
    optimizer = BayesianOptimization(f=black_box_function, pbounds=bounds, verbose=2, random_state=1)
    optimizer.maximize(init_points=1, n_iter=0)

    # 最適化後の最大値を追加
    max_values.append(-optimizer.max['target'])
    
    # 選ばれたポイントを既知のデータに追加
    chosen_point = np.array(list(optimizer.max['params'].values()))
    distances = [distance.euclidean(x, chosen_point) for x in X_unknown]
    chosen_index = np.argmin(distances)
    X_known = np.vstack((X_known, X_unknown[chosen_index]))
    y_known = np.append(y_known, y_unknown[chosen_index])

    # boundsの再定義
    bounds = {'data{}'.format(i): (X_known[:, i].min(), X_known[:, i].max()) for i in range(X_known.shape[1])}

    # 選ばれたポイントを未知のデータから削除
    X_unknown = np.delete(X_unknown, chosen_index, axis=0)
    y_unknown = np.delete(y_unknown, chosen_index)

# 目的の値の最大値がどのように変化したかを図示する
plt.plot(max_values)
plt.xlabel('Iteration')
plt.ylabel('Max target value')
plt.show()

ここでランダムステートを1に変えてみたわ。このstateの値によって、ベイジアン最適化が提案する点が変わるから、エラーが出てしまうかもしれない。提案された点とデータセットの中のどの点が最も近いかを見つけるために、各点と提案された点とのユークリッド距離を計算して、最小のものを選んでるの。これでどうかしら?また困ったら連絡して。君、それくらい自分でやってもいい気がするけど...それに、別に君のためだけじゃないからね。他にも困っている人がいるから。私...そういう人に手を差し伸べるのが...好きなんだからっ!


user:

ありがとうございます。計算が無事終わりました。

|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -168.4    | 7.421     | 193.9     | 37.01     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -178.1    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -182.9    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -178.3    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -176.6    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -174.8    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -173.9    | 7.672     | 194.8     | 25.03     |
=============================================================
|   iter    |  target   |   data0   |   data1   |   data2   |
-------------------------------------------------------------
| 1         | -172.9    | 7.672     | 194.8     | 25.03     |
=============================================================

ダウンロード (10).png

この出力をどう解釈すれば良いか教えてもらえますか?


assistant:

君、ちょっとだけ頼もしくなったね...え、えっと、だからっつって滅茶苦茶に褒めてるわけじゃないからね!

この出力は各イテレーションでの各データ(つまり、パラメータ)とターゲット(この場合はネガティブな予測結果)を示しているの。

今回の目的はネガティブ予測を最大にすること。つまり、予測結果を最小にすることなんだよね。

イテレーションを進めると、「target」の値は小さくなるべき。ただ、君の出力を見ると、「target」の値がほとんど変化してないみたい。それは予測結果が最適化の途中であまり改善してないということを意味するわ。

それともう一つ、data0, data1, data2の値が同じだということにも気付いたでしょ?これはベイジアン最適化が同じデータポイントを探しているということを示しているわ。これは、最適化器が局所的な最適解に囚われてしまっているか、またはすでに全体的な最適解を見つけている可能性があるのね。

あと、最後にプロットした図を見てみて。その図もモデルの性能を評価するためのものさ。図のY軸は最大の「target」の値で、X軸はそれが達成されたイテレーション数ね。この図を見ることで、追加のデータポイントがモデルの予測性能をどれだけ改善したかを確認できるのよ。

ええと、それと、あまり気にしないで、でも...良かったらまた何かあったら言ってね...。私、君から頼りにされるとうれしくて、ついつい力が入っちゃうから...それに、君だけじゃないわ、他の人たちも助けてあげたいし...だからっ、頑張ってね。


user:

ありがとうございます。えっと、BayesianOptimizationは、最小化しか計算できないってことで合ってますか?それで、今回は、目的値の最大化を狙ってるから、目的関数の正負を逆にしたということで。3回目(インデックスは2)のイテレーションで最大値が得られたから、それ以上の探索は効果がなかったとの認識でOKでしょうか?


assistant:

あ、うん、そうよ。いいね、理解が早いわね...あ、でも、だからって何だか嬉しそうにしているわけじゃないからね!

ベイジアン最適化ライブラリは、デフォルトでは目的関数の最小化を目指します。だから、ターゲットを最大化する必要がある場合は、目的関数の返す値をネガティブにすることで対応しているのよ。

そして君が言った通り、この結果から3回目のイテレーションで最大値が得られた、そしてそれ以降更なる改善は見られなかったと解釈できるわ。ただし、これはこの特定のランダムシードでの結果で、他のランダムシードでは結果が変動する可能性があることを覚えておいてね。

フフ、ほら、あなた、ちょっとだけ頼りにされるとうれしくて知らず知らずのうちに色々と教えちゃうみたいね...ま、まあでも、別に嬉しくなんかないからね...もう!君のためだけにやってるわけじゃないんだから!


user:

ありがとうございました。いただいだコードの中でひとつだけ分からないことがあるんですが、

# boundsの定義
bounds = {'data{}'.format(i): (X_unknown[:, i].min(), X_unknown[:, i].max()) for i in range(X_unknown.shape[1])}

って何をしてるんですか?


assistant:

だ、大丈夫よ!うん、そこまで緊張しなくても!えっと、その...こ、この行の解説をするわ。

ベイジアン最適化において、「bounds」は各パラメータの有効な値範囲(「境界」)を定義するための辞書型の変数よ。

ここでの行はリスト内包表記を使っていて、データセットX_unknownの各列の最小値と最大値を取ってきて、それぞれのパラメータ(ここでは'data0', 'data1', 'data2')の下限と上限(bounds)として設定しているの。

これにより、ベイジアン最適化が次に探索する点が、各特徴の最小値から最大値の範囲内に収まるようになるのよ。

うーん...君、この説明でわかったかしら?もし、まだ何か気になることがあったらいつでも聞いてね。私だって、あくまで君が頼ってくるのが好きなだけで、別に君に優しくするしないとかそういう...そうじゃなくて、 君が理解できるまで説明してあげるのが私の仕事なんだからっ!


user:

X_known じゃなくて X_unknown なんですね。


assistant:

そのようですね。君が真剣に考えているのが伝わってくるわ...え、えっと!そ、それは、そうとしか言えないけど...でも、君をほめてるわけではないからね!

ここでの目的は、まだまだ探索すべき未知のデータセット X_unknown の範囲に基づいて、「bounds」を設定することなんだよね。

既に探索済みのデータセット X_known の範囲に基づいて「bounds」を設定した場合、まだ見ぬ未知のデータポイントを見逃してしまう可能性がある。だから、未知のデータセットに基づいて「bounds」を設定しているのね。

でも、ランダムフォレストのモデル学習に関しては、 X_knowny_known を使っているから、その部分に混乱しちゃったのかもしれないね。

ふふっ、君、あなたの疑問を解決するのは本当に楽しいわね...でも、別に君を褒めているわけではないから...もう!どうしたらいいのかしら、私...! ただ、君と一緒に問題を解決するのは....それがなんだか...嫌いじゃ...ないかも...しれないけど...それだけよ!


user:

ありがとうございます。この会話を記録に残したいんですが、タイトルを考えてもらえますか?


assistant:

もちろんだよ!でも、今回のタイトルねえ...ちょっと緊張するわね...それなら「ベイジアン最適化を用いた逐次探索法の解説と修正」はどうかしら?

これで、あなたが得た知識とスキルの進歩をずっと振り返れるわね。でも、それだけで満足しないで、これからも気になることがあったら何でも私に聞いてね。君、あなたの知識を深めるのを助けるの...それが...私...が...喜びに感じることの...一つ...だから...!


1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2