qiskit_machine_learningでQSVM

# はじめに

Qiskitのライブラリ `qiskit_machine_learning` を使って簡単にQSVMが出来ます。

実はqiskit.aquaにあった機能です。が、aquaは廃止になりました。
にもかかわらず、未だに検索するとqiskit.aquaによる実装がたくさん出てきて困りますので
ここにqiskit_machine_learningによる実装を書いておきます。

以下のチュートリアルを参考に、QSVMを実装してみます。
https://qiskit.org/documentation/machine-learning/tutorials/03_quantum_kernel.html

## qiskit による実装

```python
#!pip install qiskit_machine_learning
```

```python
import matplotlib.pyplot as plt
import numpy as np

from sklearn.svm import SVC
from sklearn.cluster import SpectralClustering
from sklearn.metrics import normalized_mutual_info_score

from qiskit import BasicAer
from qiskit.circuit.library import ZZFeatureMap
from qiskit.utils import QuantumInstance, algorithm_globals
from qiskit_machine_learning.algorithms import QSVC
from qiskit_machine_learning.kernels import QuantumKernel
from qiskit_machine_learning.datasets import ad_hoc_data

seed = 12345
algorithm_globals.random_seed = seed

# from sklearn.svm import SVC
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

X, y = load_iris(return_X_y=True)

# pick inputs and labels from the first two classes only,
# corresponding to the first 100 samples
X = X[:100,0:2]
y = y[:100]

# scaling the inputs is important since the embedding we use is periodic
scaler = StandardScaler().fit(X)
X_scaled = scaler.transform(X)

# scaling the labels to -1, 1 is important for the SVM and the
# definition of a hinge loss
y_scaled = 2 * (y - 0.5)

train_features, test_features, train_labels, test_labels = train_test_split(X_scaled, y_scaled, train_size=80, test_size=20)

n_qubits = 2

adhoc_feature_map = ZZFeatureMap(feature_dimension=n_qubits,
                                 reps=1, entanglement='linear')
adhoc_backend = QuantumInstance(BasicAer.get_backend('statevector_simulator'), shots=1,
                                seed_simulator=seed, seed_transpiler=seed)
adhoc_kernel = QuantumKernel(feature_map=adhoc_feature_map, quantum_instance=adhoc_backend)
```

ZZ Feature map を見てみます。

`print(adhoc_feature_map)`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/878664/5f53ca38-5b7b-a14f-9135-19fd04117b42.png)

少し見にくいですが、H→RZ→CNOT(0,1)→RZ→CNOT(0,1)となっています。
CNOT(0,1)→RZ→CNOT(0,1) は、RZZと等価です。

QSVMの学習・予測の実行時間と、予測精度をみてみます。

```python
import time
qsvc = QSVC(quantum_kernel=adhoc_kernel)
start = time.time()
qsvc.fit(train_features, train_labels)#train
qsvc_score = qsvc.score(test_features, test_labels) #predict
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")

print(f'QSVC classification test score: {qsvc_score}')
```

```
elapsed_time:1.3650000095367432[sec]
QSVC classification test score: 0.45
```

予測精度は全くだめです。
ZZ feature map はこの問題に適さないということです。


## (参考) PennyLane　での実装

PennyLaneでも同じことをやってみます。

```python


from sklearn.svm import SVC
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import pennylane as qml
from pennylane.operation import Tensor
from pennylane import numpy as np

import matplotlib.pyplot as plt

def my_feature_map(a):
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
        qml.RZ(2*a[i], wires=i)

    qml.IsingZZ(2*(np.pi-a[0])*(np.pi-a[1]),wires=[0,1])

dev_kernel = qml.device("lightning.qubit", wires=n_qubits)

#|00...0><0.....00|
projector = np.zeros((2**n_qubits, 2**n_qubits))
projector[0, 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)))

def kernel_matrix(A, B):
    """Compute the matrix whose entries are the kernel
       evaluated on pairwise data from sets A and B."""
    return np.array([[kernel(a, b) for b in B] for a in A])

svm = SVC(kernel=kernel_matrix)
start = time.time()
svm.fit(train_features, train_labels)
predictions = svm.predict(test_features)
elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
accuracy_score(predictions, test_labels)
```

```
elapsed_time:15.84500002861023[sec]
0.45
```

予測精度は当然同じ結果になりますが、実行時間が10倍程度かかっています。
PennyLaneは勾配降下が必要な機械学習には強いですが、QSVMではその必要性がないので、別に早くはないと考えられます。

# 所感

QSVMのライブラリについては、Qiskitのほうが整備されており、使いやすい。
PennyLaneもQSVM関係の機能が地味に増えたりしていますので、今後はQiskit並の使い勝手になると期待しています。