Skip to main content
IBM Quantum Platform

Entraînement du noyau quantique

Estimation du temps d'exécution : moins d'une minute sur un processeur Heron r3 (REMARQUE : il s'agit uniquement d'une estimation.) (Votre temps d'exécution peut varier.)


Résultats d'apprentissage

À l'issue de ce tutoriel, vous devriez être en mesure de comprendre les points suivants :

  • Les méthodes du noyau et leurs applications
  • Les noyaux quantiques et leur capacité à fournir des espaces de caractéristiques améliorés
  • Construction de circuits à noyau quantique
  • Comment entraîner un noyau quantique à l'aide d'un modèle Qiskit : cartographier, optimiser, exécuter et post-traiter

Prérequis

Il est recommandé de vous familiariser avec les noyaux quantiques, de comprendre leur importance et de découvrir comment ils sont utilisés dans la pratique.

Il est également utile d'avoir des connaissances de base en théorie des groupes.


Arrière-plan

Les méthodes basées sur les noyaux sont couramment utilisées dans les applications d'apprentissage automatique. Dans ce contexte, le terme « noyau » désigne la matrice du noyau ou les éléments qui la composent. En général, un noyau est une mesure de similarité entre des données codées dans un espace de caractéristiques de haute dimension; il 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. On sait que les ordinateurs quantiques peuvent coder des données dans des espaces de caractéristiques améliorés par la mécanique quantique, remplaçant ainsi efficacement leurs équivalents classiques. Pour xR\vec{x} \in \mathbb{R} et Ψ(x)Rd\Psi(\vec{x}) \in \mathbb{R}^{d'}, généralement avec d>dd' >d, Ψ(x)\Psi(\vec{x}) est une carte de caractéristiques, xΨ(x)\vec{x} \mapsto \Psi(\vec{x}). L'objectif de Ψ(x)\Psi(\vec{x}) est de séparer les catégories de données à l'aide d'un hyperplan. En prenant comme arguments les vecteurs de l'espace de représentation des caractéristiques, la fonction noyau K(x,y)=Ψ(x)Ψ(y)K(\vec{x}, \vec{y}) = \langle{\Psi(\vec{x}) | \Psi(\vec{y}) \rangle{}} renvoie leur produit scalaire : K:RdK: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d. Classiquement, les représentations de caractéristiques qui présentent un intérêt sont celles pour lesquelles la fonction noyau peut être facilement évaluée; c'est-à-dire lorsque le produit scalaire dans l'espace de représentation des caractéristiques peut s'écrire en fonction des vecteurs de données d'origine et qu'il n'est pas nécessaire de construire Ψ(x)\Psi(\vec{x}) et Ψ(y)\Psi(\vec{y}). Dans le cas des noyaux quantiques, la mise en correspondance des caractéristiques est effectuée par un circuit quantique, et le noyau est estimé à partir des probabilités de mesure échantillonnées à partir du circuit.

Ce tutoriel explique comment créer un modèle Qiskit permettant d'évaluer les entrées d'une matrice de noyau quantique utilisée pour la classification binaire.


Exigences

Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :

  • Qiskit SDK v2.3.1 ou version ultérieure, avec prise en charge de la visualisation
  • Qiskit Runtime v0.44.0 ou version ultérieure (pip install qiskit-ibm-runtime)

Configuration

# 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 de simulateur à petite échelle

Dans cette section, nous passons en revue les quatre étapes du modèle Qiskit sur une instance à sept qubits du problème de l'étiquetage des classes de cosets avec erreur, et nous évaluons une seule entrée de la matrice du noyau à l'aide de la StatevectorSampler primitive de Qiskit. Un simulateur de vecteur d'état est précis (à l'exception du bruit de shot) et nous permet de visualiser la méthode de bout en bout sans mobiliser de temps de calcul sur le QPU. Nous reproduisons ensuite cette même opération sur du matériel réel dans la section consacrée aux exemples matériels.

Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique

  • Entrée : Ensemble de données d'apprentissage.
  • Sortie : Circuit abstrait pour le calcul d'une entrée de la matrice du noyau.

Le problème de classification binaire que nous cherchons à résoudre ici est appelé « étiquetage de classes d'équivalence avec erreur » L'ensemble de données d'apprentissage d'entrée présente une structure de groupe, composée de deux classes de cosets formées par un groupe et un sous-groupe. On considère que ce groupe est l' G=SU(2)nG = SU(2)^{\otimes n} e des qubits, qui est le groupe unitaire particulier des matrices de 2×22 \times 2 et qui trouve de nombreuses applications dans la nature; par exemple, dans le Modèle standard de la physique des particules. Nous considérons le sous-groupe (stabilisateur du graphe) Sgraph<GS_\text{graph} < G, avec 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 pour un graphe dont les arêtes sont E\mathcal{E} et les sommets V\mathcal{V}. Notons que les stabilisateurs fixent un état de stabilisateur tel que Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}. Enfin, nous définissons deux classes de gauche C±=c±SgraphC_\pm = c_\pm S_\text{graph} en tirant au hasard deux c±Gc_\pm \in G.

Pour plus d'informations sur l'ensemble de données et son mode de génération, consultez ce cahier de notes issu du Quantum Kernel Training Toolkit.

Nous créons le circuit quantique servant à évaluer un élément de la matrice noyau. Les données d'entrée servent à déterminer les angles de rotation des portes paramétrées du circuit. Par souci de simplicité, nous utiliserons les échantillons x1=14 de données et x2=19.

Note : 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")

Output:

Output of the previous code cell

É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.
  • Résultat : circuit cible, optimisé pour le QPU sélectionné.

Pour le chemin du simulateur de vecteur d'état utilisé dans cette section, aucune optimisation spécifique au backend n'est nécessaire : le circuit abstrait peut être échantillonné directement. Nous mettons cette étape en pratique dans l'exemple matériel ci-dessous, où le circuit est transcompilé pour un QPU réel à l'aide de generate_preset_pass_manager avec optimization_level=3.

Étape 3 : Exécutez à l'aide d' Qiskit primitives

  • Entrée : Circuit abstrait.
  • Résultat : distribution quasi-probabiliste.

Utilisez la StatevectorSampler primitive de Qiskit pour reconstruire une distribution de quasi-probabilité des états obtenus à partir de l'échantillonnage du circuit. Pour ce qui est de la génération d'une matrice de noyau, nous nous intéressons tout 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)

Output:

Output of the previous code cell

Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité

  • Entrée : distribution de probabilité.
  • Résultat : un seul élément de la matrice du noyau.

Calculez la probabilité de mesurer une valeur de 0|0 \rangle e sur le circuit de chevauchement, puis remplissez la matrice du noyau à l'emplacement correspondant aux échantillons représentés par ce circuit de chevauchement particulier (ligne 15, colonne 20).

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

Output:

Fidelity (simulator): 0.8261

Exemple de matériel

Une matrice de noyau quantique comporte un nombre d'entrées égal à O(N2)\mathcal{O}(N^2) pour NN échantillons d'apprentissage, et chaque entrée nécessite l'exécution d'un circuit de superposition dont la profondeur en portes à deux qubits augmente avec la taille de la carte de caractéristiques. Par conséquent, l'adaptation de ce tutoriel à un problème de plus grande envergure entraîne deux coûts cumulés : le temps de calcul du QPU par matrice de noyau augmente de manière quadratique avec l' NN, et la profondeur de unitary_overlap (qui compose la carte de caractéristiques avec son adjoint) réduit la fidélité compte tenu de la taille du système et de la connectivité du matériel actuel. Afin de limiter la durée de la démonstration et de permettre une comparaison claire, nous exécutons donc la même instance à sept qubits que celle de l'exemple à petite échelle sur un QPU réel, puis nous comparons la fidélité d'une seule entrée de la matrice du noyau à la valeur calculée précédemment par le simulateur.

# ------------------------------ 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]}")

Output:

Using backend: ibm_pittsburgh
Output of the previous code cell
Fidelity (hardware): 0.7517

Pour remplir l'intégralité de la matrice du noyau, il faudrait réaliser une expérience quantique pour chacune de ses N(N+1)/2N(N+1)/2 entrées uniques. La figure ci-dessous présente la matrice obtenue pour cet ensemble de données; les nuances de rouge plus foncées indiquent des fidélités plus proches de 1.0.

kernel_matrix.png

Etapes suivantes

Recommandations

Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :

Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.