주 콘텐츠로 건너뛰기

양자 커널 훈련

사용 예상 시간: Heron r3 프로세서에서 1분 미만 (참고: 이는 추정치이며, 실제 실행 시간은 다를 수 있습니다.)

학습 목표​

  • 커널 방법과 그 사용 사례

  • 양자 커널, 그리고 양자 커널이 어떻게 향상된 특성 공간을 제공하는지

  • 양자 커널 Circuit 구성

  • Qiskit 패턴을 사용한 양자 커널 훈련 방법: 매핑, 최적화, 실행 및 후처리

사전 요구 사항​

군론에 대한 기본적인 이해도 있으면 도움이 됩니다.

배경​

커널 방법은 머신러닝 응용 분야에서 일반적으로 사용됩니다. 이 맥락에서 "커널"은 커널 행렬 또는 그 개별 항목을 의미합니다. 일반적으로 커널은 고차원 _특성 공간_에 인코딩된 데이터 간의 유사도 측정값으로, 예를 들어 서포트 벡터 머신을 사용한 분류 작업에 활용될 수 있습니다.

양자 커널 방법은 양자 컴퓨터를 사용하여 커널을 추정하는 방법입니다. 양자 컴퓨터가 데이터를 양자 강화 특성 공간에 인코딩할 수 있다는 것은 잘 알려져 있으며, 이를 통해 고전적인 유사 방법을 효과적으로 대체할 수 있습니다. x⃗∈R\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:Rd→K: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d. 고전적으로는 커널 함수를 쉽게 평가할 수 있는 특성 맵, 즉 특성 매핑된 공간에서의 내적을 원래 데이터 벡터로 표현할 수 있고 Ψ(x⃗)\Psi(\vec{x})와 Ψ(y⃗)\Psi(\vec{y})를 직접 구성할 필요가 없는 경우가 관심 대상입니다. 양자 커널의 경우, 특성 매핑은 양자 Circuit을 통해 수행되며 커널은 Circuit에서 샘플링된 측정 확률을 사용하여 추정됩니다.

이 튜토리얼에서는 이진 분류에 사용되는 양자 커널 행렬의 항목을 평가하기 위한 Qiskit 패턴을 구축하는 방법을 설명합니다.

요구 사항​

이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:

  • Qiskit SDK v2.3.1 이상 (시각화 지원 포함)
  • 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 qubit 인스턴스에 대한 Qiskit 패턴의 네 단계를 살펴보고, Qiskit의 StatevectorSampler 프리미티브를 사용하여 단일 커널 행렬 항목을 평가합니다. 상태 벡터 시뮬레이터는 샷 노이즈를 제외하면 정확하며 QPU 시간을 소비하지 않고 방법 전체를 보여줍니다. 이후 하드웨어 예제 섹션에서 실제 하드웨어로 동일한 인스턴스를 반복합니다.

1단계: 고전적 입력을 양자 문제로 변환​

  • 입력: 훈련 데이터셋.

  • 출력: 커널 행렬 항목 계산을 위한 추상 Circuit.

여기서 해결하려는 이진 분류 문제는 "오류가 있는 코셋 레이블링"이라고 합니다. 입력 훈련 데이터셋에는 군과 부분군으로 형성된 두 코셋으로 구성된 군 구조가 포함되어 있습니다. 군은 Qubit에 대해 G=SU(2)⊗nG = SU(2)^{\otimes n}으로 취하며, 이는 2×22 \times 2 행렬의 특수 유니타리 군으로 자연에서 광범위하게 적용됩니다. 예를 들어, 입자 물리학의 표준 모델이 있습니다. 간선 E\mathcal{E}와 정점 V\mathcal{V}를 갖는 그래프에 대해 Sgraph=⟨{Xi⊗k:(k,i)∈EZk}i∈V}⟩S_\text{graph} = \langle \{ X_i \otimes _{k:(k,i) \in \mathcal{E}} Z_k\} _{i \in \mathcal{V}} \} \rangle인 (그래프 안정자) 부분군 Sgraph<GS_\text{graph} < G를 취합니다. 안정자는 Ds∣ψ⟩=∣ψ⟩, ∀s∈SgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}이 되도록 안정자 상태를 고정한다는 점에 유의하세요. 마지막으로, 두 c±∈Gc_\pm \in G를 무작위로 추출하여 두 좌잉여류 C±=c±SgraphC_\pm = c_\pm S_\text{graph}을 정의합니다.

데이터셋과 생성 방법에 대한 자세한 내용은 Quantum Kernel Training Toolkit의 이 노트북을 참조하세요.

커널 행렬의 한 항목을 평가하는 데 사용되는 양자 Circuit을 생성합니다. 입력 데이터를 사용하여 Circuit의 매개변수화된 게이트에 대한 회전 각도를 결정합니다. 간단히 하기 위해 데이터 샘플 x1=14와 x2=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단계: 양자 하드웨어 실행을 위한 문제 최적화​

  • 입력: 특정 Backend에 최적화되지 않은 추상 Circuit.

  • 출력: 선택된 QPU에 최적화된 목표 Circuit.

이 섹션에서 사용하는 상태 벡터 시뮬레이터 경로의 경우, Backend별 최적화가 필요하지 않습니다: 추상 Circuit을 직접 샘플링할 수 있습니다. 이 단계는 아래의 하드웨어 예제에서 실행하며, 여기서는 optimization_level=3을 사용하는 generate_preset_pass_manager로 실제 QPU에 맞게 Circuit을 트랜스파일합니다.

3단계: Qiskit 프리미티브를 사용한 실행​

  • 입력: 추상 Circuit.

  • 출력: 준확률 분포.

Qiskit의 StatevectorSampler 프리미티브를 사용하여 Circuit 샘플링으로부터 얻은 상태의 준확률 분포를 재구성합니다. 커널 행렬 생성 작업에서는 특히 |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)

이전 코드 셀의 출력

4단계: 후처리 및 원하는 고전적 형식으로 결과 반환​

  • 입력: 확률 분포.

  • 출력: 단일 커널 행렬 요소.

오버랩 Circuit에서 ∣0⟩|0 \rangle을 측정할 확률을 계산한 뒤, 이 오버랩 Circuit이 나타내는 샘플에 해당하는 커널 행렬 위치(행 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 qubit 게이트 깊이가 증가하는 오버랩 Circuit을 실행해야 합니다. 따라서 이 튜토리얼을 더 큰 문제로 확장하면 두 가지 비용이 복합적으로 발생합니다: 커널 행렬당 QPU 시간이 NN에 따라 이차적으로 증가하고, unitary_overlap의 깊이(특성 맵과 그 수반 연산자를 합성함)가 현재 하드웨어의 시스템 크기 및 연결성에서 충실도를 저하시킵니다. 데모를 짧게 유지하고 깔끔한 비교를 위해, 소규모 예제에서와 동일한 7 qubit 인스턴스를 실제 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

이전 코드 셀의 출력

Fidelity (hardware): 0.7517

전체 커널 행렬을 채우려면 N(N+1)/2N(N+1)/2개의 고유 항목 각각에 대해 양자 실험을 실행해야 합니다. 아래 그림은 이 데이터셋에 대한 결과 행렬을 보여줍니다; 더 진한 빨간색은 1.0에 가까운 충실도를 나타냅니다.

kernel_matrix.png

다음 단계​

권장 사항

이 작업이 흥미로우셨다면 다음 자료도 살펴보세요: