Quantum Kitchen Sinks

[1806.08321] Quantum Kitchen Sinks: An algorithm for machine learning on near-term quantum computers
が気になったので、そのメモ。

どんなもの?
Quantum Kitchen Sinks(QKS) は、一連の量子回路からランダムサンプリングし、各々の回路を用いて入力データを測定されたビット列へ非線形変換を実現するものである。このアルゴリズムによって量子回路のパラメータの最適化にかかるコストを排除することができる!!そのあと、連結された結果は、(量子ではない通常の)機械学習アルゴリズムを用いて処理する(量子回路の効果が分かるように、単純なものを用いる)。
f:id:puyokw:20180626230407p:plain:w400

技術や手法のキモはどこ?
Quantum Kitchen Sinks(QKS) は、Random Kitchen Sinks(RKS) にinspire されたもので、RKS では例えばcos 関数が使用されているが、これは単一の量子ビットにRabi 回転(下図では, RX) を適用することでそのような変換ができる。そのときのパラメータはランダムに決める。

f:id:puyokw:20180626233156p:plain:w400
上図の(a) とか(c) はうまくいくが、(b) はダメらしい。

入力データは実数だけど、どうやって量子回路に入れるの??
前処理:
 \Theta_{i,e} = \Omega_e u_i + \beta_e
ただし,  \Omega_e \mathcal N(0, \sigma^2) に,  \beta_e \mathcal U(0, 2\pi) に従う。
この \theta RX(\theta) に入れることになります。

メインの量子回路の処理を行う。

後処理:観測を行って, 出力される0, 1 を全エピソード数E 個分つなげていく(ここの部分は, 複数回観測した合計や平均などでも可) 。パラメータ付き量子回路のパラメータ数が4 個なら, 各トレーニングデータは長さ4E のベクトルになる。
E は大きい方が良い。

どうやって有効だと検証した?
(1) "picture frames" の二値分類
|- (a) logistic regression (scikit-learn) (~50%)
|- (b) QKS + logistic regression (> 99.9%)

(2) MNIST の数字の3 と5 の二値分類
|- (a) logistic regression (95.9%)
|- (b) QKS + logistic regression (98.6%)

ロジスティク回帰のみと量子回路+ロジスティク回帰を比較していて、QKS の段階で量子回路をたくさん作ることで精度を向上させることができる。
f:id:puyokw:20180626231513p:plain

もう一つの戦略として, qubit 数を増やすのもあるが, こちらでは途中から過学習している。
f:id:puyokw:20180626231538p:plain:w400
これは4 qubit の場合の例で, qubit 数を増やしたら精度が上がりそうに思うのですが…
f:id:puyokw:20180627012940p:plain:w200

議論はある?
・CNOT を含むある特定のネットワークが準最適である可能性がある。
・MNIST のデータセットでは、大きな回路のパラメータをチューニングするには不十分な大きさである。

今回試されていた回路は例えば以下のようなRX とCNOT を組み合わせたものでした。
例えば、4 qubit の場合だと以下のような構成。
RX(%x0) 0
RX(%x1) 1
RX(%x2) 2
RX(%x3) 3
CNOT 0 2
CNOT 1 3
CNOT 0 1
CNOT 2 3

感想:
量子回路の設計方針が分かりやすい!


TO DO
・再現実装をしてみる(まだ雰囲気程度)

from pyquil.quil import Program
from pyquil.api import QVMConnection
from pyquil.gates import *
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
import numpy as np
import math
from tqdm import trange
from sklearn.metrics import log_loss, accuracy_score
from sklearn.cross_validation import StratifiedKFold
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import pandas as pd

def rand():
	return np.random.rand()

def rand_u():
	return np.random.ranf()*2-1

def rand_n():
	return np.random.normal(0,1)

def make_pictureframe(num_data):
	data=np.zeros((num_data,2))
	teacher=np.zeros(num_data).astype(np.int32)
	for i in range(num_data):
		if rand()<=0.5:
			teacher[i]=0
		else:
			teacher[i]=1
	for i in range(num_data):
		tmp = rand()
		if tmp <= 0.25:
			data[i][0] = 1.0*rand_u() + rand_u()*0.1
			if rand() <= 0.5:
				data[i][1] = -1.0 + rand_u()*0.1
			else:
				data[i][1] = 1.0 + rand_u()*0.1
		elif tmp <= 0.5:
			data[i][0] = -1.0*rand_u() + rand_u()*0.1
			if rand() <= 0.5:
				data[i][1] = -1.0 + rand_u()*0.1
			else:
				data[i][1] = 1.0 + rand_u()*0.1
		elif tmp <= 0.75:
			data[i][1] = -1.0*rand_u() + rand_u()*0.1
			if rand() <= 0.5:
				data[i][0] = -1.0 + rand_u()*0.1
			else:
				data[i][0] = 1.0 + rand_u()*0.1
		else:
			data[i][1] = 1.0*rand_u() + rand_u()*0.1
			if rand() <= 0.5:
				data[i][0] = -1.0 + rand_u()*0.1
			else:
				data[i][0] = 1.0 + rand_u()*0.1
	for i in range(num_data):
		if teacher[i] == 1:
			data[i][0] *= 2
			data[i][1] *= 2
	return [data, teacher]


#np.random.seed(1)
data, target = make_pictureframe(100)

num_param=2
num_data=data.shape[0]
p=data.shape[1]
q=num_param # number of qubit
n_qubits=num_param # number of qubit
r=int(p/q)

num_episode=200
sigma=1

#np.random.normal(0, sigma)

Omega=[]
beta=[]
for e in range(num_episode):
	Omega.append( (np.concatenate( (np.random.randn(r, q), np.zeros((p-r,q))), axis=0)).T )
	beta.append( np.random.rand(q)*2*math.pi)

Omega=np.array(Omega)
beta=np.array(beta)

data_transform=[]
for i in trange(num_data):
	tmp=[]
	for e in trange(num_episode):
		u=data[i]
		theta = np.dot(Omega[e], u) + beta[e]
		qvm = QVMConnection()
		p1=Program()
		for j in range(q):
			p1.inst(RX(theta[j],j))
		
		p1.inst(CNOT(0, 1))
		p1.inst(MEASURE(0,0))
		p1.inst(MEASURE(1,1))
		tmp.append(np.array(qvm.run(p1, [j for j in range(q)], 10000)).mean(axis=0))
		
	tmp=[item for sublist in tmp for item in sublist]
	data_transform.append(tmp)

data_transform = pd.DataFrame(data_transform)
data_transform.columns=["col_"+str(i) for i in range(data_transform.shape[1])]
#data_transform.to_csv("trained.csv", index=False)

#train = pd.read_csv("trained.csv")
train=data_transform
train = np.array(train)

skf=StratifiedKFold(target, n_folds=5, shuffle=True)
for tr, te in skf:
	X_train, X_valid, y_train, y_valid = train[tr], train[te], target[tr], target[te]

lr = LogisticRegression()
lr.fit(X_train, y_train)

pred=lr.predict_proba(X_valid)
test_logloss=log_loss(y_valid, pred)
test_acc=lr.score(X_valid, y_valid)
pred=lr.predict_proba(X_train)
train_logloss=log_loss(y_train, pred)
train_acc=lr.score(X_train, y_train)
print("train acc: {}, test acc: {}".format(train_acc, test_acc))
print("train logloss: {}, test logloss {}".format(train_logloss, test_logloss))

pyquil は遅いので, 他のシミュレータで試した感じだと85% 程度しか精度でない....
ノイズがあった方が良いのかな??