דלג לתוכן הראשי

אימון ליבת קוונטים

אומדן זמן שימוש: פחות מדקה על מעבד 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 ומעלה, עם תמיכה ב-visualization
  • 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 על מופע בן שבעה קיוביטים של בעיית "labeling-cosets-with-error" ונעריך ערך יחידי של מטריצת הליבה באמצעות הפרימיטיב StatevectorSampler מ-Qiskit. סימולטור statevector הוא מדויק (עד לרעש דגימה) ומאפשר לנו לראות את השיטה מקצה לקצה מבלי לצרוך זמן QPU. לאחר מכן נחזור על אותו המופע על חומרה אמיתית בחלק דוגמת החומרה.

שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית

  • קלט: מערך נתוני אימון.
  • פלט: מעגל מופשט לחישוב ערך במטריצת ליבה.

בעיית הסיווג הבינארי שאנחנו שואפים לפתור כאן נקראת "labeling cosets with error." מערך נתוני האימון מכיל מבנה חבורה, הכולל שני cosets שנוצרו על ידי חבורה ותת-חבורה. החבורה נלקחת להיות G=SU(2)nG = SU(2)^{\otimes n} עבור קיוביטים, שהיא קבוצת היחידה המיוחדת של מטריצות 2×22 \times 2 ויש לה ישימות רחבה בטבע; למשל, המודל הסטנדרטי של פיזיקת חלקיקים. אנחנו נוקטים בתת-חבורה (ה-graph-stabilizer) 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}. שימו לב שה-stabilizers קובעים מצב stabilizer כך ש-Dsψ=ψ, sSgraphD_s | \psi \rangle = | \psi \rangle,~ \forall s \in S_\text{graph}. לבסוף, אנחנו מגדירים שני left-cosets C±=c±SgraphC_\pm = c_\pm S_\text{graph} על ידי שליפה אקראית של שני c±Gc_\pm \in G.

לפרטים נוספים על מערך הנתונים וכיצד הוא נוצר, ראו מחברת זו מ-Quantum Kernel Training Toolkit.

אנחנו יוצרים את המעגל הקוונטי המשמש להערכת ערך אחד במטריצת הליבה. נתוני הקלט משמשים לקביעת זוויות הסיבוב של השערים הפרמטריים של המעגל. לשם פשטות, נשתמש בדגימות הנתונים 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: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית

  • קלט: מעגל מופשט, לא מותאם למעבד ספציפי.
  • פלט: מעגל יעד, מותאם ל-QPU שנבחר.

עבור נתיב סימולטור ה-statevector המשמש בחלק זה, אין צורך באופטימיזציה ספציפית למעבד: ניתן לדגום את המעגל המופשט ישירות. אנחנו מבצעים שלב זה בדוגמת החומרה להלן, שם המעגל עובר טרנספילציה מול 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)

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 דגימות אימון, וכל ערך דורש הרצת מעגל חפיפה שעומק שעריו הדו-קיוביטיים גדל עם גודל מפת המאפיינים. כתוצאה מכך, הרחבת מדריך זה לבעיה גדולה יותר כרוכה בשתי עלויות מצטברות: זמן ה-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

Output of the previous code cell

Fidelity (hardware): 0.7517

כדי למלא את כל מטריצת הליבה, נריץ ניסוי קוונטי עבור כל אחד מהערכים הייחודיים N(N+1)/2N(N+1)/2 שלה. הדמות הבאה מציגה את המטריצה שהתקבלה עבור מערך נתונים זה; אדום כהה יותר מציין נאמנויות קרובות יותר ל-1.0.

kernel_matrix.png

צעדים הבאים

המלצות

אם מצאתם עבודה זו מעניינת, ייתכן שתתעניינו בחומר הבא: