こんにちは!キャスレーコンサルティングの松永です。
前回第1回で、環境構築から量子計算モジュールBlueqatの概要までを勉強しました。今回はBlueqatを使って、以下の順番で内容を見ていきます。

基本的な量子ゲート演算の基本
VQEで実際に解いてみる
機械学習の将来の展望

この記事の方針の確認

 量子コンピュータの発展は、第1回でざっと見てきましたが、実機ができる前に理論ができています。「暗号解読論」や、原子核の中身の粒子の振る舞いをシミュレーションするのに使われる「量子科学演算」、ショアのアルゴリズムやドイチェ・ジョザのアルゴリズムなどはこちらに当たります。しかし現在は、エラーが訂正できないよく間違える50量子ゲートほどの計算ができるだけです。
⇒①で、基本的な量子ゲート演算を学びます。

 上記より、少ない量子ゲートを既存コンピュータと組み合わせて使う変分回路VQE(Variational Quantum Eigensolver:変分量子固有値ソルバー)やQAOA(Quantum Approximate Optimazation Algorithm:量子近似最適化アルゴリズム)という方式が2015年頃より使われています。VQEは、量子ゲート計算に使います。QAOAは、量子断熱計算をモデルとした量子アニーリング法で使うことになります。量子アニーリング法で計算を行うQAOAは、量子ゲート方式で計算を行うVQEの発展として計算できるため、今回はVQEを実施します。
⇒②で、こちらを学びます。ここでは、現実の問題を解くことができるので、将来、最適化問題につながる計算の基礎をやってみます。

近年出てきているのが、機械学習の将来への展望です。こちらも第1回で少しお話しましが、今回は現在の実用化の方向性を再確認しておきます。
⇒③でこちらを紹介します。

基本的な量子ゲート演算の基本

ざっと理論について

Blueqatの良いところは、これまで一部の専門家のものであった量子計算が、一般のSEが見慣れたツールを使うことで、身近な存在になったことだと思います。そのため、ツール、具体的にはPythonでのコードを実際に見ることが重要と思います。数式による証明や物理の根拠の説明は単純化して進めます。
 ただし、注意が必要な点があります。Blueqatには、コードを書く人なら見慣れた記述が多数出てきますが、考え方がかなり違うため注意深くコードを見る必要があります。

事前にブロッホ球を見ておきます。
今すぐに意味が分からないという場合は、とりあえずざっと見て後で見直すとわかります。

Blueqatのコードの記述

量子計算モジュールは、IBM製でもgoogle製でもBlueqatでも記述はだいたい同じです。必ず、初期化、ゲート操作、測定・実行を繰り返して実行します(ゲートというのは、1qubitの状態を保存する入れ物です。これを単位として操作します。0、1だけでなく場合によって0、1が同時に入ります)。

初期化と測定は単純な記述の為、ゲート操作がコードを書く中心になります。
初期化と測定だけの場合、以下です。

from blueqat import Circuit

result = Circuit().m[:].run(shots=1)

print (result)

これに実際にはゲート操作を入れます。以下です。
ここで新たにゲート操作である.x[0]を入れている点が重要です。

from blueqat import Circuit

result = Circuit().x[0].m[:].run(shots=1)

print (result)

Circuit()初期化
x[0]ゲート操作
m[:].run(shots=1)測定・実行

です。[:]は、pythonの配列のsliceでの”全部”の記法です。全部のゲートを最後に測定し、.runで実行します。shotsは、計算の繰り返し回数です。

なお、from blueqat import Circuitprint (result)は、これ以下では記述を省略します。また前回のブログにある通り、Blueqatのインスールは事前にしておいてください。

Circuit() で初期化すると、1つ以上の量子ゲートを保持可能なCircuitができます。”以上”というのは、とりあえず初期化で1つ作っておき、後で追加できるからです。
1つ目の量子ゲートをこのブログでは1番目と呼びます。配列と同じく、コード上では0と表記しますので注意してください。

Circuit().x[0]
Circuit(1)

上記はどちらも、1つの量子ゲートを扱う記述です。
上は、量子ゲート数を”以上”と指定し、.x[0]で量子ゲート1番目を追加しました。.x[0]については次に説明します。
下は、量子ゲートを最初から1つ指定しています。()に数字を入れ、その数だけゲートができます。なお最初から指定していても後で追加できるようです。

