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

Навчання квантового ядра

Оцінка використання: менше однієї хвилини на процесорі Heron r3 (ПРИМІТКА: Це лише оцінка. Твій час виконання може відрізнятися.)

Результати навчання

Після завершення цього посібника ти зможеш розуміти таку інформацію:

  • Методи ядра та їх застосування
  • Квантові ядра та те, як вони можуть забезпечити покращені простори ознак
  • Побудова схем квантового ядра
  • Як навчати квантове ядро за допомогою шаблону Qiskit: відображення, оптимізація, виконання та постобробка

Передумови

Рекомендується ознайомитися з квантовими ядрами, чому вони важливі та як вони використовуються на практиці.

Також корисно мати базове розуміння теорії груп.

Передумови

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

Методи квантового ядра — це ті, які використовують квантові комп'ютери для оцінки ядра. Відомо, що квантові комп'ютери можуть кодувати дані в квантово-покращених просторах ознак, ефективно замінюючи класичні аналоги. Для xR\vec{x} \in \mathbb{R} та Ψ(x)Rd\Psi(\vec{x}) \in \mathbb{R}^{d'}, як правило з d>dd' >d, Ψ(x)\Psi(\vec{x}) є відображенням ознак, xΨ(x)\vec{x} \mapsto \Psi(\vec{x}). Мета Ψ(x)\Psi(\vec{x}) полягає в тому, щоб категорії даних були розділені гіперплощиною. Беручи вектори у просторі відображених ознак як аргументи, функція ядра K(x,y)=Ψ(x)Ψ(y)K(\vec{x}, \vec{y}) = \langle{\Psi(\vec{x}) | \Psi(\vec{y}) \rangle{}} повертає їхній внутрішній добуток: K:RdK: \mathbb{R}^d \rightarrow Rd\mathbb{R}^d. Класично, відображення ознак, що становлять інтерес, — це ті, в яких функція ядра може бути легко обчислена; тобто коли внутрішній добуток у просторі відображених ознак можна записати через початкові вектори даних, і Ψ(x)\Psi(\vec{x}) та Ψ(y)\Psi(\vec{y}) не потрібно будувати явно. У випадку квантових ядер відображення ознак виконується квантовою схемою, а ядро оцінюється за ймовірностями вимірювань, вибраних зі схеми.

Цей посібник показує, як побудувати шаблон Qiskit для обчислення елементів матриці квантового ядра, що використовується для бінарної класифікації.

Вимоги

Перед початком цього посібника переконайся, що у тебе встановлено наступне:

  • Qiskit SDK v2.3.1 або новіший, з підтримкою візуалізації
  • Qiskit Runtime v0.44.0 або новіший (pip install qiskit-ibm-runtime)

Налаштування

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy pandas qiskit qiskit-ibm-runtime
# General Imports and helper functions
import urllib.request

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.circuit.library import unitary_overlap
from qiskit.primitives import StatevectorSampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, Sampler

# Download the dataset (portable across platforms)
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv",
"dataset_graph7.csv",
)

def visualize_counts(res_counts, num_qubits, num_shots):
"""Visualize the outputs from the Qiskit Sampler primitive."""
zero_prob = res_counts.get(0, 0.0)
top_10 = dict(
sorted(res_counts.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]))
x_vals, y_vals = list(zip(*by_key.items()))
x_vals = [bin(x_val)[2:].zfill(num_qubits) for x_val in x_vals]
y_vals_prob = []
for t in range(len(y_vals)):
y_vals_prob.append(y_vals[t] / num_shots)
y_vals = y_vals_prob
plt.bar(x_vals, y_vals)
plt.xticks(rotation=75)
plt.title("Results of sampling")
plt.xlabel("Measured bitstring")
plt.ylabel("Probability")
plt.show()

def get_training_data():
"""Read the training data."""
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None)
training_data = df.values[:20, :]
ind = np.argsort(training_data[:, -1])
X_train = training_data[ind][:, :-1]

return X_train

Приклад на симуляторі малого масштабу

У цьому розділі ми розглядаємо чотири кроки шаблону Qiskit на семикубітному екземплярі задачі розмітки суміжних класів із помилкою та обчислюємо один елемент матриці ядра за допомогою примітиву StatevectorSampler з Qiskit. Симулятор вектора стану є точним (з точністю до шуму вибірки) і показує метод від початку до кінця без витрат часу QPU. Потім ми повторюємо той самий екземпляр на реальному обладнанні в розділі з прикладом на обладнанні.

Крок 1: Відображення класичних вхідних даних на квантову задачу

  • Вхідні дані: Навчальний набір даних.
  • Вихідні дані: Абстрактна схема для обчислення елемента матриці ядра.

Задача бінарної класифікації, яку ми прагнемо тут вирішити, називається «розмітка суміжних класів із помилкою». Вхідний навчальний набір даних містить групову структуру, що складається з двох суміжних класів, утворених групою та підгрупою. Групу беремо як G=SU(2)nG = SU(2)^{\otimes n} для кубітів, що є спеціальною унітарною групою матриць 2×22 \times 2 і має широке застосування в природі; наприклад, Стандартна модель фізики елементарних частинок. Беремо (граф-стабілізаторну) підгрупу Sgraph<GS_\text{graph} < G з Sgraph={Xik:(k,i)EZk}iV}S_\text{graph} = \langle \{ X_i \otimes _{k:(k,i) \in \mathcal{E}} Z_k\} _{i \in \mathcal{V}} \} \rangle для графа з ребрами E\mathcal{E} та вершинами V\mathcal{V}. Зауважимо, що стабілізатори фіксують стабілізаторний стан так, що Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}. Нарешті, ми визначаємо два лівих суміжних класи C±=c±SgraphC_\pm = c_\pm S_\text{graph}, обираючи два c±Gc_\pm \in G навмання.

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

