Pular para o conteúdo principal

Loops de otimização

Nesta lição, aprenderemos como usar um otimizador para explorar iterativamente os estados quânticos parametrizados do nosso ansatz:

  • Inicializar um loop de otimização com bootstrapping
  • Entender as compensações ao usar otimizadores locais e globais
  • Explorar platôs estéreis e como evitá-los

Em alto nível, os otimizadores são centrais para explorar nosso espaço de busca. O otimizador usa avaliações da função de custo para selecionar o próximo conjunto de parâmetros em um loop variacional, e repete o processo até atingir um estado estável. Nesse ponto, um conjunto ótimo de valores de parâmetros θ\vec\theta^* é retornado.

Diagrama de alguns fatores importantes na otimização, incluindo platôs estéreis, otimizadores com e sem gradiente, e bootstrapping.

Otimizadores locais e globais

Vamos primeiro configurar nosso problema antes de explorar cada classe de otimizador. Começaremos com um circuito contendo oito parâmetros variacionais:

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

Saída da célula de código anterior

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

Otimizadores locais

Os otimizadores locais buscam um ponto que minimize a função de custo a partir de um ponto (ou pontos) inicial C(θ0)C(\vec{\theta_0}) e se movem para outros pontos com base no que observam na região que estão avaliando em iterações sucessivas. Isso implica que a convergência desses algoritmos costuma ser rápida, mas pode depender fortemente do ponto inicial. Os otimizadores locais são incapazes de ver além da região onde estão avaliando e podem ser especialmente vulneráveis a mínimos locais, reportando convergência quando encontram um e ignorando outros estados com avaliações mais favoráveis.

# 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

Otimizadores globais

Os otimizadores globais buscam o ponto que minimiza a função de custo em várias regiões de seu domínio (ou seja, de forma não local), avaliando-a iterativamente (ou seja, na iteração ii) sobre um conjunto de vetores de parâmetros Θi:=θi,jjJopti\Theta_i := \\{ {\vec\theta_{i,j} | j \in \mathcal{J}_\text{opt}^i} \\} determinado pelo otimizador. Isso os torna menos suscetíveis a mínimos locais e relativamente independentes da inicialização, mas também significativamente mais lentos para convergir para uma solução proposta.

Bootstrapping da otimização

O bootstrapping, ou definição do valor inicial dos parâmetros θ\vec\theta com base em uma otimização anterior, pode ajudar o otimizador a convergir para uma solução mais rapidamente. Chamamos isso de ponto inicial θ0\vec\theta_0, e ψ(θ0)=UV(θ0)ρ|\psi(\vec\theta_0)\rangle = U_V(\vec\theta_0)|\rho\rangle de estado inicial. Esse estado inicial difere do nosso estado de referência ρ|\rho\rangle: o primeiro foca nos parâmetros iniciais definidos durante o loop de otimização, enquanto o segundo foca no uso de soluções de "referência" conhecidas. Eles podem coincidir se UV(θ0)IU_V(\vec\theta_0) \equiv I (ou seja, a operação identidade).

Quando os otimizadores locais convergem para mínimos locais não ótimos, podemos tentar inicializar a otimização globalmente e refinar a convergência localmente. Embora isso exija a configuração de duas cargas de trabalho variacionais, permite que o otimizador encontre uma solução mais ótima do que o otimizador local sozinho.

Otimizadores baseados em gradiente e sem gradiente

Baseados em gradiente

Para nossa função de custo C(θ)C(\vec\theta), se tivermos acesso ao gradiente da função C(θ)\vec{\nabla} C(\vec\theta) a partir de um ponto inicial, a forma mais simples de minimizar a função é atualizar os parâmetros na direção da descida mais íngreme da função. Ou seja, atualizamos os parâmetros como θn+1=θnηC(θ)\vec\theta_{n+1} = \vec\theta_n - \eta \vec{\nabla} C(\vec\theta), onde η\eta é a taxa de aprendizado — um hiperparâmetro pequeno e positivo que controla o tamanho da atualização. Continuamos fazendo isso até convergirmos para um mínimo local da função de custo, C(θ)C({\vec\theta^*}). Podemos usar essa função de custo e um otimizador para calcular os parâmetros ótimos

# 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

As principais desvantagens desse tipo de otimização são a velocidade de convergência, que pode ser muito lenta, e a ausência de garantia de alcançar a solução ótima.

Gráfico de f(theta) contra theta, vários pontos mostram diferentes estados de um algoritmo de descida de gradiente encontrando o mínimo de uma curva.

Sem gradiente

Os algoritmos de otimização sem gradiente não precisam de informações sobre o gradiente e podem ser úteis em situações onde calcular o gradiente é difícil, caro ou muito ruidoso. Eles também tendem a ser mais robustos na busca de ótimos globais, enquanto os métodos baseados em gradiente tendem a convergir para ótimos locais. Vamos explorar alguns casos em que um otimizador sem gradiente pode ajudar a evitar platôs estéreis. No entanto, os métodos sem gradiente exigem mais recursos computacionais, especialmente para problemas com espaços de busca de alta dimensionalidade.

Aqui está um exemplo que usa o otimizador 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

Platôs estéreis

Na prática, o cenário da função de custo pode ser bastante complexo, como mostrado pelas colinas e vales no exemplo abaixo. O método de otimização nos guia pelo cenário da função de custo em busca do mínimo, conforme indicado pelos pontos e linhas pretas. Podemos ver que duas das três buscas terminam em um mínimo local do cenário, em vez do mínimo global.

Um manifold curvo complexo com muitos picos e vales.

Independentemente do tipo de método de otimização utilizado, se o cenário da função de custo for relativamente plano, pode ser difícil para o método determinar a direção adequada de busca. Esse cenário é chamado de platô estéril, onde o cenário da função de custo se torna progressivamente mais plano (e, portanto, mais difícil de determinar a direção para o mínimo). Para uma ampla gama de circuitos quânticos parametrizados, a probabilidade de que o gradiente em qualquer direção razoável seja diferente de zero para uma determinada precisão diminui exponencialmente com o aumento do número de qubits.

Diagrama de um platô geográfico comparado a uma encosta de montanha, para explicar por que um gradiente nos ajuda a encontrar um mínimo e um platô dificulta nossos esforços.

Embora essa área ainda esteja sob pesquisa ativa, temos algumas recomendações para melhorar o desempenho da otimização:

  • Bootstrapping pode ajudar o loop de otimização a evitar ficar preso em um espaço de parâmetros onde o gradiente é pequeno.
  • Experimente com ansatz eficiente em hardware: Como usamos um sistema quântico ruidoso como um oráculo de caixa preta, a qualidade dessas avaliações pode afetar o desempenho do otimizador. Usar um ansatz eficiente em hardware, como EfficientSU2, pode evitar a produção de gradientes exponencialmente pequenos.
  • Experimente com supressão e mitigação de erros: as primitivas do Qiskit Runtime fornecem uma interface simples para experimentar com vários valores de optimization_level e resilience_setting, respectivamente. Isso pode reduzir o impacto do ruído e tornar o processo de otimização mais eficiente.
  • Experimente com otimizadores sem gradiente: Ao contrário dos algoritmos de otimização baseados em gradiente, otimizadores como COBYLA não dependem de informações de gradiente para otimizar os parâmetros e, portanto, são menos propensos a serem afetados pelo platô estéril.

Resumo

Com esta lição, você aprendeu como definir seu loop de otimização:

  • Inicializar um loop de otimização com bootstrapping
  • Entender as compensações ao usar otimizadores locais e globais
  • Explorar platôs estéreis e como evitá-los

Nossa carga de trabalho variacional de alto nível está completa:

Um circuito quântico agora com tanto um unitário para preparar o estado de referência quanto um segundo unitário para variar o estado usando parâmetros variacionais.

A seguir, exploraremos algoritmos variacionais específicos com esse framework em mente.