Implementing a distance-based classifier with a quantum interference circuit

# distance-based classifier 
量子回路を用いた分類器に関する以下の論文;

[Implementing a distance-based classifier with a quantum interference circuit](https://arxiv.org/abs/1703.10793)

を実装してみます。

# distance-based classifier とは？

$N$次元ベクトルの訓練データセット（2値ラベル付き)が与えられた時、
テストデータを正しいラベルに分類しようとするものです。
テストデータのラベル推定は、「どちらのラベルの訓練データセットに、より距離が近いか」で決めます。

ラベルの推定としては硬判定と軟判定が考えられるようです。
硬判定の場合、テストデータに対してラベルが0か1かを決定的に決めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/74328c82-ce03-e7db-d36f-2578ba2f26cd.png)
対して軟判定の場合、テストデータに対してラベルが0に近いか1に近いかの数値（確率、あるいは軟値）を出力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/8438de5e-f1ad-f23e-9582-e011763768fa.png)

量子計算で実現するのは軟判定のほうのようです。
（この論文の導入部では、古典かつ硬判定の場合だけ紹介し、量子ではいきなり軟判定を実装しているので、ちょっと混乱しました）


# アルゴリズムの挙動

まず、データは振幅に埋め込みます。$N$がデータベクトルの次元です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/c3963d79-4e5f-6c45-4dcf-ab923b680e79.png)

目指すのは次のような状態ベクトルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/e7717d97-49da-f57f-c36e-1b211da5863b.png)
これは、以下のような構造をしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/39e75e3c-a05f-7763-1fe5-a3a92f54f892.png)

この状態が用意できたら、test $\hat{x}$ とtraining $x^{m}$ を区別している補助ビットへアダマールゲートをかけます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/e26e34ac-2545-ccc3-8926-38a38b24973f.png)

すると、test $\hat{x}$ と　training $x^{m}$  が干渉して、”testとtrainingのベクトルの差分"のような量が出来ます。
更に、補助ビットを測定して0になるのを祈ります。（だめだったら初めからやり直し）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/4cdbc2b5-77f4-aeca-fa7a-5d0d6840c390.png)

こうなります。更に上記量子状態において分類ラベルの量子状態$|y \rangle$が0となる確率を考えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/f443e51f-2c2b-e950-1889-576620a7964d.png)

つまり分類ラベルの量子状態$|y \rangle$が0となる確率は、テストデータが0に分類される確率（軟値）と一致します。
よって、ラベル推定問題を量子状態の確率測定問題に埋め込めました。

# 回路実装

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/0f2f4503-2f24-f1f7-1142-a9a7ffdb6cc5.png)

よくわからないので補足を書き入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/edd7f529-9178-35f2-59d5-de544354977b.png)


データを量子状態の振幅としてエンコードした後は、訓練データに関してはラベルを貼る（教える）ことをして、
アダマールゲートの干渉作用でテストデータと訓練データの差分を抽出し、さらにうまいことやってそのノルムが測定確率に現れるように持っていっています。
ラベルを貼る時にSWAPゲートが入っていますが、これはラベル貼り付け用のCNOTをIBMQ実機の結合マップに合うように無理やり持っていくためです。
この回路を実行して、アンシラが0になった上で、
3番目量子ビットを測定（事前にSWAPゲートがあるので、実質は4番目量子ビットを測定していることになる）し、c(ラベル）を確認します。

- ラベルが0となる頻度（確率）が、テストデータが0に分類される確率（軟値）です。
- ラベルが1となる頻度（確率）が、テストデータが1に分類される確率（軟値）です。

# qiskit実装

```python
#必要なモジュールのインポート
from qiskit import IBMQ, QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute, Aer
from qiskit.qasm import pi
from qiskit.tools.visualization import plot_histogram
from qiskit import IBMQ, transpile
from qiskit.providers.ibmq.managed import IBMQJobManager
from qiskit.quantum_info import Statevector
from qiskit.circuit.library import RYGate
from qiskit.circuit.add_control import add_control
```

データセットの準備

```python
x0 = np.array([0, 1]); # 1st training vector
x1 = np.array([0.789, 0.615]) # 2nd training vector

#x_test = np.array([-0.549, 0.836]) # 1st test vector
x_test = np.array([0.053, 0.999]) # 2nd test vector

```

データをRYゲートでエンコードするので、その角度を事前に計算

```python
theta_test = np.arccos(x_test[0])
print("Rotation angle for encoding a test vector is {:}".format(2*theta_test)) # Ry = R(theta/2)

theta_x0 = np.arccos(x0[0])
print("Rotation angle for encoding 1st training vector is {:}".format(2*theta_x0)) # Ry = R(theta/2)

theta_x1 = np.arccos(x1[0])
print("Rotation angle for encoding 2nd training vector is {:}".format(2*theta_x1)) # Ry = R(theta/2)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/0c26cf79-4a54-70e2-af7c-bcf464f127f1.png)


回路のフレーム

```python

qr_ancilla = QuantumRegister(1, name='qr_ancilla')
qr_index = QuantumRegister(1, name='qr_index')
qr_data = QuantumRegister(1, name='qr_data')
qr_class = QuantumRegister(1, name='qr_class')
cr = ClassicalRegister(2)
circ_classifier = QuantumCircuit(qr_ancilla, qr_index, qr_data, qr_class, cr)
circ_classifier.draw('mpl')
```

エンコードとラベル付け

```python
def encode_x_test(circ):
  RY_ctrl = add_control(RYGate(2*theta_test), num_ctrl_qubits=1, label='C_RY_x_test', ctrl_state='1')
  circ.append(RY_ctrl,[0,2])
  circ.x(0)

def encode_x0(circ):
  circ.ccx(0,1,2) # toffoli(control1,cotrol2,target)
  circ.x(1)

def encode_x1(circ):
  RY_ctrl2 = add_control(RYGate(2*theta_x1), num_ctrl_qubits=2, label='CC_RY_x1', ctrl_state='11')
  circ.append(RY_ctrl2,[0,1,2])

def add_class(circ):
  circ.swap(2,3)
  circ.cx(1,2)

```

つなぎ合わせて回路にする

```python
circ_classifier = QuantumCircuit(qr_ancilla, qr_index, qr_data, qr_class, cr)

# hゲート
circ_classifier.h([0,1])
circ_classifier.barrier()

# テストデータをエンコード
encode_x_test(circ_classifier)
circ_classifier.barrier()

#トレーニングデータその1をエンコード
encode_x0(circ_classifier)
circ_classifier.barrier()

#トレーニングデータその2をエンコード
encode_x1(circ_classifier)
circ_classifier.barrier()

#トレーニングデータに対してラベルをエンコード
add_class(circ_classifier)
circ_classifier.barrier()

#干渉によりトレーニングデータとテストデータの違いを量子状態に埋め込む ; トレーニング＋データ　と　トレーニング-データ　の重ね合わせになっている
circ_classifier.h(0)

# トレーニング＋データ　と　トレーニング-データ　のうち片方を選ぶ。ここではアンシラが|0>へ射影(トレーニング＋データ)されることを期待する。
circ_classifier.measure(0,cr[0]) # ancilla 

# アンシラが|0>へ運良く射影されたとして、ラベルを測定する。ラベルが|0>となる確率　＝　テストデータがラベル0として分類される確率。
#circ_classifier.barrier()
circ_classifier.measure(2,cr[1]) # class

circ_classifier.draw('mpl')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/24cd7f8c-4649-aada-26ae-4540480afcfd.png)

