تعزيز تصنيف الميزات باستخدام نوى الكم المسقطة
تقدير الاستخدام: 80 دقيقة على معالج Heron r3 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي لديك.)
في هذا الدرس التعليمي، نستعرض كيفية تشغيل نواة الكم المسقطة (PQK) مع Qiskit على مجموعة بيانات بيولوجية من العالم الحقيقي، استناداً إلى الورقة البحثية Enhanced Prediction of CAR T-Cell Cytotoxicity with Quantum-Kernel Methods [1].
PQK هي طريقة تُستخدم في التعلم الآلي الكمومي (QML) لترميز البيانات الكلاسيكية في فضاء الميزات الكمومي وإسقاطها مجدداً إلى المجال الكلاسيكي، وذلك باستخدام الحواسيب الكمومية لتعزيز اختيار الميزات. تتضمن هذه الطريقة ترميز البيانات الكلاسيكية في حالات كمومية باستخدام دائرة كمومية، وعادةً من خلال عملية تُسمى رسم خريطة الميزات، حيث تُحوَّل البيانات إلى فضاء هيلبرت عالي الأبعاد. يشير الجانب "المسقط" إلى استخلاص المعلومات الكلاسيكية من الحالات الكمومية عبر قياس مُراقَبات محددة، من أجل بناء مصفوفة نواة يمكن استخدامها في خوارزميات النواة الكلاسيكية كآلات المتجهات الداعمة. يستفيد هذا النهج من المزايا الحسابية للأنظمة الكمومية لتحقيق أداء أفضل محتمل في مهام معينة مقارنةً بالطرق الكلاسيكية.
يفترض هذا الدرس التعليمي أيضاً إلماماً عاماً بأساليب QML. لمزيد من الاستكشاف في مجال QML، يمكن الرجوع إلى دورة التعلم الآلي الكمومي في IBM Quantum Learning.
المتطلبات
قبل البدء في هذا الدرس التعليمي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصور
- Qiskit Runtime الإصدار 0.40 أو أحدث (
pip install qiskit-ibm-runtime) - Category encoders 2.8.1 (
pip install category-encoders) - NumPy 2.3.2 (
pip install numpy) - Pandas 2.3.2 (
pip install pandas) - Scikit-learn 1.7.1 (
pip install scikit-learn) - Tqdm 4.67.1 (
pip install tqdm)
الإعداد
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q category-encoders scikit-learn tqdm
import warnings
# Standard libraries
import os
import numpy as np
import pandas as pd
# Machine learning and data processing
import category_encoders as ce
from scipy.linalg import inv, sqrtm
from sklearn.metrics.pairwise import rbf_kernel
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.svm import SVC
# Qiskit and Qiskit Runtime
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import UnitaryGate, ZZFeatureMap
from qiskit.quantum_info import SparsePauliOp, random_unitary
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import (
Batch,
EstimatorOptions,
EstimatorV2 as Estimator,
QiskitRuntimeService,
)
# Progress bar
import tqdm
warnings.filterwarnings("ignore")
الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمومية
تحضير مجموعة البيانات
في هذا الدرس التعليمي نستخدم مجموعة بيانات بيولوجية من العالم الحقيقي لمهمة تصنيف ثنائي، تم توليدها بواسطة Daniels et al. (2022) ويمكن تنزيلها من المواد التكميلية المرفقة بالورقة البحثية. تتألف البيانات من خلايا CAR T، وهي خلايا T مهندسة وراثياً تُستخدم في العلاج المناعي لعلاج أنواع معينة من السرطان. يتم تعديل خلايا T، وهي نوع من خلايا المناعة، في المختبر لتعبّر عن مستقبلات المستضد الخيمري (CARs) التي تستهدف بروتينات محددة على الخلايا السرطانية. يمكن لخلايا T المعدَّلة هذه التعرف على الخلايا السرطانية وتدميرها بفاعلية أكبر. ميزات البيانات هي بنيات خلايا CAR T، التي تشير إلى المكوّن البنيوي أو الوظيفي المحدد للـ CAR المهندَس في خلايا T. بناءً على هذه البنيات، تتمثل مهمتنا في التنبؤ بسُمِّية خلية CAR T المعطاة، وتصنيفها إما كسامة أو غير سامة. يوضح ما يلي الدوال المساعدة لمعالجة هذه المجموعة من البيانات.
def preprocess_data(dir_root, args):
"""
Preprocess the training and test data.
"""
# Read from the csv files
train_data = pd.read_csv(
os.path.join(dir_root, args["file_train_data"]),
encoding="unicode_escape",
sep=",",
)
test_data = pd.read_csv(
os.path.join(dir_root, args["file_test_data"]),
encoding="unicode_escape",
sep=",",
)
# Fix the last motif ID
train_data[train_data == 17] = 14
train_data.columns = [
"Cell Number",
"motif",
"motif.1",
"motif.2",
"motif.3",
"motif.4",
"Nalm 6 Cytotoxicity",
]
test_data[test_data == 17] = 14
test_data.columns = [
"Cell Number",
"motif",
"motif.1",
"motif.2",
"motif.3",
"motif.4",
"Nalm 6 Cytotoxicity",
]
# Adjust motif at the third position
if args["filter_for_spacer_motif_third_position"]:
train_data = train_data[
(train_data["motif.2"] == 14) | (train_data["motif.2"] == 0)
]
test_data = test_data[
(test_data["motif.2"] == 14) | (test_data["motif.2"] == 0)
]
train_data = train_data[
args["motifs_to_use"] + [args["label_name"], "Cell Number"]
]
test_data = test_data[
args["motifs_to_use"] + [args["label_name"], "Cell Number"]
]
# Adjust motif at the last position
if not args["allow_spacer_motif_last_position"]:
last_motif = args["motifs_to_use"][len(args["motifs_to_use"]) - 1]
train_data = train_data[
(train_data[last_motif] != 14) & (train_data[last_motif] != 0)
]
test_data = test_data[
(test_data[last_motif] != 14) & (test_data[last_motif] != 0)
]
# Get the labels
train_labels = np.array(train_data[args["label_name"]])
test_labels = np.array(test_data[args["label_name"]])
# For the classification task use the threshold to binarize labels
train_labels[train_labels > args["label_binarization_threshold"]] = 1
train_labels[train_labels < 1] = args["min_label_value"]
test_labels[test_labels > args["label_binarization_threshold"]] = 1
test_labels[test_labels < 1] = args["min_label_value"]
# Reduce data to just the motifs of interest
train_data = train_data[args["motifs_to_use"]]
test_data = test_data[args["motifs_to_use"]]
# Get the class and motif counts
min_class = np.min(np.unique(np.concatenate([train_data, test_data])))
max_class = np.max(np.unique(np.concatenate([train_data, test_data])))
num_class = max_class - min_class + 1
num_motifs = len(args["motifs_to_use"])
print(str(max_class) + ":" + str(min_class) + ":" + str(num_class))
train_data = train_data - min_class
test_data = test_data - min_class
return (
train_data,
test_data,
train_labels,
test_labels,
num_class,
num_motifs,
)
def data_encoder(args, train_data, test_data, num_class, num_motifs):
"""
Use one-hot or binary encoding for classical data representation.
"""
if args["encoder"] == "one-hot":
# Transform to one-hot encoding
train_data = np.eye(num_class)[train_data]
test_data = np.eye(num_class)[test_data]
train_data = train_data.reshape(
train_data.shape[0], train_data.shape[1] * train_data.shape[2]
)
test_data = test_data.reshape(
test_data.shape[0], test_data.shape[1] * test_data.shape[2]
)
elif args["encoder"] == "binary":
# Transform to binary encoding
encoder = ce.BinaryEncoder()
base_array = np.unique(np.concatenate([train_data, test_data]))
base = pd.DataFrame(base_array).astype("category")
base.columns = ["motif"]
for motif_name in args["motifs_to_use"][1:]:
base[motif_name] = base.loc[:, "motif"]
encoder.fit(base)
train_data = encoder.transform(train_data.astype("category"))
test_data = encoder.transform(test_data.astype("category"))
train_data = np.reshape(
train_data.values, (train_data.shape[0], num_motifs, -1)
)
test_data = np.reshape(
test_data.values, (test_data.shape[0], num_motifs, -1)
)
train_data = train_data.reshape(
train_data.shape[0], train_data.shape[1] * train_data.shape[2]
)
test_data = test_data.reshape(
test_data.shape[0], test_data.shape[1] * test_data.shape[2]
)
else:
raise ValueError("Invalid encoding type.")
return train_data, test_data
يمكنك تشغيل هذا الدرس التعليمي بتنفيذ الخلية التالية، التي تنشئ تلقائياً هيكل المجلدات المطلوب وتنزّل كلاً من ملفَي التدريب والاختبار مباشرةً في بيئتك. إذا كانت هذه الملفات موجودة لديك محلياً، فستقوم هذه الخطوة بالكتابة فوقها بأمان لضمان توافق الإصدارات.
## Download dataset
# Create data directory if it doesn't exist
!mkdir -p data_tutorial/pqk
# Download the training and test sets from the official Qiskit documentation repo
!wget -q --show-progress -O data_tutorial/pqk/train_data.csv \
https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/train_data.csv
!wget -q --show-progress -O data_tutorial/pqk/test_data.csv \
https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/test_data.csv
!wget -q --show-progress -O data_tutorial/pqk/projections_train.csv \
https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/projections_train.csv
!wget -q --show-progress -O data_tutorial/pqk/projections_test.csv \
https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/projections_test.csv
# Check the files have been downloaded
!echo "Dataset files downloaded:"
!ls -lh data_tutorial/pqk/*.csv
args = {
"file_train_data": "train_data.csv",
"file_test_data": "test_data.csv",
"motifs_to_use": ["motif", "motif.1", "motif.2", "motif.3"],
"label_name": "Nalm 6 Cytotoxicity",
"label_binarization_threshold": 0.62,
"filter_for_spacer_motif_third_position": False,
"allow_spacer_motif_last_position": True,
"min_label_value": -1,
"encoder": "one-hot",
}
dir_root = "./"
# Preprocess data
train_data, test_data, train_labels, test_labels, num_class, num_motifs = (
preprocess_data(dir_root=dir_root, args=args)
)
# Encode the data
train_data, test_data = data_encoder(
args, train_data, test_data, num_class, num_motifs
)
14:0:15
نُحوِّل أيضاً مجموعة البيانات بحيث يُمثَّل على شكل لأغراض التحجيم.
# Change 1 to pi/2
angle = np.pi / 2
tmp = pd.DataFrame(train_data).astype("float64")
tmp[tmp == 1] = angle
train_data = tmp.values
tmp = pd.DataFrame(test_data).astype("float64")
tmp[tmp == 1] = angle
test_data = tmp.values
نتحقق من أحجام وأشكال مجموعتَي بيانات التدريب والاختبار.
print(train_data.shape, train_labels.shape)
print(test_data.shape, test_labels.shape)
(172, 60) (172,)
(74, 60) (74,)
الخطوة 2: تحسين المسألة لتنفيذها على عتاد الكم
الدائرة الكمومية
نبني الآن خريطة الميزات التي تُضمِّن مجموعة بياناتنا الكلاسيكية في فضاء ميزات أعلى الأبعاد. لهذا التضمين، نستخدم ZZFeatureMap من Qiskit.
feature_dimension = train_data.shape[1]
reps = 24
insert_barriers = True
entanglement = "pairwise"
# ZZFeatureMap with linear entanglement and a repetition of 2
embed = ZZFeatureMap(
feature_dimension=feature_dimension,
reps=reps,
entanglement=entanglement,
insert_barriers=insert_barriers,
name="ZZFeatureMap",
)
embed.decompose().draw(output="mpl", style="iqp", fold=-1)

خيار تضمين كمومي آخر هو الـ ansatz الخاص بتطور هاميلتوني هايزنبرغ أحادي البعد. يمكنك تخطي تشغيل هذا القسم إذا أردت الاستمرار باستخدام ZZFeatureMap.
feature_dimension = train_data.shape[1]
num_qubits = feature_dimension + 1
embed2 = QuantumCircuit(num_qubits)
num_trotter_steps = 6
pv_length = feature_dimension * num_trotter_steps
pv = ParameterVector("theta", pv_length)
# Add Haar random single qubit unitary to each qubit as initial state
np.random.seed(42)
seeds_unitary = np.random.randint(0, 100, num_qubits)
for i in range(num_qubits):
rand_gate = UnitaryGate(random_unitary(2, seed=seeds_unitary[i]))
embed2.append(rand_gate, [i])
def trotter_circ(feature_dimension, num_trotter_steps):
num_qubits = feature_dimension + 1
circ = QuantumCircuit(num_qubits)
# Even
for i in range(0, feature_dimension, 2):
circ.rzz(2 * pv[i] / num_trotter_steps, i, i + 1)
for i in range(0, feature_dimension, 2):
circ.rxx(2 * pv[i] / num_trotter_steps, i, i + 1)
for i in range(0, feature_dimension, 2):
circ.ryy(2 * pv[i] / num_trotter_steps, i, i + 1)
# Odd
for i in range(1, feature_dimension, 2):
circ.rzz(2 * pv[i] / num_trotter_steps, i, i + 1)
for i in range(1, feature_dimension, 2):
circ.rxx(2 * pv[i] / num_trotter_steps, i, i + 1)
for i in range(1, feature_dimension, 2):
circ.ryy(2 * pv[i] / num_trotter_steps, i, i + 1)
return circ
# Hamiltonian evolution ansatz
for step in range(num_trotter_steps):
circ = trotter_circ(feature_dimension, num_trotter_steps)
if step % 2 == 0:
embed2 = embed2.compose(circ)
else:
reverse_circ = circ.reverse_ops()
embed2 = embed2.compose(reverse_circ)
embed2.draw(output="mpl", style="iqp", fold=-1)