結果は、

Circuit(1).m[:].run(shots=1)
⇒Counter({'0': 1})

Circuit().x[0].m[:].run(shots=1)
⇒Counter({'1': 1})

となります。以下で説明します。

基本的な量子ゲート操作を身に着ける

単純なゲート操作

まず、準備運動として、単純なゲート操作を見ていきます。1量子ゲートの状態を反転させます。
先のコードのゲート操作部分を見直します。

Circuit().x[0].m[:].run(shots=1)

図で見ることもあるので、その場合は以下のようになります。

|0> -X-[m]

図は、0番目の量子ゲートをXゲートと指定し、測定したという意味です。
x[0]は、Xゲートといいます。Xゲートは、指定した量子ゲートの値を反転させます。Circuit()で初期化する1番目のゲートは0から必ず始まります。先のブロッホ球の図を見ると、0の反転、つまり180度逆は、1の値をとることになります。

これにより、上記のコードの実行結果は、

Counter({'1': 1})

となります。この見方は左の’1’が出た値、右の1がその出力回数を示します。
Xゲートは、固定回転ゲートと呼びます。
同じグループに、I,X,Y,Z,H,S,sdg,T,tdg ゲートがあります。
これらは、1つの量子ゲートの現在の状態を、ブロッホ球の固定角度分だけ値を変更します。今はそんなものがあるということを知っていればOKです。
以下でその一部を見ていきます。

アダマールゲートで量子重ね合わせ

前回にも説明したアダマールゲート、つまりHゲートについてです。これは0、1をとる古典コンピュータと大きな違いをもたらします。
ブロッホ球では、X軸とZ軸の間45度の軸方向に180度で回転します。コードを使う上では、Hゲートを使うと、0、1のどちらかの確率的な結果が出ると理解できます。

Circuit().h[0].m[:].run(shots=1)

を何度も実行すると、

Counter({'1': 1}) //又は
Counter({'0': 1})

のどちらかの値を出力します。
アダマールゲートを使う、この状態を量子重ね合わせといいます。


補足:コーディングをするときに関数という用語が出てきます。
OOP(object-oriented programming)にも明確な数学的理論があります。しかしSEは普段それらを意識していません。
同じくここでは、そう呼ぶコーディングがとりあえずあると思っておけばいいように思います。

2つの量子ゲートの基本

ここまでは1つの量子ゲートを扱ってきましたが、ここでは2つの量子ゲートを扱います。
先ほどの1量子ゲートを扱う場合、コードと結果は以下のようになりました。

Circuit(1).m[:].run(shots=1)

結果は、

Counter({'0': 1})

図は、

|0> -[m]

となります。次にこれを2量子ビットで扱います。

Circuit(2).m[:].run(shots=1)

結果は、

Counter({'00': 1})

“00”となったのがポイントで、ビットの羅列をしています。図は、

|0> -[m]
|1> -[m]

となります。

2つの量子ゲートでのHゲート

この2つの量子ゲートを使う時、1番目と2番目の量子ゲートにそれぞれHゲートを当てるとどう動くのでしょうか?Hゲートは実行ごとに値が変わり、何度も実行するのは手間なので、shotの回数を増やしてみます。

Circuit(2).h[0].h[1].m[:].run(shots=100) 
//Pythonのsliceの表記の違いで以下でもいいです

Circuit(2).h[0,1].m[:].run(shots=100)    
//上記でもOK
 
Circuit(2).h[:].m[:].run(shots=100)       
//これは前回、CUDAのベンチマークを取得した場合の書き方です。


補足:あくまでPython表記上の問題ですが補足です。Circuit()での初期化時に、
result = Circuit().h[0].h[1].m[:].run(shots=100) は、OKです。
初期化で指定しない量子ゲートを自動で作ってくれます。しかし、
result = Circuit().h[:].m[:].run(shots=100) は、値を返しません。
未指定のゲートを全部指定するという指定の仕方の為です。

結果は、

Counter({'11': 30, '10': 25, '01': 23, '00': 22})

図は、

|0> -H-[m]
|1> -H-[m]

となります。

制御ゲート

制御ゲートには、CX,CY,CZ ゲートがあります。CはおそらくControlのCです。
制御ゲートは、コントロールビットと、ターゲットビットの組み合わせです。コントロールビットが1の時に、ターゲットビットの変換を行います。コントロールの結果、先ほど見た固定回転ゲートのX、Y、Zの軸の変換を行います。

例えば、cx[0,1] という記述で説明します。

[0,1]の0の部分がコントロールビットで、これまで見てきたのと同じように、コントロール対象の何番目のゲートかを指定します。
ここではコントロールビットが0を指定しています。つまり、1番目のゲートを指定しています。

cx[0,1]の1の部分がターゲットビットで、同じく、コントロール対象の何番目のゲートかを指定します。ここでは、2番目のゲートを指定しています。
コントロールビットが1であるときに、固定回転ゲートのXゲートを実行します。実際に行います。

Circuit().cx[0,1].m[:].run(shots=1)

結果は、

Counter({'00': 1})

コントロールビットで1番目の量子ゲートを指定しましたが、Circuit()で初期化しただけで1番目の量子ゲートは0のままです。そのため、ターゲットビットは反応せず、Xの反転は生じず0を同じく返しました。

|0> -*-[m]
|1> -X-[m]

図は、*がコントロールビットを示し、1番目の量子ゲートが1だったら2番目の量子ゲートにXを当てることを意味します。

それでは次に、1番目の量子ゲートをあらかじめ1に変換して、実行してみます。

Circuit().x[0].cx[0,1].m[:].run(shots=1)

結果は、

Counter({'11': 1})

今度は、1番目の量子ゲートをXゲートで事前に1にし、コントロールビットで1番目の量子ゲートを指定したため、ターゲットビットが反応し、2番目の量子ゲートでXの反転が生じ、1を同じく返しました。
図は以下です。

|0> -X-*-[m]
|1> ---X-[m]

コントロールビットやターゲットビットを他の値にするとどうなるのか、いくつか試します。

量子ゲートを増やす(1)
Circuit().cx[0,2].m[:].run(shots=1)

結果は、

Counter({'000': 1})

3つの量子ゲートを使いましたが、1番目を指定したコントロールビットが0なので、3番目を指定したターゲットビットで何も起こりません。

|0> -*-[m]
|1> -|-[m]
|2> -X-[m]

図の|は、電車の線路にかかる歩道橋のように、2番目の量子ゲートをまたいでいます。1番目の量子ゲートをコントロールビットにし、3番目の量子ゲートをターゲットビットにしていることを意味しています。

量子ゲートを増やす(2)
Circuit().x[0].cx[0,2].m[:].run(shots=1)

結果は、

Counter({'101': 1})

3つの量子ゲートを使い、1番目を指定したコントロールビットをXゲートで1にしているので、
3番目を指定したターゲットビットも1になります。

|0> -X-*-[m]
|1> ---|-[m]
|2> ---X-[m]
ターゲットビットとコントロールビットを入れ替える
Circuit().x[2].cx[2,1].m[:].run(shots=1)

結果は、

Counter({'011': 1})

3つの量子ゲートを使い、3番目を指定したコントロールビットをXゲートで1にしているので、2番目を指定したターゲットビットも1になります。

|0> -----[m]
|1> ---X-[m]
|2> -X-*-[m]

この制御ゲートは、先ほど出てきたHゲートと併せて使うことがあります。この場合のケースを次で見ていきます。

量子もつれの再現

量子もつれは、2つの量子重ね合わせ状態にあるHゲートの答えを絞るテクニックによく使われます。答えを絞らないと答えの洗い出しに時間がかかります。その手順は以下です。ここまでの内容がわかっていると、コーディングを理解するのは難しくありません。

Circuit().h[0].cx[0,1].m[:].run(shots=100)

結果は、

Counter({'11': 60, '00': 40})

Hゲートで確率的な答えが出ますが、CXゲートで、1番目のゲートが1だった時、2番目のゲートを1にしています。

|0> -H-*-[m]
|1> ---X-[m]

これが、量子もつれです。これを答えを減らすテクニックとして使います。これを使えば量子ビットやHゲートが増えていっても答えを絞れます。

