Bucle de optimizare
În această lecție, vom învăța cum să folosim un optimizer pentru a explora iterativ stările cuantice parametrizate ale ansatz-ului nostru:
- Pornirea unei bucle de optimizare (bootstrapping)
- Înțelegerea compromisurilor la utilizarea optimizatoarelor locale și globale
- Explorarea platouri sterile (barren plateaus) și cum să le eviți
La nivel înalt, optimizatoarele sunt esențiale pentru explorarea spațiului nostru de căutare. Optimizatorul folosește evaluările funcției de cost pentru a selecta următorul set de parametri într-o buclă variațională și repetă procesul până când ajunge la o stare stabilă. În această etapă, se returnează un set optim de valori ale parametrilor .
Optimizatoare locale și globale
Vom configura mai întâi problema noastră înainte de a explora fiecare clasă de optimizer. Vom începe cu un circuit care conține opt parametri variaționali:
# 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()
Optimizatoare locale
Optimizatoarele locale caută un punct care minimizează funcția de cost pornind de la un punct (sau puncte) inițial(e) și se deplasează către alte puncte pe baza a ceea ce observă în regiunea pe care o evaluează în iterații succesive. Aceasta implică faptul că convergența acestor algoritmi va fi de obicei rapidă, dar poate depinde puternic de punctul inițial. Optimizatoarele locale nu pot vedea dincolo de regiunea în care evaluează și pot fi deosebit de vulnerabile la minime locale, raportând convergența atunci când găsesc unul și ignorând alte stări cu evaluări mai favorabile.
# 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
Optimizatoare globale
Optimizatoarele globale caută punctul care minimizează funcția de cost pe mai multe regiuni ale domeniului său (adică, non-local), evaluând-o iterativ (adică la iterația ) peste un set de vectori de parametri determinat de optimizer. Aceasta le face mai puțin susceptibile la minime locale și parțial independente de inițializare, dar și semnificativ mai lente în convergența spre o soluție propusă.
Pornirea optimizării (Bootstrapping)
Bootstrapping, sau setarea valorii inițiale pentru parametrii pe baza unei optimizări anterioare, poate ajuta optimizatorul nostru să conveargă mai rapid spre o soluție. Ne referim la aceasta ca punct inițial , iar ca starea inițială. Această stare inițială diferă de starea noastră de referință , deoarece prima se concentrează pe parametrii inițiali setați în bucla noastră de optimizare, în timp ce cea din urmă se concentrează pe utilizarea soluțiilor „de referință" cunoscute. Ele pot coincide dacă (adică operația identitate).
Când optimizatoarele locale converg la minime locale non-optime, putem încerca să pornim optimizarea global și să rafinăm convergența local. Deși aceasta necesită configurarea a două fluxuri de lucru variaționale, permite optimizatorului să găsească o soluție mai optimă decât optimizatorul local singur.
Optimizatoare bazate pe gradient și fără gradient
Bazate pe gradient
Pentru funcția noastră de cost , dacă avem acces la gradientul funcției pornind de la un punct inițial, cel mai simplu mod de a minimiza funcția este să actualizăm parametrii în direcția descensului cel mai abrupt al funcției. Adică, actualizăm parametrii ca , unde este rata de învățare — un hiperparametru mic și pozitiv care controlează dimensiunea actualizării. Continuăm să facem acest lucru până când converg la un minim local al funcției de cost, . Putem folosi această funcție de cost și un optimizer pentru a calcula parametrii optimi
# 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
Principalele dezavantaje ale acestui tip de optimizare sunt viteza de convergență, care poate fi foarte lentă, și faptul că nu există nicio garanție că se va atinge soluția optimă.
Fără gradient
Algoritmii de optimizare fără gradient nu necesită informații despre gradient și pot fi utili în situații în care calcularea gradientului este dificilă, costisitoare sau prea zgomotoasă. Ei tind, de asemenea, să fie mai robuști în găsirea optimelor globale, în timp ce metodele bazate pe gradient tind să conveargă la optime locale. Vom explora câteva situații în care un optimizer fără gradient poate ajuta la evitarea platouri sterile. Cu toate acestea, metodele fără gradient necesită resurse computaționale mai mari, în special pentru problemele cu spații de căutare cu dimensiuni ridicate.
Iată un exemplu care folosește optimizatorul COBYLA în loc:
# 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
Platouri sterile (Barren Plateaus)
De fapt, peisajul costului poate fi destul de complicat, așa cum se arată prin dealuri și văi în exemplul de mai jos. Metoda de optimizare ne ghidează prin peisajul costului, căutând minimul, după cum se arată prin punctele și liniile negre. Putem observa că două dintre cele trei căutări ajung într-un minim local al peisajului, nu global.
Indiferent de tipul metodei de optimizare utilizate, dacă peisajul costului este relativ plat, poate fi dificil pentru metodă să determine direcția adecvată de căutare. Acest scenariu este denumit platou steril (barren plateau), unde peisajul costului devine progresiv mai plat (și astfel mai dificil de determinat direcția spre minim). Pentru o gamă largă de circuite cuantice parametrizate, probabilitatea ca gradientul de-a lungul oricărei direcții rezonabile să fie diferit de zero la o precizie fixă scade exponențial pe măsură ce numărul de qubiți crește.
Deși această zonă este încă în curs de cercetare activă, avem câteva recomandări pentru a îmbunătăți performanța optimizării:
- Bootstrapping poate ajuta bucla de optimizare să evite blocarea într-un spațiu de parametri în care gradientul este mic.
- Experimentarea cu ansatz eficient hardware: Deoarece folosim un sistem cuantic zgomotos ca oracol de tip cutie neagră, calitatea acelor evaluări poate afecta performanța optimizatorului. Utilizarea unui ansatz eficient hardware, cum ar fi
EfficientSU2, poate evita producerea de gradienți exponențial de mici. - Experimentarea cu supresie și mitigare a erorilor: primitivele Qiskit Runtime oferă o interfață simplă pentru a experimenta cu diverse valori pentru
optimization_levelși, respectiv,resilience_setting. Aceasta poate reduce impactul zgomotului și poate face procesul de optimizare mai eficient. - Experimentarea cu optimizatoare fără gradient: Spre deosebire de algoritmii de optimizare bazați pe gradient, optimizatoare precum
COBYLAnu se bazează pe informații despre gradient pentru a optimiza parametrii și, prin urmare, sunt mai puțin susceptibile de a fi afectate de platoul steril.
Rezumat
Cu această lecție, ai învățat cum să îți definești bucla de optimizare:
- Pornirea unei bucle de optimizare (bootstrapping)
- Înțelegerea compromisurilor la utilizarea optimizatoarelor locale și globale
- Explorarea platouri sterile (barren plateaus) și cum să le eviți
Fluxul nostru variațional de nivel înalt este complet:
În continuare, vom explora algoritmi variaționali specifici cu acest cadru în minte.