Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quantum-kittens
GitHub Repository: quantum-kittens/platypus
Path: blob/main/translations/ja/ch-applications/facial-expression-recognition.ipynb
3861 views
Kernel: Python 3

量子顔表情認識

顔表情認識(FER)は環境予測、コンテンツ分析、ヘルスケア支援、行動記述などの多くの応用があるヒューマン-コンピューターインタラクションに関連する極めて重要なタスクです。FERは顔画像を怒り、恐怖、驚き、悲しみ、喜びなどを示す表現のカテゴリーと結びつける分類問題です。人間の顔の不均一性、ポーズと背景の多様性のため、この問題をコンピューターモデルを用いて解くことは困難です。FERに対する伝統的なアプローチでは画像の前処理、特徴量抽出、表情分類の3つの大まかなステップを踏みます。ここでは教師ありの表情分類がどのように量子コンピューターで実装できるかをみていきます。最初の予備的なステップである画像の前処理はデータベースのアイテムを生成する古典的なタスクです。一方で、特徴量抽出の段階では前処理済みの画像をグラフにマッピングします。私たちの実装では、分類の段階が量子パートで、ここでは画像分類器を表す量子回路の入力となる量子状態の振幅に特徴がマップされます。

顔認証と分析は強力な道具であるものの、特に偏見やプライバシー、同意をめぐる倫理的な懸念が伴います。これを念頭に置いて、量子表情認識の高速化については未検証であることを考慮し、この研究は低リスクだとみなしています。しかしながら、将来の研究、特に応用ではプライバシーや同意の問題に抵触するかもしれず、同様の評価が必要になります。私たちは自分たちのソフトウェアの潜在的な正と負のインパクトを必ず考慮する必要があります。一方でまた、ソフトウェアを訓練するときは多様性のあるデータベースを使い、差別的な影響を与える人間の偏見を入れることを避けるように責任を持つ必要があります。

私たちのアプローチでは写真のデータセット(以下の数値実験ではFFHQデータセット[2]を使います)の一部を取り出し、その部分集合に含まれるアイテムのラベル付けを行います。 そしてラベル付けされたインスタンスとラベル付けされていないテストインスタンスを量子状態にエンコードします。この量子状態はテストデータのラベルを推測する量子回路の入力として利用されます。グラフ表示におけるユークリッド距離で測った時に最も近いアイテムのラベルが推論されるラベルに対応するため[3]、回路は古典的な機械学習における最近接セントロイド法[5] と似た方法で動作します。下記で述べる量子分類器は [4]の研究を発展させたものになります。後述するように、この回路の最後の測定によって得られた出力はそれぞれのクラスのラベルとラベル付けされていないテストデータとの距離を表現する特定のコスト関数の最小化とみなせます。従って距離測度をカーネルと解釈すると、このモデルはカーネル法による二値分類器とみなすことができます。

距離を計算する処理の(操作やゲートの数の観点からみた)複雑性は量子ビット数に対して線形です。一方で一般的な古典の手法では特徴量の数に対して線形な数の命令を実行します。

依存関係のインポート

import numpy as np import json import os import matplotlib.pyplot as plt import matplotlib.image as pltimg import matplotlib.tri as plttri from qiskit import * from qiskit.extensions import Initialize import itertools import random

タスク

私たちは(人間の)嬉しい表情または悲しい表情のどちらかを含む写真が与えられたときに、その写真と結びつく正しい感情を出力するプログラムを設計しなければなりません。

タスクは以下の段階に分けることができます。

  1. 画像の前処理: 68個のランドマークの位置(下記の図中の抽象的な顔の上にこれらの点を確認することができます)を抽出する過程である 顔ランドマークの検出 [1]を実行します。この過程の出力は変形された点の座標の集合になります。

  1. 特徴量抽出: これらの点の集合から特徴量のリストを抽出します。特徴量は任意の2つの点の間の距離になります。

  2. 分類: 写真と結びついた特徴量の配列をうれしいか悲しいかのどちらかに分類します。

データセットと画像の前処理

このチュートリアルを完了するためには表情の写真の集合が必要です。あなた自身の写真を使うこともできますし、代わりにdatasets/FFHQのフォルダーに付属している写真も使えます。フォルダーには ここから無料でダウンロード可能な.FFHQデータセット[2]から取ってきた写真が入っています。

それぞれの写真のライセンスはCreative Commons BY 2.0、Creative Commons BY-NC 2.0、Public Domain Mark 1.0、 Public Domain CC0 1.0、U.S. Government Worksのいずれかです。これらのライセンスはすべて、非営利目的であれば自由に使用、再配布、翻案することを許可しています。

画像の前処理の操作は顔のランドマーク検出だけで構成されており、古典的に実行されるため私たちの目的とは関係がありません。FFHQデータではすでに一つの写真に対して68のランドマークが含まれています。

datasets/FFHQ/happydatasets/FFHQ/sad のサブフォルダーの中にはそれぞれxxxxx.pngというファイル名の10個の写真とxxxxx_landmarks.jsonのファイル名のランドマークが入っています。このデータセットについては写真と感情が結びついていないので、後で手動でラベル付けを行います。

データセットを読み込むために以下のコマンドを実行してください。

def load_images_in_folder(folder, label): """load all the images (file ending with .png) within the given folder. Expects the image filename is a five digits code 'xxxxx' and that the file 'xxxxx_landmarks.json' exists within the same folder. Return a structure containing for each image: the path of the image, the given label, the set of landmark points.""" container = [] # get a list of all files ending with '.png' within the given folder images = filter(lambda f: f.endswith('.png'), os.listdir(folder)) # for each png file: for filename in images: # get the id, which is the filename without extension id = os.path.splitext(filename)[0] # calculate the path of the corrisponding landmark points file landmarks_path = "{}/{:05d}_landmarks.json".format(folder, int(id)) # add the current item into the container container.append({ "image": "{}/{}".format(folder, filename), "label": label, "landmarks": json.load(open(landmarks_path)) }) return container faces_dataset = [] faces_dataset += load_images_in_folder("datasets/FFHQ/happy", "happy") faces_dataset += load_images_in_folder("datasets/FFHQ/sad", "sad")

以下の方法で写真とランドマークを可視化することができます。

def visualize_image(face): image = pltimg.imread(face["image"]) x, y = zip(*face["landmarks"]) # divide the list of (x,y) tuple in the list of x's and y's plt.figure(figsize=(8, 6), dpi=80) # set width of figure plt.imshow(image) # show image plt.scatter(x, y, color='red') # overlay the landmark points in red over the image plt.show() # show picture

うれしい表情:

visualize_image(faces_dataset[3])
Image in a Jupyter notebook

悲しい表情:

visualize_image(faces_dataset[13])
Image in a Jupyter notebook

座標の集合から特徴量ベクトルへの変換

顔を表すランドマークの集合は無向重み付きグラフを通してエンコードされます。それぞれのグラフは1つの頂点に1つのランドマーク点が対応し、68の頂点を持ちます。二つの頂点の間の辺は、頂点と結びついている座標間のユークリッド距離でラベル付けされます。グラフに好きなだけ多くの辺を追加することは可能です。辺を追加するほどグラフはより豊富になり(また冗長な可能性もあります)、量子回路へのエンコードのコストもより高くなります。私たちは二つのエンコード手法を提案します。:

  • 完全グラフエンコーディング: 各頂点間に1本の辺があるグラフです。

  • 弦グラフエンコーディング: 弦グラフはドロネー三角形分割アルゴリズムによって作られ、それぞれの頂点は少なくとも2つの辺を持ち、誘導閉路グラフも常に3つの頂点を持ちます。

def build_complete_graph(the_coordinates): """Given a list of N items in form (x,y), return the N*N matrix representing the weighted undirected graph whose vertices are the points, an edge exists between each couple of points and is labelled with the distance between them""" N = len(the_coordinates) # find the number of coordinates graph = np.zeros((N,N)) # create an N*N matrix initializated to zero # for each couple of points calculate the distance: for i, point_i in enumerate(the_coordinates): for j, point_j in enumerate(the_coordinates): distance = np.linalg.norm(np.asarray(point_i) - np.asarray(point_j)) graph[i][j] = distance graph[j][i] = distance return graph def build_chordal_graph(the_coordinates): """Given a list of N items in form (x,y), return the N*N matrix representing the weighted undirected graph whose vertices are the points, an edge exists between the couple of points decided by the Delaunay triangulation procedure, and is labelled with the distance between them""" N = len(the_coordinates) # find the number of coordinates graph = np.zeros((N,N)) # create an N*N matrix initializated to zero x, y = zip(*the_coordinates) # split (x,y) into array of x's and array of y's triang = plttri.Triangulation(x, y) # calculate Delaunay triangulation # for each edge decided by the Delaunay triangulation procedure: for edge in triang.edges: i, j = edge[0], edge[1] point_i, point_j = the_coordinates[i], the_coordinates[j] distance = np.linalg.norm(np.asarray(point_i) - np.asarray(point_j)) graph[i][j] = distance graph[j][i] = distance return graph

グラフを作れたので、そのグラフの隣接行列を構築することができます。行列は対称行列で対角成分は0なので、対角成分を除いた上三角成分だけを考えればよいです。

def pick_upper_triangular_wo_diagonal(matrix): """Given an N*N matrix, return a vector of (N-1)*(N-2) elements containing the items of the input matrix in the upper triangular without diagonal positions""" N, _ = matrix.shape return matrix[np.triu_indices(N, k=1)] # k=1 removes the diagonal

分類器

分類器はベクトル xtestx_\text{test}とそれぞれ、うれしいと悲しい表情を表す2つのベクトル x0,x1x_0, x_1 を入力として受け取ります。最初にユークリッド距離distance(xtest,x0)\mathrm{distance}(x_\text{test}, x_0)distance(xtest,x1)\mathrm{distance}(x_\text{test}, x_1)を計算します。これらの距離をもとにして、最も近い方の代表点、つまりテストアイテムからの距離がもう一方よりも小さい代表点にテストアイテムを分類することができます。

このきわめて簡明な分類器は二つの代表点の選び方に大きく依存します。しかし、この後にこの方法が満足のいく結果を与えることを確認します。さらに、この分類器の精度は代表点を変えて classifier 関数を複数回呼び出し、最頻出のラベルにテストインスタンスを分類することによって改善することができます。

距離関数の古典計算

量子分類器を古典の対応物と比較するために、距離を古典的に計算して、推論したラベルを返します。

def classical_distance(G_happy, G_sad, G_test, tol=0.00001): """Receives in input the three instances as numpy array of equal length, optionally a tolerance for equality operation and outputs HAPPY if happy_instance is closer to test_instance than to sad_instance else SAD if happy_instance is further from test_instance than from sad_instance else EQUAL if happy_instance and sad_instance are equally distant by test_instance""" # distance between the test instance and the happy instance distance_happy = np.linalg.norm(G_happy - G_test) # distance between the test instance and the sad instance distance_sad = np.linalg.norm(G_sad - G_test) # the difference is = 0 if the distance is equal, > 0 if sad is closest, < 0 if happy is closest difference = distance_happy - distance_sad # remove some numerical error that can classify numbers like +-0.00001 as Y1 or Y0 instead of EQUALS the_difference = 0 if np.abs(difference) <= tol else difference # sign(difference) = 0 -> EQUAL; sign(difference) = 1 -> SAD; sign(difference) = -1 -> HAPPY return difference, ["EQUAL", "SAD", "HAPPY"][int(np.sign(the_difference))]

距離の量子計算: 補助的な関数

配列を操作する二つの補助的な関数を定義すると便利です。

一つ目の関数はデータを格納する量子レジスターとサイズを合わせるために配列に0を付け加えるのに使われます。 mm 量子ビットの量子レジスターはちょうど2m2^m 個の係数を持ちます。これはnn個の要素をもつ配列は少なくともm=log2nm = \lceil \log_2 n \rceil個の量子ビットを必要とすることを意味します。ここで最後の 2mn2^m - n 個の係数は0になります。ある配列と量子ビットの個数mmが与えられたときに、zero_padding関数は必要な0係数を付け加え、0埋めされた新しい配列を返します。

def zero_padding(vector, qubits): """Given a complex vector of size n, adds up zeros until size 2**qubits is reached""" zeros = np.zeros((2**qubits,), dtype=np.cdouble) zeros[:len(vector)] = vector return zeros

配列を保持する量子レジスターはノルムが1でなければなりません。normalize メソッドは配列を規格化します。

def normalize(vector): """Return the normalized input vector""" max_iter = 10 while np.linalg.norm(vector) != 1.0 and max_iter > 0: vector = vector / np.linalg.norm(vector) max_iter -= 1 return vector

最後にデータをエンコードする処理が必要です。この目的のためにInitialize ゲートを使います。データをエンコードするユニタリー変換に続いて作用する、量子ビットを0|0\rangleに初期化する非線形なReset ゲートを1つの量子ビットに対し1つずつ並べることでInitialize が構成されていたことに注意してください。Initialize は非常に非効率になり得るため、アルゴリズムの速さはこのゲートの効率性に依存します。

量子分類器の回路を実装するためには制御Initialize ゲートが必要になります。 これは(必要な制御量子ビットの数だけをパラメーターとしてもつ)controlメソッドを呼び出すことで自動的に計算することができるでしょう。しかしながら、control は線形回路でのみ動作するので、先頭の Reset ゲートを取り除く方法を見つける必要があります。

現在実現可能な最も簡単な方法は、

  1. Initialize ゲートを作る。

  2. 回路を逆にしてReset ゲートを削除するgates_to_uncompute メソッドを呼び出す。

  3. 回路を再度逆にするためにinvert メソッドを呼び出す。これによりリセットを含まない初期化が行えます。

  4. control メソッドを呼び出す。

def controlled_initialize(normalized_vector, control_qubits): # create the circuit for initialization with leading Reset gates initialize_circuit = Initialize(normalized_vector) # gates_to_uncompute inverts the circuit, removing the Reset gates # and by inverting again we obtain the initialization circuit w/o Reset initialize_circuit_wo_reset = initialize_circuit.gates_to_uncompute().inverse() # return controlled version of the gate return initialize_circuit_wo_reset.control(control_qubits)

距離の量子計算: 回路

距離関数は以下の回路で計算します。

回路の入力は4つのレジスターで構成されています:

  • 一つ目の単一量子ビットのレジスターa|a\rangleは補助レジスターと呼ばれます。

  • 二つ目のレジスターi|i\rangleはインデックスレジスターです。

  • 三つ目のレジスター d|d\rangle はデータレジスターです。

  • 四つ目のレジスター c|c\rangle はクラスレジスターです。

グラフを表現する特徴量ベクトルG=[g1,...,gd]G = [g_1, ..., g_d] が与えられたとき、量子状態G=1γi=1dgii|G\rangle = \frac{1}{\gamma}\sum_{i=1}^d g_i |i\rangle を構築することができます。ここでγ=i=1dgk2\gamma = \sqrt{\sum_{i=1}^d |g_k|^2} は規格化係数です。

回路による発展は以下のようになります:

  1. 回路は 0a0i0d0c;|0\rangle_a |0\rangle_i |0\rangle_d |0\rangle_c;の状態から開始します。

  2. 最初は補助レジスターとインデックスレジスターが均一な重ね合わせになるように二つのアダマールゲートが作用し、その後の状態は12(0+1)a(0+1)i0d0c\frac{1}{2} (|0\rangle+|1\rangle)_a (|0\rangle+|1\rangle)_i |0\rangle_d |0\rangle_cです。

  3. 制御初期化ゲートがテストインスタンスに作用したあとの状態は120a(0+1)i0d0c+121a(0+1)iGtestd0c;\frac{1}{2} |0\rangle_a (|0\rangle+|1\rangle)_i |0\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a (|0\rangle+|1\rangle)*i |G*\text{test}\rangle_d |0\rangle_c;です。

  4. 補助量子ビットにXが作用した後の状態は120a(0+1)iGtestd0c+121a(0+1)i0d0c;\frac{1}{2} |0\rangle_a (|0\rangle+|1\rangle)*i |G*\text{test}\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a (|0\rangle+|1\rangle)*i |0\rangle_d |0\rangle_c;です。データ GtestG_\text{test} は今、補助ビットの 00と結びついており、これによって後でデータとG1G_1G2G_2とを干渉させることができます。*

  5. 二つの制御レジスターをもつ制御初期化ゲートが一つ目の代表点の状態に作用した後の状態は120a(0+1)iGtestd0c+121a0i0d0c+121a1iG0d0c;\frac{1}{2} |0\rangle_a (|0\rangle+|1\rangle)*i |G*\text{test}\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |0\rangle_i |0\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |1\rangle_i |G_0\rangle_d |0\rangle_c;です。

  6. Xがインデックスレジスターに作用した後の状態は120a(0+1)iGtestd0c+121a0iG0d0c+121a1i0d0c;\frac{1}{2} |0\rangle_a (|0\rangle+|1\rangle)*i |G*\text{test}\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |0\rangle_i |G_0\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |1\rangle_i |0\rangle_d |0\rangle_c; です。データ G1G_1 は今、補助ビットの 11 とインデックスの 00と結びついています。

  7. 二つの制御レジスターをもつ制御初期化ゲートが二つ目の代表点の状態に作用した後の状態は 120a(0+1)iGtestd0c+121a0iG0d0c+121a1iG1d0c;\frac{1}{2} |0\rangle_a (|0\rangle+|1\rangle)*i |G*\text{test}\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |0\rangle_i |G_0\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |1\rangle_i |G_1\rangle_d |0\rangle_c; です。データ G2G_2 は今、補助ビットの 11 とインデックスの11と結びついています。

  8. インデックスレジスターとクラスレジスターの間のCNOTゲートの後の状態は120a0iGtestd0c+120a1iGtestd1c+121a0iG0d0c+121a1iG1d1c;\frac{1}{2} |0\rangle_a |0\rangle_i |G_\text{test}\rangle_d |0\rangle_c + \frac{1}{2} |0\rangle_a |1\rangle_i |G_\text{test}\rangle_d |1\rangle_c + \frac{1}{2} |1\rangle_a |0\rangle_i |G_0\rangle_d |0\rangle_c + \frac{1}{2} |1\rangle_a |1\rangle_i |G_1\rangle_d |1\rangle_c;です。可視化のため、クラスレジスターが二つのラベルを含むことを思い出せるよう 0c|0\rangle_cy0c|y_0\rangle_c に、1c|1\rangle_cy1c|y_1\rangle_c に対応させます。120a0iGtestdy0c+120a1iGtestdy1c+121a0iG0dy0c+121a1iG1dy1c;\frac{1}{2} |0\rangle_a |0\rangle_i |G_\text{test}\rangle_d |y_0\rangle_c + \frac{1}{2} |0\rangle_a |1\rangle_i |G_\text{test}\rangle_d |y_1\rangle_c + \frac{1}{2} |1\rangle_a |0\rangle_i |G_0\rangle_d |y_0\rangle_c + \frac{1}{2} |1\rangle_a |1\rangle_i |G_1\rangle_d |y_1\rangle_c; この式は以下のように整理できます。 12k0,1(0aGtestd+1aGkd)kiykc; \frac{1}{2} \sum_{k \in {0, 1}} \Big( |0\rangle_a |G_\text{test}\rangle_d + |1\rangle_a |G_k\rangle_d \Big) |k\rangle_i |y_k\rangle_c; この操作は インデックス 00をクラス y0y_0 にインデックス11をクラスy1y_1に結びつけます。

  9. 最後のアダマールゲート後の状態は122k0,1(0a(Gtest+Gk)d+1a(GtestGk)d)kiykc;\frac{1}{2\sqrt{2}} \sum_{k \in {0, 1}} \Big( |0\rangle_a (|G_\text{test}\rangle + |G_k\rangle)*d + |1\rangle_a (|G*\text{test}\rangle - |G_k\rangle)*d \Big) |k\rangle_i |y_k\rangle_c; です。この操作はデータ GtestG*\text{test}G1,G2G_1, G_2と干渉させ、距離の計算を可能にします。

def quantum_distance_circuit(G_happy, G_sad, G_test): """Create the quantum circuit for classification""" # calculate how many qubits are needed for the data register qubit_per_feature = int(np.ceil(np.log2(len(G_happy)))) # zero padding and normalization x_0 = normalize(zero_padding(G_happy, qubit_per_feature)) x_1 = normalize(zero_padding(G_sad, qubit_per_feature)) x_test = normalize(zero_padding(G_test, qubit_per_feature)) # define the registers qr_auxiliary = QuantumRegister(1, 'anc') qr_index = QuantumRegister(1, 'index') qr_data = QuantumRegister(qubit_per_feature, 'data') qr_class = QuantumRegister(1, 'class') cr_auxiliary = ClassicalRegister(1, 'cr_anc') cr_class = ClassicalRegister(1, 'cr_class') # initialize the circuit qc = QuantumCircuit(qr_auxiliary, qr_index, qr_data, qr_class, cr_auxiliary, cr_class) # initialize index and data registers qc.h([0, 1]) qc.append(controlled_initialize(x_test, 1), [qr_auxiliary[0]] + qr_data[:]) qc.x(0) qc.append(controlled_initialize(x_0, 2), [qr_auxiliary[0], qr_index[0]] + qr_data[:]) qc.x(1) qc.append(controlled_initialize(x_1, 2), [qr_auxiliary[0], qr_index[0]] + qr_data[:]) # correlate the index with the class qc.cx(qr_index[0], qr_class[0]) # work on auxiliary qc.h(0) # measure qc.measure(qr_auxiliary[0], cr_auxiliary[0]) qc.measure(qr_class[0], cr_class[0]) return qc

