---
title: [BoTorch]Knowledge GradientによるMulti-fidelityベイズ最適化の基礎と実装
tags: ベイズ最適化 ガウス過程 BoTorch GPyTorch
author: narrowlyapplicable
slide: false
---
- Multi-fidelity
- Multi-fidelity Knowledge Gradient
- BoTorch実装
BoTorch関連記事の3本目です。
ベイズ最適化をはじめとしたBlack Box最適化は、評価(=目的関数値の取得)が容易でない対象での最適化に威力を発揮します。シミュレーションであれば計算コストが高い、実験であれば長時間を要する、といった状況です。代表的な適用先である深層学習のハイパーパラメータ調整も、やはり1回の学習に多大な計算コストを要するexpensiveな例です。
こうした用途では、最適化にかける計算コストや時間(Budgetと呼びます)を抑えたい場合が多くあります。Budgetを抑えながら最適値を探す際に有効な考え方として、Multi-Fidelity(MF)があります。
本記事では、BoTorchに実装されているqMultiFidelityKnowledgeGradient
獲得関数を例に、Multi-fidelityの基礎を解説することを目的とします。
使い方だけを知りたい場合は、公式のTutorialを参照することをお勧めします。
多くの実験やシミュレーションにおいて、速度・コストは精度とのトレードオフになります。
- シミュレーションであれば、精度を犠牲にして高速に計算させる事ができるでしょう。
- 化学実験などにおいても、機材の動作や測定の精度を落として早く結果を得られるものがあります。
- ベイズ最適化の最もメジャーな適用先である機械学習のハイパーパラメータ調整においても同様です。深層ニューラルネット(DNN)であれば、エポック数や使用するデータ量を落とすことで、その性能を粗く高速に見積もる事ができます。
このように、評価したい系に 「速度と精度のトレードオフ」を引き起こすパラメータ があるとき、当該パラメータを "fidelity"(忠実度) と呼びます。
一般化のため、表記法を導入しておきます。
目的関数$f(x), x\in\mathbf{A}$を、fidelityを持つ$g(x, s)$に拡張することを考えます。ここで、fidelityパラメータが1次元であれば、$s\in[0,1]$は1が最高・0が最低(逆なこともあります)すなわち
fidelityパラメータが多次元の場合も、
となります。ただし$\mathbf{1}=(1, 1,..., 1)\in\mathbb{R}^m$です。
-
例として、DNNのハイパーパラメータ調整を考えてみましょう。 この場合、精度が目的関数$f$に、ハイパーパラメータが入力$x$に当たります。さらにfidelityパラメータ$s$として、学習データ量を変動させる事にします。
本来の学習データ全てを使う場合が$s=1$であり、学習データを半分しか使わない場合を$s=1/2$とします。
$f(x)=g(x, 1)$ の評価には全データを用いた学習が必要であり、相応の計算時間を要します。これに対し$g(x, 1/2)$の評価では、使うデータ量が半分となるため、より短い計算時間で済む事が期待できます。このように、fidelityによって精度と計算コストのトレードオフを調整できる訳です。
最適化全体にかかる時間ないしコスト("Budget"と呼びます)を抑える目的で、fidelityパラメータを変動させる手法をMulti-fidelityと呼びます。
通常のベイズ最適化では、目的関数$f(x)$の最適値を探すため、
次に評価すべき入力点$x_{new}$を提案 →
という作業を繰り返します(outer-loop)。
しかし$f(x_{new})$の評価が重すぎる場合、outer-loop全体に要するBudgetが大きくなりすぎる事があります。そこで評価の一部を低fidelityな$g(x_{new}, s), s<1$で代替することで、outer-loop全体のBudget抑制を狙います。これがMulti-fidelityの基本的考え方です。
[図:outerloop]
具体的には、以下のような変更が加わります。
- ガウス過程回帰モデル(GP)による推定対象を、$f(x)$ではなく$g(x, s)$とする。
- GPの入力変数は$x$ではなく、fidelityが加わった$(x,s)$となる。
- GPの目的関数は$g:(x, s)\mapsto\mathbb{R}$となる。
- 入力$x, x^{'}\in\mathbf{A}$間の内積を定義するカーネルに加えて、fidelityに関するカーネルを定める必要がある。
- 評価コストも、別のGPモデルで推定する。
- 評価コストは入力とfidelityで定まるものとして推定し、$cost(x, s)$などと表す。
Mulit-fidelityを用いたBOは2017年ごろから登場し、すでに多くの手法が存在します。 代表的なものとしてはFABOLAS (2017)やFreeze-Thaw(2014)などがあります。
- FABOLAS論文:Fast Bayesian Optimization of Machine Learning Hyperparameters on Large Datasets
- Freeze-Thaw論文:Freeze-Thaw Bayesian Optimization
しかし本記事では、より後発の手法でBoTorchに採用されているtaKGに対象を絞ります。
taKGは性能面での改善の他に、fidelityをエポック数のような連続的なもの(trace fidelity)と、データ数のような非連続的なものに分けることで、1回の試行から最大限の情報を取得できる利点があります。
また前段階であるcfKGがシンプルで説明しやすいため、Multi-fidelityの入門に向いているという利点もあります。
次章において、まずKnowledge Gradient獲得関数を導入し、そのMulti-fidelity拡張としてcfKG, taKGについて説明します。そして§3において、taKGのBoTorch実装であるqMultiFidelityKnowledgeGradient
を用いた実装について説明します。
Multi-fidelityによるベイズ最適化(MFBO)の詳細に立ち入る前に、Knowledge Gradient(KG)獲得関数について説明しておきます。
- MFBOでは、KGやEntropy Search系など比較的新しい獲得関数を使う傾向があります。
- 代表的な獲得関数であるEI(Expected Improvement)には、$s<1$では使えないという問題があります。FrazierのTutorialにある通り、fidelityを変動させた場合$EI=0$となるため、EIはそのままでは使用困難です。EIをベースにした手法も存在しますが、Entropy Search(ES)やKnowledge Gradient(KG)は自然にMFBOに拡張できるため、これらをベースにした手法が多いようです。
- BoTorchでは、ES系のMES(Max-value Entropy Search)とKGをぞれぞれ拡張したMFBO用獲得関数が実装されています。本記事はKG系に対象を絞ります。
EIなど従来の獲得関数が各入力点$x_{new}$での事後分布を評価するのに対し、KGは定義域$\mathbf{A}$全体での事後平均に注目します。
ガウス過程にn点のデータ$D_{1:n}$を与えた事後平均を$\mu_n(x)$とし、その下での最適値を
と表します(最小化問題の場合)。
追加の観測として$D_{n+1:n+q} = (x_{n+1}, y_{n+1}), ..., (x_{n+q}, y_{n+q})$ を得ると、この値は
が、追加観測$D_{n+1:n+q}$から得られる情報量です。 この情報利得の期待値
を獲得関数とし、値が最大になるように新規観測点
ただし、KGの値は直接計算できません。なぜならば新規評価点における評価 $(y {n+1}, ..., y {n+q})$ は不明であり、したがってn+q点を使用した事後平均 $\mu_n(x)$およびその最小値$\tau{n+q}$は求められないからです。 代わりに現状の事後分布から$y{n+1}, ..., y_{n+q}$をサンプリングし、その下で事後平均を$\mu_n(x) \rightarrow \mu _{n+q}$に更新します。
簡単のため$q=1$とすると、新規評価点の候補$x_c$における評価$y_c$を
とサンプリングします。ただし$p(y|x_c, D_{1:n})$はGP事後分布です。
サンプルを1つだけ得た場合、サンプル
サンプリング個数をMに増やす場合は $\set{y_c^{(i)}}{i=1}^M$ それぞれについて上記の手順を繰り返し、M通りの$\set{\tau{n+1}^{(i)}}_{i=1}^M$を計算します。 あとはKGの期待値部分をモンテカルロ近似して
と算出します。
下記の記事では、KGの定義をグラフを用いて解説しています。図形的に理解したい方(私は苦手ですが……)には特におすすめです。
以下に概要をなぞっておきます。上記記事を読んだ方には不要ですので、次章に進んでください。
出典:An Illustrated Guide to the Knowledge Gradient Acquisition Function
- 事後平均の最小値を求める ガウス過程にn点のデータ$D_{1:n}$を与えた事後平均$\mu_n(x)$(上図の赤線)から、最小値$\tau_n$(上図の赤い★)を求めます。
- 候補点
$x_c$ における評価$y_c$ をサンプリング GP事後分布から$\set{y_c^{(i)}}_i^M$をサンプリングします。上図では1点だけサンプリングしており、青●が$y_c^{(1)}$を示しています。 - 更新後の最小値$\tau_{n+1}$を取得 データセットに$(x_c, y_c)$を加えてGPのハイパーパラメータを再学習し、更新後の事後平均(上図の青線)から最小値$\tau_{n+1}$(上図の青い★)を求めます。
- KGのモンテカルロ近似計算 2.で得たサンプル$\set{y_c^{(i)}}$の各々について3.を繰り返し、更新後事後平均のサンプル$\set{\mu_{n+1}^{(i)}}$(下図の緑〜青線)を得ます。さらに事後平均の最小値サンプル$\set{\tau_{n+1}^{(i)}}$(下図の破線)を求め、KGのモンテカルロ近似を得ます。
Frazier et al. 2009では、ノイズの多い場合で特に有効とされています。
また下記のブログでは、現状のモデル(事後分布)を信頼し貪欲に探索する傾向があるとされています。
https://sigopt.com/blog/expected-improvement-vs-knowledge-gradient/
KGをMulti-fidelityに拡張します。 cfKG(Continous-Fidelity KG)は、その中でも比較的素直な拡張です。
cfKGの基本となる考え方は、通常のKGを評価コスト(の推定値)で割り、探索の費用対効果を考慮するというものです。
MFでは通常の入力
またカーネルも$\set{\mathbf{A}\times [0,1]^m}\times\set{\mathbf{A}\times[0,1]^m}\mapsto\mathbb{R}$とする必要があります。
探索空間にfidelityが加わったため、獲得関数で評価すべき候補点は、入力候補点とfidelity候補点の組
この候補点$(x_c, s_c)$ による情報の利得は、fidelity=1での最小値がどの程度小さくなるか、で定義します。
すなわちfidelity付きGPの事後平均
とし、差分
通常の KGであれば情報利得の期待値
を取りますが、cfKGはこの期待値を評価コスト$cost_n(x, s)$で割ります。
分母の評価コスト
これにより、評価コストと情報利得のトレードオフを考慮した候補点を選ぶことができます。 cfKGが最大となる入力とfidelityの組$(x, s)$を選択し、次の評価対象とします。この$(x, s)$は(推定)評価コスト当たりの情報利得が最も大きくなるため、評価コストを抑えながら効率よく最適化を進められると考えられます。
同時に複数点を選ぶParallel Selectionに拡張をすることを考えます。
通常の入力変数
分母(評価コスト)は、新規評価点
cfKGの大きな弱点として、評価コストが非常に小さくなると分母が0に近づくため、分子=情報利得がほぼ0の候補点を選択しやすくなる性質があります。$s=0$付近に嵌まってしまうと、Budgetの大半をほぼ情報のない探索に費やすこともあります。これはcfKG特有の問題ではなく、FABOLASやESベースの類似手法でも生じる普遍的なものです。 対策としては、分母である評価コストに小さな固定値を足し込む・fidelityが$s\fallingdotseq0$での評価コストに罰則を付ける、といったものがあります。ただし固定値や罰則の付け方などは問題ごとに設定する必要があります。
また、DNNにおけるエポック数のように 「途中経過」があるfidelityの情報を有効活用できないという問題もあります。
cfKGはfidelityを単一の値と見なしますが、エポック数がNの場合、fidelity
fidelityを2種類に分け、エポック数のように複数fidelityでの評価を一括で得られるもの(trace fidelity)と、学習データ量のように単一fildelityでの評価しか得られないもの(non-trace fidelity)とを考えます。
例えばエポック数を
cfKGでは、trace fidelity
同時に観測できるfidelityの組は、$[0, s]$ と
の評価結果を同時に得ることになります。
taKGはcfKGの拡張であり、trace fidelityに対応した修正を加えたものです。
まずtrace fidelityにおける複数の評価結果を扱う表記法を導入します。
1度に観測できるfidelityが1点ではなく、集合を
とします。これは、上図で言えば赤い↔︎の範囲を評価した結果にあたります。
この表記を用いて、cfKGの分子・分母を修正していきます。
BoTorchのMulti-fidelity KG実装はqMultiFidelityKnowledgeGradient
です。
その使い方も、公式のTutorialに例が示されています。このTutorialを完全に理解できるのであれば、使い方については以下を読む必要はありません。
Tutorialのモデル定義は下記の関数でなされています。
def initialize_model(train_x, train_obj):
# define a surrogate model suited for a "training data"-like fidelity parameter
# in dimension 6, as in [2]
model = SingleTaskMultiFidelityGP(
train_x,
train_obj,
outcome_transform=Standardize(m=1), ##出力を正規化
data_fidelity=6 ##入力の第6次元がnon-trace fidelity.
)
mll = ExactMarginalLogLikelihood(model.likelihood, model)
return mll, model
- GPモデルが通常の
SingleTaskGP
ではなく、SingleTaskMultiFidelityGP
になっています。- これは通常の
SingleTaskGP
のMulti-fidelity版であり、カーネルが上述のfidelity対応版に拡張されています。 data fidelity
と呼ばれている引数でnon-trace fidelityを指定します。入力Tensorの対応する列番号を引数として与えれば、この列のデータはnon-trace fidelityとして扱われ、ガウス過程においては通常DownsamplingKernel
への入力となります。- この例では使われていないものの、
iteration_fidelity
引数によってtrace fidelityを指定できます。こちらは通常ExponentialDecayKernel
が適用されます。
- これは通常の
- 内部的には、
_setup_multifidelity_covar_module
でfidelityのカーネル(§2.3.4.参照)を追加しています。- 通常の入力に対するカーネルはRBF固定です。ただし引数
linear_truncated
引数をTrueとした場合は、全体にLinearTruncatedFidelityKernel
が適用され、入力部分はnu
引数で指定した滑らかさのMaternカーネルに変更となります。
- 通常の入力に対するカーネルはRBF固定です。ただし引数
kernel = RBFKernel(
ard_num_dims=len(active_dimsX),
batch_shape=aug_batch_shape,
lengthscale_prior=GammaPrior(3.0, 6.0),
active_dims=active_dimsX,
)
additional_kernels = []
if iteration_fidelity is not None:
exp_kernel = ExponentialDecayKernel(
batch_shape=aug_batch_shape,
lengthscale_prior=GammaPrior(3.0, 6.0),
offset_prior=GammaPrior(3.0, 6.0),
power_prior=GammaPrior(3.0, 6.0),
active_dims=[iteration_fidelity],
)
additional_kernels.append(exp_kernel)
if data_fidelity is not None:
ds_kernel = DownsamplingKernel(
batch_shape=aug_batch_shape,
offset_prior=GammaPrior(3.0, 6.0),
power_prior=GammaPrior(3.0, 6.0),
active_dims=[data_fidelity],
)
additional_kernels.append(ds_kernel)
kernel = ProductKernel(kernel, *additional_kernels)
Tutorialで獲得関数の定義部分は以下の通りです。
bounds = torch.tensor([[0.0] * problem.dim, [1.0] * problem.dim], **tkwargs)
target_fidelities = {6: 1.0} ## 本来の(最適化対象である)fidelityの列番号と値
cost_model = AffineFidelityCostModel(fidelity_weights={6: 1.0}, fixed_cost=5.0)
cost_aware_utility = InverseCostWeightedUtility(cost_model=cost_model)
## (獲得関数値を)測定コストで割るよう指定
def project(X): ## Design Point を本来のfidelityに移動させる関数
return project_to_target_fidelity(X=X, target_fidelities=target_fidelities)
def get_mfkg(model):
curr_val_acqf = FixedFeatureAcquisitionFunction(
acq_function=PosteriorMean(model),
d=7,
columns=[6],
values=[1],
)
_, current_value = optimize_acqf(
acq_function=curr_val_acqf,
bounds=bounds[:,:-1],
q=1,
num_restarts=10 if not SMOKE_TEST else 2,
raw_samples=1024 if not SMOKE_TEST else 4,
options={"batch_limit": 10, "maxiter": 200},
)
return qMultiFidelityKnowledgeGradient(
model=model,
num_fantasies=128 if not SMOKE_TEST else 2,
current_value=current_value,
cost_aware_utility=cost_aware_utility,
project=project,
)
- 1行目:探索範囲を
bounds
として定義 - 2行目:最適化対象となる本来のfidelityについて、列番号(6)と値(1.0)をdictで指定
- 4〜5行目:取得コストのモデルを
AffineFidelityCostModel
で定義- f
-
project()
:候補点$(x, s)$を$(x, 1)$に書き換える(だけの)関数。獲得関数定義時に引数として与えます。 -
get_mfkg()
:獲得関数を定義します。-
まず現状での事後平均$\mu_n(x, s)$ に対して、fidelity=1での最小値
\tau_n = \min_{x\in\mathbf{A}}{\mu_n(x, \mathbf{1})}
を求めます。
-
FixedFeatureAcquisitionFunction
は、特定の入力特徴量を固定するために用いるWrapperです。通常、第3引数の特徴量を第4引数の値に固定した状態で、第1引数の獲得関数値を最適化するために用います(第2引数は入力次元数です)。 - この
FixedFeatureAcquisitionFunction
を用いて、事後平均PosteriorMean
を最適化する(擬似)獲得関数curr_val_acqf
を定義します。ここでfidelityのあるcolumns=[6]
をvalues=[1]
に固定し、fidelity=1での最小値するよう設定しています。 - (擬似)獲得関数
curr_val_acqf
をoptimize_acqf
に与え、$\tau_n$に当たる事後平均最小値current_value
を求めます。
-
-
MF獲得関数
qMultiFidelityKnowledgeGradient
を定義し、返します。
-