Feature maps d'ordre supérieur (ZZ Feature Map) & Concevoir l'ansatz
Enrichir l'encodage avec des corrélations entre features via le ZZ Feature Map, puis concevoir l'ansatz paramétré qui complète la boucle variationnelle — avec du code Qiskit exécutable.
Feature maps d’ordre supérieur et ZZ Feature Map
L’idée en une phrase
Un feature map d’ordre supérieur enrichit l’encodage des données en ajoutant des termes d’interaction entre paires de features — typiquement un produit croisé φ(xᵢ, xⱼ) — via des portes d’intrication, augmentant l’expressivité de la représentation quantique au sein de la boucle variationnelle hybride (encodage → ansatz → mesure → optimisation classique).
Analogie : Une carte topographique qui ne noterait que l’altitude de chaque point donnerait un semis de cotes isolées. En reliant ces points par des courbes de niveau — c’est-à-dire en capturant les pentes entre paires de points —, la carte révèle vallées, cols et crêtes invisibles autrement. Le ZZ Feature Map procède de même dans l’espace de Hilbert : les termes
Zindividuels encodent chaque feature, les termesZZcroisés capturent les corrélations entre paires de features.
Points clés
- Le ZZFeatureMap de Qiskit combine des termes de Pauli
Z(single-qubit) etZZ(two-qubit). Chaque répétition applique : H sur tous les qubits,P(2xᵢ)sur chaque qubit i, puis pour chaque paire(i, j)une interactionCNOT(i,j) → P(2·φ(xᵢ,xⱼ)) → CNOT(i,j). - La fonction de données par défaut est
φ(xᵢ, xⱼ) = (π − xᵢ)(π − xⱼ). Ce produit croisé crée une dépendance non linéaire entre features dans l’espace de Hilbert, inaccessible aux encodages purement single-qubit (angle, dense angle du chapitre 5). - Augmenter le nombre de répétitions (
reps) enrichit l’espace de features mais accroît la profondeur du circuit. Sur un processeur NISQ, chaque couche supplémentaire accumule du bruit — le compromis expressivité ↔ entraînabilité se manifeste directement ici. - Le ZZ Feature Map est un cas particulier du PauliFeatureMap (termes
['Z', 'ZZ']). On peut construire des feature maps d’ordre supérieur avec des termesZZZou d’autres combinaisons de Pauli, au prix d’une profondeur croissante. - En pratique, la plupart des benchmarks QML utilisent 1 à 3 répétitions. Au-delà, le bruit NISQ annule le gain en expressivité.
Exemple concret
On encode 2 features x = [0.8, 1.2] dans un ZZ Feature Map à 2 qubits avec 1 répétition. La première couche applique H sur les 2 qubits, les plaçant en superposition uniforme. Puis P(2 × 0.8) = P(1.6) sur le qubit 0 et P(2 × 1.2) = P(2.4) sur le qubit 1 ajoutent des phases proportionnelles aux features individuelles. Le terme ZZ calcule φ(0.8, 1.2) = (π − 0.8)(π − 1.2) ≈ 2.34 × 1.94 ≈ 4.54 et applique CNOT(0,1) → P(9.08) → CNOT(0,1). Ce bloc crée de l’intrication conditionnelle aux données : l’état final n’est plus un produit tensoriel. Avec un encodage angle seul (chapitre 5), les qubits resteraient indépendants — aucune corrélation entre x₀ et x₁ ne serait capturée par le feature map.
Comparaison des feature maps
| Feature map | Termes | Qubits (n features) | Profondeur par rep | Intrication |
|---|---|---|---|---|
| Angle (ch. 5) | — | n | 1 | Aucune |
| Dense Angle (ch. 5) | — | ⌈n/2⌉ | 2 | Aucune |
| ZFeatureMap | Z | n | O(1) | Aucune |
| ZZFeatureMap | Z + ZZ | n | O(n²) | Oui (paires) |
Code Python — ZZ Feature Map depuis la bibliothèque Qiskit
from qiskit.circuit.library import ZZFeatureMap
from qiskit.quantum_info import Statevector
# ZZ Feature Map pour 2 features, 1 répétition
zz_fm = ZZFeatureMap(feature_dimension=2, reps=1)
print(zz_fm.decompose().draw())
print(f"Paramètres : {sorted(zz_fm.parameters, key=lambda p: p.name)}")
# Encoder des données concrètes
x = [0.8, 1.2]
bound = zz_fm.assign_parameters(x)
sv = Statevector.from_instruction(bound)
print("Probabilités :", {k: round(v, 4) for k, v in sv.probabilities_dict().items()})
Code Python — construction manuelle du ZZ Feature Map
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.quantum_info import Statevector
import numpy as np
def manual_zz_feature_map(n_qubits: int = 2) -> QuantumCircuit:
"""Construire un ZZ Feature Map à la main pour comprendre chaque porte."""
x = ParameterVector('x', n_qubits)
qc = QuantumCircuit(n_qubits)
# Couche Hadamard — préparer la superposition uniforme
for i in range(n_qubits):
qc.h(i)
# Termes single-qubit Z : phase proportionnelle à chaque feature
for i in range(n_qubits):
qc.p(2 * x[i], i)
# Terme ZZ : interaction entre la paire (0, 1)
# Implémente exp(i · φ(x₀,x₁) · Z₀Z₁) via CNOT + P + CNOT
qc.cx(0, 1)
qc.p(2 * (np.pi - x[0]) * (np.pi - x[1]), 1)
qc.cx(0, 1)
return qc
qc = manual_zz_feature_map()
print(qc.draw())
# Vérifier que le résultat coïncide avec la version bibliothèque
bound_manual = qc.assign_parameters([0.8, 1.2])
sv = Statevector.from_instruction(bound_manual)
print("Probabilités (manuel) :", {k: round(v, 4) for k, v in sv.probabilities_dict().items()})
Piège courant : « Le ZZ Feature Map encode les corrélations entre features, donc il est toujours supérieur à l’encodage angle » est une simplification. La profondeur supplémentaire introduite par les termes ZZ accumule du bruit sur un processeur NISQ. Pour des données où les features sont largement indépendantes, un encodage angle de profondeur 1 (chapitre 5) peut donner de meilleurs résultats qu’un ZZ Feature Map bruité.
Concevoir l’ansatz
L’idée en une phrase
L’ansatz est le circuit paramétré qui suit le feature map dans la boucle variationnelle hybride : il définit l’espace des hypothèses que l’optimiseur classique explore en ajustant les paramètres θ — choisir un ansatz, c’est fixer le compromis entre expressivité (capacité à représenter la solution) et entraînabilité (facilité d’optimisation).
Analogie : Une table de mixage audio dispose, sur chaque canal, de boutons de volume et d’égalisation (les rotations paramétrées sur chaque qubit) et de commandes de cross-fading entre canaux (les portes d’intrication). Avec trop peu de boutons, tous les morceaux se ressemblent ; avec trop de boutons, le moindre ajustement produit des effets imprévisibles. L’art du mixage consiste à disposer de juste assez de contrôles pour le genre musical visé — et l’art de concevoir un ansatz, à doser paramètres et profondeur pour le problème QML ciblé.
Points clés
- L’ansatz est composé de couches alternant des rotations paramétrées (
Ry,Rz,Rx) et des portes d’intrication (CNOT,CZ). Le nombre de couches (reps) contrôle la profondeur et le nombre de paramètres. - Un ansatz hardware-efficient (comme
EfficientSU2) est conçu pour respecter la topologie de connexion du processeur quantique cible. Il minimise la profondeur après transpilation mais ne garantit pas que l’espace des hypothèses est adapté au problème. - RealAmplitudes est un cas particulier utilisant uniquement des rotations
Ryet des CNOT. Il ne produit que des amplitudes réelles, ce qui le rend plus simple à optimiser mais moins expressif qu’EfficientSU2(qui ajouteRzpour manipuler les phases). - L’expressibilité d’un ansatz mesure sa capacité à couvrir l’espace de Hilbert uniformément. Un ansatz trop expressif avec peu de données mène au surapprentissage ; un ansatz trop restreint ne peut pas capturer la structure des données.
- Le nombre de paramètres d’un ansatz
TwoLocalà n qubits avec g rotations par qubit par couche estn × g × (reps + 1). Par exemple,EfficientSU2(3, reps=2)a3 × 2 × 3 = 18paramètres.
Exemple concret
On construit un classificateur variationnel pour 2 features sur 2 qubits. Le feature map est un ZZ Feature Map (2 paramètres de données). L’ansatz est un RealAmplitudes(2, reps=1) : la première couche applique Ry(θ₀) et Ry(θ₁) puis CNOT(0,1) ; la couche finale applique Ry(θ₂) et Ry(θ₃). Le circuit complet a 6 paramètres : 2 features (non entraînables) + 4 θ (entraînables). L’optimiseur classique (COBYLA, SPSA) ajuste les 4 θ pour minimiser la loss. Avec EfficientSU2(2, reps=1), l’ansatz aurait 8 paramètres (Ry + Rz par qubit par couche), offrant plus de flexibilité au prix d’un paysage d’optimisation plus complexe.
Comparaison des ansatzes courants
| Ansatz | Rotations | Params (n=2, reps=1) | Expressivité | Cas d’usage typique |
|---|---|---|---|---|
| RealAmplitudes | Ry | 4 | Modérée | Classification binaire simple |
| EfficientSU2 | Ry + Rz | 8 | Élevée | Problèmes nécessitant des phases |
| TwoLocal | Configurable | Variable | Variable | Personnalisation fine |
Code Python — comparer des ansatzes Qiskit
from qiskit.circuit.library import EfficientSU2, RealAmplitudes
# RealAmplitudes : rotations Ry uniquement, amplitudes réelles
ra = RealAmplitudes(num_qubits=2, reps=1, entanglement='linear')
print(f"RealAmplitudes — {len(ra.parameters)} paramètres, profondeur {ra.decompose().depth()}")
print(ra.decompose().draw())
# EfficientSU2 : rotations Ry + Rz, couverture plus complète de l'espace de Hilbert
esu2 = EfficientSU2(num_qubits=2, reps=1, entanglement='linear')
print(f"\nEfficientSU2 — {len(esu2.parameters)} paramètres, profondeur {esu2.decompose().depth()}")
print(esu2.decompose().draw())
Code Python — assembler un circuit variationnel complet
from qiskit.circuit.library import ZZFeatureMap, RealAmplitudes
# Feature map : encode les données (paramètres non entraînables)
fm = ZZFeatureMap(feature_dimension=2, reps=1)
# Ansatz : espace des hypothèses (paramètres entraînables)
ansatz = RealAmplitudes(num_qubits=2, reps=1)
# Composer le circuit complet : feature map suivi de l'ansatz
vqc = fm.compose(ansatz)
print(f"Circuit complet : {len(vqc.parameters)} paramètres")
print(f" Features (données) : {fm.num_parameters}")
print(f" Ansatz (entraînables) : {ansatz.num_parameters}")
print(vqc.decompose().draw())
Piège courant : « Un ansatz plus profond donne toujours de meilleurs résultats » est inexact. Au-delà d’une certaine profondeur, les gradients s’aplatissent exponentiellement — c’est le phénomène des plateaus de Barren (chapitre 12). Sur un processeur NISQ, le bruit dégrade en plus la fidélité. En pratique, 1 à 3 couches suffisent pour la plupart des problèmes de classification QML.
Fil rouge — la frontière quantique/classique
Ce chapitre couvre les deux briques qui composent le cœur quantique de la boucle variationnelle : le feature map et l’ansatz. Le QPU exécute le circuit complet (feature map + ansatz), mesure les qubits et renvoie des statistiques au processeur classique. L’optimiseur classique n’intervient que sur les paramètres θ de l’ansatz — les paramètres du feature map sont les données d’entrée, fixées à chaque itération. Le compromis expressivité ↔ entraînabilité se joue sur deux fronts : un ZZ Feature Map enrichit la représentation des données mais ajoute de la profondeur, et un ansatz expressif augmente la capacité du modèle mais rend l’optimisation plus difficile. L’art du QML NISQ consiste à doser ces deux sources de profondeur pour rester dans le budget de portes tolérable par le matériel.
Kata Qiskit — construire un circuit variationnel ZZ + ansatz
Objectif : construire manuellement un ZZ Feature Map et un ansatz hardware-efficient, puis les assembler en un circuit variationnel complet.
Squelette :
# kata_day_11_12.py
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
import numpy as np
def build_zz_feature_map(n_qubits: int = 2) -> QuantumCircuit:
"""Construire un ZZ Feature Map pour 2 qubits (1 répétition).
Structure : H⊗n → P(2·xᵢ) sur chaque qubit → CNOT(0,1) → P(2·(π-x₀)(π-x₁)) → CNOT(0,1)
"""
x = ParameterVector('x', n_qubits)
qc = QuantumCircuit(n_qubits)
# TODO 1 : appliquer une porte Hadamard sur chaque qubit
# TODO 2 : appliquer P(2 * x[i]) sur chaque qubit i
# TODO 3 : appliquer le terme ZZ — CNOT(0,1), P(2*(π - x[0])*(π - x[1])) sur qubit 1, CNOT(0,1)
return qc
def build_hardware_ansatz(n_qubits: int = 2, reps: int = 1) -> QuantumCircuit:
"""Construire un ansatz hardware-efficient.
Structure par couche : Ry(θ) sur chaque qubit → CNOT(0,1).
Couche finale : Ry(θ) sur chaque qubit (sans CNOT).
"""
n_params = n_qubits * (reps + 1)
theta = ParameterVector('θ', n_params)
qc = QuantumCircuit(n_qubits)
idx = 0
# TODO 4 : pour chaque répétition, appliquer Ry(theta[idx]) sur chaque qubit puis CNOT(0,1)
# TODO 5 : couche finale — Ry(theta[idx]) sur chaque qubit, sans CNOT
return qc
def assemble_vqc(feature_map: QuantumCircuit, ansatz: QuantumCircuit) -> QuantumCircuit:
"""Assembler le feature map et l'ansatz en un seul circuit variationnel."""
# TODO 6 : composer feature_map suivi de ansatz
return QuantumCircuit(1) # placeholder
Auto-correction :
# test_kata_day_11_12.py
import numpy as np
from kata_day_11_12 import build_zz_feature_map, build_hardware_ansatz, assemble_vqc
# --- Test du ZZ Feature Map ---
fm = build_zz_feature_map(2)
assert fm.num_qubits == 2, "Le feature map doit avoir 2 qubits"
assert len(fm.parameters) == 2, f"Le feature map doit avoir 2 paramètres, trouvé {len(fm.parameters)}"
ops = [instr.operation.name for instr in fm.data]
assert 'h' in ops, "Il manque les portes Hadamard"
assert 'p' in ops, "Il manque les portes Phase (P)"
assert 'cx' in ops, "Il manque les CNOT pour l'interaction ZZ"
# --- Test de l'ansatz ---
ansatz = build_hardware_ansatz(2, reps=1)
assert ansatz.num_qubits == 2, "L'ansatz doit avoir 2 qubits"
assert len(ansatz.parameters) == 4, f"L'ansatz doit avoir 4 paramètres, trouvé {len(ansatz.parameters)}"
ops_a = [instr.operation.name for instr in ansatz.data]
assert 'ry' in ops_a, "L'ansatz doit contenir des rotations Ry"
assert 'cx' in ops_a, "L'ansatz doit contenir au moins un CNOT"
# --- Test du circuit assemblé ---
vqc = assemble_vqc(fm, ansatz)
assert vqc.num_qubits == 2, "Le VQC doit avoir 2 qubits"
assert len(vqc.parameters) == 6, f"Le VQC doit avoir 6 paramètres (2 + 4), trouvé {len(vqc.parameters)}"
print("Kata validé !")
Solution et explication
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
import numpy as np
def build_zz_feature_map(n_qubits: int = 2) -> QuantumCircuit:
x = ParameterVector('x', n_qubits)
qc = QuantumCircuit(n_qubits)
for i in range(n_qubits):
qc.h(i)
for i in range(n_qubits):
qc.p(2 * x[i], i)
qc.cx(0, 1)
qc.p(2 * (np.pi - x[0]) * (np.pi - x[1]), 1)
qc.cx(0, 1)
return qc
def build_hardware_ansatz(n_qubits: int = 2, reps: int = 1) -> QuantumCircuit:
n_params = n_qubits * (reps + 1)
theta = ParameterVector('θ', n_params)
qc = QuantumCircuit(n_qubits)
idx = 0
for _ in range(reps):
for i in range(n_qubits):
qc.ry(theta[idx], i)
idx += 1
qc.cx(0, 1)
for i in range(n_qubits):
qc.ry(theta[idx], i)
idx += 1
return qc
def assemble_vqc(feature_map: QuantumCircuit, ansatz: QuantumCircuit) -> QuantumCircuit:
return feature_map.compose(ansatz)Pourquoi : le kata reconstruit les deux briques centrales de la boucle variationnelle. Le ZZ Feature Map ajoute des corrélations entre features via le terme (π − x₀)(π − x₁), implémenté par le triplet CNOT-P-CNOT. L’ansatz hardware-efficient alterne rotations Ry et CNOT pour créer un espace d’hypothèses entraînable. La composition via compose produit le circuit complet que le QPU exécutera — les 2 paramètres du feature map sont les données, les 4 de l’ansatz sont les variables d’optimisation.