Pętle optymalizacji
W tej lekcji nauczysz się, jak używać optymalizatora do iteracyjnego eksplorowania sparametryzowanych stanów kwantowych naszego ansatz:
- Uruchamianie pętli optymalizacji (bootstrapping)
- Rozumienie kompromisów przy korzystaniu z optymalizatorów lokalnych i globalnych
- Eksplorowanie jałowych płaskowyżów (barren plateaus) i sposobów ich unikania
Na wysokim poziomie optymalizatory są kluczowe dla eksplorowania przestrzeni poszukiwań. Optymalizator używa ewaluacji funkcji kosztu do wyboru kolejnego zestawu parametrów w pętli wariantowej i powtarza ten proces, aż osiągnie stabilny stan. Na tym etapie zwracany jest optymalny zestaw wartości parametrów .
Optymalizatory lokalne i globalne
Najpierw skonfigurujemy nasz problem, zanim przejdziemy do eksploracji każdej klasy optymalizatorów. Zaczniemy od Circuit zawierającego osiem parametrów wariantowych:
# 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()
Optymalizatory lokalne
Optymalizatory lokalne poszukują punktu minimalizującego funkcję kosztu, startując od punktu (lub punktów) początkowego , i przechodzą do kolejnych punktów na podstawie tego, co obserwują w aktualnie ewaluowanym obszarze w kolejnych iteracjach. Oznacza to, że zbieżność tych algorytmów jest zazwyczaj szybka, ale może być silnie uzależniona od punktu startowego. Optymalizatory lokalne nie widzą poza obszarem, który aktualnie ewaluują, i są szczególnie podatne na lokalne minima — raportują zbieżność po ich znalezieniu, ignorując inne stany z korzystniejszymi ewaluacjami.
# 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
Optymalizatory globalne
Optymalizatory globalne poszukują punktu minimalizującego funkcję kosztu w kilku obszarach jej dziedziny (tzn. nielokalnie), ewaluując ją iteracyjnie (tzn. w iteracji ) na zestawie wektorów parametrów wyznaczonych przez optymalizator. Sprawia to, że są mniej podatne na lokalne minima i w pewnym stopniu niezależne od inicjalizacji, ale też znacznie wolniej zbiegają do proponowanego rozwiązania.
Bootstrapping optymalizacji
Bootstrapping, czyli ustawianie wartości początkowej parametrów na podstawie wcześniejszej optymalizacji, może pomóc optymalizatorowi szybciej zbiec do rozwiązania. Nazywamy to punktem startowym , a stanem startowym. Ten stan startowy różni się od naszego stanu referencyjnego : ten pierwszy skupia się na parametrach początkowych ustawianych podczas pętli optymalizacji, a ten drugi polega na używaniu znanych rozwiązań „referencyjnych". Mogą się one pokrywać, jeśli (tzn. operacja identyczności).
Gdy optymalizatory lokalne zbiegają do nieoptymalnych minimów lokalnych, możemy spróbować globalnego bootstrappingu optymalizacji i lokalnego doprecyzowania zbieżności. Choć wymaga to skonfigurowania dwóch wariantowych workloadów, pozwala optymalizatorowi znaleźć bardziej optymalne rozwiązanie niż sam optymalizator lokalny.
Optymalizatory oparte na gradiencie i bez gradientu
Oparte na gradiencie
Dla naszej funkcji kosztu , jeśli mamy dostęp do gradientu funkcji startując od punktu początkowego, najprostszym sposobem minimalizacji funkcji jest aktualizowanie parametrów w kierunku największego spadku funkcji. Tzn. aktualizujemy parametry jako , gdzie to tempo uczenia — mały, dodatni hiperparametr kontrolujący rozmiar aktualizacji. Kontynuujemy to, aż zbiegniemy do lokalnego minimum funkcji kosztu, .
Możemy użyć tej funkcji kosztu i optymalizatora do obliczenia optymalnych parametrów
# 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
Głównymi wadami tego rodzaju optymalizacji są szybkość zbieżności, która może być bardzo wolna, oraz brak gwarancji osiągnięcia optymalnego rozwiązania.
Bez gradientu
Algorytmy optymalizacji bez gradientu nie wymagają informacji o gradiencie i mogą być przydatne w sytuacjach, gdy obliczanie gradientu jest trudne, kosztowne lub zbyt zaszumione. Mają też tendencję do bycia bardziej odpornym na znajdowanie globalnych optimów, podczas gdy metody oparte na gradiencie mają skłonność do zbiegania do lokalnych optimów. Zbadamy kilka przypadków, w których optymalizator bez gradientu może pomóc uniknąć jałowych płaskowyżów. Metody bez gradientu wymagają jednak większych zasobów obliczeniowych, szczególnie w przypadku problemów z wielowymiarowymi przestrzeniami poszukiwań.
Oto przykład używający optymalizatora 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
Jałowe płaskowyże
W rzeczywistości krajobraz kosztu może być dość skomplikowany, co widać po wzgórzach i dolinach w poniższym przykładzie. Metoda optymalizacji prowadzi nas po tym krajobrazie, szukając minimum, co pokazują czarne punkty i linie. Widzimy, że dwa z trzech poszukiwań kończą się w lokalnym minimum krajobrazu, a nie globalnym.
Niezależnie od rodzaju zastosowanej metody optymalizacji, jeśli krajobraz kosztu jest stosunkowo płaski, metodzie może być trudno ustalić właściwy kierunek poszukiwań. Ten scenariusz określa się mianem jałowego płaskowyżu (barren plateau), gdzie krajobraz kosztu staje się stopniowo coraz płaszy (a tym samym coraz trudniej jest ustalić kierunek do minimum). Dla szerokiego zakresu sparametryzowanych Circuit kwantowych prawdopodobieństwo, że gradient w dowolnym rozsądnym kierunku jest niezerowy z określoną precyzją, maleje wykładniczo wraz ze wzrostem liczby qubitów.
Choć ta dziedzina jest wciąż aktywnie badana, mamy kilka rekomendacji poprawiających wydajność optymalizacji:
- Bootstrapping może pomóc pętli optymalizacji uniknąć utknięcia w przestrzeni parametrów, gdzie gradient jest mały.
- Eksperymentowanie z hardware-efficient ansatz: Ponieważ używamy zaszumionego systemu kwantowego jako wyroczni czarnej skrzynki, jakość tych ewaluacji może wpływać na wydajność optymalizatora. Użycie hardware-efficient ansatz, takiego jak
EfficientSU2, może pomóc uniknąć wykładniczo małych gradientów. - Eksperymentowanie z tłumieniem błędów i mitygacją błędów: prymitywy Qiskit Runtime zapewniają prosty interfejs do eksperymentowania z różnymi wartościami odpowiednio
optimization_leveliresilience_setting. Może to zmniejszyć wpływ szumu i sprawić, że proces optymalizacji będzie bardziej wydajny. - Eksperymentowanie z optymalizatorami bez gradientu: W przeciwieństwie do algorytmów optymalizacji opartych na gradiencie, optymalizatory takie jak
COBYLAnie polegają na informacji o gradiencie do optymalizacji parametrów i dlatego są mniej podatne na jałowe płaskowyże.
Podsumowanie
Dzięki tej lekcji nauczyłeś się, jak definiować pętle optymalizacji:
- Uruchamianie pętli optymalizacji (bootstrapping)
- Rozumienie kompromisów przy korzystaniu z optymalizatorów lokalnych i globalnych
- Eksplorowanie jałowych płaskowyżów i sposobów ich unikania
Nasz wysokopoziomowy wariantowy workload jest kompletny:
Następnie zbadamy konkretne algorytmy wariantowe, mając na uwadze ten framework.