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 é retornado.
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")
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 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 ) sobre um conjunto de vetores de parâmetros 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 com base em uma otimização anterior, pode ajudar o otimizador a convergir para uma solução mais rapidamente. Chamamos isso de ponto inicial , e de estado inicial. Esse estado inicial difere do nosso estado de referência : 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 (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 , se tivermos acesso ao gradiente da função 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 , onde é 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, . 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.
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.
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.
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_leveleresilience_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
COBYLAnã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:
A seguir, exploraremos algoritmos variacionais específicos com esse framework em mente.