OpenJij SA Parameter Dependency Experiment with MINTO#

ここでは、OpenJijのシミュレーテッドアニーリング(SA)アルゴリズムのパラメータが、SAのパフォーマンスにどう影響を与えるかを簡単に調べてみます。 SAアルゴリズムは温度をパラメータとして持ち、OpenJijではQUBOもしくはIsing模型の係数をベースに温度を自動で設定します。 グリッドサーチを用いて、このパラメータの自動設定の有効性を調べてみましょう。 グリッドサーチの場合でも、MINTOをログ記録として利用すれば、データをpandas.DataFrameに簡単に変換し可視化することができます。 これにより、様々なパラメータの組合せと、それらが解の品質や実行時間に与える影響を分析することが可能です。

import openjij as oj
import numpy as np
import minto
import matplotlib.pyplot as plt
import seaborn as sns

1. Create Random QUBO#

def random_qubo(n, sparsity=0.5):
    q = np.random.uniform(-1, 1, (n, n))
    q = (q + q.T) / 2
    zero_num = int(n**2 * sparsity)
    zero_i = np.random.choice(n, zero_num, replace=True)
    zero_j = np.random.choice(n, zero_num, replace=True)
    q[zero_i, zero_j] = 0
    q[zero_j, zero_i] = 0
    qubo = {}
    for i in range(n-1):
        for j in range(i, n):
            qubo[(i, j)] = q[i, j]
            qubo[(j, i)] = q[j, i]
    return qubo
n = 200
q = random_qubo(n)

2. デフォルトの設定で実行する#

OpenJijでは、.sample_quboの戻り値の中にある.info['schedule']を通して、自動で決定されたSAパラメータを確認することができます。 SAの温度設定はアニーリングスケジュールと呼ばれ、これらのパラメータは'schedule'のキーの下に保存されます。

sampler = oj.SASampler()
response = sampler.sample_qubo(q)
schedule = response.info["schedule"]
schedule
{'beta_max': 4274.216268569347,
 'beta_min': 0.19943913796088258,
 'num_sweeps': 1000}

MINTOを用いたグリッドサーチ#

SAの逆温度パラメータを変化させることで、最適化結果がどのように変わるかを見てみましょう。 比較のために、OpenJijのデフォルトのパラメータも検索範囲に含めておきます。 OpenJijの結果から重要な値は、.log_parameterメソッドを用いて保存されます。 完全なOpenJijのresponseオブジェクトも、.to_serializableを指定した.log_objectを用いて保存されます。

exp = minto.Experiment(auto_saving=False, verbose_logging=False)

# log_object accepts only serializable objects
# so we need to convert qubo to serializable object
qubo_edges = [[i, j] for i, j in q.keys()]
qubo_vales = [q[i, j] for i, j in qubo_edges]
exp.log_global_object("qubo", {"qubo_edges": qubo_edges, "qubo_vales": qubo_vales})

beta_min_list = [schedule["beta_min"], 0.1, 1.0, 2.0, 3.0, 4.0, 5.0]
beta_max_list = [10.0, 20, 30.0, 40.0, 50.0, 12404, schedule["beta_max"]]
num_reads = 300

exp.log_global_parameter("num_reads", num_reads)

for beta_min in beta_min_list:
    for beta_max in beta_max_list:
        run = exp.run()
        with run:
            # Log the beta parameters for this run
            run.log_parameter("beta_min", beta_min)
            run.log_parameter("beta_max", beta_max)

            sampler = oj.SASampler()
            response = exp.log_solver(sampler.sample_qubo)(q, num_reads=num_reads, beta_min=beta_min, beta_max=beta_max)
            run.log_object("response", response.to_serializable())
            energies = response.energies
            run.log_parameter("mean_energy", np.mean(energies))
            run.log_parameter("std_energy", np.std(energies))
            run.log_parameter("min_energy", np.min(energies))
            run.log_parameter("exec_time", response.info["execution_time"])
run_table = exp.get_run_table()
run_table
parameter metadata
beta_min beta_max solver_name num_reads mean_energy std_energy min_energy exec_time run_id elapsed_time
run_id
0 0.199439 10.000000 sample_qubo 300 -369.642340 0.963114 -370.290042 5593.806260 0 1.761768
1 0.199439 20.000000 sample_qubo 300 -369.613328 0.999489 -370.290042 5240.703193 1 1.663921
2 0.199439 30.000000 sample_qubo 300 -369.537418 1.073169 -370.290042 4906.612117 2 1.559711
3 0.199439 40.000000 sample_qubo 300 -369.672725 1.013148 -370.290042 4496.780430 3 1.432121
4 0.199439 50.000000 sample_qubo 300 -369.535609 1.081694 -370.290042 4376.558900 4 1.394427
5 0.199439 12404.000000 sample_qubo 300 -369.404223 1.182786 -370.290042 3403.594203 5 1.114759
6 0.199439 4274.216269 sample_qubo 300 -369.447492 1.059979 -370.290042 2819.412353 6 0.926421
7 0.100000 10.000000 sample_qubo 300 -369.526662 1.020109 -370.290042 9079.544150 7 2.815181
8 0.100000 20.000000 sample_qubo 300 -369.546022 1.063859 -370.290042 8179.150270 8 2.555219
9 0.100000 30.000000 sample_qubo 300 -369.607539 1.050563 -370.290042 7707.950683 9 2.408204
10 0.100000 40.000000 sample_qubo 300 -369.643295 1.016970 -370.290042 7205.072490 10 2.260860
11 0.100000 50.000000 sample_qubo 300 -369.603176 1.025462 -370.290042 7745.090413 11 2.415411
12 0.100000 12404.000000 sample_qubo 300 -369.400657 1.108009 -370.290042 3871.795337 12 1.245607
13 0.100000 4274.216269 sample_qubo 300 -369.430557 1.092600 -370.290042 4207.392926 13 1.346105
14 1.000000 10.000000 sample_qubo 300 -369.698762 0.925739 -370.290042 1924.700261 14 0.666851
15 1.000000 20.000000 sample_qubo 300 -369.607248 1.023604 -370.290042 1895.249834 15 0.659730
16 1.000000 30.000000 sample_qubo 300 -369.707480 0.979563 -370.290042 1806.719320 16 0.632811
17 1.000000 40.000000 sample_qubo 300 -369.627673 1.044668 -370.290042 1780.520723 17 0.628205
18 1.000000 50.000000 sample_qubo 300 -369.499219 1.088815 -370.290042 1923.635270 18 0.685863
19 1.000000 12404.000000 sample_qubo 300 -369.369871 1.113930 -370.290042 1200.470696 19 0.446781
20 1.000000 4274.216269 sample_qubo 300 -369.479161 1.074741 -370.290042 1538.579983 20 0.547317
21 2.000000 10.000000 sample_qubo 300 -369.316021 1.092758 -370.290042 1821.728457 21 0.650285
22 2.000000 20.000000 sample_qubo 300 -369.292811 1.175131 -370.290042 1296.084567 22 0.468967
23 2.000000 30.000000 sample_qubo 300 -369.278594 1.189824 -370.290042 1254.532113 23 0.456947
24 2.000000 40.000000 sample_qubo 300 -369.214526 1.157426 -370.290042 1297.206670 24 0.474989
25 2.000000 50.000000 sample_qubo 300 -369.325817 1.105511 -370.290042 1226.851943 25 0.448698
26 2.000000 12404.000000 sample_qubo 300 -368.872647 1.452835 -370.290042 1001.881663 26 0.379956
27 2.000000 4274.216269 sample_qubo 300 -368.858743 1.323323 -370.290042 1572.209427 27 0.617545
28 3.000000 10.000000 sample_qubo 300 -368.996994 1.258217 -370.290042 1286.629837 28 0.470875
29 3.000000 20.000000 sample_qubo 300 -368.860823 1.294394 -370.290042 1178.745013 29 0.435657
30 3.000000 30.000000 sample_qubo 300 -368.632845 1.590694 -370.290042 1358.222256 30 0.531722
31 3.000000 40.000000 sample_qubo 300 -368.667235 1.436215 -370.290042 1147.840564 31 0.425768
32 3.000000 50.000000 sample_qubo 300 -368.493427 1.667043 -370.290042 1197.350967 32 0.445489
33 3.000000 12404.000000 sample_qubo 300 -367.923403 2.303434 -370.290042 1014.915981 33 0.388164
34 3.000000 4274.216269 sample_qubo 300 -367.960931 2.214097 -370.290042 1021.568090 34 0.397896
35 4.000000 10.000000 sample_qubo 300 -368.261301 1.716965 -370.290042 1232.981947 35 0.456698
36 4.000000 20.000000 sample_qubo 300 -368.055285 1.916874 -370.290042 1165.294880 36 0.432358
37 4.000000 30.000000 sample_qubo 300 -367.872730 2.261690 -370.290042 1200.346153 37 0.451068
38 4.000000 40.000000 sample_qubo 300 -367.735213 2.097804 -370.290042 1164.449153 38 0.438306
39 4.000000 50.000000 sample_qubo 300 -367.677898 2.196177 -370.290042 1121.263457 39 0.418124
40 4.000000 12404.000000 sample_qubo 300 -366.455199 3.236642 -370.290042 1003.003917 40 0.384492
41 4.000000 4274.216269 sample_qubo 300 -366.756632 2.963267 -370.290042 985.183883 41 0.376653
42 5.000000 10.000000 sample_qubo 300 -367.586018 2.431858 -370.290042 1164.855014 42 0.431104
43 5.000000 20.000000 sample_qubo 300 -367.349294 2.555882 -370.290042 2012.891947 43 0.743601
44 5.000000 30.000000 sample_qubo 300 -367.077661 2.775944 -370.290042 2401.926950 44 0.840649
45 5.000000 40.000000 sample_qubo 300 -367.292769 2.675066 -370.290042 1143.586283 45 0.431095
46 5.000000 50.000000 sample_qubo 300 -367.020018 2.771812 -370.290042 1109.062753 46 0.416199
47 5.000000 12404.000000 sample_qubo 300 -366.328475 3.202330 -370.290042 901.930843 47 0.350047
48 5.000000 4274.216269 sample_qubo 300 -366.356028 3.215536 -370.290042 905.300684 48 0.347173

