Nobisuke
Dekisugi
RAG
Privacy policy
2
この記事では Barren Plateau の論文についてざっと眺めた上で簡単な検証用の回路を実装および実行して様子を見てみたいと思います。論文については各種勉強会で聞いたものを中心に眺めますが、幾らか興味で追加しています。
検証には blueqat SDK を用いますが、実験的に cuStateVec のバックエンドを用いてみます。恐らく大袈裟すぎる実装なので、numpy
バックエンドで良いとは思います。
実験環境は Google Colab としました。最近ランタイムが Python 3.8 になりましたので、cuquantum-python のインストールも楽になりました。
まず、Barren Plateau について概要を見てみます。参考文献を全般に眺めた上でのまとめですので、やや広範な記述になっているかもしれません。
日本語で読める気楽な資料としては文献 [1] Qiskit textbook が該当すると思います。「変分訓練」のところで説明されているように、損失関数のランドスケープ内に台地のような平坦な形状が出てきて、最適化計算が進まないということです。同文献の解説は基本的に文献 [2] Barren plateaus in quantum neural network training landscapes のものですが、それから色々なことが分かってきました。
勾配が指数関数的に抑制 のニュアンスが分かりにくいので、論文 [10] Effect of barren plateaus on gradient-free optimization を参考にどういうものかを数式で書き下してみます。
コスト関数 (例えばハミルトニアンの期待値) を
とします。記号の意味については論文 [10] を参照してください。
すべての に対してコスト関数の微分の期待値が 0、即ち
であって、コスト関数の微分の分散が量子ビット数 に関して指数関数的に減少する、即ち適当な に対して
を満たす時、コスト関数 は Barren Plateau を呈すると言う。———
要するに、勾配の微分が小さい上にばらつきも指数関数的に小さいことを指します。論文 [5] によると、パラメータをランダムに初期化したような深いパラメータ付き回路の場合、似たり寄ったりの期待値を算出することからこのような条件に陥るそうです。
以下、個々の論文について少し眺めてみましょう。全体的な総括は「Barren Plateau とは」にて既にまとめましたので、ここは全て飛ばしても問題ありません。
Qiskit textbook 或は論文 [2] では
ことが示唆されました。以下、“Barren Plateau” は長いので、しばしば “BP” と略します。
量子ビット数が少ない時や回路が浅い時は比較的訓練が進むと思います。一方、良さそうなアルゴリズムが開発された時、
が気になってきます。各論文ではこの観点で議論がなされているように思われます。
論文 [3] では以下が示されました。
ということでした。全ての量子ビットを一度に測定しないとならない observable のケースでは回路深度によらず BP が起こり得るということです。 つまり、observable としては、部分的な量子ビットの測定結果の合算でコスト関数を定義できるローカルなもの を設計することが重要になります。量子化学計算では、ハミルトニアンを量子回路に実装する際に、Jordan-Wigner 変換と Bravyi-Kitaev 変換が考えられますが、後者のほうがローカルな Pauli 項を導きやすいので好ましいということのようです。
余談ですが、Ansatz には量子論に基づくものと、量子ハードウェアにとって都合の良いものがあります。後者は Hardware-efficient ansatz (HEA) と呼ばれますが、これについては論文 [14] によると、量子化学計算のような VQA では HEA の使用はデメリットのほうが多いようです。量子論に基づいて適切な ansatz を用意することが容易なのでそちらのほうが好ましいようです。
さて、ここまでで BP の厄介さが見えていますがここまでは理論的な BP です。論文 [6] によると、NISQ ならではのノイズによる BP もあるということです。
さて、では BP の回避策はあるのでしょうか?これについてはざっくりと以下のようになるようです:
もし Barren Plateau が起こるケースに該当すると、それでも訓練を進めようとすると、計算の精度を高める必要があります。これには回路をスケールした時に、BP を起こすパラメータの個数について指数関数的に増大するショット数を要求するようです。このことは古典計算に対する量子計算の優位性を否定することになるので意味がありません。論文 [7] の言葉を借りると
ということになるようです。論文 [7] では、勾配降下法は 1 階の微分だけを使っているが、高階の微分を使うと状況が打開できるのでは?という発想で研究がされました。しかし、
ということが分かりました。
違う観点では「勾配降下法を使わなければ何とかなるのでは?」という考え方もあります。これは論文 [10] で研究されました。ところが、同論文の手結果として
ということが分かりました。論文 [5] によると「パラメータをランダムに初期化したような深いパラメータ付き回路の場合、似たり寄ったりの期待値を算出する」ということでしたので、微分計算に必要な無限小の近傍だけでなく、少々離れたところでもコスト関数の差が極めて小さい、つまり台地上の形状が思った以上に広く広がっているのかもしれません。
ここまで見てきますと、量子計算で発生する Barren Plateau と古典機械学習で見られる勾配消失の現象について関係性が気になってくるかもしれません。同時にこれまで見た論文が示唆しているように、量子計算のそれは古典機械学習のそれと少し異なるのでは?という感触も得ているかもしれません。これについて研究したものが論文 [13] になります。同論文の内容を要約すると (ここでは量子ビット数の観点ですが) 以下のようになります。
以上、簡単に論文の内容を見てきました。以下では簡単な実装を通じて肌感触で Barren Plateau を見てみたいと思います。
blueqat SDK + cuStateVec で検証を行います。Ansatz の設計としては適当な内容になりますが、Barren Plateau を起こしやすそうなグローバル (非局所的) なものを用意し、回路の深さは一定として量子ビットをスケールします。初期値にも相関関係を持たせずにランダムとします。
3 量子ビットのケースで変分回路を見てみます。
「回転ゲートの適用 + CNOT での隣接量子ビット同士の巻き込み」を 1 ブロックとして、ブロックを 2 つ並べたものを としています。ハミルトニアンは として、変分回路は としています。Barren Plateau を見てみたいので、意図的に良くなさそうな回路にしています。
量子ビットのケースでは深さを保ったままで量子ビット数を増やしていきます。論文の内容によるとこのようなケースでも勾配は指数関数的に抑制されると考えられます。
ハミルトニアン はテンソル積を計算することで対角行列であることが分かり、対角成分には と しか出ません。従って固有値も と です。今回は期待値の最小化を行うので、最適化が進むと に向かって期待値は減少していくことになります。
blueqatSDKからcuStateVecを実行 を参考に、cuStateVec
バックエンドを追加する。或は既に用意したもの (下記参照) を使います。後者は前者をベースにしていますが、対応ゲートを多少増やしています。但し、今回の実験では拡張した部分は利用しません。
以下では matplotlib
をわざとダウングレードしていますが、Colab 上で新しい matplotlib の使用で支障が出る部分があったので、今回はダウングレードして対応したためです。
Copy %%bash pip install git+https://github.com/derwind/Blueqat.git@cuquantum-0.1 pip install -U pip cuquantum-python cupy-cuda11x pip install matplotlib==3.2.2
モジュールのインポート、ansatz の定義等々を行います。
Copy # Ansatz、ハミルトニアン、ハミルトニアンの期待値を得る変分回路を定義 def make_circuit(n_qubits, reps, thetas: List[float]=[]): ansatz = Circuit() for r in range(reps): for i in range(n_qubits): ansatz.rx(thetas[n_qubits*r+i])[i] for i in range(n_qubits-1): ansatz.cx[i, i+1] if n_qubits > 1: ansatz.cx[n_qubits-1, 0] hamiltonian = Circuit(n_qubits).z[:] return ansatz + hamiltonian + ansatz.dagger() # 量子回路を実行して期待値を計算 def get_expectation_value(c: Circuit): sv = c.run(backend='cusv') return float(sv[0].real) # 位相を [0, 2π] に入るように正規化 def normalize_phase(phase): return ((phase/(2*np.pi))%1) * 2*np.pi # 勾配降下法を実行してパラメータを更新。 # 勾配計算にはパラメータシフト則を使用。 def update_thetas(n_qubits, reps, thetas: List[float], lr=0.01): new_thetas = thetas[:] grads = [0]*len(thetas) # parameter shift rule for i, theta in enumerate(thetas): theta2 = thetas[:] theta2[i] = normalize_phase(theta + np.pi/2) ev2 = get_expectation_value(make_circuit(n_qubits, reps, theta2)) theta3 = thetas[:] theta3[i] = normalize_phase(theta - np.pi/2) ev3 = get_expectation_value(make_circuit(n_qubits, reps, theta3)) grad = ev2 - ev3 new_thetas[i] = theta - lr * grad grads[i] = grad return new_thetas, grads def calc_n_thetas(n_qubits, reps): return n_qubits * reps def make_initial_phases(n_qubits, reps): n_thetas = calc_n_thetas(n_qubits, reps) return np.array([random.random()*2*np.pi for _ in range(n_thetas)]) # 実験を実行 def do_experiment(n_qubits, reps=2, n_epoch=300) -> Tuple[List[float],list]: init_thetas = make_initial_phases(n_qubits, reps) expactation_values = [] grads_list = [] # grads of each epoch thetas = init_thetas[:] for epoch in range(n_epoch): c = make_circuit(n_qubits, reps, thetas) ev = get_expectation_value(c) expactation_values.append(ev) thetas, grads = update_thetas(n_qubits, reps, thetas) grads_list.append(grads) return expactation_values, grads_list
後で勾配の絶対値について平均と標準偏差を見たいので、結果を格納する辞書を用意します。各 ごとの偏導関数 の絶対値を求め、エポックごとに平均と分散を求める形にします。これを訓練序盤の 10 エポックについて計算し、量子ビットごとの傾向をプロットしたいと思います。
Copy n_qubits2grads = {}
それでは、1 量子ビットの場合を見てみましょう。
Copy n_qubits = 1 expactation_values, _ = do_experiment(n_qubits)
わりとすぐに に収束しました。次に 2 量子ビットの場合を見てみましょう。
Copy n_qubits = 2 expactation_values, _ = do_experiment(n_qubits)
初期値がランダムなので多少揺らぎますが最終的には に収束していっています。少しとばして 8 量子ビットの場合を見てみましょう。実行には GPU にもよりますが 10 分くらいかかります。
Copy n_qubits = 8 expactation_values, _ = do_experiment(n_qubits)
大分序盤に平坦な台地的な形状が出てきましたが、一応 に収束したようです。
以下のようなコードで 1 量子ビットのケースから 25 量子ビットのケースまで訓練序盤の 10 エポック分の勾配の絶対値の平均と標準偏差について集めてみます。実行は恐らく 1 時間くらいかかります。
GPU メモリ次第ですが、Colab 上であれば 28 量子ビットくらいまではいけると思いますが、余裕を見て 25 量子ビットまでにしています。
Copy for n_qubits in range(1, 25+1): expactation_values, grads_list = do_experiment(n_qubits, n_epoch=10) n_qubits2grads[n_qubits] = grads_list[:10]
グラフを可視化します。
Copy def plot_graph(n_qubits2grads, yscale_value='linear', show_errorbar=True): n_qubits_range = range(1, len(n_qubits2grads)+1) mean_abs_grads = [] std_abs_grads = [] for n_qubits, grads_list in sorted(n_qubits2grads.items(), key=lambda k_v: k_v[0]): abs_grads = [] for grads in grads_list: for grad in grads: abs_grads.append(abs(grad)) mean_abs_grads.append(np.mean(abs_grads)) std_abs_grads.append(np.std(abs_grads)) fig, ax = plt.subplots() yerr = std_abs_grads if show_errorbar else None ax.errorbar(x=n_qubits_range, y=mean_abs_grads, yerr=yerr, fmt='-o', color='b') ax.set_xlabel('num of qubits') ax.set_ylabel('mean abs grads') ax.set_title('mean abs grads per each epoch') ax.set_yscale(yscale_value) plt.grid() plt.show() display(plot_graph(n_qubits2grads, yscale_value='linear'))
リニアな可視化では勾配はどんどん減少して、その分散もほとんどなくなっていっている様子が見られます。より詳細に見るために対数グラフで見てみましょう。
Copy display(plot_graph(n_qubits2grads, yscale_value='log', show_errorbar=False))
対数グラフの中で概ね線形に減少していっています。つまり、 なので、両辺の指数をとって と指数関数的に勾配が抑制されていることが分かります。
Barren Plateau に関する簡単な論文のサーベイと検証を行ってみました。検証については本当に妥当か?と問われると疑問はありますが、雰囲気として量子ビット数が増えることで勾配降下法がかなり厳しいことになることが見えたように思います。しかも、理論的な勾配値をパラメータシフト則で求める GPU シミュレーションですので、ノイズのない指数関数的精度の勾配計算に相当するにも関わらず、です。
この方面が今後どのようになっていくのかは分かりませんが、引き続き動向を見ていきたいと思います。
© 2024, blueqat Inc. All rights reserved