Entraînement du noyau quantique
Estimation d'utilisation : moins d'une minute sur un processeur Heron r3 (REMARQUE : Il s'agit uniquement d'une estimation. Ton temps d'exécution peut varier.)
Objectifs d'apprentissage
Après avoir terminé ce tutoriel, tu devrais comprendre les informations suivantes :
- Les méthodes de noyau et leurs utilisations
- Les noyaux quantiques et comment ils peuvent fournir des espaces de caractéristiques enrichis
- La construction de circuits de noyaux quantiques
- Comment entraîner un noyau quantique à l'aide d'un patron Qiskit : cartographier, optimiser, exécuter et post-traiter
Prérequis
Il est recommandé de se familiariser avec les noyaux quantiques, pourquoi ils sont importants et comment ils sont utilisés en pratique.
- Covariant quantum kernels for data with group structure (article)
- Introduction to Quantum Kernels and Support Vector Machines (vidéo)
- Quantum Kernels in Practice (vidéo)
Il est également utile d'avoir une compréhension de base de la théorie des groupes.
Contexte
Les méthodes de noyau sont courantes dans les applications d'apprentissage automatique. Dans ce contexte, « noyau » fait référence à la matrice de noyau ou à ses entrées individuelles. En général, un noyau est une mesure de similarité entre des données encodées dans un espace de caractéristiques de haute dimension et peut être utilisé, par exemple, dans des tâches de classification avec des machines à vecteurs de support.
Les méthodes de noyau quantique sont celles qui utilisent des ordinateurs quantiques pour estimer un noyau. Il est connu que les ordinateurs quantiques peuvent encoder des données dans des espaces de caractéristiques améliorés quantiquement, remplaçant efficacement les analogues classiques. Pour et , typiquement avec , est une application de caractéristiques, . L'objectif de est de rendre les catégories de données séparées par un hyperplan. En prenant les vecteurs dans l'espace des caractéristiques mappées comme arguments, la fonction de noyau renvoie leur produit interne : . Classiquement, les applications de caractéristiques d'intérêt sont celles pour lesquelles la fonction de noyau peut être facilement évaluée ; c'est-à-dire lorsque le produit interne dans l'espace des caractéristiques mappées peut s'écrire en termes des vecteurs de données originaux et que et n'ont pas besoin d'être construits. Dans le cas des noyaux quantiques, l'application de caractéristiques est réalisée par un circuit quantique, et le noyau est estimé à partir des probabilités de mesure échantillonnées depuis le circuit.
Ce tutoriel montre comment construire un patron Qiskit pour évaluer les entrées d'une matrice de noyau quantique utilisée pour la classification binaire.
Prérequis logiciels
Avant de commencer ce tutoriel, assure-toi d'avoir installé les éléments suivants :
- Qiskit SDK v2.3.1 ou ultérieur, avec le support de visualisation
- Qiskit Runtime v0.44.0 ou ultérieur (
pip install qiskit-ibm-runtime)
Configuration
# 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
Exemple avec simulateur à petite échelle
Dans cette section, nous parcourons les quatre étapes du patron Qiskit sur une instance à sept qubits du problème de l'étiquetage des cosets avec erreur et évaluons une entrée unique de la matrice de noyau à l'aide de la primitive StatevectorSampler de Qiskit. Un simulateur à vecteur d'état est exact (à bruit de mesure près) et nous montre la méthode de bout en bout sans consommer de temps QPU. Nous répétons ensuite la même instance sur du matériel réel dans la section de l'exemple matériel.
Étape 1 : Transposer les entrées classiques en un problème quantique
- Entrée : Jeu de données d'entraînement.
- Sortie : Circuit abstrait pour le calcul d'une entrée de la matrice de noyau.
Le problème de classification binaire que nous cherchons à résoudre ici est appelé « étiquetage des cosets avec erreur ». Le jeu de données d'entraînement en entrée contient une structure de groupe, constituée de deux cosets formés par un groupe et un sous-groupe. Le groupe est pris comme étant pour les qubits, qui est le groupe unitaire spécial de matrices et a une large applicabilité dans la nature ; par exemple, le Modèle Standard de la physique des particules. Nous prenons le sous-groupe (stabilisateur de graphe) avec pour un graphe avec des arêtes et des sommets . Notons que les stabilisateurs fixent un état stabilisateur tel que . Enfin, nous définissons deux cosets gauches en tirant deux aléatoirement.
Pour plus de détails sur le jeu de données et comment il est généré, consulte ce notebook du Quantum Kernel Training Toolkit.
Nous créons le circuit quantique utilisé pour évaluer une entrée dans la matrice de noyau.
Les données d'entrée sont utilisées pour déterminer les angles de rotation des portes paramétrées du circuit.
Par souci de simplicité, nous utiliserons les échantillons de données x1=14 et x2=19.
Remarque : Le jeu de données utilisé dans ce tutoriel peut être téléchargé ici.
# 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")
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
- Entrée : Circuit abstrait, non optimisé pour un backend particulier.
- Sortie : Circuit cible, optimisé pour le QPU sélectionné.
Pour le chemin du simulateur à vecteur d'état utilisé dans cette section, aucune optimisation spécifique au backend n'est requise : le circuit abstrait peut être échantillonné directement. Nous exerçons cette étape dans l'exemple matériel ci-dessous, où le circuit est transpilé contre un vrai QPU à l'aide de generate_preset_pass_manager avec optimization_level=3.
Étape 3 : Exécuter à l'aide des primitives Qiskit
- Entrée : Circuit abstrait.
- Sortie : Distribution de quasi-probabilités.
Utilisez la primitive StatevectorSampler de Qiskit pour reconstruire une distribution de quasi-probabilités des états obtenus par échantillonnage du circuit. Pour la tâche de génération d'une matrice de noyau, nous nous intéressons particulièrement à la probabilité de mesurer l'état |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)
Étape 4 : Post-traiter et renvoyer le résultat dans le format classique souhaité
- Entrée : Distribution de probabilités.
- Sortie : Un seul élément de la matrice de noyau.
Calculez la probabilité de mesurer sur le circuit de recouvrement, et remplissez la matrice de noyau à la position correspondant aux échantillons représentés par ce circuit de recouvrement particulier (ligne 15, colonne 20).
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (simulator): {kernel_matrix[x1, x2]}")
Fidelity (simulator): 0.8261
Exemple matériel
Une matrice de noyau quantique possède entrées pour échantillons d'entraînement, et chaque entrée nécessite l'exécution d'un circuit de recouvrement dont la profondeur en portes à deux qubits croît avec la taille de l'application de caractéristiques. En conséquence, la mise à l'échelle de ce tutoriel vers un problème plus grand comporte deux coûts cumulatifs : le temps QPU par matrice de noyau croît quadratiquement avec , et la profondeur de unitary_overlap (qui compose l'application de caractéristiques avec son adjoint) érode la fidélité à la taille du système et à la connectivité du matériel actuel. Pour garder la démonstration courte et permettre une comparaison propre, nous exécutons donc la même instance à sept qubits de l'exemple à petite échelle sur un vrai QPU et comparons la fidélité d'une entrée unique de la matrice de noyau avec la valeur calculée par le simulateur ci-dessus.
# ------------------------------ 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
Pour remplir l'intégralité de la matrice de noyau, nous exécuterions une expérience quantique pour chacune de ses entrées uniques. La figure ci-dessous montre la matrice résultante pour ce jeu de données ; un rouge plus foncé indique des fidélités plus proches de 1.0.
Prochaines étapes
Si ce travail t'a intéressé, tu pourrais être intéressé par le matériel suivant :
- Quantum Kernel Training Toolkit - le dépôt prototype sur lequel ce tutoriel est basé
- Quantum Kernel Training for Machine Learning Applications - un tutoriel Qiskit Machine Learning montrant comment entraîner le paramètre entraînable
- Introduction to Quantum Machine Learning - un cours sur l'apprentissage automatique quantique
- Quantum Machine Learning from IBM Research - un aperçu de la recherche QML chez IBM
- Covariant quantum kernels for data with group structure - l'article sur lequel ce tutoriel est basé