비용 함수
이 단원에서는 비용 함수를 평가하는 방법을 배웁니다:
- 먼저, IBM Quantum 의 기본 요소에 대해 알아보겠습니다
- 비용 함수를 정의합니다 . 이는 최적화 도구가 최소화(또는 최대화)해야 하는 문제의 목표를 정의하는 문제별 함수입니다
- IBM Quantum 의 기본 기능을 활용하여 속도와 정확도의 균형을 최적화하는 측정 전략 수립
기본
고전적이든 양자든 모든 물리 시스템은 서로 다른 상태로 존재할 수 있습니다. 예를 들어, 도로 위의 자동차는 질량, 위치, 속도, 가속도 등 그 상태를 특징짓는 특정 특성을 가질 수 있습니다. 마찬가지로 양자 시스템도 다양한 구성이나 상태를 가질 수 있지만, 측정과 상태 진화를 다루는 방식이 기존 시스템과 다릅니다. 이는 양자역학에서만 볼 수 있는 중첩과 얽힘과 같은 독특한 속성으로 이어집니다. 속도나 가속도와 같은 물리적 속성을 이용해 자동차의 상태를 설명할 수 있는 것처럼, 수학적 대상인 관측값을 이용해 양자 시스템의 상태를 설명할 수도 있습니다.
양자 역학에서 상태는 정규화된 복소 열 벡터 또는 집합 ( )으로 표현되며, 관측 가능성은 집합에 작용하는 헤르미트 선형 연산자( )입니다. 관측값의 고유 벡터( )를 고유 상태라고 합니다. 고유 상태 중 하나에 대한 관측값( )을 측정하면 해당 고유값( )을 판독값으로 얻을 수 있습니다.
양자 시스템을 측정하는 방법과 무엇을 측정할 수 있는지 궁금하다면, 키스킷은 두 가지 기본적이고 기본적인 연산 또는 데이터 유형입니다. 키스킷에는 복잡한 알고리즘 워크로드를 쉽게 구성할 수 있는 빌딩 블록 역할을 하는 샘플러와 추정기 프리미티브가 있습니다. 를 통해 도움을 드릴 수 있습니다:
Sampler: 양자 상태 가 주어지면 이 프리미티브는 가능한 각 계산 기준 상태의 확률을 구합니다.Estimator: 양자 관측 가능 과 상태 가 주어지면 이 프리미티브는 의 예상 값을 계산합니다.
샘플러 프리미티브
Sampler 프리미티브는 상태를 준비하는 양자 회로가 주어졌을 때 가능한 각 상태 를 얻을 확률을 계산합니다. 다음을 계산합니다
여기서 은 큐비트 수, 은 출력 가능한 이진 문자열 의 정수 표현(즉, 정수 베이스 )입니다.
IBM Quantum 는 양자 장치에서 이 회로를 여러 번 실행하며 Sampler , 각 실행마다 측정을 수행하고, 복원된 비트열로부터 확률 분포를 재구성합니다. 실행 횟수(또는 시뮬레이션 횟수 )가 많을수록 결과의 정확도는 높아지지만, 이를 위해서는 더 많은 시간과 양자 자원이 필요합니다.
그러나 가능한 출력의 수는 큐비트 수( , 즉 )에 따라 기하급수적으로 증가하므로, 조밀한 확률 분포를 포착하려면 촬영 횟수도 기하급수적으로 증가해야 합니다. 따라서 Sampler 은 희소 확률 분포에만 효율적이며, 목표 상태 는 계산 기준 상태의 선형 조합으로 표현할 수 있어야 하며, 항의 수는 큐비트 수에 따라 최대 다항식으로 증가해야 합니다:
Sampler 은 전체 가능한 상태의 하위 집합을 나타내는 회로의 하위 섹션에서 확률을 검색하도록 구성할 수도 있습니다.
추정기 기본 요소
Estimator 프리미티브는 양자 상태 에 대한 관측 가능한 의 기대값을 계산합니다; 여기서 관측 가능한 확률은 로 표현할 수 있으며, 는 관측 가능한 의 고유 상태입니다. 그런 다음 기대값은 해당 확률로 가중치를 부여한 상태 측정의 가능한 모든 결과 (즉, 관측 가능성의 고유값)의 평균으로 정의됩니다:
그러나 관측량의 기대값을 계산하는 것이 항상 가능한 것은 아니며, 이는 대개 그 고유기저를 알지 못하기 때문이다. IBM Quantum 는 관측량을 우리가 고유기저를 알고 있는 다른 관측량들의 조합으로 분해함으로써, 복잡한 대수적 과정을 통해 Estimator 실제 양자 장치에서의 기대값을 추정합니다.
간단히 말해서 Estimator 은 측정 방법을 모르는 모든 관측값을 양자 상태를 표현하고 조작하기 위해 양자 컴퓨팅에서 일반적으로 사용되는 행렬의 집합으로, 동일성 행렬과 3개의 파울리 행렬(X, Y, Z)로 구성됩니다. 이라는 더 간단하고 측정 가능한 관측값으로 분해합니다.
모든 연산자는 폴리 연산자의 조합으로 표현할 수 있습니다.
만족합니다
여기서 은 큐비트 수, 은 (즉, 정수 기반 ), 입니다.
이 분해를 수행한 후 Estimator 는 원래 회로에서 각 관측 가능 에 대해 새로운 회로 를 도출하여 계산 기반에서 폴리 관측 가능을 효과적으로 대각선으로 대각선화하여 측정합니다. 일반적으로 다른 관측소의 경우와 달리 을 미리 알고 있기 때문에 폴리 관측소를 쉽게 측정할 수 있습니다.
각 에 대해 Estimator 은 양자 장치에서 해당 회로를 여러 번 실행하고, 계산 기반에서 출력 상태를 측정하고, 가능한 각 출력을 얻을 확률 을 계산합니다 . 그런 다음 각 출력 에 해당하는 의 고유값 을 찾고 을 곱한 다음 모든 결과를 더하여 주어진 상태 에 대한 관측 가능한 의 예상값을 얻습니다.
의 파울리 성분의 기대값을 계산하는 것은 비실용적(즉, 지수적으로 증가함)이므로, 가 0인 경우가 많을 때(즉, 밀집 파울리 분해 대신 희소 파울리 분해일 때)에만 효율적일 Estimator 수 있다. 수학적으로 말하자면, 이 계산을 효율적으로 풀기 위해서는 0이 아닌 항의 개수가 큐비트 수 에 대해 기껏해야 다항식적으로 증가해야 합니다: .
독자는 Sampler 에 대해 설명한 것처럼 확률 하나 또는 여러 가지 사물을 여러 번 측정하는 과정입니다. 도 효율적이어야 한다는 암묵적인 가정을 알 수 있습니다
기대값 계산 안내 예시
단일 큐비트 상태 와 관찰 가능한
다음과 같은 이론적 기대값을 가진다 .
이 관측값을 측정하는 방법을 모르기 때문에 기대값을 직접 계산할 수 없으며 로 다시 표현해야 합니다. 과 으로 표현하면 동일한 결과로 평가되는 것을 알 수 있습니다.
및 을 직접 계산하는 방법을 살펴보겠습니다. 과 은 서로 통근하지 않으므로(즉, 동일한 고유 기반을 공유하지 않으므로) 동시에 측정할 수 없으므로 보조 회로가 필요합니다:
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
# The following code will work for any other initial single-qubit state and observable
original_circuit = QuantumCircuit(1)
original_circuit.h(0)
H = SparsePauliOp(["X", "Z"], [2, -1])
aux_circuits = []
for pauli in H.paulis:
aux_circ = original_circuit.copy()
aux_circ.barrier()
if str(pauli) == "X":
aux_circ.h(0)
elif str(pauli) == "Y":
aux_circ.sdg(0)
aux_circ.h(0)
else:
aux_circ.id(0)
aux_circ.measure_all()
aux_circuits.append(aux_circ)
original_circuit.draw("mpl")Output:
# Auxiliary circuit for X
aux_circuits[0].draw("mpl")Output:
# Auxiliary circuit for Z
aux_circuits[1].draw("mpl")Output:
이제 Sampler 을 사용하여 수동으로 계산을 수행하고 Estimator 에서 결과를 확인할 수 있습니다:
from qiskit.primitives import StatevectorSampler, StatevectorEstimator
from qiskit.result import QuasiDistribution
import numpy as np
## SAMPLER
shots = 10000
sampler = StatevectorSampler()
job = sampler.run(aux_circuits, shots=shots)
# Run the sampler job and step through results
expvals = []
for index, pauli in enumerate(H.paulis):
data_pub = job.result()[index].data
bitstrings = data_pub.meas.get_bitstrings()
counts = data_pub.meas.get_counts()
quasi_dist = QuasiDistribution(
{outcome: freq / shots for outcome, freq in counts.items()}
)
# Use the probabilities and known eigenvalues of Pauli operators to estimate
# the expectation value.
val = 0
if str(pauli) == "X":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)
if str(pauli) == "Y":
val += -1 * quasi_dist.get(1, 0)
val += 1 * quasi_dist.get(0, 0)
if str(pauli) == "Z":
val += 1 * quasi_dist.get(0, 0)
val += -1 * quasi_dist.get(1, 0)
expvals.append(val)
# Print expectation values
print("Sampler results:")
for pauli, expval in zip(H.paulis, expvals):
print(f" >> Expected value of {str(pauli)}: {expval:.5f}")
total_expval = np.sum(H.coeffs * expvals).real
print(f" >> Total expected value: {total_expval:.5f}")
# Use estimator for comparison
observables = [
*H.paulis,
H,
] # Note: run for individual Paulis as well as full observable H
estimator = StatevectorEstimator()
job = estimator.run([(original_circuit, observables)])
estimator_expvals = job.result()[0].data.evs
# Print results
print("Estimator results:")
for obs, expval in zip(observables, estimator_expvals):
if obs is not H:
print(f" >> Expected value of {str(obs)}: {expval:.5f}")
else:
print(f" >> Total expected value: {expval:.5f}")Output:
Sampler results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00420
>> Total expected value: 1.99580
Estimator results:
>> Expected value of X: 1.00000
>> Expected value of Z: 0.00000
>> Total expected value: 2.00000
수학적 엄밀성 (선택 사항)
을 , 의 고유 상태 기반과 관련하여 표현하면 다음과 같습니다:
대상 관측량 의 고유값이나 고유상태를 알 수 없으므로, 먼저 이 관측량의 대각화를 고려해야 한다. 가 Hermitian은 자체 공액 전치와 동일한 정사각형 행렬 또는 자기접합이 있는 선형 연산자입니다. 임을 고려하면, 를 만족하는 유니터리 변환 가 존재하며, 여기서 는 대각 고유값 행렬이므로, 일 때 이고, 이다.
이는 예상값을 다음과 같이 다시 작성할 수 있음을 의미합니다:
시스템이 상태인 경우 을 측정할 확률은 이라고 가정하면 위의 예상값은 다음과 같이 표현할 수 있습니다:
확률은 대신 에서 가져온다는 점에 유의하는 것이 매우 중요합니다. 이것이 바로 행렬 이 절대적으로 필요한 이유입니다.
행렬 과 고유값 을 구하는 방법이 궁금할 수 있습니다. 이미 고유값을 가지고 있다면, 가변 알고리즘의 목표는 의 고유값을 구하는 것이므로 양자 컴퓨터를 사용할 필요가 없을 것입니다.
다행히도 이를 우회할 방법이 있습니다. 모든 행렬은 파울리 행렬과 단위 행렬의 텐서곱의 선형 조합으로 표현될 수 있으며, 이들 모두는 헤르미트 행렬이자 유니터리 행렬이며, 및 가 알려져 있습니다. 이것이 바로 IBM Quantum 가 내부적으로 수행하는 Estimator 작업으로, 임의의 Operator 객체를 로 분해합니다 SparsePauliOp.
사용할 수 있는 연산자는 다음과 같습니다:
이제 폴리스와 정체성과 관련하여 을 다시 작성해 보겠습니다:
여기서 는 (즉, 기본 ), :
여기서 이고 이며, 다음이 성립한다: .
비용 함수
일반적으로 비용 함수는 문제의 목표와 그 목표와 관련하여 시험 상태가 얼마나 잘 수행되고 있는지 설명하는 데 사용됩니다. 이 정의는 화학, 머신러닝, 금융, 최적화 등 다양한 사례에 적용할 수 있습니다.
시스템의 기저 상태를 찾는 간단한 예를 들어 보겠습니다. 우리의 목표는 에너지를 나타내는 관측 가능성의 기대값을 최소화하는 것입니다(해밀턴의 ):
Estimator 을 사용하여 기대값을 평가하고 이 값을 최적화 도구에 전달하여 최소화할 수 있습니다. 최적화가 성공하면 최적의 파라미터 값 집합 을 반환하고, 이를 통해 제안된 솔루션 상태 를 구성하고 관찰된 기대값을 으로 계산할 수 있습니다.
고려 중인 제한된 상태 집합에 대해서만 비용 함수를 최소화할 수 있다는 점에 유의하세요. 이는 두 가지 다른 가능성으로 이어집니다:
- 우리의 안사츠는 검색 공간 전체에 걸쳐 솔루션 상태를 정의하지 않습니다 : 이 경우 옵티마이저가 솔루션을 찾지 못하므로 검색 공간을 더 정확하게 표현할 수 있는 다른 안사츠를 실험해봐야 합니다.
- 최적화 도구가 이 유효한 솔루션을 찾을 수 없습니다 : 최적화는 전역적으로 정의할 수도 있고 로컬로 정의할 수도 있습니다. 뒷부분에서 이것이 무엇을 의미하는지 살펴보겠습니다.
결국, 우리는 고전적인 최적화 루프를 수행하되 비용 함수의 평가를 양자 컴퓨터에 의존할 것입니다. 이러한 관점에서 최적화는 최적화 도구가 비용 함수를 평가해야 할 때마다 특정 기능을 수행하지만 내부 작동 방식은 알 수 없는 가상의 장치 또는 소프트웨어 구성 요소입니다. 사용자는 블랙박스의 입력과 출력만 알 수 있으며, 블랙박스가 입력을 처리하여 출력을 생성하는 방식에 대해서는 전혀 알지 못합니다. 을 호출하는 순전히 고전적인 작업이라고 생각할 수 있습니다.
def cost_func_vqe(params, circuit, 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 = (circuit, hamiltonian, params)
cost = estimator.run([pub]).result()[0].data.evs
return costfrom qiskit.circuit.library import TwoLocal
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)
theta_list = (2 * np.pi * np.random.rand(1, 8)).tolist()
ansatz.decompose().draw("mpl")Output:
먼저 시뮬레이터를 사용하여 이 작업을 수행합니다: StatevectorEstimator. 이는 일반적으로 디버깅을 위해 권장되지만, 디버깅 실행 후 즉시 실제 양자 하드웨어에서 계산을 수행합니다. 최첨단 슈퍼컴퓨팅 시설 없이는 더 이상 고전적으로 시뮬레이션할 수 없는 문제들이 점점 더 많아지고 있습니다.
estimator = StatevectorEstimator()
cost = cost_func_vqe(theta_list, ansatz, observable, estimator)
print(cost)Output:
[-0.58744589]
이제 실제 양자 컴퓨터에서 실행해 보겠습니다. 구문 변경 사항에 유의하십시오. pass_manager와 관련된 단계에 대해서는 다음 예제에서 더 자세히 다루겠습니다. 변분 알고리즘에서 특히 중요한 단계 중 하나는 IBM Quantum Compute Service 세션을 사용하는 것입니다. 세션을 시작하면 매번 매개변수가 업데이트될 때마다 새로운 대기열에서 대기할 필요 없이 변분 알고리즘을 여러 번 반복 실행할 수 있습니다. 대기 시간이 길거나 반복 횟수가 많은 경우, 이는 중요합니다. Open Plan 사용자는 Quantum Compute 세션을 이용할 수 없으므로 유의하시기 바랍니다. 세션에 접근할 수 없는 경우, 한 번에 제출하는 반복 횟수를 줄이고, 향후 실행 시 사용할 수 있도록 가장 최근의 매개변수를 저장해 둘 수 있습니다. 반복 횟수가 너무 많거나 대기 시간이 지나치게 길 경우, 작업 제출 간에 지연이 길어졌음을 나타내는 오류 코드 1217이 발생할 수 있습니다.
# Estimated usage: < 1 min. Benchmarked at 7 seconds on an Eagle processor
# Load necessary packages:
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
# Select the least busy backend:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")
# Use a pass manager to transpile the circuit and observable for the specific backend being used:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_observable = observable.apply_layout(layout=isa_ansatz.layout)
# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
# Open a Quantum Compute session:
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(theta_list, isa_ansatz, isa_observable, estimator)
session.close()
print(cost)위의 두 계산에서 얻은 값은 매우 유사하다는 점에 유의하세요. 결과를 개선하기 위한 기술은 아래에서 자세히 설명합니다.
비물리적 시스템에 대한 예시 매핑
최대 절단(max-cut) 문제는 그래프의 정점들을 두 개의 서로 교집합이 없는 집합으로 나누되, 두 집합 사이의 변의 개수가 최대가 되도록 하는 조합 최적화 문제입니다. 좀 더 엄밀히 말하면, 비방향 그래프 가 주어졌을 때(여기서 는 정점의 집합이고 는 변의 집합이다), 최대 절단 문제(max-cut problem)는 정점들을 두 개의 서로 교집합이 없는 부분집합 와 로 나누되, 한쪽 끝점이 에 있고 다른 쪽 끝점이 에 있는 변의 개수가 최대가 되도록 하는 것을 구하는 문제이다.
맥스컷(max-cut)을 적용하여 클러스터링, 네트워크 설계, 상전이 등 다양한 문제를 해결할 수 있습니다. 먼저 문제 그래프를 만들어 보겠습니다:
import rustworkx as rx
from rustworkx.visualization import mpl_draw
n = 4
G = rx.PyGraph()
G.add_nodes_from(range(n))
# The edge syntax is (start, end, weight)
edges = [(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0), (1, 2, 1.0), (2, 3, 1.0)]
G.add_edges_from(edges)
mpl_draw(
G, pos=rx.shell_layout(G), with_labels=True, edge_labels=str, node_color="#1192E8"
)Output:
이 문제는 이진 최적화 문제로 표현할 수 있습니다. 각 노드 에서 은 그래프의 노드 수(이 경우 )이며, 이진 변수 를 고려합니다. 이 변수는 노드 가 그룹 중 하나에 속하는 경우 로, 다른 그룹에 속하는 경우 로 레이블을 지정할 값 을 갖습니다. 또한 노드 에서 노드 로 이동하는 에지의 가중치를 (인접성 행렬 의 요소 )로 표시합니다. 그래프는 방향이 없으므로 입니다. 그러면 다음 비용 함수를 최대화하는 것으로 문제를 공식화할 수 있습니다:
양자 컴퓨터로 이 문제를 해결하기 위해 비용 함수를 관측 가능성의 기대값으로 표현하겠습니다. 그러나 키스킷이 기본적으로 허용하는 관측 가능성은 와 대신 고유값이 와 인 폴리 연산자로 구성됩니다. 그렇기 때문에 다음과 같이 변수를 변경할 것입니다:
여기서 . 인접성 행렬 을 사용하여 모든 엣지의 가중치에 편안하게 액세스할 수 있습니다. 이것은 비용 함수를 구하는 데 사용됩니다:
이는 다음을 의미합니다.
따라서 우리가 최대화하고자 하는 새로운 비용 함수는 다음과 같습니다:
또한 양자 컴퓨터의 자연스러운 경향은 최대값이 아닌 최소값(일반적으로 가장 낮은 에너지)을 찾는 것이므로 을 최대화하는 대신 최소화하려고 합니다:
이제 누구의 변수가 와 값을 가질 수 있는지를 최소화하는 비용 함수가 생겼으므로 폴리 를 사용하여 다음과 같은 비유를 할 수 있습니다:
즉, 변수는 게이트가 쿼비트 에서 작동하는 것과 동일합니다. 게다가:
그렇다면 우리가 고려해야 할 관찰 가능성은 다음과 같습니다:
여기에 나중에 독립적인 용어를 추가해야 합니다:
연산자는 에지로 연결된 노드에서 Z 연산자를 사용한 용어의 선형 조합입니다(0번째 큐비트가 가장 오른쪽에 있다는 것을 기억하세요): . 연산자가 구성되면, QAOA 알고리즘의 연산자는 키스킷 회로 라이브러리의 QAOAAnsatz 회로를 사용하여 쉽게 구축할 수 있습니다.
from qiskit.circuit.library import QAOAAnsatz
from qiskit.quantum_info import SparsePauliOp
hamiltonian = SparsePauliOp.from_list(
[("IIZZ", 1), ("IZIZ", 1), ("IZZI", 1), ("ZIIZ", 1), ("ZZII", 1)]
)
ansatz = QAOAAnsatz(hamiltonian, reps=2)
# Draw
ansatz.decompose(reps=3).draw("mpl")Output:
# Sum the weights, and divide by 2
offset = -sum(edge[2] for edge in edges) / 2
print(f"""Offset: {offset}""")Output:
Offset: -2.5
IBM Quantum 추정기가 해밀토니안과 매개변수화된 가정을 직접 입력받아 필요한 에너지를 반환하므로, QAOA 인스턴스에 대한 비용 함수는 매우 간단합니다:
def cost_func(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
# cost = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]
return costimport numpy as np
x0 = 2 * np.pi * np.random.rand(ansatz.num_parameters)
estimator = StatevectorEstimator()
cost = cost_func_vqe(x0, ansatz, hamiltonian, estimator)
print(cost)Output:
1.473098768180865
# Estimated usage: < 1 min, benchmarked at 6 seconds on ibm_osaka, 5-23-24
# Load some necessary packages:
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Session, EstimatorV2 as Estimator
# Select the least busy backend:
backend = service.least_busy(
operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
)
# Or get a specific backend:
# backend = service.backend("ibm_brisbane")
# Use a pass manager to transpile the circuit and observable for the specific backend being used:
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_ansatz = pm.run(ansatz)
isa_hamiltonian = hamiltonian.apply_layout(layout=isa_ansatz.layout)
# Set estimator options
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
# Open a Quantum Compute session:
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
cost = cost_func_vqe(x0, isa_ansatz, isa_hamiltonian, estimator)
# Close session after done
session.close()
print(cost)Output:
1.1120776913677988
애플리케이션에서 이 예제를 다시 살펴보고 최적화 도구를 활용하여 검색 공간을 반복하는 방법을 살펴보겠습니다. 일반적으로 여기에는 다음이 포함됩니다:
- 최적화 도구를 활용하여 최적의 매개변수 찾기
- 최적의 매개 변수를 ansatz에 바인딩하여 고유값 찾기
- 고유값을 문제 정의로 변환하기
측정 전략: 속도 대 정확도
앞서 언급했듯이, 노이즈가 있는 양자 컴퓨터를 블랙박스 오라클로 사용하고 있는데, 노이즈는 검색된 값을 비결정적으로 만들어 무작위 변동을 일으켜 특정 최적화 알고리즘이 제안된 솔루션에 수렴하는 것을 방해하거나 완전히 막아버릴 수 있습니다. 이는 양자 유용성을 점진적으로 탐구하고 양자 우위를 향해 나아가면서 우리가 해결해야 할 일반적인 문제입니다:
IBM Quantum 기본 함수의 오류 억제 및 오류 완화 옵션을 활용하면 노이즈 문제를 해결하고, 현재 양자 컴퓨터의 유용성을 극대화할 수 있습니다.
오류 표시 안함
오류 억제란 오류를 최소화하기 위해 컴파일 단계에서 회로를 최적화하고 변환하는 기법을 말합니다. 이는 기본적인 오류 처리 기법으로, 대개 전체 실행 시간에 약간의 전형적인 전처리 기본 구현에 비해 새로운 기술로 인해 발생하는 추가 비용. 를 수반합니다. 이 오버헤드에는 다음 방법을 통해 회로를 양자 하드웨어에서 실행할 수 있도록 변환하는 작업이 포함됩니다:
- 양자 시스템에서 사용 가능한 네이티브 게이트를 사용하여 회로 표현하기
- 가상 큐비트를 물리적 큐비트에 매핑하기
- 연결 요구 사항에 따라 SWAP 추가하기
- 1Q 및 2Q 게이트 최적화
- 유휴 큐비트에 동적 디커플링을 추가하여 디코히어런스의 영향을 방지합니다.
프리미티브는 optimization_level 옵션을 설정하고 고급 번역 옵션을 선택하여 오류 억제 기술을 사용할 수 있습니다. 이후 강좌에서 결과를 개선하기 위한 다양한 회로 구성 방법을 살펴보겠지만 대부분의 경우 optimization_level=3 을 설정하는 것이 좋습니다.
간단한 이상적인 동작을 가진 예제 회로를 살펴봄으로써 트랜스필레이션 프로세스에서 최적화 증가의 가치를 시각화해 보겠습니다.
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
theta = Parameter("theta")
qc = QuantumCircuit(2)
qc.x(1)
qc.h(0)
qc.cp(theta, 0, 1)
qc.h(0)
observables = SparsePauliOp.from_list([("ZZ", 1)])
qc.draw("mpl")Output:
위의 회로는 과 같이 적절한 간격에 걸친 위상을 삽입하면 관측 가능한 주어진 값의 정현파 기대값을 구할 수 있습니다.
## Setup phases
import numpy as np
phases = np.linspace(0, 2 * np.pi, 50)
# phases need to be expressed as a list of lists in order to work
individual_phases = [[phase] for phase in phases]시뮬레이터를 사용하여 최적화된 번역의 유용성을 보여줄 수 있습니다. 아래에서 실제 하드웨어를 사용하여 오류 완화 기능의 유용성을 보여드리겠습니다. QiskitRuntimeService 을 사용하여 실제 백엔드(이 경우 ibm_brisbane)를 가져오고 AerSimulator 을 사용하여 해당 백엔드의 노이즈 동작을 포함한 시뮬레이션을 수행합니다.
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_aer import AerSimulator
# get a real backend from the runtime service
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
# generate a simulator that mimics the real quantum system with the latest calibration results
backend_sim = AerSimulator.from_backend(backend)이제 패스 매니저를 사용하여 회로를 백엔드의 “명령어 집합 아키텍처(ISA)”로 변환할 수 있습니다. 이는 IBM Quantum Compute Service의 새로운 요구 사항입니다. 백엔드에 제출되는 모든 회로는 해당 백엔드의 대상(target)에 대한 제약 조건을 준수해야 하며, 즉 백엔드의 ISA(명령어 집합)에 따라 작성되어야 합니다. 여기서 ISA란 해당 장치가 이해하고 실행할 수 있는 명령어 집합을 의미합니다. 이러한 목표 제약 조건은 장치의 기본 베이스 게이트, 큐비트 연결성, 그리고 해당되는 경우 펄스 및 기타 명령어 타이밍 사양과 같은 요인들에 의해 정의됩니다.
이 경우에는 최적화_level = 0으로 한 번, 3으로 설정한 상태로 한 번, 두 번 수행합니다. 매번 추정기 프리미티브를 사용하여 다양한 위상 값에서 관측 가능성의 기대값을 추정합니다.
# Import estimator and specify that we are using the simulated backend:
from qiskit_ibm_runtime import EstimatorV2 as Estimator
estimator = Estimator(mode=backend_sim)
circuit = qc# Use a pass manager to transpile the circuit and observable for the backend being simulated.
# Start with no optimization:
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=0)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
noisy_exp_values = []
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
noisy_exp_values = cost[0]
# Repeat above steps, but now with optimization = 3:
exp_values_with_opt_es = []
pm = generate_preset_pass_manager(backend=backend_sim, optimization_level=3)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
exp_values_with_opt_es = cost[0]마지막으로 결과를 그래프로 그려보면 최적화 없이도 계산의 정밀도가 상당히 좋았지만, 최적화를 레벨 3으로 높였더니 확실히 개선된 것을 확인할 수 있습니다. 더 깊고 복잡한 회로에서는 최적화 수준 0과 3의 차이가 더 클 수 있다는 점에 유의하세요. 이것은 장난감 모형으로 사용되는 매우 간단한 회로입니다.
import matplotlib.pyplot as plt
plt.plot(phases, noisy_exp_values, "o", label="opt=0")
plt.plot(phases, exp_values_with_opt_es, "o", label="opt=3")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()Output:
오류 완화
오류 완화란 실행 시점에 소자의 노이즈를 모델링함으로써 사용자가 회로 오류를 줄일 수 있게 해주는 기법을 말합니다. 일반적으로 이로 인해 모델 훈련과 관련된 양자 전처리 오버헤드와, 생성된 모델을 사용하여 원시 결과의 오류를 보정하기 위한 고전적 후처리 오버헤드가 발생하게 됩니다.
IBM Quantum 기본 함수의 resilience_level 옵션은 오류 발생 시 적용할 복원력 수준을 지정합니다. 수준이 높을수록 더 정확한 결과를 얻을 수 있지만, 양자 샘플링 오버헤드로 인해 처리 시간이 더 오래 걸립니다. 복원력 수준을 활용하면 기본 쿼리에 오류 완화 기능을 적용할 때 비용과 정확도 간의 균형을 조정할 수 있습니다.
오류 완화 기술을 구현할 경우, 이전의 완화되지 않은 편향에 비해 결과에서 일반적으로 오류로 인해 측정된 수량의 체계적인 편차가 발생합니다. 이 감소할 것으로 예상합니다. 경우에 따라 편향성이 사라질 수도 있습니다. 하지만 여기에는 대가가 따릅니다. 추정 수량의 편향을 줄이면 통계적 변동성(즉, 분산)이 증가하며, 샘플링 프로세스에서 회로당 촬영 횟수를 더 늘려서 이를 설명할 수 있습니다. 이렇게 하면 편향성을 줄이는 데 필요한 것 이상의 오버헤드가 발생하므로 기본적으로 수행되지 않습니다. 아래 예시와 같이 options.executions.shots 에서 회로당 촬영 횟수를 조정하여 이 동작을 쉽게 선택할 수 있습니다.
이 강좌에서는 전체 구현 세부 사항을 다루지 않고도 IBM Quantum 의 기본 구성 요소가 수행할 수 있는 오류 완화 기능을 설명하기 위해, 이러한 오류 완화 모델들을 개괄적으로 살펴보겠습니다.
회전식 판독 오류 소멸(T-REx)
회전형 판독 오류 소멸(T-REx)은 양자 측정 과정에서 발생하는 노이즈를 줄이기 위해 폴리 회전이라는 기술을 사용합니다. 이 기술은 특정 형태의 노이즈를 가정하지 않으므로 매우 일반적이고 효과적입니다.
전반적인 워크플로:
- 무작위 비트 플립으로 제로 상태에 대한 데이터 수집(측정 전 폴리 X)
- 무작위 비트 플립으로 원하는 (노이즈가 있는) 상태에 대한 데이터를 수집합니다(측정 전 폴리 X)
- 각 데이터 집합에 대한 특수 함수를 계산하고 나눕니다.
아래 예시처럼 options.resilience_level = 1 으로 설정할 수 있습니다.
제로 노이즈 외삽법
무소음 추정(ZNE)은 먼저 원하는 양자 상태를 준비하는 회로에서 노이즈를 증폭하고, 여러 가지 노이즈 수준에 대한 측정값을 얻은 다음, 이러한 측정값을 사용하여 노이즈 없는 결과를 추론하는 방식으로 작동합니다.
전반적인 워크플로:
- 여러 노이즈 요인에 대한 회로 노이즈 증폭
- 모든 노이즈 증폭 회로 실행
- 제로 노이즈 한계로 다시 추정하기
이것은. options.resilience_level = 2로 설정할 수 있습니다. 다양한 noise_factors, noise_amplifiers, 및 을 탐구함으로써 이를 더욱 extrapolators 최적화할 수 있지만, 이는 본 강의의 범위를 벗어납니다. 여기에 설명된 대로 이러한 옵션을 직접 사용해 보시기를 권장합니다.
각 방법에는 필요한 양자 계산 횟수(시간)와 결과의 정확성 사이의 절충점인 자체적인 오버헤드가 있습니다:
Quantum Compute의 옵션을 활용한 완화 및 억제
IBM Quantum Compute Service에서 오류 완화 및 억제 기능을 사용하면서 기대값을 계산하는 방법은 다음과 같습니다. 이전과 정확히 동일한 회로와 관측량을 활용할 수 있지만, 이번에는 최적화 수준을 2단계로 고정하고, 현재 사용 중인 복원력 또는 오류 완화 기법을 조정해 볼 수 있습니다. 이러한 오류 완화 과정은 최적화 루프 전반에 걸쳐 여러 번 발생합니다.
시뮬레이터에서는 오류 완화 기능을 사용할 수 없으므로 실제 하드웨어에서 이 부분을 수행합니다.
# Estimated usage: 8 minutes, benchmarked on an Eagle processor, 5-23-24
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import (
Session,
EstimatorOptions,
EstimatorV2 as Estimator,
)
# We select the least busy backend
# Select the least busy backend
# backend = service.least_busy(
# operational=True, min_num_qubits=ansatz.num_qubits, simulator=False
# )
# Or use a specific backend
backend = service.backend("ibm_brisbane")
# Initialize some variables to save the results from different runs:
exp_values_with_em0_es = []
exp_values_with_em1_es = []
exp_values_with_em2_es = []
# Use a pass manager to optimize the circuit and observables for the backend chosen:
pm = generate_preset_pass_manager(backend=backend, optimization_level=2)
isa_circuit = pm.run(circuit)
isa_observables = observables.apply_layout(layout=isa_circuit.layout)
# Open a session and run with no error mitigation:
estimator_options = EstimatorOptions(resilience_level=0, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em0_es = cost[0]
# Open a session and run with resilience = 1:
estimator_options = EstimatorOptions(resilience_level=1, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em1_es = cost[0]
# Open a session and run with resilience = 2:
estimator_options = EstimatorOptions(resilience_level=2, default_shots=10_000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)
pub = (isa_circuit, isa_observables, [individual_phases])
cost = estimator.run([pub]).result()[0].data.evs
session.close()
exp_values_with_em2_es = cost[0]이전과 마찬가지로, 사용된 세 가지 오류 완화 수준에 대한 결과 기대값을 위상각의 함수로 표시할 수 있습니다. 오류를 완화하면 결과가 약간 개선되는 것을 볼 수 있습니다. 다시 말하지만, 이 효과는 더 깊고 복잡한 회로에서 훨씬 더 두드러집니다.
import matplotlib.pyplot as plt
plt.plot(phases, exp_values_with_em0_es, "o", label="unmitigated")
plt.plot(phases, exp_values_with_em1_es, "o", label="resil = 1")
plt.plot(phases, exp_values_with_em2_es, "o", label="resil = 2")
plt.plot(phases, 2 * np.sin(phases / 2) ** 2 - 1, label="ideal")
plt.ylabel("Expectation")
plt.legend()
plt.show()Output:
요약
이 단원에서는 비용 함수를 만드는 방법을 배웠습니다:
- 비용 함수 만들기
- IBM Quantum 의 기본 기능을 활용하여 노이즈를 완화하고 억제하는 방법
- 속도와 정확도를 최적화하기 위한 측정 전략을 정의하는 방법
다음은 높은 수준의 변형 워크로드입니다:
비용 함수는 최적화 루프의 모든 반복 중에 실행됩니다. 다음 단원에서는 고전적인 최적화 도구가 비용 함수 평가를 사용하여 새로운 매개 변수를 선택하는 방법을 살펴봅니다.
import qiskit
import qiskit_ibm_runtime
print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())Output:
1.1.0
0.23.0