Le noyau quantique comme fidélité & La QSVM en pratique
Du noyau quantique (fidélité entre états encodés) à la QSVM complète : construire la matrice de Gram sur QPU, la brancher sur un SVM classique, et identifier les limites NISQ — avec du code Qiskit exécutable sur simulateur.
Le noyau quantique comme fidélité
L’idée en une phrase
Le noyau quantique remplace le produit scalaire classique de l’astuce du noyau (chapitre 15-16) par la fidélité entre deux états quantiques : K(x, y) = |⟨φ(x)|φ(y)⟩|², où |φ(x)⟩ = U(x)|0⟩ est l’état préparé par un circuit paramétré — la feature map. Dans la boucle hybride, le QPU se charge d’évaluer ce noyau (préparer les états, mesurer le recouvrement), tandis que l’optimiseur classique résout le programme quadratique dual du SVM avec la matrice de Gram ainsi obtenue.
Analogie : Comparer deux empreintes digitales. Plutôt que de convertir chaque empreinte en une liste exhaustive de minuties, boucles et arcs — le vecteur de redescription φ, de dimension potentiellement immense —, un capteur superpose directement les deux empreintes et mesure leur taux de recouvrement. Plus la superposition est nette, plus la similitude est grande. Le noyau quantique opère ainsi : il mesure la fidélité entre deux états sans jamais dresser la liste des composantes dans l’espace de Hilbert.
Points clés
- La feature map
U(x)encode un vecteur classiquexdans un état quantique|φ(x)⟩ = U(x)|0⟩. L’espace de Hilbert ànqubits a dimension2ⁿ: la redescription est exponentiellement grande — ce que le noyau RBF classique obtient formellement avec une dimension infinie, le noyau quantique l’obtient avecnqubits physiques. - Le noyau se mesure en appliquant le circuit
U†(x)·U(y)puis en estimant la probabilité de l’état|0...0⟩. Sur un processeur réel, cela nécessite de nombreux shots ; en simulation, leStatevectorfournit la valeur exacte. - Propriétés garanties :
K(x, x) = 1(un état normalisé recouvre parfaitement lui-même),K(x, y) = K(y, x)(symétrie),K(x, y) ∈ [0, 1](Cauchy-Schwarz). La matrice de Gram est semi-définie positive — le noyau satisfait la condition de Mercer (chapitre 15-16). - La frontière quantique/classique est nette : le QPU prépare et mesure ; l’optimisation du SVM reste entièrement classique. Contrairement au VQC (chapitre 7-8), il n’y a pas de paramètres θ à mettre à jour itérativement — le circuit est fixe.
- Concentration exponentielle : à mesure que le nombre de qubits croît, les valeurs hors-diagonale de la matrice de Gram convergent vers une constante, rendant le noyau incapable de distinguer les points. C’est la pathologie propre à cette approche, symétrique des plateaus de Barren pour les circuits variationnels.
Exemple concret
Deux points 2D, x = (0.3, 0.7) et y = (1.5, 0.1), encodés par une feature map ZZ à 2 qubits (couche de Hadamard, rotations Rz individuelles, intrication ZZ). Les deux circuits préparent des états |φ(x)⟩ et |φ(y)⟩ dans un espace de dimension 4. Le circuit combiné U†(x)·U(y) est appliqué, puis la probabilité de mesurer |00⟩ est lue : c’est K(x, y). Si les deux points sont identiques, U†(x)·U(x) = I ramène à |00⟩ avec certitude : K(x, x) = 1. Si les données diffèrent, les rotations divergent et la probabilité de |00⟩ diminue — la fidélité reflète la proximité dans l’espace de Hilbert.
Noyau classique vs noyau quantique
| Critère | Noyau classique (RBF) | Noyau quantique (fidélité) |
|---|---|---|
| Espace de redescription | Dimension infinie (formel) | Dimension 2ⁿ (espace de Hilbert) |
Calcul de K(x, y) | Formule analytique | Circuit U†(x)·U(y) + mesure |
| Coût par entrée | O(d) (dimension des données) | O(S) shots pour une estimation |
| Hyperparamètres | γ (largeur du noyau) | Architecture de la feature map |
| Pathologie | Malédiction de la dimension | Concentration exponentielle |
Code Python — mesurer le noyau quantique par fidélité
# Le noyau quantique K(x, y) = |⟨φ(x)|φ(y)⟩|² mesure la fidélité
# entre deux états encodés par la feature map.
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
def zz_feature_map(x):
"""Feature map à 2 qubits : H, Rz individuels, intrication ZZ."""
qc = QuantumCircuit(2)
qc.h([0, 1])
qc.rz(x[0], 0)
qc.rz(x[1], 1)
# Intrication ZZ : encode la corrélation x[0]*x[1]
qc.cx(0, 1)
qc.rz(x[0] * x[1], 1)
qc.cx(0, 1)
return qc
def noyau_quantique(x, y):
"""K(x, y) = |⟨φ(x)|φ(y)⟩|² — fidélité entre états encodés."""
sv_x = Statevector(zz_feature_map(x))
sv_y = Statevector(zz_feature_map(y))
return abs(sv_x.inner(sv_y)) ** 2
x = np.array([0.3, 0.7])
y = np.array([1.5, 0.1])
print("K(x, x) =", round(noyau_quantique(x, x), 4)) # 1.0
print("K(x, y) =", round(noyau_quantique(x, y), 4)) # valeur dans [0, 1]
print("K(y, x) =", round(noyau_quantique(y, x), 4)) # identique (symétrie)
Code Python — mesurer le noyau par circuit inverse
# Sur un vrai QPU, pas de Statevector disponible.
# On applique U†(x)·U(y) et on estime P(|00⟩).
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
def zz_feature_map(x):
qc = QuantumCircuit(2)
qc.h([0, 1])
qc.rz(x[0], 0)
qc.rz(x[1], 1)
qc.cx(0, 1)
qc.rz(x[0] * x[1], 1)
qc.cx(0, 1)
return qc
def noyau_par_circuit_inverse(x, y):
"""K(x, y) = P(|00⟩) après application de U†(x)·U(y)."""
qc_y = zz_feature_map(y)
qc_x_inv = zz_feature_map(x).inverse()
qc = qc_y.compose(qc_x_inv)
# La probabilité de |00⟩ est |⟨0|U†(x)U(y)|0⟩|² = K(x, y)
sv = Statevector(qc)
probs = sv.probabilities()
return probs[0] # index 0 = état |00⟩
x = np.array([0.3, 0.7])
y = np.array([1.5, 0.1])
print("K(x, y) par circuit inverse =", round(noyau_par_circuit_inverse(x, y), 4))
Piège courant : « Le noyau quantique est puissant parce que l’espace de Hilbert est de grande dimension » est trompeur. L’avantage ne vient pas de la taille brute de l’espace, mais de la structure des corrélations que la feature map encode (intrication, termes ZZ). Si la feature map n’exploite pas ces corrélations quantiques, le noyau est simulable classiquement — et la concentration exponentielle le rend alors inutilisable à mesure que le nombre de qubits augmente.
La QSVM en pratique
L’idée en une phrase
La QSVM (Quantum Support Vector Machine) assemble les deux pièces : le QPU calcule la matrice de noyau K[i,j] = |⟨φ(x_i)|φ(x_j)⟩|² pour tous les couples du jeu d’entraînement, puis un SVM classique résout le problème dual avec cette matrice comme noyau précomputé. Dans la boucle hybride, le QPU est interrogé une seule fois pour remplir la matrice — O(N²) évaluations de circuit — après quoi toute l’optimisation est classique.
Analogie : Commander une analyse de sol avant de construire une maison. Un laboratoire (le QPU) effectue toutes les mesures de compatibilité entre échantillons — une analyse par paire, soit
N²tests pourNéchantillons. Le rapport (la matrice de noyau) est ensuite remis à l’architecte (le SVM classique), qui conçoit les fondations sans jamais retourner au laboratoire. Le coût se concentre dans la phase de mesure ; la conception elle-même est un calcul classique standard.
Points clés
- Le workflow QSVM comporte trois étapes : (1) choisir une feature map, (2) évaluer la matrice de noyau quantique
K_trainde tailleN×Nsur le jeu d’entraînement, (3) entraîner unSVC(kernel='precomputed')de scikit-learn avec cette matrice. Pour prédire, on évalueK_testde tailleM×N(M points de test contre N points d’entraînement). - Le coût est dominé par l’évaluation de la matrice :
O(N²)circuits, chacun nécessitantSshots sur hardware réel. PourN = 100etS = 1 000, cela représente 10 millions d’exécutions de circuit — un obstacle pratique majeur sur les machines NISQ. - La concentration exponentielle menace la QSVM à grande échelle : les entrées hors-diagonale de
Kconvergent vers une même constantec, la matrice devientK ≈ (1−c)I + c·11ᵀ, ne contient plus d’information discriminante, et le SVM dégénère. - Quand la QSVM peut-elle offrir un avantage ? Lorsque la feature map encode des corrélations que les noyaux classiques ne capturent pas efficacement — données à structure quantique native, ou fonctions booléennes discrètes spécifiques. En l’absence de cette structure, un SVM classique avec noyau RBF fait aussi bien, sans surcoût.
- La symétrie et la diagonale unitaire de
Kpermettent une optimisation pratique : il suffit de calculerN(N−1)/2entrées (le triangle supérieur), puis de compléter par symétrie.
Exemple concret
Un jeu de données XOR à 4 points : (0,0)→−1, (0,1)→+1, (1,0)→+1, (1,1)→−1. Un SVM linéaire échoue : le XOR n’est pas linéairement séparable. Avec une feature map ZZ à 2 qubits, le noyau quantique projette les points dans un espace de dimension 4, où le XOR devient séparable. La matrice 4×4 est calculée (6 entrées distinctes + 4 valeurs diagonales = 1), passée à SVC(kernel='precomputed'), et le classifieur atteint 100 % de précision. Un noyau RBF classique résout aussi le XOR — l’intérêt est pédagogique : illustrer le pipeline complet de la QSVM.
Pipeline QSVM : étapes et acteurs
| Étape | Acteur | Entrée / Sortie | Complexité |
|---|---|---|---|
| 1. Encodage | QPU (feature map) | x_i → état quantique | O(N) circuits |
| 2. Matrice de noyau | QPU (fidélité) | Paires (i,j) → K[i,j] | O(N²) circuits × S shots |
| 3. Optimisation duale | Classique (SVM) | K_train → multiplicateurs | O(N³) standard |
| 4. Prédiction | Classique | K_test → labels prédits | O(M·N_sv) |
Code Python — QSVM complète sur le jeu XOR
# Pipeline QSVM complet :
# 1. Feature map 2. Matrice de noyau 3. SVM classique 4. Prédiction
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from sklearn.svm import SVC
def zz_feature_map(x, reps=2):
"""Feature map ZZ à 2 qubits — convention standard Qiskit.
Les facteurs 2 et (π − x_i) garantissent des rotations non triviales
même quand x_i = 0. Deux couches (reps=2) augmentent l'expressivité."""
qc = QuantumCircuit(2)
for _ in range(reps):
qc.h([0, 1])
qc.rz(2.0 * x[0], 0)
qc.rz(2.0 * x[1], 1)
qc.cx(0, 1)
qc.rz(2.0 * (np.pi - x[0]) * (np.pi - x[1]), 1)
qc.cx(0, 1)
return qc
def noyau_quantique(x, y):
"""K(x, y) = |⟨φ(x)|φ(y)⟩|² — fidélité."""
sv_x = Statevector(zz_feature_map(x))
sv_y = Statevector(zz_feature_map(y))
return abs(sv_x.inner(sv_y)) ** 2
def matrice_noyau(X1, X2=None):
"""Matrice de Gram. Si X2 est None, matrice carrée K(X1, X1)."""
if X2 is None:
X2 = X1
n1, n2 = len(X1), len(X2)
K = np.zeros((n1, n2))
for i in range(n1):
for j in range(n2):
K[i, j] = noyau_quantique(X1[i], X2[j])
return K
# Données XOR
X_train = np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y_train = np.array([-1, 1, 1, -1])
# Étape 2 : matrice de noyau quantique
K_train = matrice_noyau(X_train)
print("Diagonale de K :", np.round(np.diag(K_train), 4))
# Étape 3 : SVM classique avec noyau précomputé
clf = SVC(kernel="precomputed")
clf.fit(K_train, y_train)
# Étape 4 : prédiction sur les données d'entraînement
y_pred = clf.predict(K_train)
print("Prédictions :", y_pred)
print("Précision :", np.mean(y_pred == y_train))
Piège courant : « La QSVM entraîne un circuit quantique de manière itérative, comme le VQC » est inexact. La feature map est fixe : aucun paramètre θ n’est optimisé par descente de gradient. Le QPU est interrogé une seule fois pour remplir la matrice de Gram, puis le SVM classique prend le relais. C’est un schéma hybride en deux phases séquentielles (quantum puis classique), pas une boucle.
Fil rouge — la frontière quantique/classique
Dans la QSVM, la répartition des rôles est franche : le QPU est un calculateur de similarité — il prépare les états et mesure leur recouvrement. L’optimisation reste entièrement classique : une fois la matrice K calculée, le programme quadratique dual est résolu par un solveur standard. Contrairement à la boucle variationnelle (VQC), le circuit n’a pas de paramètres θ à entraîner — les plateaus de Barren ne s’appliquent donc pas. Mais une pathologie propre prend le relais : la concentration exponentielle, où toutes les entrées hors-diagonale convergent vers une constante lorsque le nombre de qubits augmente, effaçant toute information discriminante. Le compromis expressivité ↔ entraînabilité se réexprime en expressivité ↔ concentration : une feature map trop profonde concentre le noyau, tandis qu’une feature map trop simple est simulable classiquement.
Kata Qiskit — pipeline QSVM sur le XOR
Objectif : construire un pipeline QSVM complet — feature map, noyau quantique, matrice de Gram, entraînement et prédiction — et classifier le jeu XOR avec 100 % de précision.
Squelette :
# kata_day_17_18.py
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from sklearn.svm import SVC
def zz_feature_map(x, reps=2):
"""Feature map ZZ à 2 qubits, convention standard Qiskit.
Les facteurs 2 et (π − x_i) assurent des rotations non triviales."""
qc = QuantumCircuit(2)
for _ in range(reps):
qc.h([0, 1])
qc.rz(2.0 * x[0], 0)
qc.rz(2.0 * x[1], 1)
# TODO 1 : ajouter l'intrication ZZ —
# cx(0,1), rz(2.0 * (np.pi - x[0]) * (np.pi - x[1]), 1), cx(0,1)
return qc
def noyau_quantique(x, y):
"""K(x, y) = |⟨φ(x)|φ(y)⟩|² — fidélité entre états encodés."""
# TODO 2 : construire les Statevector et renvoyer la fidélité
return 0.0
def matrice_noyau(X1, X2=None):
"""Matrice de Gram. Si X2 est None, calculer K(X1, X1)."""
if X2 is None:
X2 = X1
n1, n2 = len(X1), len(X2)
K = np.zeros((n1, n2))
# TODO 3 : remplir K[i, j] pour tous les couples
return K
def qsvm_train(X_train, y_train):
"""Entraîner un SVM avec noyau quantique précomputé.
Renvoyer (classifieur_entraîné, K_train)."""
# TODO 4 : calculer K_train et entraîner SVC(kernel='precomputed')
return None, None
def qsvm_predict(clf, X_train, X_test):
"""Prédire les labels de X_test via le noyau quantique."""
# TODO 5 : calculer K_test = matrice_noyau(X_test, X_train) et prédire
return np.array([])
Auto-correction :
# test_kata_day_17_18.py
import numpy as np
from kata_day_17_18 import zz_feature_map, noyau_quantique, matrice_noyau
from kata_day_17_18 import qsvm_train, qsvm_predict
X = np.array([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
y = np.array([-1, 1, 1, -1])
# Test 1 : la feature map utilise l'intrication ZZ (2 CNOT par couche × 2 couches = 4)
qc = zz_feature_map(X[1])
assert qc.num_qubits == 2, "Le circuit doit avoir 2 qubits"
n_cx = sum(1 for instr in qc.data if instr.operation.name == "cx")
assert n_cx == 4, f"4 portes CNOT attendues (2 couches × 2 CNOT), trouvé {n_cx}"
# Test 2 : fidélité d'un point avec lui-même = 1
assert abs(noyau_quantique(X[0], X[0]) - 1.0) < 1e-9, "K(x, x) doit valoir 1"
# Test 3 : symétrie du noyau
k_01 = noyau_quantique(X[0], X[1])
k_10 = noyau_quantique(X[1], X[0])
assert abs(k_01 - k_10) < 1e-9, "K(x, y) doit égaler K(y, x)"
# Test 4 : matrice de noyau correcte
K = matrice_noyau(X)
assert K.shape == (4, 4), "K doit être 4x4"
assert np.allclose(K, K.T, atol=1e-9), "K doit être symétrique"
assert np.allclose(np.diag(K), 1.0, atol=1e-9), "Diagonale = 1"
# Test 5 : QSVM classifie le XOR à 100 %
clf, K_train = qsvm_train(X, y)
assert clf is not None, "qsvm_train doit renvoyer un classifieur"
y_pred = qsvm_predict(clf, X, X)
assert np.array_equal(y_pred, y), f"QSVM doit classifier le XOR à 100 %, obtenu {y_pred}"
print("Kata validé !")
Solution et explication
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from sklearn.svm import SVC
def zz_feature_map(x, reps=2):
qc = QuantumCircuit(2)
for _ in range(reps):
qc.h([0, 1])
qc.rz(2.0 * x[0], 0)
qc.rz(2.0 * x[1], 1)
qc.cx(0, 1) # TODO 1
qc.rz(2.0 * (np.pi - x[0]) * (np.pi - x[1]), 1)
qc.cx(0, 1)
return qc
def noyau_quantique(x, y):
sv_x = Statevector(zz_feature_map(x)) # TODO 2
sv_y = Statevector(zz_feature_map(y))
return abs(sv_x.inner(sv_y)) ** 2
def matrice_noyau(X1, X2=None):
if X2 is None:
X2 = X1
n1, n2 = len(X1), len(X2)
K = np.zeros((n1, n2))
for i in range(n1): # TODO 3
for j in range(n2):
K[i, j] = noyau_quantique(X1[i], X2[j])
return K
def qsvm_train(X_train, y_train):
K_train = matrice_noyau(X_train) # TODO 4
clf = SVC(kernel="precomputed")
clf.fit(K_train, y_train)
return clf, K_train
def qsvm_predict(clf, X_train, X_test):
K_test = matrice_noyau(X_test, X_train) # TODO 5
return clf.predict(K_test)Pourquoi : le pipeline QSVM sépare nettement les rôles. La feature map à 2 couches encode chaque point dans un état à 2 qubits avec intrication ZZ — le terme (π − x_i)(π − x_j) garantit des rotations non triviales même aux bords du domaine. La matrice de noyau K[i,j] = |⟨φ(x_i)|φ(x_j)⟩|² capture toutes les similarités quantiques en une seule passe. Le SVM classique (SVC(kernel='precomputed')) résout ensuite le programme dual sans jamais rappeler le QPU. Pour prédire, on calcule K_test (similarité de chaque point de test avec les points d’entraînement) et on applique la décision duale. Le XOR, non linéairement séparable en 2D, le devient dans l’espace de Hilbert grâce aux corrélations ZZ encodées par la feature map.