انتقل إلى المحتوى الرئيسي

تدريب النواة الكمية

تقدير الاستخدام: أقل من دقيقة واحدة على معالج 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 الإصدار 2.3.1 أو أحدث، مع دعم التمثيل البصري
  • Qiskit Runtime الإصدار 0.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. محاكي متجه الحالة دقيق (مع ضوضاء اللقطات) ويُري لنا الطريقة من البداية إلى النهاية دون استهلاك وقت وحدة معالجة كمية. ثم نكرر نفس النسخة على عتاد حقيقي في قسم مثال العتاد.

الخطوة 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")

Output of the previous code cell

الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمي

  • المدخل: دائرة مجردة غير محسَّنة لواجهة خلفية بعينها.
  • المخرج: الدائرة الهدف، محسَّنة للوحدة المعالجة الكمية المختارة.

في مسار محاكي متجه الحالة المستخدم في هذا القسم، لا يلزم إجراء أي تحسين خاص بالواجهة الخلفية: يمكن أخذ عينات من الدائرة المجردة مباشرةً. سنُمارس هذه الخطوة في مثال العتاد أدناه، حيث تُترجَم الدائرة مقابل وحدة معالجة كمية حقيقية باستخدام 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)

Output of the previous code cell

الخطوة 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 عينة تدريب، ويتطلب كل عنصر تشغيل دائرة تداخل يتزايد عمق بوابتها ثنائية القبيتات مع حجم خريطة الميزات. ونتيجةً لذلك، فإن توسيع نطاق هذا البرنامج التعليمي ليشمل مسألة أكبر ينطوي على تكلفتين متراكمتين: يتضاعف وقت وحدة المعالجة الكمية لكل مصفوفة نواة تربيعياً مع NN، كما أن عمق unitary_overlap (الذي يركِّب خريطة الميزات مع مرافقتها) يُضعف الدقة عند حجم النظام وتوصيلية العتاد الحالي. وللحفاظ على قِصَر العرض التوضيحي وإجراء مقارنة واضحة، نُشغِّل نفس النسخة ذات السبعة قبيتات من المثال الصغير على وحدة معالجة كمية حقيقية، ونقارن دقة عنصر واحد من مصفوفة النواة بقيمة المحاكي المحسوبة أعلاه.

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

Output of the previous code cell

Fidelity (hardware): 0.7517

لملء مصفوفة النواة بأكملها، سنُشغِّل تجربة كمية لكل عنصر من عناصرها الفريدة البالغة N(N+1)/2N(N+1)/2. يُظهر الشكل أدناه المصفوفة الناتجة لمجموعة البيانات هذه؛ يدل اللون الأحمر الداكن على قيم دقة أقرب إلى 1.0.

kernel_matrix.png

الخطوات التالية

Recommendations

إن وجدت هذا العمل مثيراً للاهتمام، فقد يستأثر اهتمامك المواد التالية: