Pennylaneを用いた量子SVM(Feature map を色々試す)

# 量子SVM

量子SVMは、古典データを量子状態ベクトル空間に移すことで得られるカーネルを用いたSVMです。
カーネルの計算までを量子コンピュータで行い、分離平面の計算は古典的なSVMと同じ方法で行います。
https://qiita.com/notori48/items/a54a5219ffd1ed6666df
https://qiita.com/notori48/items/6367876c3426350fab32

# Feature map

カーネルは、古典データを量子状態ベクトル空間にどのように移すかによります。
例えば、古典データ$x$に対して、量子状態ベクトルを角度$x$だけ回す演算　を対応させると、カーネルが決まります。
量子状態ベクトルの回転軸は$x,y,z$軸が考えられますので、回転軸も含めるとカーネルが3種類考えられます。
さらに、$x$軸周りに回した後に$y$軸回りに回すということも考えられます。
このように、埋め込み方に応じて無数のカーネルが考えられます。
埋め込み方を　Feature map　といいます。
どのようなFeature mapがどのような問題に適するかは、まだまだよくわかっていません。
今回は、いろいろなFeature mapを試してみます。

SDKはPennylaneを用います。

**[2021/10/10] 考察を追加しました**

## 1 qubit feature map (1次埋め込み)
一番かんたんなFeature mapの種類は、1 qubit 回転です。
これは先に述べたように、$x,y,z$軸のいずれかの軸回りの回転です。回転角度を古典データで決めます。

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.RX(a[i], wires=i)
```


Pennylaneのテンプレートライブラリを使うと、

```python
def my_feature_map(a):
    AngleEmbedding(a, wires=range(n_qubits), rotation='X') 
```

このようになります。
（なお、データを2倍してから回転角度に埋め込む実装もよくみられます。スケールは自由ですが、
”角度”に埋め込んでいるのですから最大値が$2\pi$を超えないように注意しましょう。)
入力データが$n$次元であるとき、$n$量子ビットをそれぞれ回転させます。
なお量子ビット数が潤沢にある（例えば$2n$)なら、異なる2つの量子ビットに同じデータを埋め込んでも良いです。

# 2 qubits feature map (二次埋め込み)

次は２量子ビットにまたがった埋め込みです。
以下はZZ feature map と呼ばれる例です。

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RZ(a[i], wires=i)
        
    qml.IsingZZ((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
```

本質的なのは、最後の
` qml.IsingZZ((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])`
です。
入力データの1次元目と2次元目を同時に取ってきて、2量子ビット間にZZゲートをかけています。
ZZゲートについては、以下を参照。
https://pennylane.readthedocs.io/en/stable/code/api/pennylane.IsingZZ.html
ZZゲートをかけると、1番目の量子ビットの状態と2番目の量子ビットの状態に相関（エンタングルメント）が生まれます。
エンタングルメントがあるのとないのとでは、古典データがアクセスできる量子状態ベクトル空間（正確にはブロッホベクトル空間)の次元の大きさが違います。
https://qiita.com/notori48/items/6367876c3426350fab32
ただ、単にエンタングルメントがあればSVMの性能が必ず良くなるかというと、そうでもないようです。
ここが難しさです。

## 性能比較
feature map をいろいろ試してみます。
データセットはmoonを用いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/2bc19e51-bf2c-20cc-8beb-b85d4eadc191.png)

実装は過去の記事（以下）と同じです。feature map と kernel だけを差し替えています。
https://qiita.com/notori48/items/a54a5219ffd1ed6666df
https://qiita.com/notori48/items/6367876c3426350fab32

学習は、100データに対して10秒程度で終わります。

```python

def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RZ(a[i], wires=i)
        
    qml.IsingZZ((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
        
@qml.qnode(dev_kernel)
def kernel(x1, x2):
    """The quantum kernel."""
    #S(x)
    my_feature_map(x1)
    #S^{\dagger}(x')
    qml.adjoint(my_feature_map)(x2)
    return qml.expval(qml.Hermitian(projector, wires=range(n_qubits)))

```

### RX のみ

```python
def my_feature_map(a):
    AngleEmbedding(a, wires=range(n_qubits), rotation='X') 
```

accuracy = 0.8
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/3edecaa0-8312-dad4-c7fc-ab1ec32b830b.png)


### RY のみ

```python
def my_feature_map(a):
    AngleEmbedding(a, wires=range(n_qubits), rotation='Y') 
```

0.8

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/dbf28eb6-0b2a-b623-87f8-5c393c17b2c3.png)



### RZのみ

```python
def my_feature_map(a):
    AngleEmbedding(a, wires=range(n_qubits), rotation='Z') 
```

0.32

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/1d96652e-8ddf-4c63-8100-89f965957e94.png)

### Hadamard + RZ 

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
    AngleEmbedding(a, wires=range(n_qubits), rotation='Z')
```

0.8

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/1931d8a2-c1d7-eefb-f5bc-4e0aa72a6888.png)

Hadamard を入れるとRZがRXと似た性能になっています。
Hadamard で座標変換するとZゲートがXゲートに見えるからだと思います。(X=HZH)

**どうやらmoonデータセットに対してはXやYが相性が良さそうです。**

### XX feature map

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RX(a[i], wires=i)
        
    qml.IsingXX((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
```

0.32

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/b5e7b8df-8154-4eb3-05cf-5314a82b02cc.png)

Xゲートの系譜なのにだめになっています。
これは、初段に入れているHadamard によってXがZへ変換されているためと考えられます。

### XX feature map　から　Hadamard　を引っこ抜く

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.RX(a[i], wires=i)
        
    qml.IsingXX((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
```

0.96

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/95095963-e582-7fac-df2a-0b790fb56b81.png)

Hadamardを引っこ抜くことにより、純粋にXゲートの系譜になっていますので、うまくいっています。
そして、RXのみ（一次）のときより性能が改善しています(0.8→0.96)。
**エンタングルメントの効果が出ています。**
しかし判定領域に不連続性が出ていたりしますので、扱いづらそう。

### YY feature map

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RY(a[i], wires=i)
        
    qml.IsingYY((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
```

`AttributeError: module 'pennylane' has no attribute 'IsingYY'`
バグで実行できません。

### ZZ feature map

```python
def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RZ(a[i], wires=i)
        
    qml.IsingZZ((np.pi-a[0])*(np.pi-a[1]),wires=[0,1])
```

0.96

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/16831ff1-df23-4588-f45f-14c490c2e035.png)


Zの系譜ですが性能が良いです。
これは、Hadamard によってZがXに化けているためと考えられます。
やはりXが相性が良さそうです。

### 古典カーネル(RBFカーネル）

1.0

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/8f017df9-c080-885c-ed51-c8e1d4385fc5.png)

古典がベストでした・・・

## 比較結果

feature map の決め方によって分類精度が全く異なる。
moonデータセットに対してはXゲートやYゲートのfeature map の相性が良いと考えられる。
この例では一次よりも二次のほうが性能が良かった。
が、判定領域に不連続性が入っていたりするので、これが悪さをすることも考えられ、一般に二次のほうがいいとは限らないだろう。
何も考えない古典RBFカーネルだと一発で分類エラー0になるし、学習も200倍ぐらい早い。
量子の表現力、とは。

## まとめ
何も考えない古典RBFカーネルが一番いいのでは？という悲しい結果。
なお量子カーネルSVMのほうが性能が良くなるデータセットも存在しますが、そのような例は人為的な場合が多いのが現状と思います。
qiskitチュートリアルの　アドホックデータ　がその例です。
https://qiskit.org/documentation/stable/0.24/locale/ja_JP/tutorials/machine_learning/01_qsvm_classification.html