多目的最適化¶
複数の目的関数を扱う場合、実装アプローチとして主に 2 つの方法があります。
単一の目的関数へ落とし込む |
複数の目的関数を直接考慮する |
|
|---|---|---|
使用クラス |
||
ブラックボックス戻り値 |
|
|
サロゲートモデル化対象 |
目的関数値の重み付き和(集約後の値) |
各目的関数値(集約前の値) |
適しているケース |
目的関数間の重みが固定 |
目的関数間の重みを最適化中に動的に調整 |
単一の目的関数へ落とし込む方法¶
複数の目的関数を重み付き和として 1 つの float 値にまとめ、通常の Optimizer で最適化します。重みが最初から決まっており変更する予定がない場合に適しています。サロゲートモデルが「最終的に最小化したい値(重み付き和)」を直接近似するため、その重み設定に特化したモデルが得られます。
from amplify_bbopt import blackbox, KMTrainer, Optimizer, RealVariable
# それぞれの目的関数の重み
w_g = 10
w_h = 2
@blackbox
def bbfunc(
x: float = RealVariable((0, 1)),
y: float = RealVariable((0, 1)),
z: float = RealVariable((0, 1)),
) -> float:
return w_g * g(x, y) + w_h * h(y, z)
optimizer = Optimizer(
blackbox=bbfunc,
trainer=KMTrainer(),
client=my_client,
)
optimizer.add_random_training_data(num_data=10)
optimizer.optimize(10)
Note
この方法では、1 つのサロゲートモデルが重み付き和を近似します。重みが変わる場合、過去の学習データは新しい目的に対して不適切になるため、重みの変更は慎重に行う必要があります。
複数の目的関数を直接考慮する方法¶
list[float] を返すブラックボックス関数と MultiOptimizer を組み合わせることで、各目的関数を個別のサロゲートモデルで近似しながら最適化できます。
各サロゲートモデルが目的関数ごとに独立して近似されるため、支配的なある目的関数による、サロゲートモデルへの悪影響を避けながら、それぞれの目的の形状を個別に学習できます。また、各目的値を個別に保存しているため、重みを weight でイテレーションごとに変更しても学習データを取り直す必要がありません(詳細はこちら)。
ブラックボックス関数の定義¶
多目的最適化では、ブラックボックス関数の戻り値を list[float] とします。
from amplify_bbopt import blackbox, IntegerVariable
@blackbox
def my_multi_blackbox_func(
x0: int = IntegerVariable((0, 10)),
x1: int = IntegerVariable((0, 10)),
) -> list[float]:
y0 = simulator0(x0, x1)
y1 = simulator1(x0, x1)
return [y0, y1]
上記のブラックボックス関数は、複数の目的関数が共通の決定変数を考慮するという依存関係を考慮しています。様々な依存関係については、こちらを参照してください。
MultiOptimizer のインスタンス化¶
多目的最適化には MultiOptimizer を使用します。trainer に目的関数の数だけ Trainer インスタンスのリストを渡します。各目的ごとに別々のインスタンスを生成する必要があり、同一インスタンスを複数の要素に使い回すと ValueError が発生します。
from amplify_bbopt import KMTrainer, MultiOptimizer
optimizer = MultiOptimizer(
blackbox=my_multi_blackbox_func,
trainer=[KMTrainer(), KMTrainer()], # 目的関数ごとに1つの Trainer
client=my_client,
)
Caution
Trainer は学習のたびに内部状態を更新するオブジェクトです。同一インスタンスを複数の目的に使うと、2 つ目以降の学習時に前の目的の学習データが混入し、サロゲートモデルが誤ったデータで更新されます。
Caution
単目的最適化と同様に、surrogate_data_transformer で各目的関数の学習データに対する変換を指定できます。多目的の場合は、DataTransformer インスタンスのリストを目的関数ごとに 1 つずつ渡します。各目的ごとに別々のインスタンスを生成する必要があり、同一インスタンスを複数の要素に使い回すと ValueError が発生します。
from amplify_bbopt import ExpScaler, KMTrainer, MultiOptimizer
optimizer = MultiOptimizer(
blackbox=my_multi_blackbox_func,
trainer=[KMTrainer(), KMTrainer()],
client=my_client,
surrogate_data_transformer=[ExpScaler(), ExpScaler()], # 目的関数ごとに1つの DataTransformer
)
変換が不要な目的には None を指定します。
optimizer = MultiOptimizer(
blackbox=my_multi_blackbox_func,
trainer=[KMTrainer(), KMTrainer()],
client=my_client,
surrogate_data_transformer=[ExpScaler(), None], # 1つ目の目的のみ変換する場合
)
最適化の実行¶
初期学習データの追加と最適化の実行は基本的な流れは単目的と同様ですが、既存データを training_data として渡す場合は y の shape が異なります。単目的では (n_samples,) の 1D 配列ですが、多目的では (n_samples, n_objectives) の 2D 配列が必要です。
# 初期ランダム学習データを追加する
optimizer.add_random_training_data(num_data=10)
# 最適化サイクルを実行する
optimizer.optimize(10)
import numpy as np
from amplify_bbopt import Dataset
data_x = ... # shape: (n_samples, n_variables)
data_y = ... # shape: (n_samples, n_objectives) ← 2D
optimizer = MultiOptimizer(
blackbox=my_multi_blackbox_func,
trainer=[KMTrainer(), KMTrainer()],
client=my_client,
training_data=Dataset(data_x, data_y),
)
最適化結果の取得¶
単目的と同様に best で最良解を取得できます。多目的の場合、objective は各目的関数値からなる list[float] となります。
print(optimizer.best.values) # ベスト解(入力値)
print(optimizer.best.objective) # 目的関数値: list[float]