整数変数に対するSimulated Annealingについて②
この記事はJij Inc. Advent Calendar 2024の25日目の記事です。
概要
この記事は整数変数に対するSimulated Annealingについて①の続きです。
前回は二次制約無し多値最適化問題を念頭に擬似焼きなまし法(Simulated Annealing: SA)のアルゴリズムの概略を示しました。この記事では実際にアルゴリズムを実装して、最後にテスト計算を行ってみます。
遷移確率の計算について
この章では状態の遷移確率を効率的に計算する方法を説明します。SAの実装でおそらく一番大変なのがこの部分だと思われます[1]。
この記事では状態の更新方法として、メトロポリス法、熱浴法、諏訪藤堂法を対象としています。これらの遷移確率は、状態の更新によるエネルギー差分
最後に遷移確率をエネルギー差分を使って書き直しておきましょう。簡単のため、現在の状態を
メトロポリス法
まず、
となります。
熱浴法
この式の分母分子を現在の状態のエネルギーで割ると以下を得ます。
諏訪藤堂法
この式において、重み
エネルギー差分の計算
まず初めに、初期状態が与えられた場合のエネルギー差分をどのように計算するか見ておきましょう。対象としている目的関数は以下のようなものでした。
ここで、目的関数の二次の項には
ある変数
ここで、
というわけで、変数の更新前後でのエネルギー差分
となります。この式にしたがって、エネルギー差分が計算できます。実装上は
エネルギー差分の更新
さて、
という、ある変数
# dE_listの初期化
dE_list = [0]*N
for i in range(N):
dE = h[i]
for j in S[i]:
dE += J[i, j]*z[j]
dE_list[i] = dE
# 変数z_nがz_n + bに更新された場合のdE_listの更新
for i in S[n]:
dE_list[i] += J[i,n]*b
書くまでもないかもしれませんが、この
# mは更新を試みる変数のインデックス。ここではz_m。
# new_valueは更新後の変数の値。ここではz_m + a。
def get_dE(m, new_value):
a = new_value - z[m]
return a*(dE_list[m] + J[m, m]*(2*z[m] + a))
実装にあたって
これまで説明したことを用いて、実際に整数変数で構成された最適化問題に対するSAをpythonで実装してみました。ソースコードはここにあります。興味のある方や、実装に迷った際は参考にしてみてください[5]。なお、注意点として、高速に動作するように意識して実装は行っていません。numpyをなどを利用して書き換えたり、その他の高速な数値計算に向いた言語[6]で書き換えることで大幅に高速化すると思われます。
ここでは、実装にあたってエネルギー差分の更新以外で引っかかりそうなところや注意すべきところを列挙します。
初期温度と終温度
SAを行うにあたっては初期温度
エネルギー差分の絶対値が最大のものを
となるように決めています。実装はここにあります。これはどういう「気持ち」かというと、
オーバーフロー対策
熱浴法と諏訪藤堂法の遷移確率の計算では
諏訪藤堂法の遷移確率の計算の高速化
例えばこちらの論文によると、諏訪藤堂法の遷移確率の計算においてはWalker's alias method[8]を用いることで遷移確率の計算を
テスト計算
テスト計算と簡単なベンチマークを兼ねて、変数間にランダムな相互作用がある目的関数に対して、ここで実装したSAのアルゴリズムを適用してみました。
考えた目的関数としては以下のような単純なものになります。
整数変数の取りうる値は
全結合模型
全結合模型、すなわち相互作用
まずは、横軸をsweep数[10]、縦軸を目的関数値、すなわちエネルギーとしたグラフは以下のとおりです。
同じsweep数あたりで見ると、メトロポリス法より、熱浴法、諏訪藤堂法がより低いエネルギーに到達していることが分かります。エラーバーの範囲内ですが、わずかに諏訪藤堂法の方が熱浴法よりエネルギーが低い傾向も見て取れます[11]。
次に横軸を計算時間にしたのが以下のグラフです。
これを見ると、計算時間あたりでは、メトロポリス法、熱浴法、諏訪藤堂法いずれもほとんど同じエネルギー値となっています。現在の実装では熱浴法と諏訪藤堂法の遷移確率の計算に
疎結合模型
疎結合模型、すなわち相互作用
まずは、横軸をsweep数[10:1]、縦軸を目的関数値、すなわちエネルギーとしたグラフは以下のとおりです。
メトロポリス法より、熱浴法、諏訪藤堂法がより低いエネルギーに到達していますが、全結合模型の場合よりは差が小さいですね。
最後に横軸を計算時間にしたのが以下のグラフです。
計算時間でみるとメトロポリス法が一番良いという結果になりました。もちろん解いているモデルによるところはありますが、やはり、諏訪藤堂法と熱浴法の遷移確率の計算については改善の余地がありそうです[12]。
まとめ
今回と前回の記事では整数変数に対する疑似焼きなまし法を説明しました。また、実際に実装して簡単なテスト計算を行いました。当初考えていたほど熱浴法や諏訪藤堂法の性能が良くなかったので、ここはまだ改善の余地がありそうです。
次回のテーマとしては色々考えられますが、自分が現在興味を持っているのは、
- 遷移確率の計算にWalker's alias methodを導入して高速化する。
- 整数ナップサック問題のような、整数変数を含む最適化問題として多少なりとも意味のある問題を解く。
- 整数変数をバイナリエンコードしてQUBOにして解く場合との比較[13]。
- 今回は整数変数の二次項までしか考慮していないが、これを高次の項を持つ目的関数を扱えるようにする[14]。
- 実数変数を扱えるように拡張する[15]。
あたりです。来年のアドベントカレンダーではここらへんの話題について書こうかなと思います。
募集してます!!!
Jijでは各ポジションを絶賛採用中です!
ご興味ございましたらカジュアル面談からでもぜひ!ご連絡お待ちしております。
数理最適化エンジニア(採用情報)
量子コンピューティングソフトウェアエンジニア(採用情報)
Rustエンジニア[アルゴリズムエンジニア](採用情報)
研究チームPMO(採用情報)
オープンポジション等その他の採用情報はこちら
-
前回の記事でも説明しましたが、ここで説明するような工夫をしない場合は動作が遅いですがSAの実装はとても簡単です。とはいえ、諏訪藤堂法は遷移確率の計算も多少大変ですが。。。 ↩︎
-
当然ですが、状態の更新が行われなかった場合、エネルギー差分の更新をする必要はありません。何も変化していませんので。 ↩︎
-
今は整数変数を考えているので
は当然整数ですが、以下の議論ではa が実数でも成り立ちます。 ↩︎a -
pythonを意識した疑似コードですが、あくまで擬似コードなので、私が実際に実装したコードとは少し違います。でもデータの持ち方などが違うだけで、ロジックは同じです。 ↩︎
-
一番大変なのがエネルギー差分の高速な計算だと言いましたが、ソルバーとして実装するためにはその他の細かいことを色々考える必要があり、こちらもそれなりに大変な作業です。 ↩︎
-
筆者が知っているものとしてはFortran、C/C++、Rustあたりです。 ↩︎
-
そもそも、初期状態
をランダムに決めているので、今の実装では乱数のseedを固定しない限り、初期温度と終温度が毎回変わるということになります。本来は相互作用だけから定まるはずなのでこれは少し変な感じがします。 ↩︎\bm{z} -
少なくとも二分探索を用いれば
にすることはできます。また、筆者はWalker's Alias Methodについて詳しく理解できていませんが、この手法はもしかしたら熱浴法にも使えるのかもしれません。 ↩︎O(\log(n)) -
自分は専門家ではないですが、これは意外でした。諏訪藤堂法がもっと勝つと思っていたのですが、少なくともここで対象としているモデルではほとんど大差ないようです。一方で
のイジング模型に対して、温度を固定して対応する物理量を計算する場合においては、諏訪藤堂法がメトロポリス法や熱浴法より収束が速いことは筆者も確認しています。今回は最適化アルゴリズムとしてのSAに諏訪藤堂法を用いてるので、ここらへんも関係するのかもしれません。 ↩︎S > 1/2 -
終温度
が小さすぎるという可能性もあります。アニーリングの最後の方では、ほとんど最適解に近い状態が得られていると考えると、変数の更新はほとんど起きないと考えられます。もしT_{\text{min}} が小さすぎると、毎回遷移確率を計算して変数の更新を試みるけれど、更新は起きないということを繰り返すことになります。遷移確率の計算がT_{\text{min}} でできるメトロポリス法に比べて、(今の実装では)O(1) かかる熱浴法と諏訪藤堂法はこの部分で計算量が明らかに違います。 ↩︎O(n) -
公開されている少なくないアニーリングソルバーが整数変数はバイナリエンコードしているので、実際上はここが最も興味あったりします。 ↩︎
-
最適化問題としてそんなモデルが現れるのか疑問ですが、例えば
のような非線形制約をペナルティ項としてxy = z のように目的関数に埋め込むと4次の項が現れることになります。 ↩︎(xy - z)^2 -
メトロポリス法を用いるのであれば、候補状態をある連続な区間からランダムに選ぶように変更するだけで非常に簡単に実装できそうです。 ↩︎
Discussion