4. 結果の可視化#

pivotメソッドをしようし、run_tableをヒートマップでの可視化に適した形式に変換しましょう。 MINTO.get_run_table()は二重ヘッダーを持つDataFrameを返します。 そのため、関連するDaFrameを抽出するためにparameterキーを使用し、得られたものをヒートマップで可視化するために変換します。

param_table = run_table["parameter"].pivot(index="beta_min", columns="beta_max", values="mean_energy")
param_table
beta_max 10.000000 20.000000 30.000000 40.000000 50.000000 4274.216269 12404.000000
beta_min
0.100000 -369.526662 -369.546022 -369.607539 -369.643295 -369.603176 -369.430557 -369.400657
0.199439 -369.642340 -369.613328 -369.537418 -369.672725 -369.535609 -369.447492 -369.404223
1.000000 -369.698762 -369.607248 -369.707480 -369.627673 -369.499219 -369.479161 -369.369871
2.000000 -369.316021 -369.292811 -369.278594 -369.214526 -369.325817 -368.858743 -368.872647
3.000000 -368.996994 -368.860823 -368.632845 -368.667235 -368.493427 -367.960931 -367.923403
4.000000 -368.261301 -368.055285 -367.872730 -367.735213 -367.677898 -366.756632 -366.455199
5.000000 -367.586018 -367.349294 -367.077661 -367.292769 -367.020018 -366.356028 -366.328475
sns.heatmap(param_table, annot=True, fmt="1.1f", cmap="coolwarm")
plt.xlabel("beta_max")
plt.ylabel("beta_min")
plt.title("mean energy")
plt.show()
../_images/9da89a8f64e95d5b216de7edc462983e9c9338dff88bd8d219afac8424381e5b.png

5. 結果の解析#

この結果を見ると、OpenJijのデフォルトのパラメータは悪くありません。 しかし、beta_max=40.0, beta_min=0.1付近でより最適なパラメータがあるように見えます。 別の視点から見てみましょう。 OpenJijのアルゴリズムでは、スピンフリップが拒否された場合、その計算は行われません。 スピンフリップが発生した場合には、そのエネルギー差を計算します。 そのため、温度が高い(逆温度\(\beta\)が低い)状態が長期間続くと、スピンフリップ回数が増加し、計算時間が長くなります。
結果として、温度設定に依存して計算時間が変化します。 これらの結果を可視化してみましょう。 OpenJijの計算時間は、マイクロ秒単位で測定されています。

exec_table = run_table["parameter"].pivot(
    index="beta_min", columns="beta_max", values="exec_time"
)
sns.heatmap(exec_table, annot=True, cmap="coolwarm", annot_kws={"size": 8})
plt.xlabel("beta_max")
plt.ylabel("beta_min")
plt.title("Execution time")
plt.show()
../_images/a7d30895d84de35876e81a3343748c5da8e1310e29b665a6a5d6bc7d1eec7ed5.png

OpenJijのデフォルトのパラメータについての解析#

残念なことに、OpenJij のデフォルトのパラメータは、計算時間の点で最適ではないことがわかりました。 温度パラメータは、SAのステップ数を制御するnum_sweepsパラメータの影響も受けます。 この数値実験により、OpenJijの温度パラメータ設定には、依然として改善の余地があることが明らかとなりました。 そしてMINTOは、ソルバーパラメータの設定に関する知見を得る上で有用であることがわかります。