メインコンテンツへスキップ

量子カーネルトレーニング

使用量の目安: Heron r3 プロセッサで1分未満(注意: これはあくまで目安です。実際の実行時間は異なる場合があります。)

学習成果

このチュートリアルを完了すると、以下の内容を理解できることが期待されます:

  • カーネル手法とその用途
  • 量子カーネルと、それが強化された特徴空間をどのように提供できるか
  • 量子カーネル回路の構築
  • Qiskit パターンを使用した量子カーネルのトレーニング方法: マッピング、最適化、実行、後処理

前提条件

量子カーネルについて、その重要性と実際の使用方法を事前に理解しておくことをお勧めします。

群論の基礎知識があると役立ちます。

背景

カーネル手法は機械学習アプリケーションで広く使われています。 この文脈での「カーネル」とは、カーネル行列またはその個々のエントリを指します。 一般的に、カーネルは高次元の 特徴空間 にエンコードされたデータ間の類似度の指標であり、たとえばサポートベクターマシンを使用した分類タスクに利用できます。

量子カーネル手法とは、量子コンピュータを使用してカーネルを推定する手法です。 量子コンピュータは量子強化された特徴空間にデータをエンコードでき、古典的な類似手法を効果的に置き換えられることが知られています。 xR\vec{x} \in \mathbb{R} および Ψ(x)Rd\Psi(\vec{x}) \in \mathbb{R}^{d'} において、通常 d>dd' >d であり、Ψ(x)\Psi(\vec{x}) は特徴マップ xΨ(x)\vec{x} \mapsto \Psi(\vec{x}) です。 Ψ(x)\Psi(\vec{x}) の目的は、データのカテゴリを超平面で分離できるようにすることです。 特徴マッピングされた空間のベクトルを引数として、カーネル関数 K(x,y)=Ψ(x)Ψ(y)K(\vec{x}, \vec{y}) = \langle{\Psi(\vec{x}) | \Psi(\vec{y}) \rangle{}} はそれらの内積を返します: K:RdK: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d。 古典的には、興味深い特徴マップとは、カーネル関数を容易に評価できるもの、すなわち特徴マッピングされた空間での内積を元のデータベクトルで記述でき、Ψ(x)\Psi(\vec{x})Ψ(y)\Psi(\vec{y}) を陽に構築する必要がないものです。 量子カーネルの場合、特徴マッピングは量子回路によって行われ、カーネルは回路からサンプリングされた測定確率を用いて推定されます。

このチュートリアルでは、二値分類に使用される量子カーネル行列のエントリを評価するための Qiskit パターンを構築する方法を示します。

要件

このチュートリアルを始める前に、以下がインストールされていることを確認してください:

  • Qiskit SDK v2.3.1 以降(visualization サポートを含む)
  • Qiskit Runtime v0.44.0 以降 (pip install qiskit-ibm-runtime)

セットアップ

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy pandas qiskit qiskit-ibm-runtime
# General Imports and helper functions
import urllib.request

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.circuit.library import unitary_overlap
from qiskit.primitives import StatevectorSampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, Sampler

# Download the dataset (portable across platforms)
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv",
"dataset_graph7.csv",
)

def visualize_counts(res_counts, num_qubits, num_shots):
"""Visualize the outputs from the Qiskit Sampler primitive."""
zero_prob = res_counts.get(0, 0.0)
top_10 = dict(
sorted(res_counts.items(), key=lambda item: item[1], reverse=True)[
:10
]
)
top_10.update({0: zero_prob})
by_key = dict(sorted(top_10.items(), key=lambda item: item[0]))
x_vals, y_vals = list(zip(*by_key.items()))
x_vals = [bin(x_val)[2:].zfill(num_qubits) for x_val in x_vals]
y_vals_prob = []
for t in range(len(y_vals)):
y_vals_prob.append(y_vals[t] / num_shots)
y_vals = y_vals_prob
plt.bar(x_vals, y_vals)
plt.xticks(rotation=75)
plt.title("Results of sampling")
plt.xlabel("Measured bitstring")
plt.ylabel("Probability")
plt.show()

def get_training_data():
"""Read the training data."""
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None)
training_data = df.values[:20, :]
ind = np.argsort(training_data[:, -1])
X_train = training_data[ind][:, :-1]

return X_train

小規模シミュレータの例

このセクションでは、ラベリング・コセット・エラー問題の7量子ビットインスタンスに対してQiskitパターンの4ステップを順に説明し、QiskitのStatevectorSamplerプリミティブを使用して単一のカーネル行列エントリを評価します。状態ベクトルシミュレータは(ショットノイズを除いて)厳密であり、QPU時間を消費せずに手法全体をエンドツーエンドで示すことができます。続いて、ハードウェア例のセクションで実際のハードウェア上で同じインスタンスを繰り返します。

ステップ 1: 古典的な入力を量子問題にマッピングする

  • 入力: トレーニングデータセット。
  • 出力: カーネル行列のエントリを計算するための抽象回路。

ここで解こうとする二値分類問題は「コセットのエラーラベリング」と呼ばれています。入力トレーニングデータセットには群構造が含まれており、ある群とその部分群によって形成される2つのコセットから成ります。 群は量子ビットに対して G=SU(2)nG = SU(2)^{\otimes n} とし、これは 2×22 \times 2 行列の特殊ユニタリ群であり、自然界に広く適用できます(例: 素粒子物理学の標準模型)。 グラフの辺集合 E\mathcal{E} と頂点集合 V\mathcal{V} に対して、(グラフスタビライザー)部分群 Sgraph<GS_\text{graph} < GSgraph={Xik:(k,i)EZk}iV}S_\text{graph} = \langle \{ X_i \otimes _{k:(k,i) \in \mathcal{E}} Z_k\} _{i \in \mathcal{V}} \} \rangle と定義します。 スタビライザーはスタビライザー状態 Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph} を固定することに注意してください。 最後に、c±Gc_\pm \in G をランダムに選ぶことで、2つの左コセット C±=c±SgraphC_\pm = c_\pm S_\text{graph} を定義します。

データセットの詳細と生成方法については、Quantum Kernel Training Toolkitこのノートブックを参照してください。

カーネル行列の1つのエントリを評価するために使用する量子回路を作成します。 回路のパラメータ化されたゲートの回転角を決定するために入力データを使用します。 簡略化のため、データサンプル x1=14x2=19 を使用します。

注意: このチュートリアルで使用するデータセットはこちらからダウンロードできます。

# Prepare training data
X_train = get_training_data()

# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)

# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)

# Assign tunable parameter to known optimal value and set the data params for
# first two samples
x1 = 14
x2 = 19
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])

# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
overlap_circ.draw("mpl", scale=0.6, style="iqp")

前のコードセルの出力

ステップ 2: 量子ハードウェア実行のために問題を最適化する

  • 入力: 特定のバックエンドに最適化されていない抽象回路。
  • 出力: 選択したQPUに最適化されたターゲット回路。

このセクションで使用する状態ベクトルシミュレータのパスでは、バックエンド固有の最適化は不要です。抽象回路を直接サンプリングできます。このステップは、後述のハードウェア例で実際に行います。そこでは、generate_preset_pass_manageroptimization_level=3 を使用して実際のQPUに対して回路をトランスパイルします。

ステップ 3: Qiskit プリミティブを使用して実行する

  • 入力: 抽象回路。
  • 出力: 擬似確率分布。

Qiskit の StatevectorSampler プリミティブを使用して、回路のサンプリングから得られる状態の擬似確率分布を再構築します。カーネル行列を生成するタスクでは、|0> 状態を測定する確率に特に注目します。

sampler = StatevectorSampler()

# Execute and get counts
num_shots = 10_000
results = sampler.run([overlap_circ], shots=num_shots).result()
counts = results[0].data.meas.get_int_counts()

# Plot counts
visualize_counts(counts, num_qubits, num_shots)

Output of the previous code cell

ステップ 4: 後処理を行い、目的の古典形式で結果を返す

  • 入力: 確率分布。
  • 出力: 単一のカーネル行列要素。

オーバーラップ回路で 0|0 \rangle を測定する確率を計算し、この特定のオーバーラップ回路が表すサンプルに対応する位置(行15、列20)のカーネル行列を埋めます。

kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (simulator): {kernel_matrix[x1, x2]}")
Fidelity (simulator): 0.8261

ハードウェアの例

量子カーネル行列は NN 個のトレーニングサンプルに対して O(N2)\mathcal{O}(N^2) 個のエントリを持ち、各エントリでは特徴マップのサイズとともに2量子ビットゲート深度が増加するオーバーラップ回路を実行する必要があります。そのため、このチュートリアルをより大きな問題にスケールさせると、2つのコストが複合します: QPU時間はカーネル行列あたり NN について二次的に増加し、unitary_overlap(特徴マップとその随伴を合成する)の深度は現在のハードウェアのシステムサイズと接続性でフィデリティを低下させます。デモを短く保ち、明確な比較を行うため、小規模の例と同じ7量子ビットインスタンスを実際のQPU上で実行し、単一カーネル行列エントリのフィデリティを上記で計算したシミュレータの値と比較します。

# ------------------------------ Step 1 ------------------------------
# Prepare training data
X_train = get_training_data()

# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)

# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)

# Assign tunable parameter to known optimal value and
# set the data params for first two samples
x1 = 14
x2 = 19
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])

# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()

# ------------------------------ Step 2 ------------------------------
service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=overlap_circ.num_qubits
# )
backend = service.backend("ibm_pittsburgh")
print(f"Using backend: {backend.name}")
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)

# ------------------------------ Step 3 ------------------------------
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT_QKT"]

num_shots = 10_000
results = sampler.run([overlap_ibm], shots=num_shots).result()
counts = results[0].data.meas.get_int_counts()
visualize_counts(counts, num_qubits, num_shots)

# ------------------------------ Step 4 ------------------------------
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (hardware): {kernel_matrix[x1, x2]}")
Using backend: ibm_pittsburgh

Output of the previous code cell

Fidelity (hardware): 0.7517

カーネル行列全体を埋めるには、その N(N+1)/2N(N+1)/2 個のユニークなエントリそれぞれに対して量子実験を実行します。以下の図はこのデータセットに対する結果の行列を示しています。濃い赤色ほどフィデリティが1.0に近いことを示します。

kernel_matrix.png

次のステップ

Recommendations

この研究に興味を持った方は、以下の資料もご覧ください: