Skip to main content
IBM Quantum Platform

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

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


学習成果

このチュートリアルを完了すると、以下の内容を理解できるようになります:

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

前提条件

量子カーネルとは何か、なぜ重要なのか、そして実際にどのように活用されているのかについて、あらかじめ理解しておくことをお勧めします。

また、群論の基礎的な知識を持っておくと役立ちます。


背景

カーネル法は、機械学習の応用分野において広く用いられている。 この文脈において、「カーネル」とは、カーネル行列、またはその個々の要素を指す。 一般的に、カーネルとは、高次元の特徴空間に符号化されたデータ間の類似度を測る尺度であり、例えばサポートベクターマシン(SVM)を用いた分類タスクなどで活用される。

量子カーネル法とは、量子コンピュータを用いてカーネルを推定する手法のことである。 量子コンピュータは、量子強化された特徴空間にデータを符号化することができ、これにより従来のアナログ方式を事実上置き換えることができることが知られている。 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 以降、 可視化機能を搭載
  • Qiskit Runtime v0.44.0 以降 (pip install 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 を使用してカーネル行列の1つの要素を評価します。 状態ベクトルシミュレータは(ショットノイズの範囲内では)正確であり、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} となるような安定化子状態を固定することに注意されたい。 最後に、2つの c±Gc_\pm \in G をランダムに選び、2つの左剰余類 C±=c±SgraphC_\pm = c_\pm S_\text{graph} を定義する。

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

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

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

# 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")

Output:

Output of the previous code cell

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

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

このセクションで使用される状態ベクトルシミュレータパスについては、バックエンド固有の最適化は不要であり、抽象回路を直接サンプリングすることができます。 optimization_level=3この手順は、以下のハードウェアの例で実行します。この例では、実際のQPUに対して、`` generate_preset_pass_manager を使用して回路のトランスパイルを行います。

ステップ3: Qiskit primitivesを使用して実行する

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

StatevectorSampler Qiskitのプリミティブを使用して、回路のサンプリングから得られる状態の準確率分布を再構築します。 カーネル行列を生成する課題において、我々は特に|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:

Output of the previous code cell

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

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

オーバーラップ回路において 0|0 \rangle が測定される確率を計算し、この特定のオーバーラップ回路によって表されるサンプルに対応する位置(行15、列20)にカーネル行列を格納する。

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

Output:

Fidelity (simulator): 0.8261

ハードウェアの例

量子カーネル行列には、 NN 個の学習サンプルに対応する O(N2)\mathcal{O}(N^2) 個の要素があり、各要素を処理するには、特徴マップのサイズに応じて2量子ビットゲートの深さが増加するオーバーラップ回路を実行する必要がある。 その結果、このチュートリアルをより大規模な問題に適用する際には、2つの相乗的なコストが生じます。すなわち、カーネル行列あたりのQPU処理時間は NN に比例して2乗的に増加し、また(特徴マップとその随伴行列を合成する)深さが深くなるほど 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]}")

Output:

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

次のステップ

推奨事項

この作品に興味を持たれた方は、以下の資料もご参照ください:

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。