Trening kwantowego jądra (Quantum kernel training)
Szacowany czas użycia: poniżej jednej minuty na procesorze Heron r3 (UWAGA: to tylko szacunek. Twój czas wykonania może się różnić.)
Cele uczenia
Po ukończeniu tego samouczka możesz spodziewać się zrozumienia następujących zagadnień:
- Metody jądrowe i ich zastosowania
- Kwantowe jądra i to, jak mogą zapewniać rozszerzone przestrzenie cech
- Budowa kwantowych obwodów jądra
- Jak trenować kwantowe jądro przy użyciu wzorca Qiskit: odwzorowanie, optymalizacja, wykonanie i post-processing
Wymagania wstępne
Zaleca się zapoznanie z kwantowymi jądrami, ich znaczeniem i praktycznym zastosowaniem.
- Covariant quantum kernels for data with group structure (artykuł)
- Introduction to Quantum Kernels and Support Vector Machines (wideo)
- Quantum Kernels in Practice (wideo)
Przydatna jest również podstawowa znajomość teorii grup.
Tło
Metody jądrowe są powszechnie stosowane w zastosowaniach uczenia maszynowego. W tym kontekście „jądro" odnosi się do macierzy jądra lub jej poszczególnych wpisów. Ogólnie rzecz biorąc, jądro jest miarą podobieństwa między danymi zakodowanymi w wielowymiarowej przestrzeni cech i może być wykorzystywane, na przykład, w zadaniach klasyfikacji z maszynami wektorów nośnych.
Kwantowe metody jądrowe to te, które używają komputerów kwantowych do szacowania jądra. Wiadomo, że komputery kwantowe mogą kodować dane w kwantowo-rozszerzonych przestrzeniach cech, skutecznie zastępując klasyczne odpowiedniki. Dla i , zazwyczaj z , jest odwzorowaniem cech, . Celem jest uczynienie kategorii danych rozdzielonymi przez hiperpłaszczyznę. Biorąc wektory w przestrzeni z odwzorowanymi cechami jako argumenty, funkcja jądra zwraca ich iloczyn skalarny: . Klasycznie interesujące odwzorowania cech to te, w których funkcja jądra może być łatwo obliczona; tzn. gdy iloczyn skalarny w przestrzeni z odwzorowanymi cechami można wyrazić w kategoriach oryginalnych wektorów danych, a i nie muszą być konstruowane. W przypadku kwantowych jąder odwzorowanie cech jest wykonywane przez obwód kwantowy, a jądro jest szacowane przy użyciu prawdopodobieństw pomiarów próbkowanych z obwodu.
Ten samouczek pokazuje, jak zbudować wzorzec Qiskit do obliczania wpisów macierzy kwantowego jądra używanej w klasyfikacji binarnej.
Wymagania
Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane następujące elementy:
- Qiskit SDK v2.3.1 lub nowszy, z obsługą wizualizacji
- Qiskit Runtime v0.44.0 lub nowszy (
pip install qiskit-ibm-runtime)
Konfiguracja
# 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
Przykład z symulatorem na małą skalę
W tej sekcji przechodzimy przez cztery kroki wzorca Qiskit na siedmio-qubitowej instancji problemu etykietowania klas bocznych z błędem i obliczamy pojedynczy wpis macierzy jądra przy użyciu prymitywu StatevectorSampler z Qiskit. Symulator wektora stanu jest dokładny (z wyjątkiem szumu strzałów) i pokazuje nam metodę od początku do końca bez zużywania czasu QPU. Następnie powtarzamy tę samą instancję na rzeczywistym sprzęcie w sekcji przykładu sprzętowego.
Krok 1: Odwzorowanie klasycznych danych wejściowych na problem kwantowy
- Dane wejściowe: Zbiór danych treningowych.
- Dane wyjściowe: Abstrakcyjny Circuit do obliczania jednego wpisu macierzy jądra.
Problem klasyfikacji binarnej, który tutaj rozwiązujemy, jest określany jako „etykietowanie klas bocznych z błędem". Wejściowy zbiór danych treningowych zawiera strukturę grupową, składającą się z dwóch klas bocznych utworzonych przez grupę i podgrupę. Grupę przyjmuje się jako dla qubitów, czyli specjalną grupę unitarną macierzy , mającą szerokie zastosowanie w przyrodzie; np. w Modelu Standardowym fizyki cząstek. Przyjmujemy podgrupę (stabilizatora grafu) z dla grafu z krawędziami i wierzchołkami . Należy zauważyć, że stabilizatory ustalają stan stabilizatora taki, że . Na koniec definiujemy dwie lewe klasy boczne , losując dwa .
Więcej szczegółów na temat zbioru danych i sposobu jego generowania znajdziesz w tym notebooku z Quantum Kernel Training Toolkit.
Tworzymy obwód kwantowy służący do obliczenia jednego wpisu macierzy jądra.
Dane wejściowe są używane do określenia kątów obrotu dla sparametryzowanych bramek obwodu.
Dla uproszczenia użyjemy próbek danych x1=14 i x2=19.
Uwaga: Zbiór danych użyty w tym samouczku można pobrać tutaj.
# 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")
Krok 2: Optymalizacja problemu pod kątem sprzętu kwantowego
- Dane wejściowe: Abstrakcyjny Circuit, nieoptymalizowany pod kątem konkretnego Backend.
- Dane wyjściowe: Docelowy Circuit, zoptymalizowany pod wybrany QPU.
W przypadku ścieżki z symulatorem wektora stanu używanej w tej sekcji nie jest wymagana optymalizacja specyficzna dla Backend: abstrakcyjny obwód może być próbkowany bezpośrednio. Ten krok realizujemy w przykładzie sprzętowym poniżej, gdzie obwód jest transpilowany na rzeczywisty QPU przy użyciu generate_preset_pass_manager z optimization_level=3.
Krok 3: Wykonanie przy użyciu prymitywów Qiskit
- Dane wejściowe: Abstrakcyjny Circuit.
- Dane wyjściowe: Rozkład quasi-prawdopodobieństwa.
Użyj prymitywu StatevectorSampler z Qiskit, aby zrekonstruować rozkład quasi-prawdopodobieństwa stanów uzyskanych przez próbkowanie obwodu. W zadaniu generowania macierzy jądra szczególnie interesuje nas prawdopodobieństwo zmierzenia stanu |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)
Krok 4: Post-processing i zwrócenie wyniku w żądanym formacie klasycznym
- Dane wejściowe: Rozkład prawdopodobieństwa.
- Dane wyjściowe: Pojedynczy element macierzy jądra.
Oblicz prawdopodobieństwo zmierzenia na obwodzie nakładkowym i wypełnij macierz jądra na pozycji odpowiadającej próbkom reprezentowanym przez ten konkretny obwód (wiersz 15, kolumna 20).
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (simulator): {kernel_matrix[x1, x2]}")
Fidelity (simulator): 0.8261
Przykład sprzętowy
Macierz kwantowego jądra ma wpisów dla próbek treningowych, a każdy wpis wymaga uruchomienia obwodu nakładkowego, którego głębokość bramek dwu-qubitowych rośnie wraz z rozmiarem odwzorowania cech. W rezultacie skalowanie tego samouczka do większego problemu ma dwa nakładające się koszty: czas QPU na macierz jądra rośnie kwadratowo z , a głębokość unitary_overlap (która składa odwzorowanie cech z jego sprzężeniem hermitowskim) obniża wierność przy rozmiarze i połączeniach obecnego sprzętu. Aby demo było krótkie i umożliwiało czystą porównanie, uruchamiamy tę samą siedmio-qubitową instancję z przykładu na małą skalę na rzeczywistym QPU i porównujemy wierność pojedynczego wpisu macierzy jądra z wartością symulatora obliczoną powyżej.
# ------------------------------ 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
Aby wypełnić całą macierz jądra, uruchomilibyśmy eksperyment kwantowy dla każdego z jej unikalnych wpisów. Poniższy rysunek pokazuje wynikową macierz dla tego zbioru danych; ciemniejszy czerwony oznacza wierności bliższe 1,0.
Następne kroki
Jeśli ta praca jest dla ciebie interesująca, możesz zainteresować się następującymi materiałami:
- Quantum Kernel Training Toolkit - repozytorium prototypu, na którym oparty jest ten samouczek
- Quantum Kernel Training for Machine Learning Applications - samouczek Qiskit Machine Learning pokazujący, jak trenować konfigurowalny parametr
- Introduction to Quantum Machine Learning - kurs dotyczący kwantowego uczenia maszynowego
- Quantum Machine Learning from IBM Research - przegląd badań QML w IBM
- Covariant quantum kernels for data with group structure - artykuł, na którym oparty jest ten samouczek