Sari la conținutul principal

Antrenarea kernelului cuantic

Estimare de utilizare: sub un minut pe un procesor Heron r3 (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)

Rezultate de învățare

După finalizarea acestui tutorial, te poți aștepta să înțelegi următoarele informații:

  • Metodele kernel și utilizările lor
  • Kernelurile cuantice și cum pot oferi spații de caracteristici îmbunătățite
  • Construcția circuitului kernel cuantic
  • Cum să antrenezi un kernel cuantic folosind un pattern Qiskit: mapare, optimizare, execuție și post-procesare

Condiții prealabile

Se recomandă să te familiarizezi cu kernelurile cuantice, de ce sunt importante și cum sunt folosite în practică.

De asemenea, este util să ai o înțelegere de bază a teoriei grupurilor.

Fundal

Metodele kernel sunt frecvente în aplicațiile de învățare automată. În acest context, „kernel" se referă la matricea kernel sau la intrările individuale din aceasta. În general, un kernel este o măsură de similitudine între date codificate într-un spațiu de caracteristici de dimensiune înaltă și poate fi utilizat, de exemplu, în sarcini de clasificare cu mașini cu vectori suport.

Metodele kernel cuantice sunt cele care utilizează calculatoare cuantice pentru a estima un kernel. Se știe că calculatoarele cuantice pot codifica date în spații de caracteristici îmbunătățite cuantic, înlocuind efectiv analogii clasici. Pentru xR\vec{x} \in \mathbb{R} și Ψ(x)Rd\Psi(\vec{x}) \in \mathbb{R}^{d'}, de obicei cu d>dd' >d, Ψ(x)\Psi(\vec{x}) este o hartă de caracteristici, xΨ(x)\vec{x} \mapsto \Psi(\vec{x}). Scopul lui Ψ(x)\Psi(\vec{x}) este de a face categoriile de date separate printr-un hiperplan. Luând vectorii din spațiul mapat de caracteristici ca argumente, funcția kernel K(x,y)=Ψ(x)Ψ(y)K(\vec{x}, \vec{y}) = \langle{\Psi(\vec{x}) | \Psi(\vec{y}) \rangle{}} returnează produsul lor scalar: K:RdK: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d. Clasic, hărțile de caracteristici de interes sunt cele în care funcția kernel poate fi evaluată cu ușurință; adică atunci când produsul scalar din spațiul mapat de caracteristici poate fi scris în termenii vectorilor de date originali, iar Ψ(x)\Psi(\vec{x}) și Ψ(y)\Psi(\vec{y}) nu trebuie construiți. În cazul kernelurilor cuantice, maparea caracteristicilor este realizată de un circuit cuantic, iar kernelul este estimat folosind probabilitățile de măsurare eșantionate din circuit.

Acest tutorial arată cum să construiești un pattern Qiskit pentru evaluarea intrărilor într-o matrice de kernel cuantic utilizată pentru clasificare binară.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai instalate următoarele:

  • Qiskit SDK v2.3.1 sau mai recent, cu suport pentru vizualizare
  • Qiskit Runtime v0.44.0 sau mai recent (pip install qiskit-ibm-runtime)

Configurare

# 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

Exemplu cu simulator la scară mică

În această secțiune, parcurgem cei patru pași ai pattern-ului Qiskit pe o instanță cu șapte qubiți a problemei de etichetare a coseturilor cu erori și evaluăm o singură intrare din matricea kernel folosind primitiva StatevectorSampler din Qiskit. Un simulator cu vector de stare este exact (cu excepția zgomotului de eșantionare) și ne arată metoda de la capăt la capăt fără a consuma timp QPU. Apoi repetăm aceeași instanță pe hardware real în secțiunea cu exemplul hardware.

Pasul 1: Mapează intrările clasice la o problemă cuantică

  • Intrare: Set de date de antrenament.
  • Ieșire: Circuit abstract pentru calculul unei intrări în matricea de kernel.

Problema de clasificare binară pe care ne propunem să o rezolvăm aici este denumită „etichetarea coseturilor cu erori." Setul de date de antrenament de intrare conține o structură de grup, constând din două coseturi formate de un grup și un subgrup. Grupul este luat ca G=SU(2)nG = SU(2)^{\otimes n} pentru qubiți, care este grupul unitar special de matrice 2×22 \times 2 și are o largă aplicabilitate în natură; de ex., Modelul Standard al fizicii particulelor. Luăm subgrupul (stabilizator de graf) Sgraph<GS_\text{graph} < G cu 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 pentru un graf cu muchii E\mathcal{E} și vârfuri V\mathcal{V}. Observă că stabilizatorii fixează o stare stabilizator astfel încât Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}. În final, definim două coseturi stângi C±=c±SgraphC_\pm = c_\pm S_\text{graph} trăgând aleatoriu două c±Gc_\pm \in G.

Pentru mai multe detalii despre setul de date și cum este generat, vezi acest notebook din Quantum Kernel Training Toolkit.

Creăm circuitul cuantic utilizat pentru evaluarea unei intrări din matricea kernel. Datele de intrare sunt folosite pentru a determina unghiurile de rotație ale porților parametrizate ale circuitului. Pentru simplitate, vom folosi eșantioanele de date x1=14 și x2=19.

Notă: Setul de date utilizat în acest tutorial poate fi descărcat aici.

# 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

Pasul 2: Optimizează problema pentru execuția pe hardware cuantic

  • Intrare: Circuit abstract, neoptimizat pentru un anumit Backend.
  • Ieșire: Circuit țintă, optimizat pentru QPU-ul selectat.

Pentru calea cu simulatorul de vector de stare folosită în această secțiune, nu este necesară nicio optimizare specifică Backend-ului: circuitul abstract poate fi eșantionat direct. Vom aplica acest pas în exemplul hardware de mai jos, unde circuitul este transpilat față de un QPU real folosind generate_preset_pass_manager cu optimization_level=3.

Pasul 3: Execută folosind primitivele Qiskit

  • Intrare: Circuit abstract.
  • Ieșire: Distribuție quasi-probabilistică.

Folosește primitiva StatevectorSampler din Qiskit pentru a reconstitui o distribuție quasi-probabilistică a stărilor obținute prin eșantionarea circuitului. Pentru sarcina de generare a unei matrice de kernel, suntem în special interesați de probabilitatea de a măsura starea |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

Pasul 4: Post-procesează și returnează rezultatul în formatul clasic dorit

  • Intrare: Distribuție de probabilitate.
  • Ieșire: Un singur element din matricea de kernel.

Calculează probabilitatea de a măsura 0|0 \rangle pe circuitul de suprapunere și populează matricea de kernel în poziția corespunzătoare eșantioanelor reprezentate de acest circuit de suprapunere particular (rândul 15, coloana 20).

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

Exemplu hardware

O matrice kernel cuantică are O(N2)\mathcal{O}(N^2) intrări pentru NN eșantioane de antrenament, iar fiecare intrare necesită rularea unui circuit de suprapunere a cărui adâncime de porți cu doi qubiți crește cu dimensiunea hărții de caracteristici. Ca urmare, scalarea acestui tutorial la o problemă mai mare are două costuri cumulative: timpul QPU per matrice kernel crește pătratic cu NN, iar adâncimea lui unitary_overlap (care compune harta de caracteristici cu adjuncta sa) erodează fidelitatea la dimensiunea sistemului și conectivitatea hardware-ului actual. Pentru a menține demonstrația scurtă și pentru o comparație clară, rulăm deci aceeași instanță cu șapte qubiți din exemplul la scară mică pe un QPU real și comparăm fidelitatea unei singure intrări din matricea kernel cu valoarea calculată de simulator mai sus.

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

Pentru a completa întreaga matrice kernel, am rula un experiment cuantic pentru fiecare dintre cele N(N+1)/2N(N+1)/2 intrări unice. Figura de mai jos arată matricea rezultată pentru acest set de date; roșul mai închis indică fidelități mai aproape de 1.0.

kernel_matrix.png

Pași următori

Recommendations

Dacă ai găsit această lucrare interesantă, s-ar putea să fii interesat de următoarele materiale: