Перейти до основного вмісту

Цикли оптимізації

У цьому уроці ми навчимося використовувати оптимізатор для ітераційного дослідження параметризованих квантових станів нашого ансатцу:

  • Запускати цикл оптимізації (bootstrapping)
  • Розуміти компроміси між локальними та глобальними оптимізаторами
  • Досліджувати «порожні плато» та способи їх уникнення

На високому рівні: оптимізатори є ключовими для дослідження простору пошуку. Оптимізатор використовує обчислення функції витрат для вибору наступного набору параметрів у варіаційному циклі і повторює процес до досягнення стабільного стану. На цьому етапі повертається оптимальний набір значень параметрів θ\vec\theta^*.

Схема важливих факторів оптимізації: порожні плато, оптимізатори з градієнтом і без, bootstrapping.

Локальні та глобальні оптимізатори

Спочатку налаштуємо задачу перед тим, як досліджувати кожен клас оптимізаторів. Почнемо зі схеми, що містить вісім варіаційних параметрів:

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

Локальні оптимізатори

Локальні оптимізатори шукають точку мінімуму функції витрат, починаючи з початкової точки C(θ0)C(\vec{\theta_0}), і переміщуються до інших точок на основі того, що спостерігають у поточній ділянці. Ці алгоритми зазвичай швидко збігаються, але їхня робота може сильно залежати від початкової точки. Локальні оптимізатори не можуть «побачити» за межі поточної ділянки і особливо вразливі до локальних мінімумів — вони повідомляють про збіжність, знаходячи такий мінімум, ігноруючи стани з кращими значеннями.

# 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

Глобальні оптимізатори

Глобальні оптимізатори шукають точку мінімуму функції витрат у кількох ділянках її домену (тобто нелокально), ітераційно обчислюючи (на ітерації ii) набір векторів параметрів Θi:=θi,jjJopti\Theta_i := \\{ {\vec\theta_{i,j} | j \in \mathcal{J}_\text{opt}^i} \\}, що визначаються оптимізатором. Це робить їх менш чутливими до локальних мінімумів і певною мірою незалежними від ініціалізації, але й значно повільнішими у збіжності.

Bootstrapping (розкрутка) оптимізації

Bootstrapping, або встановлення початкового значення параметрів θ\vec\theta на основі попередньої оптимізації, допомагає оптимізатору швидше знайти рішення. Ми позначаємо це як початкову точку θ0\vec\theta_0, а ψ(θ0)=UV(θ0)ρ|\psi(\vec\theta_0)\rangle = U_V(\vec\theta_0)|\rho\rangle — початковий стан. Він відрізняється від нашого референсного стану ρ|\rho\rangle: перший фокусується на початкових параметрах під час циклу оптимізації, тоді як другий — на використанні відомих «еталонних» рішень. Вони збігаються, якщо UV(θ0)IU_V(\vec\theta_0) \equiv I (тобто тотожна операція).

Коли локальні оптимізатори збігаються до неоптимальних локальних мінімумів, можна спробувати глобально «розкрутити» оптимізацію, а потім локально уточнити збіжність. Хоча це потребує налаштування двох варіаційних навантажень, дає змогу знайти більш оптимальне рішення, ніж лише локальний оптимізатор.

Оптимізатори з градієнтом і без

З градієнтом

Для функції витрат C(θ)C(\vec\theta), маючи доступ до градієнта C(θ)\vec{\nabla} C(\vec\theta) у початковій точці, найпростіший спосіб мінімізувати функцію — оновлювати параметри у напрямку найстрімнішого спуску: θn+1=θnηC(θ)\vec\theta_{n+1} = \vec\theta_n - \eta \vec{\nabla} C(\vec\theta), де η\eta — швидкість навчання — малий додатний гіперпараметр, що контролює розмір оновлення. Продовжуємо, поки не досягнемо локального мінімуму функції витрат C(θ)C({\vec\theta^*}). Можна використати цю функцію витрат та оптимізатор для обчислення оптимальних параметрів:

# 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

Основні недоліки цього типу оптимізації — повільна швидкість збіжності та відсутність гарантії досягнення оптимального рішення.

Графік f(theta) відносно theta; численні точки показують різні стани алгоритму градієнтного спуску, що знаходить мінімум кривої.

Без градієнта

Оптимізатори без градієнта не потребують інформації про градієнт і можуть бути корисні в ситуаціях, коли обчислення градієнта складне, дороге або занадто зашумлене. Вони також, як правило, краще знаходять глобальні оптимуми, тоді як методи на основі градієнта схильні збігатися до локальних. Розглянемо кілька прикладів, коли оптимізатор без градієнта може допомогти уникнути порожніх плато. Однак такі методи потребують більших обчислювальних ресурсів, особливо для задач із великими пошуковими просторами.

Приклад із використанням оптимізатора 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

Порожні плато

Ландшафт функції витрат може бути доволі складним, як показують пагорби та западини у прикладі нижче. Метод оптимізації «навігує» по цьому ландшафту, шукаючи мінімум (чорні точки та лінії). Видно, що два з трьох пошуків потрапляють до локальних мінімумів ландшафту, а не до глобального.

Складний вигнутий різновид з багатьма піками і западинами.

Незалежно від типу методу оптимізації, якщо ландшафт функції витрат відносно плаский, для методу може бути важко визначити правильний напрямок пошуку. Цей сценарій називається порожнім плато: ландшафт функції витрат стає дедалі пласкішим (і, отже, складнішим для визначення напрямку до мінімуму). Для широкого класу параметризованих квантових схем ймовірність того, що градієнт вздовж будь-якого розумного напрямку є ненульовим із фіксованою точністю, зменшується експоненційно зі збільшенням кількості кубітів.

Схема географічного плато порівняно зі схилом гори, яка пояснює, чому градієнт допомагає знаходити мінімум, а плато — заважає.

Хоча ця область все ще активно досліджується, ось кілька рекомендацій для покращення продуктивності оптимізації:

  • Bootstrapping може допомогти циклу оптимізації уникнути застрягання в просторі параметрів з малим градієнтом.
  • Експерименти з апаратно-ефективним ансатцом: оскільки ми використовуємо шумну квантову систему як оракула «чорного ящика», якість цих обчислень може впливати на продуктивність оптимізатора. Використання апаратно-ефективного ансатцу, наприклад EfficientSU2, може запобігти виникненню експоненційно малих градієнтів.
  • Експерименти з придушенням і пом'якшенням помилок: примітиви Qiskit Runtime надають простий інтерфейс для роботи з різними значеннями optimization_level і resilience_setting відповідно. Це може зменшити вплив шуму та зробити процес оптимізації ефективнішим.
  • Експерименти з оптимізаторами без градієнта: на відміну від оптимізаторів на основі градієнта, оптимізатори типу COBYLA не покладаються на інформацію про градієнт і тому менш чутливі до порожніх плато.

Підсумок

У цьому уроці ти навчився визначати цикл оптимізації:

  • Запускати цикл оптимізації (bootstrapping)
  • Розуміти компроміси між локальними та глобальними оптимізаторами
  • Досліджувати порожні плато та способи їх уникнення

Наше варіаційне навантаження на високому рівні завершено:

Квантова схема з унітарним перетворенням для підготовки референсного стану і другим унітарним перетворенням для варіювання стану за допомогою варіаційних параметрів.

Далі ми розглянемо конкретні варіаційні алгоритми в рамках цього фреймворку.