IBM Qiskit 量子サポートベクターマシンによる量子機械学習とMNIST多クラス分類の進め方

## :scorpius: この記事の目的

この記事では、IBM社製の量子コンピューティングSDKである**Qiskit**を使用し、機械学習課題に取り組みます。
今回取り組むのは、このQiskitに備わっている**量子サポートベクターマシン(Quantum Support Vector Machine：QSVM)**による**多クラス分類(マルチクラス分類)**機能を用いたMNISTの実行です。

IBM Qiskit量子サポートベクターマシンについてはこちらにも記事を書きましたので、併せてお読み下さい。

:paperclip: 量子SVMの基本 - IBM Qiskit SVM(QSVM)を実装し、線形・非線形の基本的な2クラス分類機能を確認する
https://qiita.com/ttabata/items/937c3b1edf3ead03c179

#### 当記事のタイトルは「進め方」としましたが、あくまでプログラミングの一例です。今後、様々な議論やさらなる発展が出てくることと思います。


## :scorpius: はじめに ～ IBM Qiskit QSVMに関して

この記事は令和3年3月18日に執筆しています。現執筆時点でのIBM Qiskit QSVMの性能や今回の取組結果について、前置きとして説明します。
量子コンピューティング技術はまだまだ黎明期であり、IBM Qiskit QSVMについても同様のことが言えます。実装面や性能面でもまだまだ発展途上であり、今後の進化・進展に期待したいところです。

今回取り組んでみたところ、以下のような問題点が感じられましたので、まずは前提知識として記載したいと思います。

**問題(1)：**
**教師データやテストデータなどの入力データの特徴量(ベクトルまたは配列データの次元)を増やすと計算時間が多大になり、実用的な運用範囲を超えてしまう**

入力データの特徴量次元が大きいと計算処理に時間がかかります。
画像サイズが 28 x 28 bitのMNISTでさえ、画像の特徴量次元が大きく、処理に時間がかかるようです。

**問題(2)：**
**シミュレータで計算に使用される量子ビットの最大数は32bit**

今回使用した量子コンピュータは、Qiskitの「QASM simulator」と呼ばれる仮想的な量子コンピュータのシミュレータです。
このシミュレータの内部に仮想的な量子ビットが存在し、この量子ビットが量子力学に基づく挙動を示すことで、量子コンピューティングを行う仕組みとなっています。
現時点でシミュレータに内包されている量子ビットは32bitです。32bitを超える量子計算を実行しようとするとエラーが出ます。

**問題(3)：**
**計算速度が遅い故、入力として取り込むデータ量も少なくせざるを得ない**

計算に時間がかかる状況で何とか結果を出そうとした場合、少しでも計算時間を短縮するためには、どうしても入力データを減らさざるを得ません。今回も同様に教師データ及びテストデータについて最低限のデータ量に絞っての実行となりました。

## :scorpius: IBM Qiskit 量子サポートベクターマシンとは

機械学習分野で多用される分類器に、サポートベクターマシンがあります。
サポートベクターマシンは、もともとはデータを線形性(直線)の境界線(超平面と呼びます)で2つのグループにデータ分けるための理論です。2つのグループに分けることを**二値分類**とも呼びます。

さらに、直線では分離できないようなデータに対して、カーネル関数と呼ばれる計算手法を用いて、非線形境界でも分類を行えるように拡張されています。この手法を**カーネル・トリック**とも呼びます。

量子サポートベクターマシンは、このようなサポートベクターマシン理論を量子コンピューティングに応用した技術です。
特に、IBM Qiskitにはこの量子サポートベクターマシン(QSVM)がモデルとして実装されており、このモデルを用いて機械学習等の問題に取り組むことができます。
さらに、二値分類だけでなく、複数のグループにデータを分類する**多値分類(他クラス分類、マルチクラス分類)**機能も実装されており、注目に値する機能です。

IBM Qiskit量子サポートベクターマシンについてのさらなる情報は、下部「関連情報」欄をご参照下さい。

## :scorpius: 実行環境とその準備

今回の実行環境は以下です。当記事執筆時点のバージョンであり、Qiskitの機能変更やバージョンアップにより環境が動作しなくなる可能性もありますのでご了承お願い致します。

#### 言語・ミドルウェア

- Qiskit
 - qiskit               0.23.6
 - qiskit-aer           0.7.5
 - qiskit-aqua          0.8.2
 - qiskit-ibmq-provider 0.11.1
 - qiskit-ignis         0.5.2
 - qiskit-terra         0.16.4
- Python 3.9.2

#### マシン・OS

<dl>
  <dt>　マシン：</dt>
  <dd>AWSインスタンス(x3.large)　CPU:4コア　Memory:16GB</dd>
</dl>
<dl>
  <dt>　OS：</dt>
  <dd>Ubuntu Server 20.04 LTS 64bit</dd>
</dl>

#### :large_orange_diamond: 環境構築

Qiskitのインストールは以下のコマンドで実行しました。

```ruby
pip install qiskit
pip install qiskit-aqua[cvx]
```

さらに、Pythonモジュールである**numpy**, **opencv**もインストールしておいてください。

```ruby
pip install opencv-python
pip install numpy
```


MNISTデータセットは以下のサイトから取得しました。
http://yann.lecun.com/exdb/mnist/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399135/09f84672-3669-6d46-9048-d959cfde2116.png)
以下の4ファイルを取得し、解凍します。

http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz

フォルダ構成は以下とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399135/7f071e1f-325f-63db-84d2-9d43e7eae3a1.png)

## :scorpius: 実装内容と解説

プログラムの実装とその解説です。

#### 1. プログラムの流れ

サポートベクターマシンは教師あり学習モデルですので、まずは教師データを与えてモデルがデータの学習を行い、その後テストデータを読み込んで、それぞれのテストデータがどのクラス(どの数字)に分類されるかの分類処理を行い、結果を返すという流れとなります。

プログラムの主な処理の流れは以下となります。

　**(1)定数の定義**
　↓
　**(2)関数の定義**
　↓
　**(3)教師データの用意**
　↓
　**(4)テストデータの用意**
　↓
　**(5)学習と分類の実行**
　↓
　**(6)結果の表示**


#### 2. プログラムの流れごとの処理の説明

プログラムの流れを処理ごとに分けて解説します。

#####(1)定数の定義

```ruby:(1)
# MNIST画像分割数(画像サイズを何分の一に縮小するか?の設定値)
img_divisor = 7

# 取込教師データ数
training_limit = 20
# 取込テストデータ数
testing_limit = 10
```

冒頭に述べた問題(1)～問題(3)の通り、データをそのまま取り扱うことは困難になるため、データ量やデータそのものを削減します。
MNISTは、縦28pics x 横28picsサイズの手書きの数字の白黒画像が教師データとして60,000枚、テストデータとして10,000枚入ったデータセットです。

画像サイズをそのまま取り込むとデータ量が大きくなるため、サイズを縮小します。
画像サイズを何分の一に縮小するか、を決定する変数をimg_divisorとしました。
例えば `img_divisor=7` の場合、縦横それぞれ1/7とし、縦4pics x 横4picsに縮小します。

さらに、取り込むデータの量(画像の枚数)とテストデータの枚数も各変数training_limit, testing_limitで設定します。

#####(2)関数の定義

3つの関数を定義しました。

```ruby:(2)-1
# 画像縮小
def reduce_size(img_file):
    img = cv2.imread(img_file)
    reduced_img_data = cv2.resize(img, (img.shape[1] // img_divisor, img.shape[0] // img_divisor))
    return reduced_img_data
```

上記関数について、画像の縮小はOpenCVモジュールの機能を使用します。
まず、画像パスを指定しimreadメソッドで画像データを読み込みます。
読み込んだ画像データをresizeメソッドで縮小します。この時、縮小するサイズをパラメータとして設定します。
最後に縮小した画像データを返します。

```ruby:(2)-2
# 特徴量ベクトルの作成
def get_feature_vector(img_data):
    
    feature_list = []    
    for d1 in img_data:
        for d2 in d1:
            feature_list.append(d2[0] / 255)
    
    return np.array(feature_list)
```

上記関数について、画像データを集約した特徴量ベクトルを生成する関数です。
画像データは[縦x横xRGB値]の3次元配列として与えられます。
今回取り扱うMNISTの画像データにおいてはカラー情報は不要なので、3次元目のRGBデータを一つの値に集約します。このことで、データを削減しています。
さらに、RGB値は0～255までの値を取るため、これを255で割って正規化します。
一次元配列に変換したデータをnumpyのndarray配列として返します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399135/7b48592e-f8ca-6ecd-23e6-43138f8716e7.png)

```ruby:(2)-3
# 全データ保持リストにすでに存在するデータかどうかの判定
def check_exists_total_list(total_data_list, feature_vector):
    for data_row in total_data_list:
        if str(data_row) == str(feature_vector):
            return True

    # 存在しない場合、Falseを返す
    return False
```

上記関数について、逐次読み込んだ教師画像データが、全データ保持リストにすでに存在するデータかどうかの判定を行います。
これは、Qiskit QSVM独特の仕様なのですが、教師データとして重複したデータを与えることはできません。よって、同じ教師画像データが取り込まれないようにチェックするための関数です。
返却値として、すでに存在する同じ教師画像データとわかった場合はTrueを、まだ取り込んでいない新しいデータである場合はFalseを返します。

#####(3)教師データの用意

教師データとして、MNISTの以下の2ファイルの読み込みを行います。読み込みデータはバイナリーデータです。
　(a)train-labels.idx1-ubyte
　(b)train-images.idx3-ubyte

(a)のファイルは正解ラベルの教師データです。
以下のソースでは、ファイルの先頭から8バイト読み込み、正解ラベル数lbl_countを取得し、コンソールに表示しています。

```ruby:qsvm.py
# 正解ラベルの中身を確認
lbl_f = open('./mnist/train-labels.idx1-ubyte', 'rb')
dummy, lbl_count = struct.unpack('>II', lbl_f.read(8))
# 正解ラベル数
print('正解ラベル数:', lbl_count)
```

(b)のファイルは教師画像データセットです。
以下のソースでは、ファイルの先頭から8バイト分を読み込み、画像データ数img_countを取得、さらに8バイト分読み込み、画像サイズrows,colsを取得し、コンソールに表示しています。

```ruby:qsvm.py
# 教師画像データの数値化
img_f = open('./mnist/train-images.idx3-ubyte', 'rb')
dummy, img_count = struct.unpack('>II', img_f.read(8))
rows, cols = struct.unpack('>II', img_f.read(8))
print('画像サイズ:', rows, 'x', cols)
```

以下は、コメント文通りの配列を宣言しています。

```ruby:qsvm.py
# 教師データをとテストデータの両方を保持するリスト(全データ保持リスト)
total_data_list = []

# ラベル別教師データ格納配列
train_data_label_0 = []
train_data_label_1 = []
train_data_label_2 = []
train_data_label_3 = []
train_data_label_4 = []
train_data_label_5 = []
train_data_label_6 = []
train_data_label_7 = []
train_data_label_8 = []
train_data_label_9 = []
```

これ以降はfor文でループ処理をしながら、それぞれのファイルを指定したバイト数分ずつ読み込みを行います。

以下では、正解ラベルを1バイトずつ読み込み、label変数に取得します<1>。
続いて、教師画像データとして画像サイズ(縦x横)分のバイトデータを読み込み、その値を3次元のRGB値を保持する配列変数RGB_dataに順次、画像の各ビット単位で格納します。合計で28x28=784ビット分の配列データが得られます<2>。
取得した配列を画像データとして取り扱いできるようにreshapeを行い、画像ファイル名'mnist_tmp.jpg'として一旦画像にして保存します<3>。
一旦保存した画像を再度読み込み、縮小した画像データを配列型データで取得します<4>。

