Loop di ottimizzazione
In questa lezione impareremo come usare un ottimizzatore per esplorare iterativamente gli stati quantistici parametrizzati del nostro ansatz:
- Avviare un loop di ottimizzazione
- Comprendere i compromessi nell'uso di ottimizzatori locali e globali
- Esplorare i barren plateau e come evitarli
A livello generale, gli ottimizzatori sono fondamentali per esplorare il nostro spazio di ricerca. L'ottimizzatore utilizza le valutazioni della funzione di costo per selezionare il prossimo set di parametri in un loop variazionale, e ripete il processo fino a raggiungere uno stato stabile. A questo punto, viene restituito un set ottimale di valori dei parametri .
Ottimizzatori locali e globali
Prima di esplorare ciascuna classe di ottimizzatori, configureremo il nostro problema. Inizieremo con un circuito contenente otto parametri variazionali:
# 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()
Ottimizzatori locali
Gli ottimizzatori locali cercano un punto che minimizzi la funzione di costo partendo da uno o più punti iniziali e si spostano verso altri punti in base a ciò che osservano nella regione che stanno valutando nelle iterazioni successive. Questo implica che la convergenza di questi algoritmi è solitamente rapida, ma può dipendere fortemente dal punto iniziale. Gli ottimizzatori locali non riescono a vedere oltre la regione in cui stanno valutando e possono essere particolarmente vulnerabili ai minimi locali, segnalando la convergenza quando ne trovano uno e ignorando altri stati con valutazioni più favorevoli.
# 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
Ottimizzatori globali
Gli ottimizzatori globali cercano il punto che minimizza la funzione di costo su diverse regioni del suo dominio (cioè, in modo non locale), valutandola iterativamente (cioè all'iterazione ) su un insieme di vettori di parametri determinati dall'ottimizzatore. Questo li rende meno suscettibili ai minimi locali e in qualche misura indipendenti dall'inizializzazione, ma anche significativamente più lenti a convergere verso una soluzione proposta.
Bootstrapping dell'ottimizzazione
Il bootstrapping, ovvero impostare il valore iniziale dei parametri sulla base di un'ottimizzazione precedente, può aiutare l'ottimizzatore a convergere più velocemente verso una soluzione. Ci riferiamo a questo come punto iniziale , e come stato iniziale. Questo stato iniziale differisce dal nostro stato di riferimento : il primo si concentra sui parametri iniziali impostati durante il loop di ottimizzazione, mentre il secondo si concentra sull'uso di soluzioni "di riferimento" note. Possono coincidere se (cioè, l'operazione identità).
Quando gli ottimizzatori locali convergono verso minimi locali non ottimali, possiamo provare il bootstrapping dell'ottimizzazione a livello globale e raffinare la convergenza localmente. Sebbene questo richieda di configurare due workload variazionali, consente all'ottimizzatore di trovare una soluzione più ottimale rispetto al solo ottimizzatore locale.
Ottimizzatori con gradiente e senza gradiente
Con gradiente
Per la nostra funzione di costo , se abbiamo accesso al gradiente della funzione a partire da un punto iniziale, il modo più semplice per minimizzare la funzione è aggiornare i parametri nella direzione della discesa più ripida. Cioè, aggiorniamo i parametri come , dove è il tasso di apprendimento — un iperparametro piccolo e positivo che controlla la dimensione dell'aggiornamento. Continuiamo a farlo finché non convergiamo verso un minimo locale della funzione di costo, . Possiamo usare questa funzione di costo e un ottimizzatore per calcolare i parametri ottimali
# 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
I principali svantaggi di questo tipo di ottimizzazione sono la velocità di convergenza, che può essere molto lenta, e il fatto che non vi è alcuna garanzia di raggiungere la soluzione ottimale.
Senza gradiente
Gli algoritmi di ottimizzazione senza gradiente non richiedono informazioni sul gradiente e possono essere utili in situazioni in cui calcolare il gradiente è difficile, costoso o troppo rumoroso. Tendono inoltre a essere più robusti nel trovare ottimi globali, mentre i metodi basati sul gradiente tendono a convergere verso ottimi locali. Esploreremo alcuni casi in cui un ottimizzatore senza gradiente può aiutare a evitare i barren plateau. Tuttavia, i metodi senza gradiente richiedono risorse computazionali maggiori, specialmente per problemi con spazi di ricerca ad alta dimensionalità.
Ecco un esempio che utilizza l'ottimizzatore COBYLA:
# 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
Barren plateau
In realtà, il panorama della funzione di costo può essere piuttosto complicato, come mostrato dalle colline e dalle valli nell'esempio seguente. Il metodo di ottimizzazione ci guida attraverso il panorama della funzione di costo alla ricerca del minimo, come mostrato dai punti e dalle linee nere. Possiamo osservare che due delle tre ricerche finiscono in un minimo locale del panorama, anziché in quello globale.
Indipendentemente dal tipo di metodo di ottimizzazione utilizzato, se il panorama della funzione di costo è relativamente piatto, può essere difficile per il metodo determinare la direzione corretta di ricerca. Questo scenario è noto come barren plateau, dove il panorama della funzione di costo diventa progressivamente più piatto (e quindi più difficile da cui determinare la direzione verso il minimo). Per una vasta gamma di circuiti quantistici parametrizzati, la probabilità che il gradiente lungo qualsiasi direzione ragionevole sia diverso da zero con una precisione fissa decresce esponenzialmente all'aumentare del numero di qubit.
Sebbene quest'area sia ancora oggetto di ricerca attiva, abbiamo alcune raccomandazioni per migliorare le prestazioni dell'ottimizzazione:
- Bootstrapping: può aiutare il loop di ottimizzazione a evitare di rimanere bloccato in uno spazio dei parametri dove il gradiente è piccolo.
- Sperimentare con ansatz hardware-efficienti: poiché utilizziamo un sistema quantistico rumoroso come oracolo a scatola nera, la qualità di tali valutazioni può influire sulle prestazioni dell'ottimizzatore. Usare ansatz hardware-efficienti, come
EfficientSU2, può evitare la produzione di gradienti esponenzialmente piccoli. - Sperimentare con la soppressione e la mitigazione degli errori: le primitive di Qiskit Runtime forniscono una semplice interfaccia per sperimentare con vari valori di
optimization_leveleresilience_setting, rispettivamente. Questo può ridurre l'impatto del rumore e rendere il processo di ottimizzazione più efficiente. - Sperimentare con ottimizzatori senza gradiente: a differenza degli algoritmi di ottimizzazione basati sul gradiente, ottimizzatori come
COBYLAnon si affidano alle informazioni sul gradiente per ottimizzare i parametri e sono quindi meno soggetti ai barren plateau.
Riepilogo
Con questa lezione hai imparato come definire il tuo loop di ottimizzazione:
- Avviare un loop di ottimizzazione
- Comprendere i compromessi nell'uso di ottimizzatori locali e globali
- Esplorare i barren plateau e come evitarli
Il nostro workload variazionale di alto livello è completo:
Nel prossimo modulo esploreremo algoritmi variazionali specifici tenendo presente questo framework.