ลูปการปรับแต่ง
ในบทเรียนนี้ เราจะเรียนรู้วิธีใช้ optimizer เพื่อสำรวจสถานะควอนตัมแบบมีพารามิเตอร์ของ ansatz ซ้ำ ๆ:
- เริ่มต้นลูปการปรับแต่ง
- เข้าใจการแลกเปลี่ยนระหว่างการใช้ optimizer แบบ local และ global
- สำรวจ barren plateaus และวิธีหลีกเลี่ยง
ในระดับสูง optimizer มีบทบาทสำคัญในการสำรวจพื้นที่การค้นหาของเรา optimizer ใช้การประเมินฟังก์ชันต้นทุนเพื่อเลือกชุดพารามิเตอร์ถัดไปในลูป variational และทำซ้ำจนกว่าจะถึงสภาวะเสถียร ณ จุดนั้น ชุดค่าพารามิเตอร์ที่เหมาะสมที่สุด จะถูกส่งคืน
Local และ Global Optimizers
เราจะตั้งค่าปัญหาก่อนที่จะสำรวจคลาสของ optimizer แต่ละคลาส เราจะเริ่มด้วย Circuit ที่มีพารามิเตอร์ variational 8 ตัว:
# 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()
Local Optimizers
Local optimizer ค้นหาจุดที่ลดค่าฟังก์ชันต้นทุนให้น้อยที่สุด โดยเริ่มจากจุดเริ่มต้น และเคลื่อนไปยังจุดต่าง ๆ ตามสิ่งที่สังเกตได้ในบริเวณที่กำลังประเมินในการวนซ้ำแต่ละครั้ง ซึ่งหมายความว่าอัลกอริทึมเหล่านี้มักจะ converge เร็ว แต่ผลลัพธ์ขึ้นอยู่กับจุดเริ่มต้นเป็นอย่างมาก Local optimizer ไม่สามารถมองเห็นเกินบริเวณที่กำลังประเมินและอาจติดอยู่ใน local minima ได้ง่าย โดยรายงานว่า converge แล้วเมื่อพบ local minima และละเลยสถานะอื่น ๆ ที่มีการประเมินที่ดีกว่า
# 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
Global optimizers
Global optimizer ค้นหาจุดที่ลดค่าฟังก์ชันต้นทุนให้น้อยที่สุดในหลายบริเวณของโดเมน (นั่นคือ แบบ non-local) โดยประเมินซ้ำ (นั่นคือ ที่การวนซ้ำ ) บน vector พารามิเตอร์ชุดหนึ่ง ที่กำหนดโดย optimizer ทำให้มีความเสี่ยงต่อ local minima น้อยกว่าและค่อนข้างเป็นอิสระจากการกำหนดค่าเริ่มต้น แต่ก็ converge ไปสู่วิธีแก้ปัญหาช้ากว่าอย่างมีนัยสำคัญ
Bootstrapping Optimization
Bootstrapping หรือการกำหนดค่าเริ่มต้นสำหรับพารามิเตอร์ จากการปรับแต่งก่อนหน้า ช่วยให้ optimizer ของเรา converge ไปสู่วิธีแก้ปัญหาได้เร็วขึ้น เราเรียกสิ่งนี้ว่าจุดเริ่มต้น และ ว่าสถานะเริ่มต้น สถานะเริ่มต้นนี้แตกต่างจาก reference state ของเรา เนื่องจากสถานะแรกมุ่งเน้นที่พารามิเตอร์เริ่มต้นที่กำหนดในลูปการปรับแต่งของเรา ในขณะที่สถานะหลังมุ่งเน้นที่การใช้วิธีแก้ปัญหา "อ้างอิง" ที่รู้จัก ทั้งสองอาจตรงกันหาก (นั่นคือ identity operation)
เมื่อ local optimizer converge ไปสู่ local minima ที่ไม่เหมาะสมที่สุด เราสามารถลอง bootstrap การปรับแต่งแบบ global และปรับ convergence แบบ local ให้ละเอียดขึ้น แม้ว่าจะต้องตั้งค่า variational workload สองชุด แต่ช่วยให้ optimizer ค้นหาวิธีแก้ปัญหาที่เหมาะสมกว่า local optimizer เพียงอย่างเดียว
Gradient-Based และ Gradient-Free Optimizers
Gradient-Based
สำหรับฟังก์ชันต้นทุน ของเรา หากเราเข้าถึง gradient ของฟังก์ชัน จากจุดเริ่มต้น วิธีง่ายที่สุดในการลดค่าฟังก์ชันคือการอัปเดตพารามิเตอร์ตามทิศทางที่ลาดชันลงมากที่สุดของฟังก์ชัน นั่นคือ เราอัปเดตพารามิเตอร์เป็น โดยที่ คืออัตราการเรียนรู้ ซึ่งเป็น hyperparameter ขนาดเล็กบวกที่ควบคุมขนาดของการอัปเดต เราทำสิ่งนี้ต่อไปจนกว่าจะ converge ไปสู่ local minimum ของฟังก์ชันต้นทุน เราสามารถใช้ฟังก์ชันต้นทุนนี้และ optimizer เพื่อคำนวณพารามิเตอร์ที่เหมาะสมที่สุด
# 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
ข้อเสียหลักของการปรับแต่งประเภทนี้คือความเร็วในการ converge ซึ่งอาจช้ามาก และไม่มีการรับประกันว่าจะบรรลุวิธีแก้ปัญหาที่เหมาะสมที่สุด
Gradient-Free
อัลกอริทึมการปรับแต่งแบบ gradient-free ไม่ต้องการข้อมูล gradient และมีประโยชน์ในสถานการณ์ที่การคำนวณ gradient ทำได้ยาก มีค่าใช้จ่ายสูง หรือมีสัญญาณรบกวนมากเกินไป อัลกอริทึมเหล่านี้มักมีความทนทานมากกว่าในการค้นหา global optima ในขณะที่วิธีการที่ใช้ gradient มักจะ converge ไปสู่ local optima เราจะสำรวจตัวอย่างบางส่วนที่ gradient-free optimizer ช่วยหลีกเลี่ยง barren plateaus อย่างไรก็ตาม วิธี gradient-free ต้องการทรัพยากรการคำนวณสูงกว่า โดยเฉพาะสำหรับปัญหาที่มีพื้นที่การค้นหามิติสูง
นี่คือตัวอย่างที่ใช้ COBYLA optimizer แทน:
# 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
ในความเป็นจริง ภูมิทัศน์ต้นทุนอาจซับซ้อนมาก ดังแสดงโดยเนินและหุบเขาในตัวอย่างด้านล่าง วิธีการปรับแต่งนำเราไปรอบ ๆ ภูมิทัศน์ต้นทุน ค้นหาค่าต่ำสุด ดังแสดงโดยจุดและเส้นสีดำ เราจะเห็นว่าสองในสามการค้นหาจบลงด้วย local minimum ของภูมิทัศน์ แทนที่จะเป็น global one
ไม่ว่าจะใช้วิธีการปรับแต่งประเภทใด หากภูมิทัศน์ต้นทุนค่อนข้างแบนราบ วิธีการดังกล่าวอาจเป็นเรื่องยากที่จะกำหนดทิศทางการค้นหาที่เหมาะสม สถานการณ์นี้เรียกว่า barren plateau, ซึ่งภูมิทัศน์ต้นทุนยิ่งแบนราบมากขึ้นเรื่อย ๆ (และยิ่งยากต่อการกำหนดทิศทางไปยังค่าต่ำสุด) สำหรับ Circuit ควอนตัมแบบมีพารามิเตอร์ที่หลากหลาย ความน่าจะเป็นที่ gradient ตามทิศทางที่สมเหตุสมผลใด ๆ จะไม่เป็นศูนย์ที่ความแม่นยำคงที่บางค่าลดลงแบบเอกซ์โพเนนเชียลตามจำนวน Qubit ที่เพิ่มขึ้น
แม้ว่าพื้นที่นี้ยังอยู่ในการวิจัยที่ยังดำเนินอยู่ แต่เรามีคำแนะนำสองสามข้อเพื่อปรับปรุงประสิทธิภาพการปรับแต่ง:
- Bootstrapping ช่วยให้ลูปการปรับแต่งหลีกเลี่ยงการติดอยู่ในพื้นที่พารามิเตอร์ที่ gradient มีขนาดเล็ก
- การทดลองกับ hardware-efficient ansatz: เนื่องจากเราใช้ระบบควอนตัมที่มีสัญญาณรบกวนเป็น oracle แบบ black-box คุณภาพของการประเมินเหล่านั้นอาจส่งผลต่อประสิทธิภาพของ optimizer การใช้ hardware-efficient ansatz เช่น
EfficientSU2อาจหลีกเลี่ยงการสร้าง gradient ที่เล็กแบบเอกซ์โพเนนเชียล - การทดลองกับ error suppression และ error mitigation: Qiskit Runtime primitives มี interface ง่าย ๆ สำหรับทดลองกับค่าต่าง ๆ ของ
optimization_levelและresilience_settingตามลำดับ ซึ่งช่วยลดผลกระทบของสัญญาณรบกวนและทำให้กระบวนการปรับแต่งมีประสิทธิภาพมากขึ้น - การทดลองกับ gradient-free optimizers: ต่างจากอัลกอริทึมการปรับแต่งที่ใช้ gradient, optimizer เช่น
COBYLAไม่พึ่งพาข้อมูล gradient ในการปรับพารามิเตอร์ จึงมีโอกาสน้อยกว่าที่จะได้รับผลกระทบจาก barren plateau
สรุป
ในบทเรียนนี้ คุณได้เรียนรู้วิธีกำหนดลูปการปรับแต่งของคุณ:
- เริ่มต้นลูปการปรับแต่ง
- เข้าใจการแลกเปลี่ยนระหว่างการใช้ optimizer แบบ local และ global
- สำรวจ barren plateaus และวิธีหลีกเลี่ยง
variational workload ระดับสูงของเราสมบูรณ์แล้ว:
ต่อไป เราจะสำรวจอัลกอริทึม variational เฉพาะโดยคำนึงถึงกรอบงานนี้