距離の量子計算: 回路

ここで補助量子ビットを測定し、0状態に射影します。すると回路は 1Gtest+G00a0iGtest+G0dy0c+1Gtest+G10a1iGtest+G1dy1c\frac{1}{\sqrt{|| G_\text{test} + G_0 ||}} |0\rangle_a |0\rangle_i |G_\text{test} + G_0\rangle_d |y_0\rangle_c + \frac{1}{\sqrt{|| G_\text{test} + G_1 ||}} |0\rangle_a |1\rangle_i |G_\text{test} + G_1\rangle_d |y_1\rangle_c の状態になります。対称性から上記の公式を少し修正することで補助量子ビットが1になる結果も同様に考えることができます。

クラスがy_0=0$または y1=1y_1=1と読み出す確率を推定することでGtest,G1G_\text{test}, G_1間の距離に対するGtest,G0G_\text{test}, G_0間の距離を推定することができます。これより、

ytest={y0,>.5y1,<.5equals,=.5y_\text{test} = \begin{cases} y_0, & > .5 \\ y_1, & < .5 \\ \text{equals}, & = .5 \end{cases}

です。計算の確率的性質に起因する誤差を緩和するために、二つの距離の間の境界に小さな許容誤差ϵ\epsilonを導入できます。 ytest={y0,>.5+ϵy1,<.5ϵequals,otherwisey_\text{test} = \begin{cases} y_0, & > .5+\epsilon \\ y_1, & < .5-\epsilon \\ \text{equals}, & \text{otherwise} \end{cases}

データが一度読み込みされると、距離を計算する部分の回路では定数回の操作が行わるため複雑性はO(1)O(1)になり、一方で、どんな古典的なアルゴリズムもnnをベクトルの成分数とするとO(n)O(n) ステップ必要になるということが簡単に分かります。

def quantum_distance(G_happy, G_sad, G_test, tol=0.00001, backend=None): """Creates the quantum circuit, runs it and return the difference between the two distances and the expected label""" SHOTS = 10000 qc = quantum_distance_circuit(G_happy, G_sad, G_test) # custom backend if backend is None: backend = Aer.get_backend('qasm_simulator') counts = execute(qc, backend, shots=SHOTS).result().get_counts() # add missing values keys = ['0 0', '0 1', '1 0', '1 1'] # LSB = auxiliary, MSB = Class for key in keys: if key not in counts: counts[key] = 0 # calculate distance keep_auxiliary_zero = False # you can equivalently keep results having auxiliary = 0 or auxiliary = 1 if keep_auxiliary_zero: distance_happy = counts['1 0'] distance_sad = counts['0 0'] else: distance_happy = counts['0 1'] distance_sad = counts['1 1'] difference = (distance_happy - distance_sad)/SHOTS the_difference = 0 if np.abs(difference) <= tol else difference return difference, ["EQUAL", "SAD", "HAPPY"][int(np.sign(the_difference))]

画像分類

上記の分類器を使ってFER問題([3])を解くことができます。

簡単のために、顔の点の部分集合だけを考慮します。これにより、量子回路にエンコードする情報量を減らせます。特ににっこりした口は幸福感に関係し、むっつりした口は哀感に関係していると仮定すれば口に関係する点だけを考えれば良いです。

分類に考慮される口の点の数をさらに制限したときに精度がどう変わるかを研究することは興味深いです。このために、 繰り返しなしに48以上68以下のnn個の頂点の部分集合をランダムに生成するpick_mouth_points 関数を定義します。

def pick_mouth_points(n_points): """Returns a numpy array containing 'n_points' numbers between 48 and 68-1 without repetitions, given 0 <= n_points <= 20.""" MOUTH_START, MOUTH_END = 48, 68 return np.sort(np.random.choice(range(MOUTH_START, MOUTH_END), n_points, replace=False))

最後にfaces_classifier メソッドは(load_landmarks)を使ってデータセットから読み込まれた)座標のリストとしての三つの顔happy_instance, sad_instance, test_instance といくつかの構成パラメーターを受け取ります。

  • is_quantumquantum_distanceのときtrue、 classical_distanceのときfalse

  • is_complete_graphbuild_complete_graphのときtrue、 build_complete_graphのときfalse

  • n_pointsは分類に考慮される口の点を何個ランダムに選ぶかを表し、1以上20以下の整数でなければなりません。

出力は HAPPY または SADの文字列です。

def faces_classifier(happy_instance, sad_instance, test_instance, is_quantum=True, is_complete_graph=True, n_points=20, backend=None): # pick construction method construction_method = build_complete_graph if is_complete_graph else build_complete_graph # choose points chosen_points = pick_mouth_points(n_points) happy_instance = np.array(happy_instance["landmarks"])[chosen_points] sad_instance = np.array(sad_instance["landmarks"])[chosen_points] test_instance = np.array(test_instance["landmarks"])[chosen_points] # calculate features feature_happy = pick_upper_triangular_wo_diagonal(construction_method(happy_instance)) feature_sad = pick_upper_triangular_wo_diagonal(construction_method(sad_instance)) feature_test = pick_upper_triangular_wo_diagonal(construction_method(test_instance)) # classify if is_quantum: distance, label = quantum_distance(feature_happy, feature_sad, feature_test, backend=backend) else: distance, label = classical_distance(feature_happy, feature_sad, feature_test) return label

精度を確認するためにデータセットを訓練セットとテストセットに分けます。

happy_dataset = load_images_in_folder("datasets/FFHQ/happy", "HAPPY") sad_dataset = load_images_in_folder("datasets/FFHQ/sad", "SAD") N_TRAINING_ITEMS = 5 happy_training_set = happy_dataset[:N_TRAINING_ITEMS] sad_training_set = sad_dataset[:N_TRAINING_ITEMS] happy_testing_set = happy_dataset[N_TRAINING_ITEMS:] sad_testing_set = sad_dataset[N_TRAINING_ITEMS:] testing_set = happy_testing_set + sad_testing_set

最後に精度を計算します。

n_points = 15 # from 1 to 20 is_complete_graph = True is_quantum = False correct, wrong = 0, 0 for test_instance in testing_set: happy_instance = random.choice(happy_training_set) sad_instance = random.choice(sad_training_set) label = faces_classifier(happy_instance, sad_instance, test_instance, is_quantum=is_quantum, is_complete_graph=is_complete_graph, n_points=n_points) if label == test_instance["label"]: correct += 1 print(".", end="") else: wrong += 1 print("X", end="") print(f"\nWe have classified {correct} faces correctly and {wrong} faces wrongly")
.......X.. We have classified 9 faces correctly and 1 faces wrongly

明らかに量子、古典のどちらの場合も二つの代表点の選び方に精度は大きく依存します。代表点を変えて顔の分類器を複数回呼び出し、最頻出のラベルを割り当てることによってテストインスタンスの分類を行うことで精度は改善するかもしれません。

def most_common(lst): return max(set(lst), key=lst.count)

最後に一つのアイテムに対して複数回分類を実行します。

n_points = 15 # from 1 to 20 is_complete_graph = True is_quantum = True CLASSIFICATION_PER_ITEM = 3 correct, wrong = 0, 0 for test_instance in testing_set: labels = [] for _ in range(CLASSIFICATION_PER_ITEM): happy_instance = random.choice(happy_training_set) sad_instance = random.choice(sad_training_set) label = faces_classifier(happy_instance, sad_instance, test_instance, is_quantum=is_quantum, is_complete_graph=is_complete_graph, n_points=n_points) labels.append(label) label = most_common(labels) if label == test_instance["label"]: correct += 1 print(".", end="") else: wrong += 1 print("X", end="") print(f"\nWe have classified {correct} faces correctly and {wrong} faces wrongly")
.....X..XX We have classified 7 faces correctly and 3 faces wrongly

参考文献

[1] Facial Landmark Detection, https://paperswithcode.com/task/facial-landmark-detection.

[2] A Style-Based Generator Architecture for Generative Adversarial Networks, Tero Karras (NVIDIA), Samuli Laine (NVIDIA), Timo Aila (NVIDIA), http://stylegan.xyz/paper.

[3] Facial Expression Recognition on a Quantum Computer (2021, DOI 10.1007/s42484-020-00035-5), R. Mengoni, M. Incudini, A. Di Pierro.

[4] Implementing a distance-based classifier with a quantum interference circuit (2017, DOI 10.1209/0295-5075/119/60002), M. Schuld, M. Fingerhuth, F. Petruccione.

[5] 最近接セントロイド分類器