```ruby:qsvm.py
# 正解ラベルを取得
label = struct.unpack('B', lbl_f.read(1))[0]　　… <1>

# 教師データを取得
bdata = img_f.read(rows * cols)
RGB_data = list(map(lambda n: [n, n, n], bdata))　　… <2>
img_data = np.array(RGB_data).reshape((28,28, 3))
cv2.imwrite('mnist_tmp.jpg', img_data)　　… <3>
reduced_img_data = reduce_size('mnist_tmp.jpg')　　… <4>
```

以下では、取得した画像データを特徴量ベクトルに変換しています。

```ruby:qsvm.py
# 特徴量ベクトルの変換(画像データを1 x 16次元に変換)
feature_vector = get_feature_vector(reduced_img_data)
```

以下、教師画像データから特徴量ベクトルに変換できたら、それをラベルごと(グループごと)の配列に分けて格納します。
ただし、同じ教師データが重複しないようにif文で切り分けを行っています。

```ruby:qsvm.py
# 全データ保持リストにすでに存在するデータかどうかの判定
if check_exists_total_list(total_data_list, feature_vector) == False:
    # もし存在しないデータパターンの場合、全データ保持リストに追加する
    total_data_list.append(feature_vector)
    
    # ラベルごとに教師データ(特徴量ベクトル)を格納
    if label == 0:
        train_data_label_0.append(feature_vector)
    elif label == 1:
        train_data_label_1.append(feature_vector)
    elif label == 2:
        train_data_label_2.append(feature_vector)
    elif label == 3:
        train_data_label_3.append(feature_vector)
    elif label == 4:
        train_data_label_4.append(feature_vector)
    elif label == 5:
        train_data_label_5.append(feature_vector)
    elif label == 6:
        train_data_label_6.append(feature_vector)
    elif label == 7:
        train_data_label_7.append(feature_vector)
    elif label == 8:
        train_data_label_8.append(feature_vector)
    elif label == 9:
        train_data_label_9.append(feature_vector)
```

以下、教師データを保持しているグループごとの配列を最終的に一つの教師データセットにまとめます。これはQSVM特有のデータの保持の仕方で、教師データ及びテストデータはPythonの辞書型として保持する仕様となっています。

```ruby:qsvm.py
# 最終教師データ
train_data = {
    '0':np.array(train_data_label_0),
    '1':np.array(train_data_label_1),
    '2':np.array(train_data_label_2),
    '3':np.array(train_data_label_3),
    '4':np.array(train_data_label_4),
    '5':np.array(train_data_label_5),
    '6':np.array(train_data_label_6),
    '7':np.array(train_data_label_7),
    '8':np.array(train_data_label_8),
    '9':np.array(train_data_label_9),
}
```

以上が教師データの用意の仕方に関するプログラムです。
次に、同様の処理手順で、テストデータも用意します。

#####(4)テストデータの用意

テストデータの用意の仕方も、上述の教師データの用意の仕方とほぼ同じ手順で進めます。

テストデータとして、MNISTの以下の2ファイルの読み込みを行います。読み込みデータはバイナリーデータです。
　(a)t10k-labels.idx1-ubyte
　(b)t10k-images.idx3-ubyte

以下はテストデータのラベルの取得及びテスト画像データのデータ数と画像サイズの取得処理です。

```ruby:qsvm.py
# テストデータの中身を確認
test_lbl_f = open('./mnist/t10k-labels.idx1-ubyte', 'rb')
dummy, test_lbl_count = struct.unpack('>II', test_lbl_f.read(8))
print('テストデータ正解ラベル数:', test_lbl_count)

# テスト画像データの数値化
test_img_f = open('./mnist/t10k-images.idx3-ubyte', 'rb')
dummy, test_img_count = struct.unpack('>II', test_img_f.read(8))
rows, cols = struct.unpack('>II', test_img_f.read(8))
```

以下、テストデータ格納配列を用意します。

```ruby:qsvm.py
# ラベル別テストデータ格納配列
test_data_label_0 = []
test_data_label_1 = []
test_data_label_2 = []
test_data_label_3 = []
test_data_label_4 = []
test_data_label_5 = []
test_data_label_6 = []
test_data_label_7 = []
test_data_label_8 = []
test_data_label_9 = []
```