回路実行

```python
shots = 8192
results = execute(circ_classifier, backend=Aer.get_backend('qasm_simulator'), shots=shots).result()

```

測定結果を処理する。

```python
# アンシラが|0>へ射影された確率
print((counts.get('00')+counts.get('10'))/shots)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/9cc3108f-d9fa-334f-ad81-03bfe7ae8b79.png)


```python
# テストデータがラベル0に属する確率
print(counts.get('00') / (counts.get('00')+counts.get('10')) )
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/c3880b66-f203-e8b9-5bb0-72e09908f50b.png)


```python
# テストデータがラベル1に属する確率
print(counts.get('10') / (counts.get('00')+counts.get('10')) )
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/45e40682-6192-a14a-9a7b-fd359480c40c.png)


上記の確率は、論文中の値と概ね一致しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/694185d0-0c73-cc38-1d09-60e0fb6ab48b.png)

ちなみにひとつ上の行がIBMQ実機の結果だそうです。アンシラが0になる確率がかなり小さくなってしまっているのと、
分類確率の誤差もそこそこ出ているようです。

私の方でも実機実行してみました。ibmqx2です。

```python
job = execute(circ_classifier, backend, shots=8192)
job_monitor(job, interval=2)
results_IBMQ = job.result()

counts = results_IBMQ.get_counts() # dict , cr[0] corresponds to the most right bit
# アンシラが|0>へ射影された確率
print((counts.get('00')+counts.get('10'))/shots)
# テストデータがラベル0に属する確率
print(counts.get('00') / (counts.get('00')+counts.get('10')) )
# テストデータがラベル1に属する確率
print(counts.get('10') / (counts.get('00')+counts.get('10')) )
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/18003602-ddf6-1dae-880d-d1220e1ab5cd.png)

が、論文ほどうまくいっていません。差分はよくわかりませんが、
CCRY等を特にコスト気にせずに作っているので、回路の深さが無駄に大きいのかもしれません。
もうちょっと調査したいところですね。

なおテストデータをもう一種類試しましたが、こちらも（シミュレーターは）論文とよく一致しました。

## 実機実行の検証 (2021/01/23追記)

実機実行について検証しました。どうも、「実機で分類ができている」という主張が
正しくないように思います。
論文にはIBMQで使用した回路も載っているのですが、それを完全にトレースしても結果は変わらずでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/6b95865d-fda0-12bf-5bf1-644867608b34.png)

論文にはIBMQで8192shots測定した際のカウントが載っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/10c8a0f1-5df9-d6d9-09a7-2e11aa44407a.png)

これをシミュレーションと比べてみました。
ただし、どのbit位置がどの量子ビットのカウントなのかが書かれていません。
$p_{acc}$や分類結果が出てくるように辻褄を合わせてみると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/7289718c-525c-23c0-9469-36387765a79e.png)

このようになっていることは分かりました。上位2桁はindexかdataのどちらかですが、分からないので二通り試してみます。
（赤色はクラス0への分類、緑色はクラス1への分類を意味してます。色がないものは、アンシラが1へ射影されてしまったパターンです。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/c8eaa5ec-9dfb-0493-1b6d-0a639491d7d8.png)

いずれにせよ、論文の測定結果はシミュレーションと全く乖離しています。
この結果から、量子計算がうまくいったように見えたのは「雑音によって偶然それらしい値が出ただけ」なのではないかと
思いました。

# 所感

- アンシラが0になる確率を引き上げるために、amplitude amplificationが使えないか？と思いました。実機実行は難しくなりますが。
- 一般にデータのエンコードはRYゲート一個で出来ると決まったわけでは有りません。

# 結論

自力で論文を読んで、簡単な量子回路が実装できた。