はじめに
今回はまだ回路が小さすぎるのですが、最適化計算を学ぶために、Stochastic Gradient Descent, Momentum SGD, Adagradを実装してみたいと思います。
参考
深層学習の最適化アルゴリズム
回転ゲートの数値微分
量子ゲートで回転ゲートというのがあります。xyz軸周りでの任意回転を行います。回転角によって、測定値の期待値が変わりますが、求めたいターゲットの値に対してどのように数値微分を使って更新ができるのかを考えてみたいと思います。
数値微分
関数の微小な変化を捉えて、その変化の度合いを数値化します。関数
となります。例題として
((x+h)*(x+h)-x*x)/h = (1.1*1.1-1*1)/0.1 = 2.100000000000002
のようになりました。
2.0100000000000007
のように、2に近づきました。
偏微分
複数の変数のうち一つの変数に対して微分をかけるのを偏微分と呼びます。例えば、xとyからなる関数f(x,y)があり、
とか、
となり、それぞれ、xについてとyについての微分に。xで偏微分する際にはyは定数として扱われます。
勾配
勾配は複数の偏微分をまとめてベクトル表記したもので、
勾配は一番低い点に向かってベクトルが向かうことになるので、勾配を小さくすることで、多変数の極小値や最小値を求めることができそうです。
これを利用して、少しずつ勾配を使って値を更新することで、求めたい極小値や最小値に向かって値を更新することができます。
更新は、
勾配法
上記の偏微分を使って、最小値・極小値を求めたいので、繰り返し値を更新して行きます。
SGD
深層学習などの学習ステップでは、データをランダムに選択し、それを利用して勾配更新を行うため、stochastic gradient descent, SGDというのが使われるようです。
日本語では、確率的勾配降下法です。元のデータに対して、勾配計算をしたベクトルを使って少しずつ更新をします。その際に訓練データの選択がミニバッチで確率的に選択するためにSGDとよく呼ばれるようです。
wikipedia
勾配計算
微分係数は傾きを表すので、極小値・最小値を求めるためには、現在の値から傾きを引くことで実現できます。
x2 = x1 - a*2
更新をすることで最小値に近づきそうです。
回転ゲート
量子回路でryゲートを見てます。
from blueqat import Circuit
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
#確率振幅の絶対値の2乗
def abs_sq(k):
return np.square(np.abs(k))
#ryゲートを適用した時の期待値
def expt(a):
res = Circuit().ry(a)[0].run()
expt = abs_sq(res[0])-abs_sq(res[1])
return expt
#微分係数
def dfx(a,h):
return (expt(a+h) - expt(a))/h
#勾配計算
def grad(a):
for i in range(10):
a.append(a[i] - dfx(a[i],h))
return a
#初期化
ainit = [np.random.rand()*2*np.pi]
a = ainit.copy()
h = 0.1
ra = grad(a)
print(ra[-1])
plt.plot(list(map(expt, ra)))
plt.show()
3.0915926535897933
<Figure size 432x288 with 1 Axes>
角度のパラメータは落ちきっていませんが、
3.0915926535897924
期待値はこんな感じで最適化されて行きました。
hを小さくしていくと誤差は小さくなります。
a = ainit.copy()
h=0.0001
ra = grad(a)
print(ra[-1])
plt.plot(list(map(expt, ra)))
plt.show()
3.1415426535901574
<Figure size 432x288 with 1 Axes>
3.141542653589349
数値微分でできた
当たり前ですが、簡単な回路だったので、簡単な数値微分で最適化できました。今回はZ測定の期待値が-1とわかっていたので簡単に行きました。
損失関数
量子ゲートの回転ゲートのパラメータを使って、測定値の期待値を勾配計算で最小化するのを進めました。前回は変分原理を利用した計算でしたが、今後の機械学習応用を考え、損失関数を導入してみます。
ry回転ゲートの数値微分更新
ざっくりコードを見てみます。今回は勾配計算にryゲートで回転した後に、Z測定の期待値とX測定の期待値の両方を見てみます。
from blueqat import Circuit
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
#確率振幅の絶対値の2乗
def abs_sq(k):
return np.square(np.abs(k))
#ryゲートを適用した時の期待値
def exptz(a):
res = Circuit().ry(a)[0].run()
expt = abs_sq(res[0])-abs_sq(res[1])
return expt
def exptx(a):
res = Circuit().ry(a)[0].h[0].run()
expt = abs_sq(res[0])-abs_sq(res[1])
return expt
#微分係数
def dfxz(a,h):
return (exptz(a+h) - exptz(a))/h
def dfxx(a,h):
return (exptx(a+h) - exptx(a))/h
#勾配計算
def gradz(a,h,e=1):
for i in range(1000):
a.append(a[i] - e*dfxz(a[i],h))
return a
def gradx(a,h,e=1):
for i in range(1000):
a.append(a[i] - e*dfxx(a[i],h))
return a
#初期化
ainit = [np.random.rand()*2*np.pi]
a = ainit.copy()
h = 0.001
e = 0.01
ra = gradz(a,h,e)
print(ra[-1])
plt.plot(list(map(exptz, ra)))
plt.show()
3.139674692143748
<Figure size 432x288 with 1 Axes>
今回は学習率を採用しました。最小値の-1に向かっているのがみれます。
a = ainit.copy()
ra = gradx(a,h,e)
print(ra[-1])
plt.plot(list(map(exptx,ra)))
plt.show()
-1.5711980401990788
<Figure size 432x288 with 1 Axes>
次はX測定、
きちんと最小化されて|->状態に行っているか確認します。
Circuit().ry(ra[-1])[0].run()
#array([ 0.70690838+0.j, -0.70730513-0.j])
array([ 0.70696474+0.j, -0.70724879+0.j])
だいたい良さそうです。
損失関数
次に変分原理での計算を損失関数に置き換えてみます。ターゲットは-1にします。
from blueqat import Circuit
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
#確率振幅の絶対値の2乗
def abs_sq(k):
return np.square(np.abs(k))
#ryゲートを適用した時の期待値
def expt(a):
res = Circuit().ry(a)[0].run()
return abs_sq(res[0])-abs_sq(res[1])
#損失関数
def loss(res,tar):
return np.square(res-tar)
#微分係数
def dfx(a,h):
return (loss(expt(a+h),-1) - loss(expt(a),-1))/h
# return (expt(a+h)-expt(a))/h
#勾配計算
def grad(a,h,e):
for i in range(1000):
a.append(a[i] - e*dfx(a[i],h))
return a
#初期化
ainit = [np.random.rand()*2*np.pi]
a = ainit.copy()
h = 0.001
e = 0.01
ra = grad(a,h,e)
print(ra[-1])
arr = [loss(expt(i),-1) for i in ra]
plt.plot(arr)
plt.show()
print(arr[-1])
2.9091867481319693
<Figure size 432x288 with 1 Axes>
0.0007227986442505645
結果が出ました。損失関数を使った収束は変分原理よりも遅い気がします。QAOAやVQEを使う場合には損失関数を使う必要はなさそうです。学習データに対応した機械学習などに使うのが良さそうです。
また、学習率など勾配計算の手法によっても計算精度は変わりそうでした。今後はより充実した勾配計算法を見ていくことで、更新速度の向上が期待できそうです。tensorflow quantumなどの既存ツールとの統合はその辺りのメリットが大きそうです。
SGD
確率的にデータ選ぶほどデータないんですが、gradient descentで更新をしたいと思います。
from blueqat import Circuit
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
def abs_sq(k):
return np.square(np.abs(k))
#expectation value
def expt(a):
res = Circuit().ry(a)[0].run()
return abs_sq(res[0])-abs_sq(res[1])
def loss(res,tgt):
return np.square(res-tgt)
#derivative
def dfx(a,h,tgt):
return (loss(expt(a+h),tgt) - loss(expt(a),tgt))/h
#(stochastic) gradient descent
def sgd(a,h,e,tgt):
for i in range(100):
a.append(a[i] - e*dfx(a[i],h,tgt))
return a
#initialization
ainit = [np.random.rand()*2*np.pi]
a = ainit.copy()
h = 0.001
e = 0.01
tgt = -1
result = sgd(a,h,e,tgt)
arr = [loss(expt(i),tgt) for i in result]
plt.plot(arr)
plt.show()
<Figure size 432x288 with 1 Axes>
パラメータ類は、数値微分にはh=0.001,学習率eには0.01,Z測定の期待値には-1を設定しました。ステップ数は100回計算してみました(前回は1000回)。下がりきってないですが、なんとなくいい感じです。
momentum SGD
慣性率を導入してみます。
#momentum sgd
def msgd(a,h,e,tgt,alpha):
p_delta = 0
for i in range(100):
update = -e*dfx(a[i],h,tgt) + alpha*p_delta
a.append(a[i] + update)
p_delta = update
return a
#initialization
a = ainit.copy()
h = 0.001
e = 0.01
alpha = 0.9
tgt = -1
result = msgd(a,h,e,tgt,alpha)
arr = [loss(expt(i),tgt) for i in result]
plt.plot(arr)
plt.show()
<Figure size 432x288 with 1 Axes>
パラメータは、alphaが加わり、0.9で計算しています。
効率は上がった気がします。
Adagrad
今回はここまでにしますが、adagradです。過去の勾配を記録して行き、学習率を調整します。
def adagrad(a,h,e,tgt,epsilon):
G = epsilon
for i in range(100):
g = dfx(a[i],h,tgt)
G += g*g
update = -e/np.sqrt(G)*g
a.append(a[i] + update)
return a
#initialization
a = ainit.copy()
h = 0.001
e = 0.1
epsilon = 1e-08
tgt = -1
result = adagrad(a,h,e,tgt,epsilon)
arr = [loss(expt(i),tgt) for i in result]
plt.plot(arr)
plt.show()
<Figure size 432x288 with 1 Axes>
momentum SGDからはalphaがなくなりました。初期の学習率を0.1にしています。epsilonは適度に小さい値を選びました。
学習率をちょっといじったのですが、いい感じです。
まとめ
よくわからず始めた最適化の試みも、損失関数の導入、学習率の導入、モーメンタムや学習率の減衰などいろんな工夫を導入することで、最適化が変化していくのがわかりました。
今後は量子ビットを増やしたり、いろいろやりたいのですが、とりあえずadamくらいまで最適化アルゴリズムを実装してみて様子を見てみたいと思います。