トフォリゲート

では、制御ゲートのコントロールゲートの条件が複数ある時、どうなるのでしょうか?
先ほどのcxゲートを以下のように書くことができますね。
cx[ControlBit,TargetBit]

トフォリゲートの場合、3つの量子ゲートで再現します。コントロールビットが1つ新しく加わり、2つのコントロールビットが1になれば、ターゲットビットが1になります。
ccx[ControlBit,ControlBit,TargetBit]

理屈は非常に簡単で、種類はCCX CCY CCZと書いていきます。X、Y、Zはこれまでと同じくブロッホ球の回転軸周りを意味しています。ここではCCXを使います。

Circuit().x[0].x[1].ccx[0,1,2].m[:].run(shots=100)

結果は、

Counter({'111': 100})

図は、

|0> -X-*-[m]
|1> -X-*-[m]
|2> ---X-[m]

トフォリゲートは、コーディング上のゲートで、内部的には1量子ゲートと2量子ゲートの組み合わせで動いているそうです。

SWAPゲート

SWAPゲートは、量子ビットの状態の順番を変更します。

Circuit(2).x[0].cx[0,1].cx[1,0].cx[0,1].m[:].run(shots=1)

若干複雑なので解説すると、最初に1番目の量子ゲートを1に回転しています。この時点で、結果はCounter({'10': 1})となっています。1番目のコントロールビットを監視するCXゲートが1番目の量子ゲートの回転を検知します。この時点で、結果はCounter({'11': 1})となっているでしょう。その時、cx[1,0]を実行します。すると、2番目の量子ゲートが1であるため、コントロールビットである1番目の量子ゲートが反転します。その時点で、Counter({'01': 1})となり、Counter({'10': 1})が反転しています。最後のCXゲートはとりあえずおまじないだと考えます。
結果は、

Counter({'01': 1})
|0> -X-*-X-*-[m]
|1> ---X-*-X-[m]

四則計算・具体的課題

ここから少し具体的な計算を行います。量子ゲート計算は、すべての計算で量子重ね合わせ量子もつれが生じるわけではありません。そのため、古典コンピュータの足し算等ができるはずです。ゲートの理解を深めるために、Blueqatにサンプルがありますが、ここでは拡張し3量子ビットによる2進数計算を行います。

0 + 0 + 0 ⇒ Counter({'00000': 1})
 (00000は、0と0を足し、2進数の00、つまり10進数の0が出力されるケース)
0 + 0 + 1 ⇒  Counter({'00101': 1})
 (00101は、0と1を足し、2進数の01、つまり10進数の1が出力されるケース)
0 + 1 + 0 ⇒  Counter({'01001': 1})
 (01001は、0と1を足し、2進数の01、つまり10進数の1が出力されるケース)
1 + 0 + 0 ⇒  Counter({'10001': 1})
 (10001は、1と0を足し、2進数の01、つまり10進数の1が出力されるケース)
0 + 1 + 1 ⇒  Counter({'01110': 1})
  ...
1 + 1 + 0 = 11010  
 (1110は、0と0を足し、2進数の10、つまり10進数の2が出力されるケース)
1 + 0 + 1 = 10111 
 ...
1 + 1 + 1 = 11111 
 ...

上記は正しいので、これが出力できれば3量子ビットによる2進数計算は正しいことになります。
ここでは、Circuit部分を分離して書く書き方も覚えます。

最初に、0 + 1 + 0 = 01001 を計算してみます。

adder = Circuit().ccx[0,1,3].cx[0,4].cx[1,4].cx[2,4]
result = (Circuit().x[1] + adder).m[:].run(shots=1)

書き方を見るとわかるのですが、足し算の式部分(今回は0 + 1)は、それぞれ1番目と2番目の量子ゲートが当たります。今回は最初に2番目の量子ゲートにXゲートを当て0を1に回転しています。なので、0と1が入ります。結果は以下になります。

Counter({'01001': 1})

adder部分が量子ゲート回路部分です。その部分を見ると、以下のようになっています。

|0> -*-*----- [m]
|1> -*-|-*--- [m]
|2> ---|-|-*- [m]
|3> -X-|-|-|- [m]
|4> ---X-X-X- [m]

引き続き行います。

#0+0
(Circuit() + adder).m[:].run(shots=100)
Counter({'00000': 100})

#1+0
(Circuit().x[0] + adder).m[:].run(shots=100)
Counter({'10001': 100})

#1+1
(Circuit().x[0,1] + adder).m[:].run(shots=100)
Counter({'11111': 100})

期待する結果が出力できました。これで一度、基本的な量子ゲート演算は終わりです。

VQEを実際に解いてみる

VQEを試す

 ここからは、上記の基本の知識を生かして、VQEで問題を解いていきます。VQEというのは、少ない量子ゲートの計算を行い、その結果を古典コンピュータに伝え、その結果から任意角回転ゲートの角度の変更を行って、同じ計算を何度も行うという動作をします。

 任意角回転ゲートとは何でしょうか。ここまでに固定回転ゲートが出てきました。例えば、Hゲートではブロッホ球上で、X軸とZ軸の間45度の軸方向に180度で回転しました。この回転ゲートの中に、任意の角度を回転できる任意角回転ゲートというグループがあります。具体的には、RX、RY、RZ、RXX、RYY、YZZゲートなどです。これらは、ゲートに対して45度や180度といった決まった数字ではなく任意の数字を与えることができます。

 VQEでは、長い量子ゲートの再現を目指して、この任意角に変更を加えてゆき、最終的にその計算が前提とする固有値ベクトルを求めていきます。その固有値ベクトルがVQEの答えになります。
 それでは以下でVQEを見ていくことにします。

VQEで計算

VQEでは与えられた行列(ハミルトニアン)の固有値の期待値を求めます。最適に落ち着く値を効率的に探すための量子回路をAnsatzと呼びます。現在、量子化学にはUCC Ansatzや組合せ最適化問題にはQAOA Ansatzなどの効率的な量子回路が見つかっているため、アプリケーションとして期待されています。Ansatzはある程度分野ごとにルールが決まっていて、ルールに沿って記述します。

 今回は、rx(a)[0].rz(b)[0]というaとbの2つの角度を使って1量子ビットの計算をしてみたいと思います。最後にVQEの計算と数値ライブラリのnumpyの計算結果を比較してみます。

import numpy as np
from blueqat import Circuit
from blueqat.pauli import X, Y, Z, I
from blueqat.vqe import AnsatzBase, Vqe

class OneQubitAnsatz(AnsatzBase):
    def __init__(self, hamiltonian):
        super().__init__(hamiltonian.to_expr(), 2)
        self.step = 1

    def get_circuit(self, params):
        a, b = params
        return Circuit().rx(a)[0].rz(b)[0]

# hamiltonian
h = 1.23 * I - 4.56 * X(0) + 2.45 * Y(0) + 2.34 * Z(0)

runner = Vqe(OneQubitAnsatz(h))
result = runner.run()

print('Result by VQE')
print(runner.ansatz.get_energy_sparse(result.circuit))

# This is for check
mat = h.to_matrix()
print('Result by numpy')
print(np.linalg.eigh(mat)[0][0])

出典:VQEのリンク先でサンプルを見てください

 こちらの結果は以下になります。
ポイントとなるのが、.rx(a)[0].rz(b)[0]hamiltonian です。
ここ以外の箇所は、どんな最適化計算でもだいたいコピペで書けます。
.rx(a)[0].rz(b)[0]は、先に任意角回転ゲートについて解説した中の、古典コンピュータ側が次第に固有値ベクトルを寄せていく部分です。

h = 1.23 * I - 4.56 * X(0) + 2.45 * Y(0) + 2.34 * Z(0)
runner = Vqe(OneQubitAnsatz(h))
の部分でハミルトニアンを式ごとに設定できれば、あとは、runner.ansatz.get_energy_sparse(result.circuit)の部分で、繋げて計算してくれます。結果は以下となります。
Result by VQE
-4.257563916842687
Result by numpy
-4.128741458775983

VQEとnumpyの計算結果がほぼ同じになることで、VQEが少ない量子ゲートでも正確に計算できることが証明されました。

機械学習の将来への展望

TensorFlow Quantum

