양자 커널
양자 커널 소개
“양자 커널 방법”이란 양자 컴퓨터를 사용하여 커널을 추정하는 모든 방법을 말합니다. 이러한 맥락에서, “커널”은 커널 행렬 또는 그 안의 개별 요소를 의미합니다. 특성 매핑 은 에서 로 가는 매핑이며, 일반적으로 를 만족하고, 이 매핑의 목표는 데이터의 범주를 초평면으로 분리 가능하게 만드는 것임을 상기하자. 커널 함수는 특징 매핑 공간 내의 벡터를 인수로 받아 이들의 내적, 즉 와 의 곱을 반환합니다. 일반적으로 우리는 커널 함수를 쉽게 계산할 수 있는 특징 매핑에 관심을 갖습니다. 이는 종종, 와 를 전혀 구성할 필요 없이, 특징 매핑된 공간에서의 내적을 원본 데이터 벡터로 표현할 수 있는 커널 함수를 찾는 것을 의미합니다. 양자 커널 방법에서는 특징 매핑이 양자 회로를 통해 수행되며, 커널은 해당 회로에 대한 측정 결과와 상대적 측정 확률을 이용하여 추정됩니다.
이 강의에서는 상당한 얽힘을 사용하는 사전 코딩된 인코딩 회로의 깊이를 살펴보고 이를 직접 코딩하는 회로의 깊이와 비교합니다. 이는 한 가지 방법을 다른 방법보다 더 옹호하려는 것이 아닙니다. 사전 코딩된 회로가 너무 깊고 사용자 지정 회로의 얽힘이 충분하지 않아 유용하지 않을 수 있습니다. 다시 말하지만, 이는 탐색을 활성화하기 위해서만 표시됩니다.
커널 행렬 추정을 자세히 살펴보기 전에 키스킷 패턴 언어를 사용한 워크플로우를 간략하게 설명하겠습니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
- 입력: 학습 데이터 세트
- 출력: 커널 행렬 항목 계산을 위한 추상 회로
데이터 세트가 주어졌을 때 시작점은 데이터를 양자 회로로 인코딩하는 것입니다. 다시 말해, 데이터를 양자 컴퓨터의 힐버트 상태 공간에 매핑해야 합니다. 데이터 종속 회로를 구성하여 이를 수행합니다. 이를 수행하는 방법에는 여러 가지가 있으며 이전 단원에서 여러 가지 옵션에 대해 설명했습니다. 자체 회로를 구성하여 데이터를 인코딩하거나 zz_feature_map 과 같이 미리 만들어진 피처 맵을 사용할 수 있습니다. 이 레슨에서는 두 가지를 모두 해보겠습니다.
단일 커널 행렬 요소를 계산하기 위해서는 두 개의 서로 다른 점을 인코딩하여 내부 곱을 추정할 수 있어야 한다는 점에 유의하세요. 물론 완전한 퀀텀 커널 워크플로에는 매핑된 데이터 벡터와 고전적인 머신 러닝 방법 사이에 이러한 내부 산물이 많이 포함됩니다. 그러나 반복되는 핵심 단계는 단일 커널 행렬 요소의 추정입니다. 이를 위해 데이터 종속형 양자 회로를 선택하고 두 개의 데이터 벡터를 특징 공간에 매핑합니다.
커널 행렬을 생성하는 작업의 경우, 모든 큐비트가 상태에 있는 상태를 측정할 확률에 특히 관심이 있습니다. 이를 확인하기 위해 하나의 데이터 벡터 의 인코딩과 매핑을 담당하는 회로는 으로, 의 인코딩과 매핑을 담당하는 회로는 으로 작성할 수 있다고 가정하고 매핑된 상태를 나타냅니다
이러한 상태는 데이터를 상위 차원으로 매핑하는 것이므로, 우리가 원하는 커널 항목은 내부 곱입니다
기본 초기 상태 에서 두 회로 와 를 모두 사용하여 작동하면 상태 를 측정할 확률은 다음과 같습니다
이것이 바로 우리가 원하는 값입니다(최대 ). 회로의 측정 계층은 측정 확률(또는 특정 오류 완화 방법을 사용하는 경우 소위 "준확률"이라고도 함)을 반환합니다. 관심 확률은 0 상태의 확률입니다( ).
2단계: 양자 실행을 위한 문제 최적화
- 입력: 특정 백엔드에 최적화되지 않은 추상 회로
- 출력: 대상 회로 및 관찰 가능, 선택한 QPU에 최적화됨
이 단계에서는 키스킷의 generate_preset_pass_manager 함수를 사용하여 실험을 실행할 실제 양자 컴퓨터와 관련하여 회로에 대한 최적화 루틴을 지정합니다. optimization_level=3 으로 설정하여 최고 수준의 최적화를 제공하는 사전 설정 패스 매니저를 사용합니다. 여기서 '최적화'란 실제 양자 컴퓨터에서 회로의 구현을 최적화하는 것을 의미합니다. 여기에는 게이트 깊이를 최소화할 수 있는 추상 양자 회로의 큐비트에 대응하는 물리적 큐비트를 선택하거나 사용 가능한 오류율이 가장 낮은 물리적 큐비트를 선택하는 것과 같은 고려 사항이 포함됩니다. 이는 머신 러닝 문제의 최적화와는 직접적인 관련이 없습니다(COBYLA와 같은 고전적인 최적화 도구에서처럼).
2단계를 구현하는 방법에 따라 행렬 요소에 포함된 각 포인트 쌍이 측정할 회로를 다르게 생성하기 때문에 회로를 두 번 이상 최적화해야 할 수도 있습니다.
3단계: IBM Quantum 기본 함수를 사용하여 실행하기
- 입력: 대상 회로
- 출력: 확률 분포
IBM Quantum 기본 Sampler 함수를 사용하여 회로 샘플링을 통해 도출된 상태의 확률 분포를 재구성합니다. 이 개념을 “준확률 분포(quasi-probability distribution)”라고 부르기도 한다는 점에 유의하십시오. 이 용어는 노이즈가 문제가 되거나, 오차 완화 같은 추가 단계가 도입되는 경우에 적용됩니다. 이러한 경우, 모든 확률의 합이 정확히 1이 되지 않을 수 있으므로, 이를 “준확률”이라고 합니다.
4단계: 후처리, 결과를 전통적인 형식으로 반환
- 입력: 확률 분포
- 출력: 단일 커널 행렬 요소, 또는 반복되는 경우 커널 행렬
양자 회로에서 을 측정할 확률을 계산하고 사용된 두 데이터 벡터에 해당하는 위치에 커널 행렬을 채웁니다. 전체 커널 행렬을 채우려면 각 항목에 대해 양자 실험을 실행해야 합니다. 커널 행렬이 있으면 pre-calculated kernels 을 허용하는 많은 고전적인 머신 러닝 알고리즘에서 사용할 수 있습니다. 예: qml_svc = SVC(kernel="precomputed"). 그런 다음 기존 워크스트림을 사용하여 테스트 데이터에 모델을 적용하고 정확도 점수를 얻을 수 있습니다. 정확도 점수에 대한 만족도에 따라 기능 맵과 같은 계산의 측면을 다시 검토해야 할 수도 있습니다.
수업 개요
이 강의에서는 실제 양자 컴퓨터에서 시간을 최적으로 활용할 수 있는 몇 가지 방법을 소개합니다. 퀀텀 커널 방식을 다음에 적용합니다
- 비교적 기능이 적은 데이터에 대한 단일 커널 매트릭스 항목으로 실제 백엔드를 사용하여 각 단계에서 어떤 일이 일어나는지 쉽게 추적할 수 있습니다.
- 퀀텀 워크스트림이 기존 머신 러닝 방법과 어떻게 연결되는지 확인할 수 있도록 시뮬레이션된 백엔드를 사용하여 상대적으로 기능이 적은 전체 데이터 집합을 사용합니다
- 실제 양자 컴퓨터를 사용하여 다양한 기능을 가진 데이터에 대한 단일 커널 매트릭스 항목입니다. IBM® 양자 컴퓨터의 시간을 존중하기 위해 대규모 데이터 세트에 대한 전체 커널 행렬을 추정하지 않습니다.
# If you have not already, install scikit learn
#!pip install scikit-learn단일 커널 행렬 요소
1단계: 고전적 입력을 양자 문제에 매핑하기
먼저 몇 가지 기능만 있는 데이터 집합(예: 10개)을 고려해 보겠습니다. 커널 행렬 요소를 한 번에 하나씩 계산하기 때문에 데이터 세트는 원하는 만큼 커질 수 있습니다. 최소 두 개 이상의 포인트가 필요하므로 이부터 시작하겠습니다(다음 예제에서는 전체 데이터 집합을 가져오겠습니다). 필요한 몇 가지 패키지를 가져와 보겠습니다:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Two mock data points, including category labels, as in training
small_data = [
[-0.194, 0.114, -0.006, 0.301, -0.359, -0.088, -0.156, 0.342, -0.016, 0.143, 1],
[-0.1, 0.002, 0.244, 0.127, -0.064, -0.086, 0.072, 0.043, -0.053, 0.02, -1],
]
# Data points with labels removed, for inner product
train_data = [small_data[0][:-1], small_data[1][:-1]]z_feature_map 를 사용해 볼 수 있습니다.
# from qiskit.circuit.library import zz_feature_map
# fm = zz_feature_map(feature_dimension=np.shape(train_data)[1], entanglement='linear', reps=1)
from qiskit.circuit.library import z_feature_map
fm = z_feature_map(feature_dimension=np.shape(train_data)[1])
unitary1 = fm.assign_parameters(train_data[0])
unitary2 = fm.assign_parameters(train_data[1])위의 두 단위는 소개에서 설명한 및 에 정확히 일치합니다. unitary_overlap 을 사용하여 결합할 수 있습니다. 언제나 그렇듯이, 저희는 회로의 깊이를 계속 주시하고 싶습니다.
from qiskit.circuit.library import unitary_overlap
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
print("circuit depth = ", overlap_circ.decompose().depth())
overlap_circ.decompose().draw("mpl", scale=0.6, style="iqp")Output:
circuit depth = 9
2단계: 양자 실행을 위한 문제 최적화
먼저 가장 사용량이 적은 백엔드를 선택한 다음 해당 백엔드에서 실행되도록 회로를 최적화합니다.
# Import needed packages
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Get the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=fm.num_qubits
)
print(backend)Output:
<IBMBackend('ibm_brisbane')>
# Apply level 3 optimization to our overlap circuit
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)복잡한 회로의 경우, 이 단계는 실제 양자 컴퓨터의 네이티브 게이트에 매핑되므로 회로 깊이를 크게 증가시키며, 큐비트에서 큐비트로 정보를 이동해야 할 수도 있습니다. 이 간단한 경우 깊이는 거의 영향을 받지 않습니다.
print("circuit depth = ", overlap_ibm.decompose().depth())
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)Output:
circuit depth = 10
1
3단계: IBM Quantum 기본 함수를 사용하여 실행하기
시뮬레이터에서 실행하는 구문은 아래에 설명되어 있습니다. 기능 수가 적은 이 데이터 집합의 경우 시뮬레이터에서 실행하는 것이 여전히 옵션입니다. 유틸리티 규모 계산의 경우 일반적으로 시뮬레이션이 불가능합니다. 시뮬레이터는 축소된 코드를 디버깅할 때만 사용해야 합니다.
# Run this for a simulator
# from qiskit.primitives import StatevectorSampler
# from qiskit_ibm_runtime import Options, Session, Sampler
# num_shots = 10000
# Evaluate the problem using state vector-based primitives from Qiskit
# sampler = StatevectorSampler()
# results = sampler.run([overlap_circ], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results
# such as |001101...01>.
# counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent
# of the above bit string.
# counts = results[0].data.meas.get_int_counts()# Benchmarked on an Eagle processor, 7-11-24, took 4 sec.
# Import the IBM Quantum primitive
from qiskit_ibm_runtime import Session, SamplerV2 as Sampler
num_shots = 10000
# Use sampler and get the counts
sampler = Sampler(mode=backend)
results = sampler.run([overlap_ibm], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results such as |001101...01>.
counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent
# of the above bit string.
counts = results[0].data.meas.get_int_counts()4단계: 후처리, 결과를 전통적인 형식으로 반환
소개에서 설명한 것처럼 여기서 가장 유용한 측정은 제로 상태를 측정할 확률 입니다.
counts.get(0, 0.0) / num_shotsOutput:
0.6525
이것이 우리가 원하는 결과입니다. 두 데이터 요소에 해당하는 벡터의 내적 곱(최대 제곱곱)의 추정치입니다. 측정 확률(또는 준확률)의 전체 분포를 보려면 아래와 같이 plot_distribution 함수를 사용하여 확인할 수 있습니다. 큐비트 수가 많으면 이와 같은 그림은 빠르게 다루기 어려워집니다.
from qiskit.visualization import plot_distribution
plot_distribution(counts_bit)Output:
또는 가장 가능성이 높은 상위 10개 측정값만 보도록 아래 그림과 같은 시각화를 정의할 수도 있습니다. 이는 문제를 해결하거나 데이터에 대한 더 많은 직관을 얻으려고 할 때 중요할 수 있습니다. 그러나 제로 상태의 측정 확률은 커널 행렬 요소입니다.
def visualize_counts(probs, num_qubits):
"""Visualize the outputs from the Qiskit Sampler primitive."""
zero_prob = probs.get(0, 0.0)
top_10 = dict(sorted(probs.items(), key=lambda item: item[1], reverse=True)[:10])
top_10.update({0: zero_prob})
by_key = dict(sorted(top_10.items(), key=lambda item: item[0]))
xvals, yvals = list(zip(*by_key.items()))
xvals = [bin(xval)[2:].zfill(num_qubits) for xval in xvals]
plt.bar(xvals, yvals)
plt.xticks(rotation=75)
plt.title("Results of sampling")
plt.xlabel("Measured bitstring")
plt.ylabel("Counts")
plt.show()
visualize_counts(counts, overlap_circ.num_qubits)Output:
고차원 특징 공간에서 두 데이터 요소 사이의 단 하나의 내부 곱에 대한 이 정보에서 우리가 말할 수 있는 것은 최대 겹침( 1.0 )에 비해 두 데이터 요소의 겹침이 상당히 크다는 것뿐입니다. 이는 이 두 데이터 포인트가 본질적으로 유사하며 동일한 클래스 클래스로 분류될 것임을 나타내는 지표일 수 있습니다. 또는 유사 데이터는 겹치는 부분이 강하고 유사 데이터는 겹치는 부분이 적은 공간에 피처 맵을 매핑하는 데 효과적이지 않다는 지표일 수도 있습니다. 어떤 것이 사실인지 알기 위해서는 피처 맵을 전체 데이터 세트에 적용하고 그 결과 커널 행렬을 조작하여 클래스를 높은 정확도로 효과적으로 구분할 수 있는지 확인해야 합니다.
참고로 저희는 z_feature_map 을 사용했는데, 그 결과 2쿼비트 트랜스파일된 깊이(실제로는 깊이 1)가 낮았습니다. 회로가 너무 깊어지면 노이즈가 많이 발생하여 피처 맵이 데이터와 잘 일치하더라도 제로 상태를 측정할 확률이 매우 낮아집니다. 예를 들어 zz_feature_map 및 , entanglement='linear', reps=1 을 사용하여 위의 프로세스를 반복하면 동일한 데이터 포인트를 사용하여 dist.get(0,0.0) = 0.0015 이 산출됩니다. 이는 zz_feature_map 에서 훨씬 더 큰 회로 깊이와 2큐비트 깊이 때문입니다. 아래 그림은 해당 계산의 확률 분포를 보여줍니다.
좋은 결과를 얻으려면 깊이를 얼마나 낮춰야 하는지 알아보기 위해 같은 카테고리의 데이터 포인트 몇 개를 가지고 놀아보는 것도 좋습니다. 다음은 예외가 있을 수 있는 대략적인 조언입니다. 일반적으로 2큐비트, 트랜스파일된 깊이가 10 이하인 경우 문제가 없습니다. 2큐비트, 트랜스파일드 깊이 50-60은 최신 기술이며 다른 툴 중에서도 고급 오류 완화 기능이 필요합니다. 그 사이에는 데이터 유사성, 피처 맵 표현력, 회로 폭 및 기타 요인에 따라 결과가 달라질 수 있습니다.
일반적으로 후처리 단계에는 고전적인 머신러닝 프로세스도 포함됩니다. 다음 섹션에서는 이 프로세스를 전체 데이터 집합으로 확장하여 고전적인 머신 러닝 워크플로우를 보여드리겠습니다.
이해도 점검
10큐비트 양자 회로에서 일반적으로 측정할 수 있는 상태는 몇 가지가 있을까요?
또는 1024.
양자 컴퓨팅을 처음 접하는 사람이 오류 완화 기법을 적용하지 않은 채, 2-큐비트 깊이가 매우 높은 양자 회로를 사용하려고 한다고 가정해 봅시다. 더 나아가 이로 인해 각 큐비트에서 10%의 오류율이 발생한다고 가정해 보자. 이 회로에 해당하는 진정한(오류가 없는) 커널 행렬 요소가 매우 크다면(예를 들어 1.0 ), 10개의 큐비트 모두가 인 상태로 측정될 확률은 얼마일까요?
각 큐비트가 |0> 상태에서 올바르게 발견될 확률은 0.90 입니다. 10개의 큐비트가 모두 올바른 상태로 발견될 확률은 또는 약 35%입니다.
회로 깊이를 모니터링하는 것이 왜 그렇게 중요한지 직접 설명하세요. 이는 일반적으로 사실이지만 양자 커널 추정의 맥락에서 설명해 보겠습니다.
이 QKE 워크플로에서는 모든 큐비트가 상태에 있는 상태를 의미하는 제로 상태의 측정값을 기반으로 추정합니다. 매우 깊은 회로는 높은 오류율을 초래합니다. 이러한 오류율이 여러 큐비트에 걸쳐 누적되면 0 상태를 측정할 확률이 크게 감소합니다.
전체 커널 행렬
이 섹션에서는 위의 프로세스를 전체 데이터 세트의 이진 분류로 확장해 보겠습니다. 여기에는 두 가지 중요한 구성 요소가 도입됩니다: (1) 이제 후처리에서 고전적인 머신러닝을 구현할 수 있고, (2) 학습에 대한 정확도 점수를 얻을 수 있다는 점입니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
이제 분류를 위해 기존 데이터 집합을 가져옵니다. 이 데이터 세트는 128개의 행(데이터 포인트)과 각 포인트의 14개 기능으로 구성됩니다. 각 포인트의 이진 카테고리를 나타내는 15번째 요소( )가 있습니다. 데이터 세트는 아래에서 가져오거나 여기에서 데이터 세트에 액세스하여 구조를 볼 수 있습니다.
처음 90개의 데이터 포인트는 훈련에, 다음 30개는 테스트에 사용합니다.
!wget https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None)
# Prepare training data
train_size = 90
X_train = df.values[0:train_size, :-1]
train_labels = df.values[0:train_size, -1]
# Prepare testing data
test_size = 30
X_test = df.values[train_size : train_size + test_size, :-1]
test_labels = df.values[train_size : train_size + test_size, -1]Output:
--2024-07-11 23:05:22-- https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.109.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 49405 (48K) [text/plain]
Saving to: ‘dataset_graph7.csv.15’
dataset_graph7.csv. 100%[===================>] 48.25K --.-KB/s in 0.02s
2024-07-11 23:05:23 (2.11 MB/s) - ‘dataset_graph7.csv.15’ saved [49405/49405]
커널 행렬과 적절한 크기의 테스트 행렬을 구성하여 여러 출력을 저장할 준비를 이미 마쳤습니다.
# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)
test_matrix = np.full((test_size, num_samples), np.nan)이제 기존 데이터를 인코딩하고 양자 회로에 매핑하기 위한 피처 맵을 생성합니다. 자체 피처 맵을 만들거나 미리 제작된 피처 맵을 자유롭게 사용할 수 있습니다. 아래 기능 맵을 자유롭게 수정하거나 ZFeatureMap 로 다시 전환하세요. 하지만 항상 회로 깊이에 주의하세요. 이전 6-큐비트 예제에서 zz_feature_map 을 사용할 때 트랜스파일링된 회로 깊이가 감당할 수 없을 정도로 높았다는 것을 기억하십시오. 회로의 규모와 복잡성이 증가함에 따라 노이즈가 결과를 압도할 정도로 깊이가 급격히 증가할 수 있습니다. 데이터 구조에 대해 어떤 피처 맵 구조가 가장 유용할지 알 수 있는 정보를 알고 있다면 그 지식을 활용하여 자신만의 사용자 지정 피처 맵을 만드는 것이 좋습니다.
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
# To use a custom feature map use the lines below.
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)단계 2와 3: 문제를 최적화하고 기본 연산을 사용하여 실행
중첩 회로를 구성하고, 이 예제에서 실제 양자 컴퓨터에서 실행한다면 이전처럼 실행을 위해 최적화할 것입니다. 하지만 이 경우에는 모든 데이터 포인트를 단계적으로 살펴보고 전체 커널 행렬을 계산하려고 합니다. 데이터 벡터 와 의 각 쌍에 대해 서로 다른 오버랩 회로를 생성합니다. 따라서 각 데이터 포인트 쌍에 대해 회로를 최적화해야 합니다. 따라서 2단계와 3단계는 여러 반복에서 함께 수행됩니다.
아래 코드 셀은 단일 데이터 포인트 쌍에 대해 이전과 똑같은 프로세스를 수행합니다. 이번에는 두 개의 for 루프 안에서 간단히 실행되며, 마지막에 kernel_matrix[x_1,x_2] = ... 줄이 추가되어 각 계산의 결과를 저장합니다. 커널 행렬의 대칭성을 활용하여 계산 횟수를 1/2로 줄였습니다. 또한 대각선 요소는 노이즈가 없을 때와 마찬가지로 1로 설정했습니다. 구현 방식과 필요한 정밀도에 따라 대각선 요소를 사용하여 노이즈를 추정하거나 오류를 완화하기 위한 목적으로 학습할 수도 있습니다.
커널 행렬이 완전히 채워지면 테스트 데이터에 대해 이 과정을 반복하여 test_matrix를 채웁니다. 이것은 실제로 커널 매트릭스라고 할 수 있으며, 두 가지를 구분하기 위해 다른 이름을 붙였을 뿐입니다.
# To use a simulator
from qiskit.primitives import StatevectorSampler
# Remember to insert your token in the QiskitRuntimeService constructor
# to use real quantum computers
# service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=fm.num_qubits
# )
num_shots = 10000
# Evaluate the problem using state vector-based primitives from Qiskit.
sampler = StatevectorSampler()
for x1 in range(0, train_size):
for x2 in range(x1 + 1, train_size):
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
# These lines run the qiskit sampler primitive.
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
# Assign the probability of the 0 state to the kernel matrix, and the transposed element
# (since this is an inner product)
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
kernel_matrix[x2, x1] = counts.get(0, 0.0) / num_shots
# Fill in on-diagonal elements with 1, again, since this is an inner-product corresponding to
# probability (or alter the code to check these entries and verify they yield 1)
kernel_matrix[x1, x1] = 1
print("training done")
# Similar process to above, but for testing data.
for x1 in range(0, test_size):
for x2 in range(0, train_size):
unitary1 = fm.assign_parameters(list(X_test[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
test_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print("test matrix done")Output:
training done
test matrix done
4단계: 후처리, 결과를 전통적인 형식으로 반환
이제 퀀텀 커널 방법에서 커널 행렬과 유사한 형식의 테스트_행렬을 얻었으므로 고전적인 머신 러닝 알고리즘을 적용하여 테스트 데이터를 예측하고 그 정확성을 확인할 수 있습니다. 먼저 Scikit-Learn의 sklearn.svc, 서포트 벡터 분류기(SVC)를 가져옵니다. kernel = precomputed 을 사용하여 SVC가 미리 계산된 커널을 사용하도록 지정해야 합니다.
# import a support vector classifier from a classical ML package.
from sklearn.svm import SVC
# Specify that you want to use a pre-computed kernel matrix
qml_svc = SVC(kernel="precomputed")이제 SVC.fit 을 사용하여 커널 행렬과 학습 레이블을 입력하여 적합도를 얻을 수 있습니다. SVC.score 는 테스트_매트릭스를 사용하여 해당 적합성에 대한 테스트 데이터의 점수를 매기고 정확도를 반환합니다.
# Feed in the pre-computed matrix and the labels of the training data.
# The classical algorithm gives you a fit.
qml_svc.fit(kernel_matrix, train_labels)
# Now use the .score to test your data, using the matrix of test data,
# and test labels as your inputs.
qml_score_precomputed_kernel = qml_svc.score(test_matrix, test_labels)
print(f"Precomputed kernel classification test score: {qml_score_precomputed_kernel}")Output:
Precomputed kernel classification test score: 1.0
학습된 모델의 정확도가 100%라는 것을 알 수 있습니다. 이는 대단한 일이며, QKE가 효과가 있다는 것을 보여줍니다. 하지만 이는 양자 우위와는 매우 다릅니다. 기존 커널이었다면 이 분류 문제도 100% 정확도로 해결할 수 있었을 것입니다. 현재 유틸리티 시대에 양자 커널이 가장 유용하게 사용될 수 있는 분야를 파악하기 위해 다양한 데이터 유형과 데이터 관계를 특성화해야 할 작업이 많이 남아 있습니다. 이 워크플로우의 일부를 수정하고 다양한 양자 피처 맵의 효과를 연구하는 것은 학습자의 몫으로 남겨둡니다. 다음은 몇 가지 고려해야 할 사항입니다:
- 정확도는 얼마나 견고하나요? 광범위한 유형의 데이터에 적용되나요, 아니면 이 특정 훈련 데이터에만 적용되나요?
- 데이터의 어떤 구조가 양자 피처 맵이 유용하다고 생각하시나요?
- 학습 데이터의 양을 늘리거나 줄이면 정확도에 어떤 영향을 미치나요?
- 어떤 피처 맵을 사용할 수 있으며 피처 맵에 따라 결과가 어떻게 달라지나요?
- 기능 수를 늘리면 정확도와 실행 시간에 어떤 영향을 미치나요?
- 실제 양자 컴퓨터에서 어떤 트렌드가 나타날 것으로 예상하시나요?
더 많은 기능과 큐비트로 확장
이 섹션에서는 단일 행렬 요소의 계산을 반복하지만 훨씬 더 많은 수의 피처에 대해 유용성을 향한 스케일링 경로를 스케치합니다. 단일 행렬 요소로 제한하는 것은 양자 컴퓨터에서 할당된 시간을 너무 많이 사용하지 않고도 프로세스를 보여주기 위해서입니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
각 데이터 요소에 42개의 특징이 있는 데이터 집합의 시작점을 가정하겠습니다. 첫 번째 예제에서와 마찬가지로 두 개의 데이터 포인트가 필요한 단일 커널 행렬 요소를 계산합니다. 아래 두 지점에는 42개의 기능과 단일 카테고리 변수( )가 있습니다.
# Two mock data points, including category labels, as in training
large_data = [
[
-0.028,
-1.49,
-1.698,
0.107,
-1.536,
-1.538,
-1.356,
-1.514,
-0.109,
-1.8,
-0.122,
-1.651,
-1.955,
-0.123,
-1.732,
0.091,
-0.048,
-0.128,
-0.026,
0.082,
-1.263,
0.065,
0.004,
-0.055,
-0.08,
-0.173,
-1.734,
-0.39,
-1.451,
0.078,
-1.578,
-0.025,
-0.184,
-0.119,
-1.336,
0.055,
-0.204,
-1.578,
0.132,
-0.121,
-1.599,
-0.187,
-1,
],
[
-1.414,
-1.439,
-1.606,
0.246,
-1.673,
0.002,
-1.317,
-1.262,
-0.178,
-1.814,
0.013,
-1.619,
-1.86,
-0.25,
-0.212,
-0.214,
-0.033,
0.071,
-0.11,
-1.607,
0.441,
-0.143,
-0.009,
-1.655,
-1.579,
0.381,
-1.86,
-0.079,
-0.088,
-0.058,
-1.481,
-0.064,
-0.065,
-1.507,
0.177,
-0.131,
-0.153,
0.07,
-1.627,
0.593,
-1.547,
-0.16,
-1,
],
]
train_data = [large_data[0][:-1], large_data[1][:-1]]zz_feature_map 의 경우 상대적으로 적은 기능(14개)의 경우 다소 깊은 회로를 생성했다는 점을 기억하세요. 기능의 수가 늘어날수록 회로 깊이를 면밀히 모니터링해야 합니다. 이를 설명하기 위해 먼저 zz_feature_map 을 사용하여 결과 회로의 깊이를 확인해 보겠습니다.
from qiskit.circuit.library import zz_feature_map
fm = zz_feature_map(
feature_dimension=np.shape(train_data)[1], entanglement="linear", reps=1
)
unitary1 = fm.assign_parameters(train_data[0])
unitary2 = fm.assign_parameters(train_data[1])from qiskit.circuit.library import unitary_overlap
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
print("circuit depth = ", overlap_circ.decompose(reps=2).depth())
print(
"two-qubit depth",
overlap_circ.decompose().depth(lambda instr: len(instr.qubits) > 1),
)
# overlap_circ.draw("mpl", scale=0.6, style="iqp")Output:
circuit depth = 251
two-qubit depth 165
앞서 설명한 것처럼 너무 깊다는 것을 정확히 판단하는 것은 미묘한 차이가 있습니다. 하지만 트랜스퓔레이션 전이라도 2쿼비트 깊이가 100을 넘으면 시작도 하기 어렵습니다. 이 레슨에서 사용자 지정 기능 맵을 강조한 이유가 바로 여기에 있습니다. 전체 데이터 집합의 구조에 대해 알고 있다면, 그 구조를 염두에 두고 얽힘 맵을 디자인해야 합니다. 여기서는 이러한 두 데이터 포인트 사이의 내적 곱만 계산하기 때문에 데이터 구조에 대한 세부적인 고려보다 낮은 회로 깊이를 우선시했습니다.
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
# Prepare feature map for computing overlap
entangler_map = [
[3, 4],
[2, 5],
[1, 4],
[2, 3],
[4, 6],
[7, 9],
[10, 11],
[9, 12],
[8, 11],
[9, 10],
[11, 13],
[14, 16],
[17, 18],
[16, 19],
[15, 18],
[16, 17],
[18, 20],
]# Use the entangler map above to build a feature map
num_features = np.shape(train_data)[1]
num_qubits = int(num_features / 2)
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)from qiskit.circuit.library import unitary_overlap
# Assign features of each data point to a unitary, an instance of the general feature map.
unitary1 = fm.assign_parameters(list(train_data[0]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(train_data[1]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()실제로 중요한 것은 트랜스파일된 2큐비트 깊이이므로 깊이는 아직 확인하지 않겠습니다.
2단계: 양자 실행을 위한 문제 최적화
먼저 가장 사용량이 적은 백엔드를 선택한 다음 해당 백엔드에서 실행되도록 회로를 최적화합니다.
# Import needed packages
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Get the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=fm.num_qubits
)
print(backend)Output:
<IBMBackend('ibm_brisbane')>
소규모 작업에서는 사전 설정된 패스 관리자가 동일한 깊이의 동일한 회로를 안정적으로 반환하는 경우가 많습니다. 그러나 매우 크고 복잡한 회로에서는 패스 관리자가 실행할 때마다 다른 트랜스파일된 회로를 반환할 수 있습니다. 휴리스틱을 사용하고 있고, 매우 큰 회로는 가능한 최적화의 환경이 복잡하기 때문입니다. 몇 번 트랜스파일하고 가장 얕은 회로를 취하는 것이 종종 유용합니다. 이는 고전적인 오버헤드만 발생하며 양자 컴퓨터의 결과를 크게 향상시킬 수 있습니다.
여기서는 단일 중첩 회로를 20번 트랜스파일하고 얻은 회로의 깊이를 살펴봅니다.
# Apply level 3 optimization to our overlap circuit
transpiled_qcs = []
transpiled_depths = []
transpiled_twoqubit_depths = []
for i in range(1, 20):
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)
transpiled_qcs.append(overlap_ibm)
transpiled_depths.append(overlap_ibm.decompose().depth())
transpiled_twoqubit_depths.append(
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
)
print("circuit depth = ", overlap_ibm.decompose().depth())Output:
circuit depth = 61
print(transpiled_depths)
print(transpiled_twoqubit_depths)Output:
[61, 60, 60, 69, 60, 60, 60, 65, 60, 60, 69, 61, 77, 77, 65, 60, 60, 77, 61]
[13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13]
여기에서 트랜스필레이션 패스에 따라 전체 게이트 깊이에 약간의 차이가 있음을 알 수 있습니다. 우리의 회로는 아직 2쿼비트 트랜스파일 깊이의 변화를 볼 수 있을 만큼 충분히 깊거나 넓지 않습니다. 가장 깊은 회로의 깊이인 77보다 약간 낮은 60의 깊이를 가진 transpiled_qcs[1] 을 사용할 것입니다.
overlap_ibm = transpiled_qcs[1]3단계: IBM Quantum 기본 함수를 사용하여 실행하기
유틸리티에 가까워질수록 시뮬레이터는 유용하지 않게 됩니다. 여기에는 실제 양자 컴퓨터의 구문만 나와 있습니다.
# Run on ibm_osaka, 7-12-24, required 22 sec.
# Import our IBM Quantum primitive
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Open a session:
session = Session(backend=backend)
num_shots = 10000
# Use sampler and get the counts
sampler = Sampler(mode=session)
options = sampler.options
options.dynamical_decoupling.enable = True
options.twirling.enable_gates = True
counts = (
sampler.run([overlap_ibm], shots=num_shots).result()[0].data.meas.get_int_counts()
)
# Close session after done
session.close()4단계: 후처리, 결과를 전통적인 형식으로 반환
소개에서 설명한 것처럼 여기서 가장 유용한 측정은 제로 상태를 측정할 확률 입니다.
counts.get(0, 0.0) / num_shotsOutput:
0.0138
단일 커널 행렬 요소에 대한 이 프로세스를 세트의 다른 데이터 쌍 간에 반복하여 전체 커널 행렬을 얻을 수 있습니다. 커널 행렬의 크기는 특징의 수가 아니라 학습 데이터의 포인트 수에 따라 결정됩니다. 따라서 커널 행렬을 예측 모델로 조작하는 데 드는 컴퓨팅 비용은 피처나 큐비트 수처럼 확장되지 않습니다. 피처 수가 많은 비교적 작은 데이터 세트의 경우에도 효과적인 분류를 위해 데이터를 피처 맵과 일치시켜야 합니다.
확장 및 향후 작업
커널 방법을 사용하려면 를 가능한 한 정확하게 측정해야 합니다. 그러나 게이트 오류와 판독 오류로 인해, 주어진 큐비트가 상태에 있는 것으로 잘못 측정될 확률이 0이 아닌 의 확률을 가진다. 의 확률이 이어야 한다는 지나치게 단순화된 가정하더라도, 예를 들어 비트로 인코딩된 많은 특징의 경우, 모든 비트를 로 정확하게 측정할 확률은 로 줄어듭니다. 가 커질수록 이 방법의 신뢰도는 점점 떨어집니다. 이러한 어려움을 극복하고 커널 추정법을 점점 더 많은 특징에 적용할 수 있도록 확장하는 것이 현재 연구의 주요 분야입니다. 이 문제에 대해 더 자세히 알아보려면 Thanasilp, Wang, Cerezo, Holmes의 이 논문을 참고하십시오. 현재의 양자 컴퓨터로 무엇을 할 수 있는지 살펴보시기를 권장하며, 오류 정정 시대에 어떤 일이 가능해질지 기대해 보시기 바랍니다.
검토
양자 커널 계산에는 다음이 포함됩니다
- 훈련 데이터 포인트 쌍을 사용하여 커널 행렬 항목 계산하기
- 데이터를 인코딩하고 기능 매핑을 통해 매핑합니다
- 실제 양자 컴퓨터/백엔드에서 실행하기 위한 회로 최적화
그런 다음 양자 커널은 이 단원에서와 같이 고전적인 머신 러닝 알고리즘에 사용할 수 있습니다.
퀀텀 커널을 사용할 때 염두에 두어야 할 몇 가지 주요 사항은 다음과 같습니다:
- 데이터 세트가 퀀텀 커널 방식의 이점을 누릴 수 있을까요?
- 다양한 피처 맵과 얽힘 체계를 사용해 보세요.
- 회로 깊이가 허용 가능한가요?
- 패스 관리자를 여러 번 실행하고 가능한 가장 작은 깊이의 회로를 사용하세요.
양자 커널 방법은 양자 지원 기능이 있는 데이터 세트와 적절한 양자 기능 맵이 적절히 일치할 경우 잠재적으로 강력한 도구가 될 수 있습니다. 양자 커널이 유용하게 쓰일 수 있는 분야를 더 잘 이해하려면 Liu, Arunachalam & Temme(2021)을 읽어보시기 바랍니다.