続いて、テストデータにおいてもループ処理により、テスト用ラベルとテスト画像データを順次バイト数単位で読み込みを行い、特徴量ベクトルに変換し、配列に格納します。

プログラムは教師データ取得と同様のため省略します。

最終的に、以下のようにPython辞書型のテストデータセットにまとめます。

```ruby:qsvm.py
# 最終テストデータ
test_data = {
    '0':np.array(test_data_label_0),
    '1':np.array(test_data_label_1),
    '2':np.array(test_data_label_2),
    '3':np.array(test_data_label_3),
    '4':np.array(test_data_label_4),
    '5':np.array(test_data_label_5),
    '6':np.array(test_data_label_6),
    '7':np.array(test_data_label_7),
    '8':np.array(test_data_label_8),
    '9':np.array(test_data_label_9),
}
```

#####(5)学習と分類の実行

いよいよ学習処理の実行です。

まず、必要なモジュールをimportし、定数を設定します。
ここで設定する変数はrandom_seedとshotsで、shotsの方は計算回数を指定する定数です。

```ruby:qsvm.py
from qiskit.circuit.library import ZZFeatureMap
from qiskit import BasicAer
from qiskit.aqua.algorithms import QSVM
from qiskit.aqua import QuantumInstance, aqua_globals
from qiskit.aqua.components.multiclass_extensions import AllPairs
from qiskit.aqua.utils.dataset_helper import get_feature_dimension
import traceback

# 処理開始
print('processing...')

random_seed = 10598
shots = 1024
```

以下が量子サポートベクターマシンの学習と分類の実行部分です。
今回は量子コンピュータとしてシミュレータ「'qasm_simulator'」を使用するように設定します<1>。
feature_mapを定義します。これはQSVM特有の設定仕様です。ZZFeatureMapを指定しています。さらに、ここでは教師データの次元数「feature_dimension」を設定する必要があるため、次のように設定します<2>。
` feature_dimension=get_feature_dimension(train_data) `
これらについての詳細は、Qiskitチュートリアルをご参照下さい。
QSVMに教師データとテストデータを設定します<3>。
分類タスクを実行します<4>。

```ruby:qsvm.py
# 量子サポートベクターマシンの準備
backend = BasicAer.get_backend('qasm_simulator')　　... <1>
feature_map = ZZFeatureMap(feature_dimension=get_feature_dimension(train_data), reps=2)　　... <2>
qsvm = QSVM(feature_map, train_data, test_data, total_data_list, multiclass_extension=AllPairs())　　... <3>
quantum_instance = QuantumInstance(backend, shots=shots, seed_simulator=random_seed, seed_transpiler=random_seed)
# 計算実行
result = qsvm.run(quantum_instance)　　... <4>
```

#####(6)結果の表示

実行結果を表示し、またファイルに格納します。

```ruby:qsvm.py
print('正解率', result['testing_accuracy'])
print('クラス分類結果', result['predicted_classes'])

# 結果をファイルに保存
with open('qsvm_multiclass_result.txt', 'w', encoding='utf-8') as f:
    f.write(str(result))
```

以上でプログラムの流れごとの処理の説明を終わります。


#### 3. ソース全体

以下、今回実装したソースの全体像です。

```ruby:qsvm.py
import struct
import numpy as np
import cv2
import time, datetime

# プログラム開始表示
start = time.time()
print('start:', datetime.datetime.fromtimestamp(start).strftime('%Y/%m/%d %H:%M:%S'))
with open('qsvm_multiclass_start_end_check.txt', 'w', encoding='utf-8') as f:
    f.write('start:' + datetime.datetime.fromtimestamp(start).strftime('%Y/%m/%d %H:%M:%S') + '\n')


# MNIST画像分割数(画像サイズを何分の一に縮小するか?の設定値)
img_divisor = 7

# 取込教師データ数
training_limit = 20
# 取込テストデータ数
testing_limit = 10


# 画像縮小
def reduce_size(img_file):
    img = cv2.imread(img_file)
    reduced_img_data = cv2.resize(img, (img.shape[1] // img_divisor, img.shape[0] // img_divisor))
    return reduced_img_data


# 特徴量ベクトルの作成
def get_feature_vector(img_data):
    
    feature_list = []    
    for d1 in img_data:
        for d2 in d1:
            feature_list.append(d2[0] / 255)
    
    return np.array(feature_list)

# 全データ保持リストにすでに存在するデータかどうかの判定
def check_exists_total_list(total_data_list, feature_vector):
    for data_row in total_data_list:
        if str(data_row) == str(feature_vector):
            return True

    # 存在しない場合、Falseを返す
    return False
   

# 正解ラベルの中身を確認
lbl_f = open('./mnist/train-labels.idx1-ubyte', 'rb')
dummy, lbl_count = struct.unpack('>II', lbl_f.read(8))
# 正解ラベル数
print('正解ラベル数:', lbl_count)

# 教師画像データの数値化
img_f = open('./mnist/train-images.idx3-ubyte', 'rb')
dummy, img_count = struct.unpack('>II', img_f.read(8))
rows, cols = struct.unpack('>II', img_f.read(8))
print('画像サイズ:', rows, 'x', cols)

# 教師データをとテストデータの両方を保持するリスト(全データ保持リスト)
total_data_list = []

# ラベル別教師データ格納配列
train_data_label_0 = []
train_data_label_1 = []
train_data_label_2 = []
train_data_label_3 = []
train_data_label_4 = []
train_data_label_5 = []
train_data_label_6 = []
train_data_label_7 = []
train_data_label_8 = []
train_data_label_9 = []


# MNIST教師データを順に読み込む
for _ in range(training_limit):
    
    # 正解ラベルを取得
    label = struct.unpack('B', lbl_f.read(1))[0]
    
    # 教師データを取得
    bdata = img_f.read(rows * cols)
    RGB_data = list(map(lambda n: [n, n, n], bdata))
    img_data = np.array(RGB_data).reshape((28,28, 3))
    cv2.imwrite('mnist_tmp.jpg', img_data)
    reduced_img_data = reduce_size('mnist_tmp.jpg')
    
    # 特徴量ベクトルの変換(画像データを1 x 16次元に変換)
    feature_vector = get_feature_vector(reduced_img_data)
    
    # 全データ保持リストにすでに存在するデータかどうかの判定
    if check_exists_total_list(total_data_list, feature_vector) == False:
        # もし存在しないデータパターンの場合、全データ保持リストに追加する
        total_data_list.append(feature_vector)
    
        # ラベルごとに教師データ(特徴量ベクトル)を格納
        if label == 0:
            train_data_label_0.append(feature_vector)
        elif label == 1:
            train_data_label_1.append(feature_vector)
        elif label == 2:
            train_data_label_2.append(feature_vector)
        elif label == 3:
            train_data_label_3.append(feature_vector)
        elif label == 4:
            train_data_label_4.append(feature_vector)
        elif label == 5:
            train_data_label_5.append(feature_vector)
        elif label == 6:
            train_data_label_6.append(feature_vector)
        elif label == 7:
            train_data_label_7.append(feature_vector)
        elif label == 8:
            train_data_label_8.append(feature_vector)
        elif label == 9:
            train_data_label_9.append(feature_vector)

# 最終教師データ
train_data = {
    '0':np.array(train_data_label_0),
    '1':np.array(train_data_label_1),
    '2':np.array(train_data_label_2),
    '3':np.array(train_data_label_3),
    '4':np.array(train_data_label_4),
    '5':np.array(train_data_label_5),
    '6':np.array(train_data_label_6),
    '7':np.array(train_data_label_7),
    '8':np.array(train_data_label_8),
    '9':np.array(train_data_label_9),
}

# 取得した教師データ数
print('label_0 number of data:', len(train_data_label_0))
print('label_1 number of data:', len(train_data_label_1))
print('label_2 number of data:', len(train_data_label_2))
print('label_3 number of data:', len(train_data_label_3))
print('label_4 number of data:', len(train_data_label_4))
print('label_5 number of data:', len(train_data_label_5))
print('label_6 number of data:', len(train_data_label_6))
print('label_7 number of data:', len(train_data_label_7))
print('label_8 number of data:', len(train_data_label_8))
print('label_9 number of data:', len(train_data_label_9))

print('train_data', train_data)

# ファイルを閉じる
lbl_f.close()
img_f.close()


# テストデータの中身を確認
test_lbl_f = open('./mnist/t10k-labels.idx1-ubyte', 'rb')
dummy, test_lbl_count = struct.unpack('>II', test_lbl_f.read(8))
print('テストデータ正解ラベル数:', test_lbl_count)

# テスト画像データの数値化
test_img_f = open('./mnist/t10k-images.idx3-ubyte', 'rb')
dummy, test_img_count = struct.unpack('>II', test_img_f.read(8))
rows, cols = struct.unpack('>II', test_img_f.read(8))


# ラベル別テストデータ格納配列
test_data_label_0 = []
test_data_label_1 = []
test_data_label_2 = []
test_data_label_3 = []
test_data_label_4 = []
test_data_label_5 = []
test_data_label_6 = []
test_data_label_7 = []
test_data_label_8 = []
test_data_label_9 = []


# MNIST照すストデータを順に読み込む
for _ in range(testing_limit):

    # 正解ラベルを取得
    test_label = struct.unpack('B', test_lbl_f.read(1))[0]

    # テストデータを取得
    bdata = test_img_f.read(rows * cols)
    RGB_data = list(map(lambda n: [n, n, n], bdata))
    img_data = np.array(RGB_data).reshape((28,28, 3))
    cv2.imwrite('mnist_tmp.jpg', img_data)
    reduced_img_data = reduce_size('mnist_tmp.jpg')
    
    # 特徴量ベクトルの変換(画像データを1 x 16次元に変換)
    feature_vector = get_feature_vector(reduced_img_data)
    
    # ラベルごとに教師データ(特徴量ベクトル)を格納
    if test_label == 0:
        test_data_label_0.append(feature_vector)
    elif test_label == 1:
        test_data_label_1.append(feature_vector)
    elif test_label == 2:
        test_data_label_2.append(feature_vector)
    elif test_label == 3:
        test_data_label_3.append(feature_vector)
    elif test_label == 4:
        test_data_label_4.append(feature_vector)
    elif test_label == 5:
        test_data_label_5.append(feature_vector)
    elif test_label == 6:
        test_data_label_6.append(feature_vector)
    elif test_label == 7:
        test_data_label_7.append(feature_vector)
    elif test_label == 8:
        test_data_label_8.append(feature_vector)
    elif test_label == 9:
        test_data_label_9.append(feature_vector)

        
# 最終テストデータ
test_data = {
    '0':np.array(test_data_label_0),
    '1':np.array(test_data_label_1),
    '2':np.array(test_data_label_2),
    '3':np.array(test_data_label_3),
    '4':np.array(test_data_label_4),
    '5':np.array(test_data_label_5),
    '6':np.array(test_data_label_6),
    '7':np.array(test_data_label_7),
    '8':np.array(test_data_label_8),
    '9':np.array(test_data_label_9),
}

# 取得したテストデータ数
print('test_label_0 number of data:', len(test_data_label_0))
print('test_label_1 number of data:', len(test_data_label_1))
print('test_label_2 number of data:', len(test_data_label_2))
print('test_label_3 number of data:', len(test_data_label_3))
print('test_label_4 number of data:', len(test_data_label_4))
print('test_label_5 number of data:', len(test_data_label_5))
print('test_label_6 number of data:', len(test_data_label_6))
print('test_label_7 number of data:', len(test_data_label_7))
print('test_label_8 number of data:', len(test_data_label_8))
print('test_label_9 number of data:', len(test_data_label_9))

print('test_data', test_data)

# ファイルを閉じる
test_lbl_f.close()
test_img_f.close()

from qiskit.circuit.library import ZZFeatureMap
from qiskit import BasicAer
from qiskit.aqua.algorithms import QSVM
from qiskit.aqua import QuantumInstance, aqua_globals
from qiskit.aqua.components.multiclass_extensions import AllPairs
from qiskit.aqua.utils.dataset_helper import get_feature_dimension
import traceback

try:

    # 処理開始
    print('processing...')

    random_seed = 10598
    shots = 10

    # 教師データ(特徴量ベクトル)の次元を確認
    print('get_feature_dimension(train_data)', get_feature_dimension(train_data))

    # 量子サポートベクターマシンの準備
    backend = BasicAer.get_backend('qasm_simulator')
    feature_map = ZZFeatureMap(feature_dimension=get_feature_dimension(train_data), reps=2)
    qsvm = QSVM(feature_map, train_data, test_data, total_data_list, multiclass_extension=AllPairs())
    quantum_instance = QuantumInstance(backend, shots=shots, seed_simulator=random_seed, seed_transpiler=random_seed)
    # 計算実行
    result = qsvm.run(quantum_instance)

    print('正解率', result['testing_accuracy'])
    print('クラス分類結果', result['predicted_classes'])

    # 結果をファイルに保存
    with open('qsvm_multiclass_result.txt', 'w', encoding='utf-8') as f:
        f.write(str(result))

except:

    import sys
    error = str(sys.exc_info())
    print('error', error)

    # エラー結果をファイルに保存
    with open('qsvm_multiclass_error.txt', 'w', encoding='utf-8') as f:
        f.write(error)

finally:

    # プログラム終了表示
    end = time.time()
    print('end:', datetime.datetime.fromtimestamp(end).strftime('%Y/%m/%d %H:%M:%S'))

    # 処理時間(分)
    print('total time:', (end - start) / 60, 'minutes')

    with open('qsvm_multiclass_start_end_check.txt', 'a', encoding='utf-8') as f:
        f.write('end:' + datetime.datetime.fromtimestamp(end).strftime('%Y/%m/%d %H:%M:%S') + '\n')
        f.write('total time:' + str((end - start) / 60) + 'minutes' + '\n')
```

