量子回路学習の実装
概要
量子コンピューティング楽しいですよね。量子回路学習(QCL)を実装してみたのでそちらを紹介します。
量子回路学習とは、普段は私たちが使っているようなコンピュータと量子コンピュータを組み合わせて機械学習に応用するものです。VQEやQAOAではエネルギー期待値を最小化するようにパラメータを調整していきますが、QCLではそれを拡張して損失関数を最小化するように調整を行います。
それでは早速やっていきましょう!
アルゴリズム
QCLはニューラルネットワークの学習と似ているところがあります。ニューラルネットワークの学習では、活性関数からの出力とウエイトを使ったモデルを用いて、損失関数を最小化することによって教師データの入出力関係を表現していきます。
QCLでは、パラメータを回転ゲートの回転角に対応させます。


教師データ
今回は簡単のために、単回帰を教師データとして扱います。正弦波に量子回路を使った学習でフィットできるか検証していきます。

損失関数
2乗誤差を使用します。
$${\sum_{i}(f(x) - y_i)^2}$$
パラメータの更新
損失関数を使って誤差を計算したのち、その誤差を最小にするパラメータを探していきます。今回は勾配法を使ってそれらを探ります。
勾配法とは
ある関数の最小値をとる変数を探す方法です。以下のような手順で探していきます。
ある関数f(x)のスタート地点aを決めます。
f(a)の傾きが負ならxを正の方向に、傾きが正ならxを負の方向に少しずらす。
ずらしたxの値をa'とすると、f(a')の傾きを求め、2と同様の操作をする
これを繰り返していき、傾きが0となるところを見つける。
ソースコード
早速コードを見ていきましょう。全体はこちらです。
import time
import copy
from blueqat import Circuit
from blueqat.pauli import Z
import numpy as np
import matplotlib.pyplot as plt
nqubit = 2
# 教師データ
## [x_min, x_max]のうち, ランダムにnum_x_train個の点をとって教師データとする.
x_min = - 1.; x_max = 1.;
num_x_train = 20
## 学習したい1変数関数 sin()
func_to_learn = lambda x: np.sin(x*np.pi)
## 乱数
random_seed = 0
np.random.seed(random_seed)
x_train = x_min + (x_max - x_min) * np.random.rand(num_x_train)
y_train = func_to_learn(x_train)
#ノイズを付加
mag_noise = 0.05
y_train = y_train + mag_noise * np.random.randn(num_x_train)
plt.scatter(x_train,y_train)
plt.savefig("out.png")
## 入力のエンコード
def c_in(x):
c = Circuit(nqubit)
c = c.rx(np.arcsin(x))[0].rx(np.arcsin(x))[1]
return c
## 変分回路
def u_theta(u,theta):
"""
u : Circuit
theta : float[7] rad
"""
u = u.ry(theta[0])[0].ry(theta[1])[1].rz(theta[5])[0].rz(theta[6])[1]
u = u.cx[0,1].rz(-2*theta[2])[1].cx[0,1]
u = u.ry(theta[3])[0].ry(theta[4])[1]
return u
def u_out(theta):
y = []
for x in x_train:
psi = c_in(x)
psi = u_theta(psi,theta)
e = expected_value(psi)
y.append(e)
return y
## ハミルトニアン
hamiltonian = 1*Z[0]+1*Z[1]
## 期待値
def expected_value(psi):
return psi.m[:].run( hamiltonian=hamiltonian)
## 損失関数
def calc_loss(y):
return ((y - y_train)**2).sum()
theta = [2.0 * np.pi * np.random.rand() for _ in range(7)]
y = u_out(theta)
plt.scatter(x_train,y)
plt.savefig("out2.png")
print(calc_loss(y))
#loss格納用のarray
loss1_arr = []
#学習率
e = 0.01
#微分
h = 0.01
# パラメータ数
param_length = len(theta)
loop = 30
for k in range(loop):
y = u_out(theta)
loss1 = calc_loss(y)
# 勾配法
loss_arr = np.zeros(param_length)
for i in range(param_length):
theta_tmp = copy.copy(theta)
theta_tmp[i] += h
y_tmp= u_out(theta_tmp)
loss_arr[i] = calc_loss(y_tmp)
theta -= (loss_arr - loss1)*e/h
print("epoch:", k)
print("loss:",loss1)
y = u_out(theta)
plt.scatter(x_train,y)
plt.savefig("out3.png")
細かく見ていきましょう。
インポート
import time
import copy
from blueqat import Circuit
from blueqat.pauli import Z
import numpy as np
import matplotlib.pyplot as plt
nqubit = 2
必要なライブラリをインポートします。今回はblueqatさんの量子コンピューターシミュレーターSDKを使用します。また2量子ビット回路で試してみます。
教師データの用意
# 教師データ
## [x_min, x_max]のうち, ランダムにnum_x_train個の点をとって教師データとする.
x_min = - 1.; x_max = 1.;
num_x_train = 20
## 学習したい1変数関数 sin()
func_to_learn = lambda x: np.sin(x*np.pi)
## 乱数
random_seed = 0
np.random.seed(random_seed)
x_train = x_min + (x_max - x_min) * np.random.rand(num_x_train)
y_train = func_to_learn(x_train)
#ノイズを付加
mag_noise = 0.05
y_train = y_train + mag_noise * np.random.randn(num_x_train)
plt.scatter(x_train,y_train)
plt.savefig("out.png")
正弦波にノイズを加えます。

入力のエンコード
## 入力のエンコード
def c_in(x):
c = Circuit(nqubit)
c = c.rx(np.arcsin(x))[0].rx(np.arcsin(x))[1]
return c
入力はそれぞれの量子ビットにRXゲートを作用させます。RXゲートの回転角に用意したxの値を入れていきます。
変分回路
## 変分回路
def u_theta(u,theta):
"""
u : Circuit
theta : float[7] rad
"""
u = u.ry(theta[0])[0].ry(theta[1])[1].rz(theta[5])[0].rz(theta[6])[1]
u = u.cx[0,1].rz(-2*theta[2])[1].cx[0,1]
u = u.ry(theta[3])[0].ry(theta[4])[1]
return u
パラメータをエンコードする回路です。こちらの角度を変えていくことで学習を進めていくことになります。今回は以下のような回路で組みました。

ハミルトニアンと期待値と損失関数
## ハミルトニアン
hamiltonian = 1*Z[0]+1*Z[1]
## 期待値
def expected_value(psi):
return psi.m[:].run( hamiltonian=hamiltonian)
## 損失関数
def calc_loss(y):
return ((y - y_train)**2).sum()
ハミルトニアンは測定する基底にすることが一般的で、今回もH=Zとしています。また期待値の計算はこのように便利に計算してくれます。期待値計算の細かいところは別途記事を書きます。とりあえず今回はこのように期待値が計算されるとだけわかれば大丈夫です。
損失関数では、教師データと回路から算出された期待値の差を計算します。
パラーメータの初期値
theta = [2.0 * np.pi * np.random.rand() for _ in range(7)]
y = u_out(theta)
plt.scatter(x_train,y)
plt.savefig("out2.png")
print(calc_loss(y))
まずは適当なランダムの値をパラメータに入れて、動かしてみます。

オレンジ色のドットが初期のパラメータからの出力です。勾配法によってパラメータを変えていくことによってこの値が教師データに近くなるはず。
勾配法と学習
#loss格納用のarray
loss1_arr = []
#学習率
e = 0.01
#微分
h = 0.01
# パラメータ数
param_length = len(theta)
loop = 30
for k in range(loop):
y = u_out(theta)
loss1 = calc_loss(y)
# 勾配法
loss_arr = np.zeros(param_length)
for i in range(param_length):
theta_tmp = copy.copy(theta)
theta_tmp[i] += h
y_tmp= u_out(theta_tmp)
loss_arr[i] = calc_loss(y_tmp)
theta -= (loss_arr - loss1)*e/h
print("epoch:", k)
print("loss:",loss1)
y = u_out(theta)
plt.scatter(x_train,y)
plt.savefig("out3.png")
勾配法では、それぞれのパラメータ(今回は7つの回転角)に対して少しずらす操作を行い、その結果損失関数が増加するか減少するかをみています。学習は30回でやってみました。
結果

緑色のドットが30回学習させた結果です。誤差はまだ大きそうですが、それなりにフィットしているように思います。やったー!