前回のブログで書きましたが、機械学習の特定の領域については、量子コンピュータを用いることで計算速度が速くなることがわかっています。といっても現在実用化されている量子ゲート数ではあまり速度に差がないので、実務で使われるのはごく一部になると思います。

 機械学習で期待されているのは、分類を行う場合に使うデータ生成モデルです。これは、与えられたデータの中からルールに基づく新しいデータを生み出す方法です。機械学習が学習した結果を出力し、そのデータをもう一度AIに評価させることで、2つのAIがより精度の高い学習結果を出していくモデルがあります。
 敵対的生成ネットワーク(Generative Adversarial Networks GAN)という技術です。
 機械学習の中に量子ゲート計算が含まれるようになると、かなり近い将来、量子コンピューターのアプリケーションとして、このGANでの実用化が起こると思います。GANは、日本の機械学習案件でまだ少ないですが、将来は研究分野を離れ一部は実用に取り入れられると思います。ただし、繰り返しになりますが、実際の量子ゲートのアプリケーションが機械学習に取り入れられることになっても、速度が出るのはまだ先の話です。

 具体的にどうするかというと、google X から発表されているTensorFlow Quantumを使います。こちらです。現在機械学習は、高校数学の知識があれば、オンラインで基本的な理屈を数か月勉強することで、機械学習のモデルを自分で作れるくらい簡単になっています。それはGPUが10~20万円程度と比較的安価になったのと、ライブラリが充実したためでもあります。Tensor-FlowやPyTorchを使えば、教科書通りのモデル生成は可能となります。同じことがおそらく、TensorFlow Quantumでも多少なりと起こるように思います。

Blueqat + PyTorch

 Blueqatは、現在PyTorchに適合する形で機械学習と量子ゲート計算を組み合わせる方針のようです。Tensor-Flowで起こっていることが、PyTorchでも起こるでしょう。Blueqatはすでに、プラグインを使ってD-waveやAmazonやgoogleの量子クラウドに接続できます。TensorFlow Quantumと同じく、速度は変わりませんが量子クラウドを組み込んだコードを描くことはできるようになると思います。

 具体的にBlueqatがどのようにするかというと以下のような方針のようです。一見するとNISQのVQEに似たハイブリッド計算のようですが、ハイブリッドではあるもののVQEとは違うようです。損失関数による微分の最適解を手短に探す動作などは、既存の機械学習を用いて行います。期待値計算はこれまで古典コンピュータ側で行っていたものを、量子ゲート側で行うようです。


出典:量子アルゴリズムや量子機械学習全般について今の流れを再確認

TensorNetworkフレームワーク

 Blueqatの話題から離れますが、機械学習が高速化する手段として、googleが近年出したTensorNetworkがあります。(MDRでも研究が進められています。)TensorNetworkを用いると、機械学習の行列計算が高速化します。こちらは古典コンピュータだけでできます。機械学習の行列計算を行うネットワーク部分は、現在の多くのモデルで最適化は行われていません。TensorNetworkは、個々の行列計算を1つのネットワークとみなし、それを分配したり結合したりすることで、そのモデル用にネットワークを最適化して設計することができます。

 個人的にTensorNetworkに注目しているのは、単に機械学習を高速化するだけでなく、さらなる可能性があると考えているためです。物理学では以前から、TensorNetworkは物性研究などではよく知られていました。スパコンなどを使ったシミュレーションなどにも使われていたのかもしれません。近年、素粒子などの理論物理学と物性研究の間を埋める手法として、量子情報理論が研究されています。この分野により進んだ物理学から、今思われているより価値があることを示唆する研究成果が上がってきています。

 一般人でも使うことのできるポピュラーなツールとして、TensorNetworkなどの行列計算ツールが使える可能性があります。

最後に

Blueqatのお陰で、量子計算の概要がなんとなくわかり、どのような仕組みでどのようにすれば速い計算が行えるのか少しづつわかってきました。実用化にはまだ時間がありそうですが、実用化された時点でのアプリケーションは既にできているので、それまでに実践的な準備をしておきたいところです。
最後までお読みいただきましてありがとうございました。

Daichi Matsunaga
CSVIT事業部 Daichi Matsunaga
お役に立てれば幸いです