## :scorpius: 実行結果と課題及び改善策

#### 1. 実行結果

実行結果は以下となりました。

>{'testing_accuracy': 0.19999999999999996, 'test_success_ratio': 0.19999999999999996, 'predicted_labels': array([5, 0, 4, 1, 9, 2, 1, 3, 1, 4, 3, 5, 3, 6, 1, 7, 2, 8, 6, 9]), 'predicted_classes': ['5', '0', '4', '1', '9', '2', '1', '3', '1', '4', '3', '5', '3', '6', '1', '7', '2', '8', '6', '9']}

実行に要した時間は4時間ほどです。
今回はQSVMの性能を考慮し、対象画像を(4x4)ビットに縮小して実行しました。
実行環境・マシン性能・処理時間などを鑑み、現実的な範囲で解を上記としました。

#### 2. 課題及び改善策

今回はシミュレータを使用して計算を実行しましたが、シミュレータの処理能力ではその不足がはっきりわかります。シミュレータはあくまで、計算ロジックの確認や入出力データの不備の確認など、本番計算の手前に実施する事前動作確認のためのものという印象でした。

さらなる取り組みとして、IBM量子コンピュータ実機での検証が次のステップになります。

## :scorpius: 関連情報

量子SVMの基本 - IBM Qiskit SVM(QSVM)を実装し、線形・非線形の基本的な2クラス分類機能を確認する
https://qiita.com/ttabata/items/937c3b1edf3ead03c179

Qiskitで量子SVMを実装して性能評価してみた
https://qiita.com/ucc_white/items/f2ea0d019979dd675f82

Qiskit 入門
https://qiskit.org/documentation/locale/ja_JP/getting_started.html

量子サポート・ベクター・マシン (QSVM)
https://qiskit.org/documentation/locale/ja_JP/tutorials/machine_learning/01_qsvm_classification.html?highlight=qsvm



## :scorpius: ご意見など

ご意見、間違い訂正などございましたらお寄せ下さい。