Saltar al contenido principal

Bucles de optimización

En esta lección aprenderemos cómo usar un optimizador para explorar iterativamente los estados cuánticos parametrizados de nuestro ansatz:

  • Hacer bootstrapping de un bucle de optimización
  • Comprender los compromisos al usar optimizadores locales y globales
  • Explorar los barren plateaus y cómo evitarlos

A alto nivel, los optimizadores son fundamentales para la exploración de nuestro espacio de búsqueda. El optimizador utiliza evaluaciones de la función de costo para seleccionar el siguiente conjunto de parámetros en un bucle variacional, y repite este proceso hasta alcanzar un estado estable. En esta etapa, se devuelve un conjunto óptimo de valores de parámetros θ\vec\theta^*.

Un diagrama de factores importantes en la optimización, incluyendo barren plateaus, optimizadores basados en gradiente vs. libres de gradiente, y bootstrapping.

Optimizadores locales y globales

Primero configuremos nuestro problema antes de explorar cada clase de optimizador. Comenzamos con un circuito que contiene ocho parámetros variacionales:

# 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")

Output of the previous code cell

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()

Optimizadores locales

Los optimizadores locales buscan un punto que minimice la función de costo, partiendo de uno o más puntos iniciales C(θ0)C(\vec{\theta_0}), y se desplazan a otros puntos basándose en lo que observan en la región que evalúan en iteraciones sucesivas. Esto significa que la convergencia de estos algoritmos suele ser rápida, pero puede depender fuertemente del punto inicial. Los optimizadores locales no pueden ver más allá de la región que evalúan y son particularmente susceptibles a mínimos locales: reportan convergencia tan pronto como encuentran uno, ignorando otros estados con evaluaciones más 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

Optimizadores globales

Los optimizadores globales buscan el punto que minimiza la función de costo en múltiples regiones de su dominio (es decir, de forma no local), evaluándola iterativamente (es decir, en la iteración ii) sobre un conjunto de vectores de parámetros Θi:=θi,jjJopti\Theta_i := \\{ {\vec\theta_{i,j} | j \in \mathcal{J}_\text{opt}^i} \\} determinados por el optimizador. Esto los hace menos susceptibles a mínimos locales y algo independientes de la inicialización, aunque convergen significativamente más lento hacia una solución propuesta.

Bootstrapping de la optimización

El bootstrapping, es decir, establecer el valor inicial de los parámetros θ\vec\theta basándose en una optimización previa, puede ayudar a nuestro optimizador a converger más rápido hacia una solución. Nos referimos a esto como punto inicial θ0\vec\theta_0, y a ψ(θ0)=UV(θ0)ρ|\psi(\vec\theta_0)\rangle = U_V(\vec\theta_0)|\rho\rangle como estado inicial. Este estado inicial difiere de nuestro estado de referencia ρ|\rho\rangle: el primero se centra en parámetros iniciales que se establecen durante nuestro bucle de optimización, mientras que el segundo se orienta al uso de soluciones "de referencia" conocidas. Pueden coincidir si UV(θ0)IU_V(\vec\theta_0) \equiv I (es decir, la operación identidad).

Cuando los optimizadores locales convergen a mínimos locales no óptimos, podemos intentar hacer bootstrapping de la optimización globalmente y refinar la convergencia localmente. Aunque esto requiere configurar dos flujos de trabajo variacionales, permite al optimizador encontrar una solución más óptima que el optimizador local por sí solo.

Optimizadores basados en gradiente y libres de gradiente

Basado en gradiente

Para nuestra función de costo C(θ)C(\vec\theta), si tenemos acceso al gradiente de la función C(θ)\vec{\nabla} C(\vec\theta) desde un punto inicial, la forma más simple de minimizar la función es actualizar los parámetros en la dirección del descenso más pronunciado. Es decir, actualizamos los parámetros como θn+1=θnηC(θ)\vec\theta_{n+1} = \vec\theta_n - \eta \vec{\nabla} C(\vec\theta), donde η\eta es la tasa de aprendizaje, un pequeño hiperparámetro positivo que controla el tamaño de la actualización. Continuamos esto hasta converger a un mínimo local de la función de costo C(θ)C({\vec\theta^*}). Podemos usar esta función de costo y un optimizador para calcular los parámetros óptimos.

# 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

Las principales desventajas de este tipo de optimización son la velocidad de convergencia, que puede ser muy lenta, y la falta de garantía de alcanzar la solución óptima.

Gráfico de f(theta) contra theta; varios puntos muestran diferentes estados de un algoritmo de descenso por gradiente al encontrar el mínimo de una curva.

Libre de gradiente

Los algoritmos de optimización libres de gradiente no requieren información de gradiente y pueden ser útiles en situaciones donde el cálculo del gradiente es difícil, costoso o demasiado ruidoso. También tienden a ser más robustos en la búsqueda de óptimos globales, mientras que los métodos basados en gradiente tienden a converger a óptimos locales. Exploraremos algunos escenarios en los que un optimizador libre de gradiente puede ayudar a evitar barren plateaus. Sin embargo, los métodos libres de gradiente requieren mayores recursos computacionales, especialmente en problemas con espacios de búsqueda de alta dimensionalidad.

Aquí hay un ejemplo que usa el optimizador COBYLA en su lugar:

# 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 plateaus

En realidad, el paisaje de costos puede ser bastante complejo, como muestran las colinas y valles del siguiente ejemplo. El método de optimización nos navega a través del paisaje de costos en busca del mínimo, representado por los puntos y líneas negros. Podemos ver que dos de las tres búsquedas terminan en un mínimo local del paisaje, en lugar del global.

Una superficie curva compleja con muchos picos y valles.

Independientemente del método de optimización utilizado, puede ser difícil determinar la dirección de búsqueda apropiada cuando el paisaje de costos es relativamente plano. Este escenario se denomina barren plateau, donde el paisaje de costos se vuelve cada vez más plano (y por lo tanto más difícil determinar la dirección hacia el mínimo). Para una amplia gama de circuitos cuánticos parametrizados, la probabilidad de que el gradiente sea distinto de cero en una dirección razonable (con una precisión determinada) decrece exponencialmente con el número de qubits.

Un diagrama que muestra una meseta geográfica en comparación con una ladera montañosa para explicar por qué un gradiente nos ayuda a encontrar un mínimo y una meseta obstaculiza nuestros esfuerzos.

Si bien este campo aún se investiga activamente, tenemos algunas recomendaciones para mejorar el rendimiento de la optimización:

  • Bootstrapping puede ayudar a evitar que el bucle de optimización se quede atrapado en un espacio de parámetros donde el gradiente es pequeño.
  • Experimentar con ansätze eficientes en hardware: Dado que usamos un sistema cuántico ruidoso como oráculo de caja negra, la calidad de estas evaluaciones puede afectar el rendimiento del optimizador. El uso de ansätze eficientes en hardware, como EfficientSU2, puede evitar la generación de gradientes exponencialmente pequeños.
  • Experimentar con supresión y mitigación de errores: Los Qiskit Runtime Primitives ofrecen una interfaz simple para experimentar con diferentes valores de optimization_level y resilience_setting. Esto puede reducir el impacto del ruido y hacer el proceso de optimización más eficiente.
  • Experimentar con optimizadores libres de gradiente: A diferencia de los algoritmos de optimización basados en gradiente, optimizadores como COBYLA no dependen de la información de gradiente para la optimización de parámetros y, por lo tanto, son menos susceptibles al problema de los barren plateaus.

Resumen

En esta lección aprendiste cómo definir tu bucle de optimización:

  • Hacer bootstrapping de un bucle de optimización
  • Comprender los compromisos al usar optimizadores locales y globales
  • Explorar los barren plateaus y cómo evitarlos

Nuestro flujo de trabajo variacional de alto nivel está ahora completo:

Un circuito cuántico con dos operadores unitarios: uno para preparar el estado de referencia y un segundo para variar el estado usando parámetros variacionales.

A continuación, exploraremos algoritmos variacionales específicos con este marco de trabajo.