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