はじめに
今後量子機械学習が流行ると思います。量子ゲートのVQEを拡張する形でアプリケーションが加速すると思います。
勾配計算
量子ゲートの変分計算を最適化する場合、量子回路の中にパラメータ回路があるのですが、それは今までブラックボックス最適や局所最適化アルゴリズムを使う際にあまり深くは考えていませんでした。
今回、数値微分を使って量子回路の勾配計算するものを手作業で作ってみましたが、pytorchなど深層学習のツールを取り込みながら量子回路をいじるのが増えそうです。そんなか、pytorchの特徴であるテンソルの自動微分を量子回路に適用してみました。
量子回路
量子回路は通常の量子コンピュータだと、Xゲートとか、Hゲートとかを想像しますが、NISQと呼ばれる近年の量子コンピュータでは変分回路と呼ばれる角度パラメータ付きの量子回路を角度で最適化するのが主流です。
今回はこの角度パラメータを最適化するのに、pytorchを使ってみました。
勾配計算
数値微分を使って、最終的に求めた値に対してパラメータで偏微分を行い、その偏微分の勾配を適用して最適化をします。最適化手法は様々ありますので、それは別の機会にみます。
pytorchを使った自動微分概要
勾配計算の概要は、
1、量子回路のパラメータを把握する
2、パラメータ化量子回路から測定値の期待値を求める
3、測定値の期待値とターゲット値から損失関数を利用する
4、損失関数が小さくなるように数値微分と勾配法を使って更新
となります。パラメータから最終の損失関数に至るまでは多くのステップがありますが、それぞれの関数の微分係数を持つことで断続的に勾配の値を持つことができます。
pytorchにおけるtensor
量子回路のゲート操作をテンソル操作として認識することによって自動微分が可能になります。numpyやcuda tensorとも親和性が高いのでpytorchは導入がしやすいです。
状態ベクトルは、通常のベクトルとしてテンソルで扱えます。
x = torch.tensor([1.,0.])
また、各ゲートも行列なのでテンソルとして自然に扱えます。
測定は状態ベクトルから期待値を得られます。
損失関数は期待値から計算できます。
それらの操作を断続的に扱うことでpytorchで量子回路を扱うことができます。
backward
損失関数は((期待値)-(ターゲット))**2と、なるのでそれを利用して、
loss.backward()
と勾配を計算できます。実際に勾配を確認するときには、
a_grad = angle.grad.item()
これを利用して、最適化を進めることができます。
損失関数を使わず、変分原理とpytorchで計算
従来のVQEやQAOAのように、変分原理を使って計算をすることもできます。その際には期待値をそのまま使います。-1に最適化されます。
!pip install torch
import matplotlib.pyplot as plt
import torch.optim as optim
import torch
import numpy as np
%matplotlib inline
x = torch.tensor([1., 0.])
a = torch.tensor([0.2],requires_grad=True)
arr = []
#the first variable is list of paramters.
op = optim.Adam([a],lr=0.05)
for _ in range(100):
y = [[torch.cos(a/2),-torch.sin(a/2)],[torch.sin(a/2),torch.cos(a/2)]]
z = [x[0]*y[0][0]+x[1]*y[0][1],x[0]*y[1][0]+x[1]*y[1][1]]
expt = torch.abs(z[0])**2 - torch.abs(z[1])**2
arr.append(expt)
op.zero_grad()
expt.backward()
op.step()
plt.plot(arr)
plt.show()
<Figure size 432x288 with 1 Axes>
pytorchの簡単使い方
今後使う予定もあるので、まとめます。
初心者です。めちゃ簡単にやります。
量子コンピュータを想定してある角度を最適化するものをやってみたいので、とても簡単なネットワークを作りたいです。
input1 / linear10 / output3
でやってみたいと思います。
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline
#モデル適当に
model = nn.Sequential()
model.add_module('fc1', nn.Linear(1, 12))
model.add_module('relu', nn.ReLU())
model.add_module('fc2', nn.Linear(12, 2))
#最適化選ぶ
optimizer = optim.Adam(model.parameters(), lr=0.1)
#損失関数選ぶ
lossf = nn.MSELoss()
#入力値とターゲット
input = torch.Tensor([[0],[1]])
target = torch.Tensor([[1,2],[4,5]])
model.train()
arr = []
#トレーニング
for _ in range(100):
loss = lossf(model(input), target)
arr.append(loss)
optimizer.zero_grad()
loss.backward()
optimizer.step()
plt.plot(arr)
plt.show()
<Figure size 432x288 with 1 Axes>
とりあえず収束しました。
optuna
VQEを使って簡単な回路の最適化をしてみますが、なるべく早く計算を終わらせたいので、pytorchの勾配計算をoptunaを使って最適化します。また、回路自体はNISQ計算のVQEを使います。
ハミルトニアン
ハミルトニアンは、
を想定します。最小値は-2になりそうです。pytorchを使って計算してみます。
量子回路
量子回路はansatzとして、
Circuit().ry(a)[0].rz(b)[0].cx[0,1]
を使いたいと思います。
##VQE
普通にblueqatでやってみると、適当にansatzを作って期待値からハミルトニアンの最小値の期待値は、
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().ry(a)[0].rz(b)[0].cx[0,1]
h = -Z(0) - Z(0)*Z(1)
runner = Vqe(OneQubitAnsatz(h))
result = runner.run()
Result by VQE
-1.99999999994664
想定通りのだいたい-2程度になりました。
pytorchで実装
ちょっとコードが整理できてないのですが、
momentum SGDを使って、
こんな感じになりました。期待値は
-1.999962329864502
となりました。
optuna
続いてoptunaでハイパラ最適化して見ました。最適化のアルゴリズムを比較して見ました。
TRIAL_SIZE = 100
で行いました。結果は、
study.best_params
{'optimizer': 'Adam',
'weight_decay': 0.000456965918794761,
'adam_lr': 0.09922399869322862}
でしたので、
op = optim.Adam([a,b], lr=study.best_params['adam_lr'], weight_decay=study.best_params['weight_decay'])
を設定して、
となり、期待値は
-1.9999990463256836
となりました。今回は一定の精度を決めながらハイパラ最適化を行ったので、狙った通りの値が効率的に得られてとても良かったです。
Tensorflow
Tensorflow2のGradientTapeを使って同じことをしてみます。バックエンドはtensorflowのテンソル計算を量子コンピュータゲートシミュレータとして使っています。
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
def Ry(a):
return [[tf.math.cos(a/2),-tf.math.sin(a/2)],[tf.math.sin(a/2),tf.math.cos(a/2)]]
def exptZ(x,a):
z = tf.tensordot(x,Ry(a),1)
return tf.abs(z[0])**2 - tf.abs(z[1])**2
a = tf.Variable(.2)
x = tf.constant([1.,0.])
opt = tf.keras.optimizers.SGD(lr=.1)
arr= []
expt = lambda : exptZ(x,a)
for _ in range(100):
arr.append(expt().numpy())
opt.minimize(expt, var_list=[a])
plt.plot(arr)
plt.show()
print(arr[-1])
optimizerはSGDでlr=0.1と設定、
Ryゲートを変数パラメータとして、VQEを行いました。
損失関数は使わず、量子ビットのZ測定の期待値を最適化したため、最小値-1になっています。
-0.99999976
となりました。
今後
フロントエンドの量子回路でVQEではない、もうちょっと新しめのansatzも使いたいですね。