Aller au contenu principal

Boucles d'optimisation

Durant cette leçon, nous allons apprendre à utiliser un optimiseur pour explorer de façon itérative les états quantiques paramétrés de notre ansatz :

  • Amorcer une boucle d'optimisation
  • Comprendre les compromis entre optimiseurs locaux et globaux
  • Explorer les plateaux stériles et comment les éviter

À haut niveau, les optimiseurs sont au cœur de l'exploration de notre espace de recherche. L'optimiseur utilise les évaluations de la fonction de coût pour sélectionner le prochain ensemble de paramètres dans une boucle variationnelle, et répète le processus jusqu'à atteindre un état stable. À ce stade, un ensemble optimal de valeurs de paramètres θ\vec\theta^* est retourné.

Un diagramme de quelques facteurs importants dans l'optimisation, incluant les plateaux stériles, les optimiseurs avec et sans gradient, et l'amorçage.

Optimiseurs locaux et globaux

Commençons par mettre en place notre problème avant d'explorer chaque classe d'optimiseur. Nous démarrerons avec un circuit contenant huit paramètres variationnels :

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit scipy
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.circuit.library import TwoLocal
import numpy as np

theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
observable = SparsePauliOp.from_list([("XX", 1), ("YY", -3)])

reference_circuit = QuantumCircuit(2)
reference_circuit.x(0)

variational_form = TwoLocal(
2,
rotation_blocks=["rz", "ry"],
entanglement_blocks="cx",
entanglement="linear",
reps=1,
)
ansatz = reference_circuit.compose(variational_form)

ansatz.decompose().draw("mpl")

Sortie de la cellule de code précédente

def cost_func_vqe(params, ansatz, hamiltonian, estimator):
"""Return estimate of energy from estimator

Parameters:
params (ndarray): Array of ansatz parameters
ansatz (QuantumCircuit): Parameterized ansatz circuit
hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
estimator (Estimator): Estimator primitive instance

Returns:
float: Energy estimate
"""
pub = (ansatz, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return cost
from qiskit.primitives import StatevectorEstimator

estimator = StatevectorEstimator()

Optimiseurs locaux

Les optimiseurs locaux cherchent un point qui minimise la fonction de coût en partant d'un ou plusieurs points initiaux C(θ0)C(\vec{\theta_0}), puis se déplacent vers d'autres points en fonction de ce qu'ils observent dans la région qu'ils évaluent à chaque itération successive. Cela implique que la convergence de ces algorithmes est généralement rapide, mais peut dépendre fortement du point initial. Les optimiseurs locaux ne voient pas au-delà de la région qu'ils évaluent et sont particulièrement vulnérables aux minima locaux : ils signalent une convergence dès qu'ils en trouvent un, en ignorant d'autres états avec des évaluations plus favorables.

# SciPy minimizer routine
from scipy.optimize import minimize

x0 = np.ones(8)

result = minimize(
cost_func_vqe, x0, args=(ansatz, observable, estimator), method="SLSQP"
)

result
message: Optimization terminated successfully
success: True
status: 0
fun: -3.9999999964520634
x: [ 1.000e+00 1.000e+00 -1.571e+00 -4.556e-05 -1.207e+00
-1.935e+00 4.079e-01 -4.079e-01]
nit: 12
jac: [ 0.000e+00 0.000e+00 -7.957e-04 2.543e-04 1.381e-03
1.381e-03 5.430e-04 5.431e-04]
nfev: 112
njev: 12

Optimiseurs globaux

Les optimiseurs globaux cherchent le point qui minimise la fonction de coût sur plusieurs régions de son domaine (c'est-à-dire de façon non locale), en l'évaluant de façon itérative (c'est-à-dire à l'itération ii) sur un ensemble de vecteurs de paramètres Θi:=θi,jjJopti\Theta_i := \\{ {\vec\theta_{i,j} | j \in \mathcal{J}_\text{opt}^i} \\} déterminé par l'optimiseur. Cela les rend moins sensibles aux minima locaux et relativement indépendants de l'initialisation, mais aussi nettement plus lents à converger vers une solution proposée.

Amorçage de l'optimisation

L'amorçage (bootstrapping), qui consiste à fixer la valeur initiale des paramètres θ\vec\theta à partir d'une optimisation préalable, peut aider l'optimiseur à converger plus rapidement vers une solution. On appelle cela le point initial θ0\vec\theta_0, et ψ(θ0)=UV(θ0)ρ|\psi(\vec\theta_0)\rangle = U_V(\vec\theta_0)|\rho\rangle l'état initial. Cet état initial diffère de notre état de référence ρ|\rho\rangle : le premier porte sur les paramètres initiaux fixés pendant notre boucle d'optimisation, tandis que le second repose sur des solutions de « référence » connues. Ils peuvent coïncider si UV(θ0)IU_V(\vec\theta_0) \equiv I (c'est-à-dire l'opération identité).

Lorsque les optimiseurs locaux convergent vers des minima locaux non optimaux, on peut essayer d'amorcer l'optimisation globalement, puis d'affiner la convergence localement. Bien que cela nécessite de mettre en place deux charges de travail variationnelles, cette approche permet à l'optimiseur de trouver une solution plus optimale que l'optimiseur local seul.

Optimiseurs avec et sans gradient

Avec gradient

Pour notre fonction de coût C(θ)C(\vec\theta), si nous avons accès au gradient de la fonction C(θ)\vec{\nabla} C(\vec\theta) à partir d'un point initial, la façon la plus simple de minimiser la fonction est de mettre à jour les paramètres dans la direction de la descente la plus abrupte. Autrement dit, on met à jour les paramètres comme suit : θn+1=θnηC(θ)\vec\theta_{n+1} = \vec\theta_n - \eta \vec{\nabla} C(\vec\theta), où η\eta est le taux d'apprentissage — un petit hyperparamètre positif qui contrôle la taille de la mise à jour. On continue ainsi jusqu'à converger vers un minimum local de la fonction de coût, C(θ)C({\vec\theta^*}). On peut utiliser cette fonction de coût et un optimiseur pour calculer les paramètres optimaux

# SciPy minimizer routine
from scipy.optimize import minimize

x0 = np.ones(8)

result = minimize(
cost_func_vqe, x0, args=(ansatz, observable, estimator), method="BFGS"
)

result
message: Optimization terminated successfully.
success: True
status: 0
fun: -3.9999999999997025
x: [ 1.000e+00 1.000e+00 1.571e+00 3.220e-07 2.009e-01
-2.009e-01 6.342e-01 -6.342e-01]
nit: 14
jac: [-1.192e-07 -2.980e-08 8.345e-07 1.103e-06 5.960e-08
0.000e+00 -5.960e-08 2.980e-08]
hess_inv: [[ 1.000e+00 1.872e-10 ... 5.077e-05 3.847e-05]
[ 1.872e-10 1.000e+00 ... -5.208e-05 -4.060e-05]
...
[ 5.077e-05 -5.208e-05 ... 7.243e-01 -2.604e-01]
[ 3.847e-05 -4.060e-05 ... -2.604e-01 8.179e-01]]
nfev: 144
njev: 16

Les principaux inconvénients de ce type d'optimisation sont la vitesse de convergence, qui peut être très lente, et l'absence de garantie d'atteindre la solution optimale.

Graphique de f(theta) en fonction de theta, où plusieurs points montrent les différentes étapes d'un algorithme de descente de gradient cherchant le minimum d'une courbe.

Sans gradient

Les algorithmes d'optimisation sans gradient ne nécessitent pas d'information sur le gradient et peuvent être utiles dans des situations où le calcul du gradient est difficile, coûteux ou trop bruité. Ils ont également tendance à être plus robustes pour trouver des optima globaux, tandis que les méthodes avec gradient convergent plutôt vers des optima locaux. Nous allons explorer quelques cas où un optimiseur sans gradient peut aider à éviter les plateaux stériles. Cependant, les méthodes sans gradient demandent davantage de ressources de calcul, en particulier pour les problèmes à espaces de recherche de grande dimension.

Voici un exemple qui utilise l'optimiseur COBYLA à la place :

# SciPy minimizer routine
from scipy.optimize import minimize

x0 = np.ones(8)

result = minimize(
cost_func_vqe, x0, args=(ansatz, observable, estimator), method="COBYLA"
)

result
message: Optimization terminated successfully.
success: True
status: 1
fun: -3.999999973369678
x: [ 1.631e+00 1.492e+00 1.571e+00 3.142e+00 1.375e+00
-1.767e+00 1.484e+00 1.658e+00]
nfev: 137
maxcv: 0.0

Plateaux stériles

En réalité, le paysage de coût peut être assez complexe, comme le montrent les collines et les vallées de l'exemple ci-dessous. La méthode d'optimisation nous guide à travers ce paysage de coût à la recherche du minimum, comme l'indiquent les points et lignes noirs. On peut voir que deux des trois recherches aboutissent à un minimum local du paysage plutôt qu'au minimum global.

Un collecteur courbe complexe avec de nombreux pics et creux.

Quelle que soit la méthode d'optimisation utilisée, si le paysage de coût est relativement plat, il peut être difficile pour la méthode de déterminer la direction de recherche appropriée. Ce scénario est appelé plateau stérile, où le paysage de coût devient progressivement plus plat (et donc de plus en plus difficile pour déterminer la direction vers le minimum). Pour une large gamme de circuits quantiques paramétrés, la probabilité que le gradient dans une direction raisonnable quelconque soit non nul à une précision fixe donnée décroît exponentiellement à mesure que le nombre de qubits augmente.

Un diagramme comparant un plateau géographique à un versant de montagne, pour expliquer pourquoi un gradient nous aide à trouver un minimum et pourquoi un plateau entrave nos efforts.

Bien que ce domaine soit encore en cours de recherche active, voici quelques recommandations pour améliorer les performances d'optimisation :

  • L'amorçage peut aider la boucle d'optimisation à éviter de rester bloquée dans un espace de paramètres où le gradient est petit.
  • Expérimenter avec des ansatz adaptés au matériel : puisque nous utilisons un système quantique bruité comme oracle boîte noire, la qualité de ces évaluations peut affecter les performances de l'optimiseur. L'utilisation d'ansatz adaptés au matériel, comme EfficientSU2, peut éviter de produire des gradients exponentiellement petits.
  • Expérimenter avec la suppression et l'atténuation des erreurs : les primitives Qiskit Runtime offrent une interface simple pour expérimenter différentes valeurs de optimization_level et resilience_setting, respectivement. Cela peut réduire l'impact du bruit et rendre le processus d'optimisation plus efficace.
  • Expérimenter avec des optimiseurs sans gradient : contrairement aux algorithmes d'optimisation avec gradient, des optimiseurs comme COBYLA ne s'appuient pas sur l'information de gradient pour optimiser les paramètres et sont donc moins susceptibles d'être affectés par le plateau stérile.

Résumé

Avec cette leçon, tu as appris à définir ta boucle d'optimisation :

  • Amorcer une boucle d'optimisation
  • Comprendre les compromis entre optimiseurs locaux et globaux
  • Explorer les plateaux stériles et comment les éviter

Notre charge de travail variationnelle de haut niveau est complète :

Un circuit quantique avec à la fois un unitaire pour préparer l'état de référence, et un second unitaire pour faire varier l'état à l'aide de paramètres variationnels.

Ensuite, nous allons explorer des algorithmes variationnels spécifiques en gardant ce cadre à l'esprit.