Ми створюємо квантову схему, що використовується для обчислення одного елемента матриці ядра. Вхідні дані використовуються для визначення кутів обертання параметризованих вентилів схеми. Для простоти ми будемо використовувати зразки даних x1=14 та x2=19.

Примітка: Набір даних, що використовується в цьому посібнику, можна завантажити тут.

# Prepare training data
X_train = get_training_data()

# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)

# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
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)

# Assign tunable parameter to known optimal value and set the data params for
# first two samples
x1 = 14
x2 = 19
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()
overlap_circ.draw("mpl", scale=0.6, style="iqp")

Вихідні дані попередньої комірки коду

Крок 2: Оптимізація задачі для виконання на квантовому обладнанні

  • Вхідні дані: Абстрактна схема, не оптимізована для конкретного бекенду.
  • Вихідні дані: Цільова схема, оптимізована для обраного QPU.

Для шляху через симулятор вектора стану, що використовується в цьому розділі, оптимізація під конкретний бекенд не потрібна: абстрактну схему можна безпосередньо вибирати. Ми виконуємо цей крок у прикладі з обладнанням нижче, де схема транспілюється під реальний QPU за допомогою generate_preset_pass_manager з optimization_level=3.

Крок 3: Виконання за допомогою примітивів Qiskit

  • Вхідні дані: Абстрактна схема.
  • Вихідні дані: Квазіймовірнісний розподіл.

Використай примітив StatevectorSampler з Qiskit для реконструкції квазіймовірнісного розподілу станів, отриманих шляхом вибірки зі схеми. Для задачі генерації матриці ядра нас особливо цікавить ймовірність вимірювання стану |0>.

sampler = StatevectorSampler()

# Execute and get counts
num_shots = 10_000
results = sampler.run([overlap_circ], shots=num_shots).result()
counts = results[0].data.meas.get_int_counts()

# Plot counts
visualize_counts(counts, num_qubits, num_shots)

Вихідні дані попередньої комірки коду

Крок 4: Постобробка та повернення результату в бажаному класичному форматі

  • Вхідні дані: Розподіл ймовірностей.
  • Вихідні дані: Один елемент матриці ядра.

Обчисли ймовірність вимірювання 0|0 \rangle на схемі перекриття та заповни матрицю ядра в позиції, що відповідає зразкам, представленим цією конкретною схемою перекриття (рядок 15, стовпець 20).

kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (simulator): {kernel_matrix[x1, x2]}")
Fidelity (simulator): 0.8261

Приклад на обладнанні

Матриця квантового ядра має O(N2)\mathcal{O}(N^2) елементів для NN навчальних зразків, і кожен елемент потребує запуску схеми перекриття, глибина двокубітних вентилів якої зростає з розміром відображення ознак. Як наслідок, масштабування цього посібника на більшу задачу має два накопичувальні витрати: час QPU на матрицю ядра зростає квадратично з NN, а глибина unitary_overlap (яка складає відображення ознак з його спряженим) знижує точність при розмірі системи та зв'язності поточного обладнання. Щоб тримати демонстрацію короткою та зробити чисте порівняння, ми запускаємо той самий семикубітний екземпляр із прикладу малого масштабу на реальному QPU та порівнюємо точність одного елемента матриці ядра зі значенням симулятора, обчисленим вище.

# ------------------------------ Step 1 ------------------------------
# Prepare training data
X_train = get_training_data()

# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)

# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
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)

# Assign tunable parameter to known optimal value and
# set the data params for first two samples
x1 = 14
x2 = 19
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()

# ------------------------------ Step 2 ------------------------------
service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=overlap_circ.num_qubits
# )
backend = service.backend("ibm_pittsburgh")
print(f"Using backend: {backend.name}")
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)

# ------------------------------ Step 3 ------------------------------
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT_QKT"]

num_shots = 10_000
results = sampler.run([overlap_ibm], shots=num_shots).result()
counts = results[0].data.meas.get_int_counts()
visualize_counts(counts, num_qubits, num_shots)

# ------------------------------ Step 4 ------------------------------
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print(f"Fidelity (hardware): {kernel_matrix[x1, x2]}")
Using backend: ibm_pittsburgh

Вихідні дані попередньої комірки коду

Fidelity (hardware): 0.7517

Щоб заповнити всю матрицю ядра, ми б виконали квантовий експеримент для кожного з її N(N+1)/2N(N+1)/2 унікальних елементів. На рисунку нижче показано отриману матрицю для цього набору даних; темніший червоний колір вказує на значення точності, ближчі до 1.0.

kernel_matrix.png

Наступні кроки

Рекомендації

Якщо ця робота зацікавила тебе, можливо, тебе зацікавлять такі матеріали: