Přeskočit na hlavní obsah

Trénování kvantového kernelu

Odhadovaná doba použití: méně než jedna minuta na procesoru Heron r3 (POZNÁMKA: Jedná se pouze o odhad. Tvá skutečná doba běhu se může lišit.)

Výsledky učení

Po dokončení tohoto tutoriálu by sis měl/a umět vysvětlit následující informace:

  • Metody kernelu a jejich použití
  • Kvantové kernely a jak mohou poskytovat rozšířené prostory příznaků
  • Konstrukce obvodu kvantového kernelu
  • Jak trénovat kvantový kernel pomocí Qiskit pattern: mapovat, optimalizovat, spustit a post-processovat

Předpoklady

Doporučujeme, abys se seznámil/a s kvantovými kernely, proč jsou důležité a jak se v praxi používají.

Užitečné je také základní pochopení teorie grup.

Pozadí

Metody kernelu jsou v aplikacích strojového učení běžné. V tomto kontextu pojem „kernel" označuje matici kernelu nebo jednotlivé její položky. Obecně je kernel mírou podobnosti mezi daty zakódovanými ve vysokodimenzionálním prostoru příznaků a lze jej využít například v klasifikačních úlohách se support vector machines.

Kvantové metody kernelu jsou ty, které k odhadu kernelu používají kvantové počítače. Je známo, že kvantové počítače mohou kódovat data v kvantově rozšířených prostorech příznaků a efektivně nahrazovat klasické analogy. Pro xR\vec{x} \in \mathbb{R} a Ψ(x)Rd\Psi(\vec{x}) \in \mathbb{R}^{d'}, typicky s d>dd' >d, je Ψ(x)\Psi(\vec{x}) feature map, xΨ(x)\vec{x} \mapsto \Psi(\vec{x}). Cílem Ψ(x)\Psi(\vec{x}) je oddělení kategorií dat nadrovinou. Bere-li se jako argument vektory v prostoru feature map, vrací funkce kernelu K(x,y)=Ψ(x)Ψ(y)K(\vec{x}, \vec{y}) = \langle{\Psi(\vec{x}) | \Psi(\vec{y}) \rangle{}} jejich skalární součin: K:RdK: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d. Klasicky jsou zajímavé ty feature mapy, u nichž lze funkci kernelu snadno vyhodnotit; tedy tehdy, kdy skalární součin v prostoru feature mapy lze vyjádřit ve smyslu původních datových vektorů a Ψ(x)\Psi(\vec{x}) a Ψ(y)\Psi(\vec{y}) není třeba konstruovat. V případě kvantových kernelů provádí feature mapping kvantový obvod a kernel se odhaduje pomocí pravděpodobností měření vzorkovaných z obvodu.

Tento tutoriál ukazuje, jak sestavit Qiskit pattern pro vyhodnocování položek matice kvantového kernelu používané pro binární klasifikaci.

Požadavky

Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno následující:

  • Qiskit SDK v2.3.1 nebo novější, s podporou vizualizace
  • Qiskit Runtime v0.44.0 nebo novější (pip install qiskit-ibm-runtime)

Nastavení

# 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

Příklad s malým simulátorem

V této části projdeme čtyři kroky Qiskit pattern na sedmiqubitové instanci problému označování cosetů s chybou a vyhodnotíme jedinou položku matice kernelu pomocí primitiva StatevectorSampler z Qisku. Stavový vektorový simulátor je přesný (až na shot noise) a umožňuje nám ukázat metodu od začátku do konce bez spotřeby času na QPU. Stejnou instanci poté opakujeme na reálném hardwaru v části s hardwarovým příkladem.

Krok 1: Mapování klasických vstupů na kvantový problém

  • Vstup: Trénovací datová sada.
  • Výstup: Abstraktní Circuit pro výpočet položky matice kernelu.

Problém binární klasifikace, který zde chceme řešit, se označuje jako „označování cosetů s chybou." Vstupní trénovací datová sada obsahuje grupovou strukturu, tvořenou dvěma cosety vytvořenými skupinou a podskupinou. Skupina je pro qubity volena jako G=SU(2)nG = SU(2)^{\otimes n}, což je speciální unitární grupa 2×22 \times 2 matic s širokým uplatněním v přírodě; například ve Standardním modelu fyziky částic. Jako (grafový stabilizátor) podgrupu volíme Sgraph<GS_\text{graph} < G s Sgraph={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 pro graf s hranami E\mathcal{E} a vrcholy V\mathcal{V}. Stabilizátory fixují stabilizátorový stav tak, že Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}. Nakonec definujeme dva levé cosety C±=c±SgraphC_\pm = c_\pm S_\text{graph} výběrem dvou c±Gc_\pm \in G náhodně.

Více podrobností o datové sadě a způsobu jejího generování najdeš v tomto notebooku z Quantum Kernel Training Toolkit.

Vytvoříme kvantový Circuit používaný k vyhodnocení jedné položky v matici kernelu. Vstupní data jsou použita k určení úhlů rotace pro parametrizované Gate obvodu. Pro jednoduchost použijeme datové vzorky x1=14 a x2=19.

Poznámka: Datovou sadu použitou v tomto tutoriálu si můžeš stáhnout zde.

# 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 of the previous code cell

Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru

  • Vstup: Abstraktní Circuit, neoptimalizovaný pro konkrétní Backend.
  • Výstup: Cílový Circuit, optimalizovaný pro vybraný QPU.

V cestě se stavovým vektorem použité v této části není vyžadována žádná hardwarově specifická optimalizace: abstraktní Circuit lze vzorkovat přímo. Tento krok si vyzkoušíme v hardwarovém příkladu níže, kde je Circuit transpilován proti reálnému QPU pomocí generate_preset_pass_manager s optimization_level=3.

Krok 3: Spuštění pomocí Qiskit primitiv

  • Vstup: Abstraktní Circuit.
  • Výstup: Kvazi-pravděpodobnostní rozdělení.

Použij primitivum StatevectorSampler z Qisku k rekonstrukci kvazi-pravděpodobnostního rozdělení stavů získaných vzorkováním obvodu. Pro úlohu generování matice kernelu nás zajímá zejména pravděpodobnost naměření stavu |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

Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu

  • Vstup: Pravděpodobnostní rozdělení.
  • Výstup: Jeden prvek matice kernelu.

Vypočítej pravděpodobnost naměření 0|0 \rangle na overlap obvodu a naplň matici kernelu na pozici odpovídající vzorkům reprezentovaným tímto konkrétním overlap obvodem (řádek 15, sloupec 20).

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

Hardwarový příklad

Matice kvantového kernelu má O(N2)\mathcal{O}(N^2) položek pro NN trénovacích vzorků a každá položka vyžaduje spuštění overlap obvodu, jehož hloubka dvouqubitových gate roste s velikostí feature mapy. Škálování tohoto tutoriálu na větší problém proto přináší dvě kumulující se náklady: čas QPU na matici kernelu roste kvadraticky s NN a hloubka unitary_overlap (která skládá feature mapu s jejím adjunktem) snižuje fidelitu při velikosti systému a konektivitě současného hardwaru. Aby byla ukázka krátká a srovnání čisté, spouštíme proto stejnou sedmiqubitovou instanci z příkladu s malým simulátorem na reálném QPU a porovnáváme fidelitu jedné položky matice kernelu s hodnotou simulátoru vypočítanou výše.

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

Pro naplnění celé matice kernelu bychom spustili kvantový experiment pro každou z jejích N(N+1)/2N(N+1)/2 unikátních položek. Níže uvedený obrázek zobrazuje výslednou matici pro tuto datovou sadu; tmavší červená označuje fidelity blíže k 1,0.

kernel_matrix.png

Další kroky

Doporučení

Pokud tě tato práce zaujala, možná tě budou zajímat tyto materiály: