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 est retourné.
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")
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 , 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 ) sur un ensemble de vecteurs de paramètres 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 à partir d'une optimisation préalable, peut aider l'optimiseur à converger plus rapidement vers une solution. On appelle cela le point initial , et l'état initial. Cet état initial diffère de notre état de référence : 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 (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 , si nous avons accès au gradient de la fonction à 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 : , où 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, . 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.
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.
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.
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_leveletresilience_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
COBYLAne 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 :
Ensuite, nous allons explorer des algorithmes variationnels spécifiques en gardant ce cadre à l'esprit.