Quantum Variational Circuits dan Quantum Neural Networks
Dalam pelajaran ini, kita mengimplementasikan beberapa variational quantum circuit untuk tugas klasifikasi data, yang disebut variational quantum classifiers (VQC). Dulu, sebagian dari VQC sering disebut quantum neural networks (QNN) karena analoginya dengan jaringan saraf klasik. Memang ada kasus di mana struktur yang dipinjam dari jaringan saraf klasik, seperti convolutional layer, memainkan peran penting dalam VQC. Dalam kasus di mana analoginya kuat, QNN bisa jadi deskripsi yang berguna. Namun parameterized quantum circuit tidak harus mengikuti struktur umum jaringan saraf; misalnya, tidak semua data perlu dimuat di layer pertama (input); kita bisa memuat sebagian data di layer pertama, menerapkan beberapa Gate, lalu memuat data tambahan (proses yang disebut "reuploading" data). Kita sebaiknya memandang QNN sebagai subset dari parameterized quantum circuit, dan tidak membatasi eksplorasi quantum circuit yang berguna hanya karena analogi dengan jaringan saraf klasik.
Dataset yang dibahas dalam pelajaran ini terdiri dari gambar berisi garis horizontal dan vertikal, dan tujuan kita adalah memberi label pada gambar baru ke salah satu dari dua kategori tergantung orientasi garisnya. Kita akan melakukannya dengan VQC. Sambil berjalan, kita akan membahas cara-cara untuk meningkatkan dan menskalakan perhitungan. Dataset di sini sangat mudah diklasifikasikan secara klasik. Dataset ini dipilih karena kesederhanaannya agar kita bisa fokus pada bagian quantum dari masalah ini, dan melihat bagaimana atribut dataset bisa diterjemahkan ke bagian quantum circuit. Tidak masuk akal mengharapkan quantum speed-up untuk kasus sesederhana ini di mana algoritma klasik begitu efisien.
Di akhir pelajaran ini kamu seharusnya bisa:
- Memuat data dari gambar ke dalam quantum Circuit
- Membuat ansatz untuk VQC (atau QNN), dan menyesuaikannya agar cocok dengan masalahmu
- Melatih VQC/QNN dan menggunakannya untuk membuat prediksi akurat pada data uji
- Menskalakan masalah, dan mengenali batasan komputer quantum saat ini
Pembuatan data
Kita mulai dengan membangun data. Dataset seringkali tidak dibuat secara eksplisit sebagai bagian dari framework Qiskit patterns. Namun tipe dan persiapan data sangat penting untuk berhasil menerapkan komputasi quantum ke machine learning. Kode di bawah ini mendefinisikan dataset gambar dengan dimensi piksel tertentu. Satu baris atau kolom penuh gambar diberi nilai , dan piksel-piksel sisanya diberi nilai acak pada interval . Nilai acak tersebut adalah noise dalam data kita. Lihat sekilas kodenya untuk memastikan kamu memahami cara gambar-gambar tersebut dibuat. Nanti kita akan memperbesar ukuran gambar.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime scipy scikit-learn
# This code defines the images to be classified:
import numpy as np
# Total number of "pixels"/qubits
size = 8
# One dimension of the image (called vertical, but it doesn't matter). Must be a divisor of `size`
vert_size = 2
# The length of the line to be detected (yellow). Must be less than or equal to the smallest dimension of the image (`<=min(vert_size,size/vert_size)`
line_size = 2
def generate_dataset(num_images):
images = []
labels = []
hor_array = np.zeros((size - (line_size - 1) * vert_size, size))
ver_array = np.zeros((round(size / vert_size) * (vert_size - line_size + 1), size))
j = 0
for i in range(0, size - 1):
if i % (size / vert_size) <= (size / vert_size) - line_size:
for p in range(0, line_size):
hor_array[j][i + p] = np.pi / 2
j += 1
# Make two adjacent entries pi/2, then move down to the next row. Careful to avoid the "pixels" at size/vert_size - linesize, because we want to fold this list into a grid.
j = 0
for i in range(0, round(size / vert_size) * (vert_size - line_size + 1)):
for p in range(0, line_size):
ver_array[j][i + p * round(size / vert_size)] = np.pi / 2
j += 1
# Make entries pi/2, spaced by the length/rows, so that when folded, the entries appear on top of each other.
for n in range(num_images):
rng = np.random.randint(0, 2)
if rng == 0:
labels.append(-1)
random_image = np.random.randint(0, len(hor_array))
images.append(np.array(hor_array[random_image]))
elif rng == 1:
labels.append(1)
random_image = np.random.randint(0, len(ver_array))
images.append(np.array(ver_array[random_image]))
# Randomly select 0 or 1 for a horizontal or vertical array, assign the corresponding label.
# Create noise
for i in range(size):
if images[-1][i] == 0:
images[-1][i] = np.random.rand() * np.pi / 4
return images, labels
hor_size = round(size / vert_size)
Perhatikan bahwa kode di atas juga telah menghasilkan label yang menunjukkan apakah gambar mengandung garis vertikal (+1) atau horizontal (-1). Sekarang kita akan menggunakan sklearn untuk membagi dataset 100 gambar menjadi set pelatihan dan pengujian (beserta label-labelnya). Di sini, kita menggunakan dari dataset untuk pelatihan, dengan sisanya disimpan untuk pengujian.
from sklearn.model_selection import train_test_split
np.random.seed(42)
images, labels = generate_dataset(200)
train_images, test_images, train_labels, test_labels = train_test_split(
images, labels, test_size=0.3, random_state=246
)
Mari kita plot beberapa elemen dari dataset kita untuk melihat seperti apa garis-garis ini:
import matplotlib.pyplot as plt
# Make subplot titles so we can identify categories
titles = []
for i in range(8):
title = "category: " + str(train_labels[i])
titles.append(title)
# Generate a figure with nested images using subplots.
fig, ax = plt.subplots(4, 2, figsize=(10, 6), subplot_kw={"xticks": [], "yticks": []})
for i in range(8):
ax[i // 2, i % 2].imshow(
train_images[i].reshape(vert_size, hor_size),
aspect="equal",
)
ax[i // 2, i % 2].set_title(titles[i])
plt.subplots_adjust(wspace=0.1, hspace=0.3)
Setiap gambar ini masih dipasangkan dengan labelnya di train_labels dalam bentuk list sederhana:
print(train_labels[:8])
[1, 1, 1, 1, -1, 1, 1, 1]
Variational quantum classifier: percobaan pertama
Langkah 1 Qiskit patterns: Pemetaan masalah ke quantum Circuit
Tujuannya adalah menemukan fungsi dengan parameter yang memetakan vektor data / gambar ke kategori yang benar: . Ini akan dicapai menggunakan VQC dengan sedikit layer yang dapat diidentifikasi berdasarkan tujuannya masing-masing:
Di sini, adalah encoding circuit, yang memiliki banyak pilihan seperti yang terlihat di pelajaran sebelumnya. adalah blok circuit variational atau yang bisa dilatih, dan adalah kumpulan parameter yang akan dilatih. Parameter-parameter tersebut akan divariasikan oleh algoritma optimasi klasik untuk menemukan kumpulan parameter yang menghasilkan klasifikasi gambar terbaik oleh quantum circuit. Circuit variational ini kadang disebut "ansatz". Terakhir, adalah observable tertentu yang akan diestimasi menggunakan primitif Estimator. Tidak ada batasan yang memaksa layer-layer tersebut datang dalam urutan ini, atau bahkan harus sepenuhnya terpisah. Seseorang bisa memiliki beberapa layer variational dan/atau encoding dalam urutan apapun yang dimotivasi secara teknis.
Kita mulai dengan memilih feature map untuk meng-encode data kita. Kita akan menggunakan z_feature_map, karena menjaga kedalaman Circuit tetap rendah dibandingkan beberapa pemetaan fitur lainnya.
from qiskit.circuit.library import z_feature_map
# One qubit per data feature
num_qubits = len(train_images[0])
# Data encoding
# Note that qiskit orders parameters alphabetically. We assign the parameter prefix "a" to ensure our data encoding goes to the first part of the circuit, the feature mapping.
feature_map = z_feature_map(num_qubits, parameter_prefix="a")
Kita harus sekarang memutuskan ansatz yang akan dilatih. Ada banyak pertimbangan saat memilih ansatz. Deskripsi lengkapnya di luar cakupan pengantar ini; di sini kita hanya menyebutkan beberapa kategori pertimbangan.
- Hardware: Semua komputer quantum modern lebih rentan terhadap error dan lebih rentan terhadap noise dibandingkan komputer klasik. Menggunakan ansatz yang terlalu dalam (terutama dalam kedalaman dua-Qubit yang ditranspilasi) tidak akan menghasilkan hasil yang baik. Masalah terkait adalah bahwa komputer quantum memiliki tata letak Qubit tertentu, artinya beberapa Qubit fisik berdekatan di komputer quantum, sementara yang lain mungkin sangat jauh satu sama lain. Mengentanglement Qubit yang berdekatan tidak terlalu meningkatkan kedalaman, tetapi mengentanglement Qubit yang sangat jauh bisa meningkatkan kedalaman secara substansial, karena kita harus menyisipkan swap Gate untuk memindahkan informasi ke Qubit yang berdekatan agar bisa saling direntanglement.
- Masalah: Kapanpun kamu memiliki informasi tentang masalahmu yang bisa memandu ansatzmu, manfaatkanlah. Misalnya, data dalam pelajaran ini terdiri dari gambar garis horizontal dan vertikal. Kita bisa mempertimbangkan korelasi apa antara warna/nilai yang berdekatan yang mengidentifikasi gambar garis horizontal atau vertikal. Atribut ansatz apa yang akan sesuai dengan korelasi antara piksel-piksel yang berdekatan ini? Kita akan membahas hal ini lebih teknis nanti dalam pelajaran ini. Namun untuk saat ini, mari kita katakan bahwa menyertakan entanglement dan CNOT Gate antara Qubit yang sesuai dengan piksel yang berdekatan sepertinya ide yang bagus. Dalam gambaran yang lebih besar, pertimbangkan apakah masalahnya benar-benar paling baik diselesaikan menggunakan quantum circuit, atau apakah algoritma klasik mungkin ada yang dapat melakukan pekerjaan yang sama baiknya.
- Jumlah parameter: Setiap Gate quantum yang diparameterisasi secara independen dalam Circuit meningkatkan ruang yang harus dioptimasi secara klasik, dan ini menghasilkan konvergensi yang lebih lambat. Namun saat masalah semakin besar, seseorang mungkin menemui barren plateaus. Istilah ini mengacu pada fenomena di mana landscape optimasi algoritma quantum variational menjadi datar dan tanpa fitur secara eksponensial seiring bertambahnya ukuran masalah. Hal ini menyebabkan vanishing gradient, sehingga sulit untuk melatih algoritma secara efektif[1]. Barren plateaus relevan untuk algoritma quantum variational seperti VQC/QNN. Perlu dicatat bahwa meningkatnya jumlah parameter bukan satu-satunya pertimbangan dalam menghindari barren plateaus; pertimbangan lain termasuk fungsi biaya global dan inisialisasi parameter acak.
Dalam pelajaran ini kita akan melihat beberapa contoh sederhana praktik yang baik dalam konstruksi ansatz. Mari kita coba ansatz di bawah ini terlebih dahulu. Kita akan kembali untuk merevisinya nanti.
# Import the necessary packages
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
# Initialize the circuit using the same number of qubits as the image has pixels
qnn_circuit = QuantumCircuit(size)
# We choose to have two variational parameters for each qubit.
params = ParameterVector("θ", length=2 * size)
# A first variational layer:
for i in range(size):
qnn_circuit.ry(params[i], i)
# Here is a list of qubit pairs between which we want CNOT gates. The choice of these is not yet obvious.
qnn_cnot_list = [[0, 1], [1, 2], [2, 3]]
for i in range(len(qnn_cnot_list)):
qnn_circuit.cx(qnn_cnot_list[i][0], qnn_cnot_list[i][1])
# The second variational layer:
for i in range(size):
qnn_circuit.rx(params[size + i], i)
# Check the circuit depth, and the two-qubit gate depth
print(qnn_circuit.decompose().depth())
print(
f"2+ qubit depth: {qnn_circuit.decompose().depth(lambda instr: len(instr.qubits) > 1)}"
)
# Draw the circuit
qnn_circuit.draw("mpl")
5
2+ qubit depth: 3
┌──────────┐ ┌──────────┐
q_0: ┤ Ry(θ[0]) ├──────■──────┤ Rx(θ[8]) ├─────────────────────────
├──────────┤ ┌─┴─┐ └──────────┘┌──────────┐
q_1: ┤ Ry(θ[1]) ├────┤ X ├─────────■──────┤ Rx(θ[9]) ├─────────────
├──────────┤ └───┘ ┌─┴─┐ └──────────┘┌───────────┐
q_2: ┤ Ry(θ[2]) ├────────────────┤ X ├─────────■──────┤ Rx(θ[10]) ├
├──────────┤ └───┘ ┌─┴─┐ ├───────────┤
q_3: ┤ Ry(θ[3]) ├────────────────────────────┤ X ├────┤ Rx(θ[11]) ├
├──────────┤┌───────────┐ └───┘ └───────────┘
q_4: ┤ Ry(θ[4]) ├┤ Rx(θ[12]) ├─────────────────────────────────────
├──────────┤├───────────┤
q_5: ┤ Ry(θ[5]) ├┤ Rx(θ[13]) ├─────────────────────────────────────
├──────────┤├───────────┤
q_6: ┤ Ry(θ[6]) ├┤ Rx(θ[14]) ├─────────────────────────────────────
├──────────┤├───────────┤
q_7: ┤ Ry(θ[7]) ├┤ Rx(θ[15]) ├─────────────────────────────────────
└──────────┘└───────────┘
Dengan encoding data dan Circuit variational yang sudah disiapkan, kita bisa menggabungkannya untuk membentuk ansatz penuh kita. Dalam kasus ini, komponen-komponen quantum Circuit kita cukup analog dengan yang ada di jaringan saraf, dengan yang paling mirip dengan layer yang memuat nilai input dari gambar, dan seperti layer "bobot" variabel. Karena analogi ini berlaku dalam kasus ini, kita mengadopsi "qnn" dalam beberapa konvensi penamaan; namun analogi ini tidak boleh membatasi eksplorasimu terhadap VQC.

# QNN ansatz
ansatz = qnn_circuit
# Combine the feature map with the ansatz
full_circuit = QuantumCircuit(num_qubits)
full_circuit.compose(feature_map, range(num_qubits), inplace=True)
full_circuit.compose(ansatz, range(num_qubits), inplace=True)
# Display the circuit
full_circuit.decompose().draw("mpl", style="clifford", fold=-1)
Kita harus mendefinisikan observable sekarang, agar bisa menggunakannya dalam fungsi biaya kita. Kita akan mendapatkan nilai ekspektasi untuk observable ini menggunakan Estimator. Jika kita telah memilih ansatz yang baik dan dimotivasi oleh masalah, maka setiap Qubit akan mengandung informasi yang relevan untuk klasifikasi. Kita bisa menambahkan layer untuk menggabungkan informasi ke lebih sedikit Qubit (disebut convolutional layer), sehingga pengukuran hanya diperlukan pada subset Qubit dalam Circuit (seperti dalam convolutional neural networks). Atau kita bisa mengukur atribut tertentu dari setiap Qubit. Di sini kita akan memilih yang terakhir, jadi kita menyertakan operator Z untuk setiap Qubit. Tidak ada yang unik tentang memilih , namun ada alasan kuat di baliknya:
- Ini adalah tugas klasifikasi biner, dan pengukuran dapat menghasilkan dua kemungkinan hasil.
- Nilai eigen dari () cukup terpisah dengan baik, dan menghasilkan hasil estimator dalam interval [-1, +1], di mana 0 dapat digunakan sebagai nilai batas.
- Mudah untuk mengukur dalam basis Pauli Z tanpa overhead Gate tambahan.
Jadi, Z adalah pilihan yang sangat natural.
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_list([("Z" * (num_qubits), 1)])
Kita punya quantum Circuit dan observable yang ingin kita estimasi. Sekarang kita butuh beberapa hal untuk menjalankan dan mengoptimalkan Circuit ini. Pertama, kita butuh fungsi untuk menjalankan forward pass. Perhatikan bahwa fungsi di bawah ini menerima input_params dan weight_params secara terpisah. Yang pertama adalah kumpulan parameter statis yang mendeskripsikan data dalam suatu gambar, dan yang terakhir adalah kumpulan parameter variabel yang akan dioptimalkan.
from qiskit.primitives import BaseEstimatorV2
from qiskit.quantum_info.operators.base_operator import BaseOperator
def forward(
circuit: QuantumCircuit,
input_params: np.ndarray,
weight_params: np.ndarray,
estimator: BaseEstimatorV2,
observable: BaseOperator,
) -> np.ndarray:
"""
Forward pass of the neural network.
Args:
circuit: circuit consisting of data loader gates and the neural network ansatz.
input_params: data encoding parameters.
weight_params: neural network ansatz parameters.
estimator: EstimatorV2 primitive.
observable: a single observable to compute the expectation over.
Returns:
expectation_values: an array (for one observable) or a matrix (for a sequence of observables) of expectation values.
Rows correspond to observables and columns to data samples.
"""
num_samples = input_params.shape[0]
weights = np.broadcast_to(weight_params, (num_samples, len(weight_params)))
params = np.concatenate((input_params, weights), axis=1)
pub = (circuit, observable, params)
job = estimator.run([pub])
result = job.result()[0]
expectation_values = result.data.evs
return expectation_values
Fungsi loss
Selanjutnya, kita butuh fungsi loss untuk menghitung perbedaan antara nilai label yang diprediksi dan yang sebenarnya. Fungsi ini akan menerima label yang diprediksi oleh algoritma dan label yang benar, lalu mengembalikan selisih kuadrat rata-rata. Ada banyak fungsi loss yang berbeda. Di sini, MSE adalah contoh yang kita pilih.
def mse_loss(predict: np.ndarray, target: np.ndarray) -> np.ndarray:
"""
Mean squared error (MSE).
prediction: predictions from the forward pass of neural network.
target: true labels.
output: MSE loss.
"""
if len(predict.shape) <= 1:
return ((predict - target) ** 2).mean()
else:
raise AssertionError("input should be 1d-array")
Mari kita juga mendefinisikan fungsi loss yang sedikit berbeda yang merupakan fungsi dari parameter variabel (bobot), untuk digunakan oleh optimizer klasik. Fungsi ini hanya menerima parameter ansatz sebagai input; variabel lain untuk forward pass dan loss ditetapkan sebagai parameter global. Optimizer akan melatih model dengan mengambil sampel bobot yang berbeda dan mencoba menurunkan output dari fungsi biaya/loss.
def mse_loss_weights(weight_params: np.ndarray) -> np.ndarray:
"""
Cost function for the optimizer to update the ansatz parameters.
weight_params: ansatz parameters to be updated by the optimizer.
output: MSE loss.
"""
predictions = forward(
circuit=circuit,
input_params=input_params,
weight_params=weight_params,
estimator=estimator,
observable=observable,
)
cost = mse_loss(predict=predictions, target=target)
objective_func_vals.append(cost)
global iter
if iter % 50 == 0:
print(f"Iter: {iter}, loss: {cost}")
iter += 1
return cost
Di atas kita menyebutkan penggunaan optimizer klasik. Ketika kita mulai mencari bobot untuk meminimalkan fungsi biaya, kita akan menggunakan optimizer COBYLA:
from scipy.optimize import minimize
Kita akan menetapkan beberapa variabel global awal untuk fungsi biaya.
# Globals
circuit = full_circuit
observables = observable
# input_params = train_images_batch
# target = train_labels_batch
objective_func_vals = []
iter = 0
Langkah 2 Qiskit Patterns: Optimasi masalah untuk eksekusi quantum
Kita mulai dengan memilih Backend untuk eksekusi. Dalam kasus ini, kita akan menggunakan Backend yang paling tidak sibuk.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
print(backend.name)
ibm_brisbane
Di sini kita mengoptimalkan Circuit untuk dijalankan di Backend nyata dengan menentukan optimization_level dan menambahkan dynamical decoupling. Kode di bawah ini menghasilkan pass manager menggunakan preset pass managers dari qiskit.transpiler.
from qiskit.circuit.library import XGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
ConstrainedReschedule,
PadDynamicalDecoupling,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)
Sekarang kita menggunakan pass manager pada Circuit. Perubahan tata letak yang dihasilkan harus diterapkan pada observable juga. Untuk Circuit yang sangat besar, heuristik yang digunakan dalam optimasi Circuit mungkin tidak selalu menghasilkan Circuit terbaik dan paling dangkal. Dalam kasus tersebut, masuk akal untuk menjalankan pass manager semacam itu beberapa kali dan menggunakan Circuit terbaik. Kita akan melihat ini nanti saat kita menskalakan perhitungan kita.
circuit_ibm = pm.run(full_circuit)
observable_ibm = observable.apply_layout(circuit_ibm.layout)
Langkah 3 Qiskit Patterns: Eksekusi menggunakan Qiskit Primitives
Iterasi dataset dalam batch dan epoch
Pertama kita mengimplementasikan algoritma penuh menggunakan simulator untuk debugging awal dan untuk estimasi error. Kita sekarang bisa melewati seluruh dataset dalam batch dengan jumlah epoch yang diinginkan untuk melatih quantum neural network kita.
from qiskit.primitives import StatevectorEstimator as Estimator
batch_size = 140
num_epochs = 1
num_samples = len(train_images)
# Globals
circuit = full_circuit
estimator = Estimator() # simulator for debugging
observables = observable
objective_func_vals = []
iter = 0
# Random initial weights for the ansatz
np.random.seed(42)
weight_params = np.random.rand(len(ansatz.parameters)) * 2 * np.pi
for epoch in range(num_epochs):
for i in range((num_samples - 1) // batch_size + 1):
print(f"Epoch: {epoch}, batch: {i}")
start_i = i * batch_size
end_i = start_i + batch_size
train_images_batch = np.array(train_images[start_i:end_i])
train_labels_batch = np.array(train_labels[start_i:end_i])
input_params = train_images_batch
target = train_labels_batch
iter = 0
res = minimize(
mse_loss_weights, weight_params, method="COBYLA", options={"maxiter": 100}
)
weight_params = res["x"]
Epoch: 0, batch: 0
Iter: 0, loss: 1.0002309063537163
Iter: 50, loss: 0.9434121445008878
Langkah 4 Qiskit Patterns: Post-process, kembalikan hasil dalam format klasik
Pengujian dan akurasi
Sekarang kita menginterpretasikan hasil dari pelatihan. Kita pertama-tama menguji akurasi pelatihan pada set pelatihan.
import copy
from sklearn.metrics import accuracy_score
from qiskit.primitives import StatevectorEstimator as Estimator # simulator
# from qiskit_ibm_runtime import EstimatorV2 as Estimator # real quantum computer
estimator = Estimator()
# estimator = Estimator(backend=backend)
pred_train = forward(circuit, np.array(train_images), res["x"], estimator, observable)
# pred_train = forward(circuit_ibm, np.array(train_images), res['x'], estimator, observable_ibm)
print(pred_train)
pred_train_labels = copy.deepcopy(pred_train)
pred_train_labels[pred_train_labels >= 0] = 1
pred_train_labels[pred_train_labels < 0] = -1
print(pred_train_labels)
print(train_labels)
accuracy = accuracy_score(train_labels, pred_train_labels)
print(f"Train accuracy: {accuracy * 100}%")
[-2.27688499e-02 -1.46227204e-02 -1.73927452e-02 9.93331786e-02
-4.85553548e-01 1.43558565e-01 8.34567054e-02 -1.40133992e-02
1.52169596e-01 -1.95082515e-01 8.24373578e-03 -9.90696638e-02
-3.54268344e-02 -4.77017954e-01 1.38713848e-02 -2.99706215e-01
-5.78378029e-02 3.25528779e-02 -4.11354239e-02 -1.06483708e-01
1.53095800e-01 2.90110884e-02 1.25745450e-02 6.46323079e-02
-1.53538943e-01 -1.57694952e-02 -1.67800067e-02 -1.99820822e-01
1.70360075e-01 7.86148038e-03 -2.33373818e-02 6.64233020e-02
-1.14895445e-01 -1.11296215e-01 1.15120303e-01 -2.94096140e-01
-1.00531392e-03 -1.69209726e-01 -1.26120885e-01 3.26298176e-02
-1.33517383e-02 -5.86983444e-02 -4.32341361e-01 -4.36509551e-01
-4.17940102e-02 1.76935235e-03 8.14479984e-03 1.86985655e-01
-2.75525019e-01 -1.63229907e-03 -1.08571055e-01 -7.37452387e-04
-6.44440657e-02 6.72812834e-04 2.16785530e-03 1.41381850e-01
-9.82570410e-02 4.35973325e-01 -7.62261965e-02 -1.86193980e-01
-1.56971183e-02 -4.02757541e-01 -1.53869367e-01 2.29262129e-02
-7.02788246e-03 3.65719683e-02 4.68232163e-01 2.36434668e-02
-2.59520939e-02 3.70550137e-01 -1.19630110e-01 -5.79555318e-02
2.09554455e-01 5.04689780e-02 7.39494314e-02 -1.77647326e-02
-1.45407207e-01 -9.54908878e-02 7.56029640e-02 -2.74049696e-02
3.34885873e-01 1.58546171e-03 1.09339091e-01 -8.84693274e-02
-2.36450457e-02 1.41892239e-01 -2.34453218e-01 -7.50717757e-02
-1.13281310e-01 -1.66649414e-01 -3.17224197e-01 -6.38220597e-02
3.28916563e-02 3.04739203e-02 2.67720196e-02 -1.16485785e-01
-3.08115732e-02 -2.95372010e-02 -7.54669023e-02 6.20013872e-02
-3.85258710e-01 -1.16456443e-01 -7.38548075e-02 -3.20558243e-02
-4.22284741e-02 1.01285659e-01 -1.76949246e-01 -2.02767491e-01
-1.12407344e-01 -3.81408267e-02 -4.33345231e-01 -9.24507501e-02
-4.21765393e-02 -6.06533771e-02 -2.22257783e-01 -1.17312535e-01
-6.74132262e-02 -2.76206274e-01 -9.13971800e-02 -2.27653991e-01
1.66358563e-01 2.17230774e-04 5.76426304e-02 -2.82079169e-02
-1.15482051e-01 -3.46716009e-01 -3.21448755e-01 -5.20041405e-02
-2.16833625e-01 -1.06154654e-02 -7.74854811e-02 -3.28257935e-01
-7.83242410e-02 1.65547682e-01 -2.55294862e-01 -8.89085025e-02
4.47581491e-01 1.92351832e-02 2.74083885e-02 -3.61304571e-01]
[-1. -1. -1. 1. -1. 1. 1. -1. 1. -1. 1. -1. -1. -1. 1. -1. -1. 1.
-1. -1. 1. 1. 1. 1. -1. -1. -1. -1. 1. 1. -1. 1. -1. -1. 1. -1.
-1. -1. -1. 1. -1. -1. -1. -1. -1. 1. 1. 1. -1. -1. -1. -1. -1. 1.
1. 1. -1. 1. -1. -1. -1. -1. -1. 1. -1. 1. 1. 1. -1. 1. -1. -1.
1. 1. 1. -1. -1. -1. 1. -1. 1. 1. 1. -1. -1. 1. -1. -1. -1. -1.
-1. -1. 1. 1. 1. -1. -1. -1. -1. 1. -1. -1. -1. -1. -1. 1. -1. -1.
-1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. -1. 1. 1. 1. -1. -1. -1.
-1. -1. -1. -1. -1. -1. -1. 1. -1. -1. 1. 1. 1. -1.]
[1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1]
Train accuracy: 60.0%
Akurasi pelatihan hanya , yang jelas tidak bagus. Sulit membayangkan performa model pada set uji bisa lebih baik. Mari kita verifikasi.
pred_test = forward(circuit, np.array(test_images), res["x"], estimator, observable)
# pred_test = forward(circuit_ibm, np.array(test_images), res['x'], estimator, observable_ibm)
print(pred_test)
pred_test_labels = copy.deepcopy(pred_test)
pred_test_labels[pred_test_labels >= 0] = 1
pred_test_labels[pred_test_labels < 0] = -1
print(pred_test_labels)
print(test_labels)
accuracy = accuracy_score(test_labels, pred_test_labels)
print(f"Test accuracy: {accuracy * 100}%")
[-2.77978120e-01 -2.62194862e-01 4.59636095e-02 -8.09344165e-02
-2.97362966e-01 9.22947242e-02 2.06693174e-01 3.31629460e-02
1.10971762e-03 -2.14602152e-01 -1.62671993e-01 -6.07179155e-04
-1.59948633e-01 -8.55722523e-02 -1.13057027e-01 -3.00187433e-01
-2.92832827e-01 7.38580629e-02 -6.03706270e-02 -8.57643552e-02
-1.52402062e-02 -3.57505447e-01 -3.54890597e-02 1.36534749e-01
-1.54688180e-01 -2.93714726e-01 1.89548513e-02 -6.15715564e-02
1.11042670e-01 -2.22861100e-02 -3.84230105e-02 1.67351034e-01
-8.38766333e-02 2.56348613e-01 -1.10653111e-01 -1.18989476e-01
-6.75723266e-05 -6.88580547e-02 1.02431393e-02 -2.42125353e-01
-1.09142367e-01 -1.22540757e-01 -1.63735850e-01 3.93334838e-01
2.36705685e-01 -2.34259814e-02 -3.91877756e-02 -1.95106746e-01
1.86707523e-01 4.74775215e-02 -4.24907432e-02 -2.06453265e-01
4.09184710e-02 -3.54762080e-02 -9.47513112e-02 2.97270112e-01
-2.99708696e-02 9.93941064e-03 -1.26760302e-01 -1.36183355e-01]
[-1. -1. 1. -1. -1. 1. 1. 1. 1. -1. -1. -1. -1. -1. -1. -1. -1. 1.
-1. -1. -1. -1. -1. 1. -1. -1. 1. -1. 1. -1. -1. 1. -1. 1. -1. -1.
-1. -1. 1. -1. -1. -1. -1. 1. 1. -1. -1. -1. 1. 1. -1. -1. 1. -1.
-1. 1. -1. 1. -1. -1.]
[-1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1]
Test accuracy: 60.0%
Model ini tidak mengklasifikasikan data dengan baik. Kita harus bertanya mengapa demikian, dan khususnya, kita harus memeriksa:
- Apakah kita menghentikan pelatihan terlalu cepat? Apakah langkah optimasi yang lebih banyak diperlukan?
- Apakah kita membuat ansatz yang buruk? Ini bisa berarti banyak hal. Saat kita bekerja di komputer quantum nyata, kedalaman Circuit akan menjadi pertimbangan utama. Jumlah parameter juga berpotensi penting, begitu pula entanglement antar Qubit.
- Menggabungkan keduanya di atas, apakah kita membuat ansatz dengan terlalu banyak parameter sehingga sulit dilatih?
Kita bisa mulai dengan memeriksa konvergensi dalam optimasi:
obj_func_vals_first = objective_func_vals
# import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(obj_func_vals_first, label="first ansatz")
plt.xlabel("iteration")
plt.ylabel("loss")
plt.legend()
plt.show()
Kita mungkin mencoba memperpanjang langkah optimasi untuk memastikan optimizer tidak hanya terjebak di minimum lokal dalam ruang parameter. Tapi terlihat cukup konvergen. Mari kita lihat lebih dekat gambar-gambar yang tidak diklasifikasikan dengan benar, dan lihat apakah kita bisa memahami apa yang terjadi.
missed = []
for i in range(len(test_labels)):
if pred_test_labels[i] != test_labels[i]:
missed.append(test_images[i])
print(len(missed))
24
fig, ax = plt.subplots(12, 2, figsize=(6, 6), subplot_kw={"xticks": [], "yticks": []})
for i in range(len(missed)):
ax[i // 2, i % 2].imshow(
missed[i].reshape(vert_size, hor_size),
aspect="equal",
)
plt.subplots_adjust(wspace=0.02, hspace=0.025)
Di sini kita bisa melihat bahwa sebagian besar gambar yang salah diklasifikasikan memiliki garis vertikal. Ada sesuatu tentang model kita yang gagal menangkap informasi tentang hal tersebut. Kamu mungkin sudah menduga ini, berdasarkan Circuit variational pertama. Mari kita lihat lebih dekat.
Memperbaiki model
Langkah 1 ditinjau ulang
Saat memetakan masalah kita ke sebuah Circuit kuantum, kita seharusnya secara eksplisit memikirkan bagaimana informasi pada piksel-piksel yang berdekatan menentukan kelas. Untuk mengidentifikasi garis horizontal, kita ingin tahu "jika piksel berwarna kuning, apakah piksel juga berwarna kuning" untuk semua piksel di setiap baris. Kita juga ingin tahu tentang garis vertikal. Namun karena klasifikasinya bersifat biner, seseorang bisa saja beranggapan bahwa jika garis horizontal tidak terdeteksi, maka itu adalah garis vertikal. Circuit variasional sebelumnya berisi Gate CNOT antara qubit (dan karenanya piksel) 0 dan 1, 1 dan 2, serta 2 dan 3. Itu mencakup garis horizontal di bagian atas gambar, tetapi tidak secara langsung mendeteksi garis vertikal, dan juga tidak sepenuhnya mendeteksi garis horizontal karena mengabaikan baris bawah. Untuk mendeteksi semua garis horizontal secara penuh, kita perlu memiliki serangkaian Gate CNOT serupa antara qubit (piksel) 4 dan 5, 5 dan 6, serta 6 dan 7. Kita perlu ingat bahwa menambahkan Gate CNOT antara qubit yang sesuai dengan garis vertikal (seperti 0 dan 4, atau 2 dan 6) mungkin juga berguna. Tapi pertama-tama kita akan memeriksa apakah cukup untuk mendeteksi ada atau tidaknya garis horizontal.
# Initialize the circuit using the same number of qubits as the image has pixels
qnn_circuit = QuantumCircuit(size)
# We choose to have two variational parameters for each qubit.
params = ParameterVector("θ", length=2 * size)
# A first variational layer:
for i in range(size):
qnn_circuit.ry(params[i], i)
# Here is an extended list of qubit pairs between which we want CNOT gates. This now covers all pixels connected by horizontal lines.
qnn_cnot_list = [[0, 1], [1, 2], [2, 3], [4, 5], [5, 6], [6, 7]]
for i in range(len(qnn_cnot_list)):
qnn_circuit.cx(qnn_cnot_list[i][0], qnn_cnot_list[i][1])
# The second variational layer:
for i in range(size):
qnn_circuit.rx(params[size + i], i)
# Check the circuit depth, and the two-qubit gate depth
print(qnn_circuit.decompose().depth())
print(
f"2+ qubit depth: {qnn_circuit.decompose().depth(lambda instr: len(instr.qubits) > 1)}"
)
# Combine the feature map and variational circuit
ansatz = qnn_circuit
# Combine the feature map with the ansatz
full_circuit = QuantumCircuit(num_qubits)
full_circuit.compose(feature_map, range(num_qubits), inplace=True)
full_circuit.compose(ansatz, range(num_qubits), inplace=True)
# Display the circuit
full_circuit.decompose().draw("mpl", style="clifford", fold=-1)
5
2+ qubit depth: 3
Kita tidak menambah kedalaman Circuit. Mari kita lihat apakah kemampuannya untuk memodelkan gambar kita sudah meningkat.
Langkah 2 ditinjau ulang
Kita perlu melakukan transpilasi Circuit baru ini untuk dijalankan di Backend kuantum nyata. Mari kita lewati langkah ini dulu untuk melihat apakah revisi Circuit variasional kita sudah memberikan efek yang diinginkan pada simulator. Kita akan membahas transpilasi lebih dalam di subbagian berikutnya.
Langkah 3 ditinjau ulang
Sekarang kita terapkan model yang sudah diperbarui ke data pelatihan kita.
from qiskit.primitives import StatevectorEstimator as Estimator
batch_size = 140
num_epochs = 1
num_samples = len(train_images)
# Globals
circuit = full_circuit
estimator = Estimator() # simulator for debugging
observables = observable
objective_func_vals = []
iter = 0
# Random initial weights for the ansatz
np.random.seed(42)
weight_params = np.random.rand(len(ansatz.parameters)) * 2 * np.pi
for epoch in range(num_epochs):
for i in range((num_samples - 1) // batch_size + 1):
print(f"Epoch: {epoch}, batch: {i}")
start_i = i * batch_size
end_i = start_i + batch_size
train_images_batch = np.array(train_images[start_i:end_i])
train_labels_batch = np.array(train_labels[start_i:end_i])
input_params = train_images_batch
target = train_labels_batch
iter = 0
res = minimize(
mse_loss_weights, weight_params, method="COBYLA", options={"maxiter": 100}
)
weight_params = res["x"]
Epoch: 0, batch: 0
Iter: 0, loss: 1.0049762969140237
Iter: 50, loss: 0.8274276543780351
Langkah 4 ditinjau ulang
Mari kita mulai dengan memeriksa apakah optimizer kita sudah benar-benar konvergen.
obj_func_vals_revised = objective_func_vals
# import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(obj_func_vals_revised, label="revised ansatz")
plt.xlabel("iteration")
plt.ylabel("loss")
plt.legend()
plt.show()
Tampaknya belum sepenuhnya konvergen, karena fungsi loss belum tetap kurang lebih stabil selama banyak langkah. Tapi fungsi loss sudah ~60% lebih rendah dibanding saat menggunakan Circuit variasional sebelumnya. Kalau ini proyek penelitian, kita perlu memastikan konvergensi penuh. Tapi untuk tujuan eksplorasi, ini sudah cukup. Mari kita periksa akurasi pada data pelatihan dan data pengujian kita.
from sklearn.metrics import accuracy_score
from qiskit.primitives import StatevectorEstimator as Estimator # simulator
# from qiskit_ibm_runtime import EstimatorV2 as Estimator # real quantum computer
estimator = Estimator()
# estimator = Estimator(backend=backend)
pred_train = forward(circuit, np.array(train_images), res["x"], estimator, observable)
# pred_train = forward(circuit_ibm, np.array(train_images), res['x'], estimator, observable_ibm)
print(pred_train)
pred_train_labels = copy.deepcopy(pred_train)
pred_train_labels[pred_train_labels >= 0] = 1
pred_train_labels[pred_train_labels < 0] = -1
print(pred_train_labels)
print(train_labels)
accuracy = accuracy_score(train_labels, pred_train_labels)
print(f"Train accuracy: {accuracy * 100}%")
[ 0.46144755 0.42579688 0.35255977 0.55207273 -0.48578418 0.50805845
0.44892649 0.6173847 -0.62428139 0.40405121 0.46862421 0.29503395
-0.5740469 -0.71794562 -0.45022095 -0.45330418 -0.19795258 -0.46821777
-0.5622049 -0.32114059 0.54947838 -0.4889812 0.28327445 0.58149728
-0.27026749 0.41328304 0.21119412 0.60108606 0.39204178 -0.24974605
0.38496469 0.39867586 -0.38946996 0.62616766 0.61212525 -0.49719567
0.30860002 0.68443904 -0.27505907 -0.41508947 -0.49666422 0.67716994
-0.54696613 -0.70058779 0.42711815 -0.5285338 0.37678572 0.43888249
-0.30844464 0.42347715 -0.4250844 0.67324132 0.59914067 -0.45184567
0.13604098 0.65336342 0.26099853 0.60316559 -0.38743183 -0.54784284
-0.29549031 -0.45592302 0.41613453 -0.38781528 0.56903087 0.54955451
0.55532336 -0.3931852 -0.57599675 0.61246236 0.42014135 -0.38171749
0.56760389 0.45383135 -0.50473943 -0.47551181 0.54221517 -0.64987023
0.28845851 0.54403865 0.53841148 0.64477078 0.71912049 -0.63178323
-0.50764757 0.50304637 -0.38099972 -0.27707127 -0.24353841 -0.52045267
-0.61500665 0.65443173 0.31902266 -0.64969037 -0.4814051 0.47980608
-0.649786 -0.43048551 0.34562588 0.308998 -0.32454238 0.29558168
-0.45410187 0.54600712 0.33204827 0.22627804 0.4283921 0.56191874
-0.25400294 -0.6493613 -0.47445293 0.42272138 -0.35472546 -0.52240474
-0.45207595 0.40292125 -0.3361856 -0.46620886 0.60202719 -0.56505744
0.47169796 -0.43577622 0.40689437 0.48869108 -0.39701189 -0.57698634
-0.39236332 0.31294648 0.41797597 0.63004836 -0.52884541 -0.43805812
-0.3193499 0.36860211 -0.49190995 0.65000193 0.50260077 -0.56737168
-0.29693083 -0.40956432]
[ 1. 1. 1. 1. -1. 1. 1. 1. -1. 1. 1. 1. -1. -1. -1. -1. -1. -1.
-1. -1. 1. -1. 1. 1. -1. 1. 1. 1. 1. -1. 1. 1. -1. 1. 1. -1.
1. 1. -1. -1. -1. 1. -1. -1. 1. -1. 1. 1. -1. 1. -1. 1. 1. -1.
1. 1. 1. 1. -1. -1. -1. -1. 1. -1. 1. 1. 1. -1. -1. 1. 1. -1.
1. 1. -1. -1. 1. -1. 1. 1. 1. 1. 1. -1. -1. 1. -1. -1. -1. -1.
-1. 1. 1. -1. -1. 1. -1. -1. 1. 1. -1. 1. -1. 1. 1. 1. 1. 1.
-1. -1. -1. 1. -1. -1. -1. 1. -1. -1. 1. -1. 1. -1. 1. 1. -1. -1.
-1. 1. 1. 1. -1. -1. -1. 1. -1. 1. 1. -1. -1. -1.]
[1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1]
Train accuracy: 100.0%
pred_test = forward(circuit, np.array(test_images), res["x"], estimator, observable)
# pred_test = forward(circuit_ibm, np.array(test_images), res['x'], estimator, observable_ibm)
print(pred_test)
pred_test_labels = copy.deepcopy(pred_test)
pred_test_labels[pred_test_labels >= 0] = 1
pred_test_labels[pred_test_labels < 0] = -1
print(pred_test_labels)
print(test_labels)
accuracy = accuracy_score(test_labels, pred_test_labels)
print(f"Test accuracy: {accuracy * 100}%")
[-0.48396136 -0.57123828 0.28373249 0.38983869 -0.45799092 -0.63643031
0.69164877 -0.47749808 0.16965244 -0.39669469 0.39366915 0.44206948
0.69733951 0.40445979 -0.33663432 0.54511581 -0.49397081 0.55934553
0.69269512 0.38875983 0.39724004 -0.49635863 -0.19131387 0.38813936
0.39537369 -0.46262489 0.5307315 0.21783317 0.31949453 -0.49772087
0.56409526 -0.66254365 -0.57507262 0.37363552 0.35154205 0.69295687
-0.31205475 0.37787066 0.67903997 -0.29984861 -0.46435535 -0.32610974
0.4327188 0.64626537 0.37592731 -0.14328906 0.59694745 0.71880638
0.32414334 0.42119333 -0.60745236 -0.42520033 0.28334222 0.21699081
0.34837252 0.31538989 0.30754545 0.5995197 -0.34678026 -0.46587602]
[-1. -1. 1. 1. -1. -1. 1. -1. 1. -1. 1. 1. 1. 1. -1. 1. -1. 1.
1. 1. 1. -1. -1. 1. 1. -1. 1. 1. 1. -1. 1. -1. -1. 1. 1. 1.
-1. 1. 1. -1. -1. -1. 1. 1. 1. -1. 1. 1. 1. 1. -1. -1. 1. 1.
1. 1. 1. 1. -1. -1.]
[-1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1]
Test accuracy: 100.0%
```bash
Akurasi $100\%$ di kedua set! Dugaan kita bahwa deteksi garis horizontal yang akurat sudah cukup ternyata benar! Selain itu, pemetaan kita dari informasi piksel yang dibutuhkan ke Gate CNOT dalam Circuit kuantum sudah efektif. Sekarang mari kita lihat bagaimana proses ini berskala untuk dijalankan di komputer kuantum nyata.
## Skalabilitas dan menjalankan di komputer kuantum nyata \{#scaling-and-running-on-real-quantum-computers}
### Data \{#data}
Mari kita mulai dengan memperbesar ukuran gambar kita. Tidak ada yang istimewa dari pilihan grid 6x6, selain karena ukurannya melebihi jumlah qubit (32) yang bisa kita simulasikan untuk Circuit yang menggunakan Gate non-Clifford.
```python
# This code defines the images to be classified:
import numpy as np
# Total number of "pixels"/qubits
size = 36
# One dimension of the image (called vertical, but it doesn't matter). Must be a divisor of `size`
vert_size = 6
# The length of the line to be detected (yellow). Must be less than or equal to the smallest dimension of the image (`<=min(vert_size,size/vert_size)`
line_size = 6
def generate_dataset(num_images):
images = []
labels = []
hor_array = np.zeros((size - (line_size - 1) * vert_size, size))
ver_array = np.zeros((round(size / vert_size) * (vert_size - line_size + 1), size))
j = 0
for i in range(0, size - 1):
if i % (size / vert_size) <= (size / vert_size) - line_size:
for p in range(0, line_size):
hor_array[j][i + p] = np.pi / 2
j += 1
# Make two adjacent entries pi/2, then move down to the next row. Careful to avoid the "pixels" at size/vert_size - linesize, because we want to fold this list into a grid.
j = 0
for i in range(0, round(size / vert_size) * (vert_size - line_size + 1)):
for p in range(0, line_size):
ver_array[j][i + p * round(size / vert_size)] = np.pi / 2
j += 1
# Make entries pi/2, spaced by the length/rows, so that when folded, the entries appear on top of each other.
for n in range(num_images):
rng = np.random.randint(0, 2)
if rng == 0:
labels.append(-1)
random_image = np.random.randint(0, len(hor_array))
images.append(np.array(hor_array[random_image]))
# Randomly select one of the several rows you made above.
elif rng == 1:
labels.append(1)
random_image = np.random.randint(0, len(ver_array))
images.append(np.array(ver_array[random_image]))
# Randomly select one of the several rows you made above.
# Create noise
for i in range(size):
if images[-1][i] == 0:
images[-1][i] = np.random.rand() * np.pi / 4
return images, labels
hor_size = round(size / vert_size)
Karena waktu komputasi kuantum adalah komoditas yang berharga, kita akan menggunakan set pelatihan yang sangat kecil dan sangat sedikit langkah optimasi. Ini sudah cukup untuk menunjukkan alur kerja.
from sklearn.model_selection import train_test_split
np.random.seed(42)
# Here we specify a very small data set. Increase for realism, but monitor use of quantum computing time.
images, labels = generate_dataset(10)
train_images, test_images, train_labels, test_labels = train_test_split(
images, labels, test_size=0.3, random_state=246
)
import matplotlib.pyplot as plt
# Generate a figure with nested images using subplots.
fig, ax = plt.subplots(2, 2, figsize=(10, 6), subplot_kw={"xticks": [], "yticks": []})
for i in range(4):
ax[i // 2, i % 2].imshow(
train_images[i].reshape(vert_size, hor_size),
aspect="equal",
)
plt.subplots_adjust(wspace=0.1, hspace=0.025)
Langkah 1: Memetakan masalah ke Circuit kuantum
from qiskit.circuit.library import z_feature_map
# One qubit per data feature
num_qubits = len(train_images[0])
# Data encoding
# Note that qiskit orders parameters alphabetically. We assign the parameter prefix "a" to ensure our data encoding goes to the first part of the circuit, the feature mapping.
feature_map = z_feature_map(num_qubits, parameter_prefix="a")
# This creates a circuit with the cxs in the compressed order.
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
qnn_circuit = QuantumCircuit(size)
params = ParameterVector("θ", length=2 * size)
for i in range(size):
qnn_circuit.ry(params[i], i)
# CNOT gates between horizontally adjacent qubits.
for i in range(vert_size):
for j in range(hor_size):
if j < hor_size - 1:
qnn_circuit.cx((i * hor_size) + j, (i * hor_size) + j + 1)
# CNOT gates between vertically adjacent qubits, likely not necessary based on our preliminary simulation.
# if i<vert_size-1:
# qnn_circuit.cx((i*hor_size)+j,(i*hor_size)+j+hor_size)
for i in range(size):
qnn_circuit.rx(params[size + i], i)
qnn_circuit_large = qnn_circuit
print(qnn_circuit_large.decompose().depth())
print(
f"2+ qubit depth: {qnn_circuit_large.decompose().depth(lambda instr: len(instr.qubits) > 1)}"
)
# qnn_circuit_large.draw()
7
2+ qubit depth: 5
Ini adalah kedalaman dua-qubit yang wajar. Kita seharusnya bisa mendapatkan hasil berkualitas tinggi dari komputer kuantum nyata.
# Combine the feature map and variational circuit
ansatz = qnn_circuit
# Combine the feature map with the ansatz
full_circuit = QuantumCircuit(num_qubits)
full_circuit.compose(feature_map, range(num_qubits), inplace=True)
full_circuit.compose(ansatz, range(num_qubits), inplace=True)
# Check the depth of the full circuit
print(full_circuit.decompose().depth())
print(
f"2+ qubit depth: {full_circuit.decompose().depth(lambda instr: len(instr.qubits) > 1)}"
)
11
2+ qubit depth: 5
Karena kita menggunakan z_feature_map, yang tidak memiliki Gate CNOT, menambahkan lapisan encoding tidak meningkatkan kedalaman dua-qubit kita. Kita bisa memvisualisasikan Circuit penuh di sini.
full_circuit.decompose().draw("mpl", style="clifford", idle_wires=False, fold=-1)

Kamu mungkin memperhatikan bahwa jika meminimalkan kedalaman dua-qubit adalah hal yang paling penting, kita sebenarnya bisa menguranginya sedikit dengan mengubah urutan CNOT. Misalnya, CNOT pada dan bisa dipindahkan ke kiri dalam diagram Circuit di atas, dan bisa ditempatkan langsung di bawah CNOT pada dan , misalnya. Untuk kedalaman Gate dua-qubit sebesar 5, tidak jelas apakah ini akan membuat perbedaan setelah transpilasi, tapi ini perlu diingat. Jika urutan Gate CNOT penting untuk secara logis mencocokkan masalah yang ada, kedalaman di sini sudah baik. Jika urutan CNOT tidak kritis untuk memodelkan struktur data dalam gambar kita, maka kita bisa menulis skrip untuk mengurutkan ulang Gate CNOT ini guna meminimalkan kedalaman.
Kita juga perlu mendefinisikan ulang observable kita dengan gambar yang lebih besar:
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp.from_list([("Z" * (num_qubits), 1)])
Langkah 2 Qiskit Patterns: Optimalkan masalah untuk eksekusi kuantum
Kita mulai dengan memilih Backend untuk eksekusi. Dalam hal ini, kita akan menggunakan Backend yang paling tidak sibuk.
from qiskit_ibm_runtime import QiskitRuntimeService
# To run on hardware, select the least busy quantum computer or specify a particular one.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# backend = service.backend("ibm_brisbaneane")
print(backend.name)
ibm_brisbane
Sekali lagi, kita mendefinisikan pass manager, dengan level optimasi diatur ke 3.
from qiskit.circuit.library import XGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
ConstrainedReschedule,
PadDynamicalDecoupling,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)
Sekarang kita akan menerapkan pass manager beberapa kali. Untuk Circuit yang sangat lebar atau sangat dalam, bisa ada variasi besar dalam kedalaman dua-qubit hasil transpilasi. Untuk Circuit seperti itu, penting untuk mencoba pass manager berkali-kali dan menggunakan hasil yang terbaik (paling dangkal).
# Try pass manager several times, since heuristics can return various transpilations on large circuits, and we want the shallowest.
transpiled_qcs = []
transpiled_depths = []
transpiled_2q_depths = []
for i in range(1, 10):
circuit_ibm = pm.run(full_circuit)
transpiled_qcs.append(circuit_ibm)
transpiled_depths.append(circuit_ibm.decompose().depth())
transpiled_2q_depths.append(
circuit_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
)
# print(i)
print(transpiled_depths)
print(transpiled_2q_depths)
# Use the shallowest
minpos = transpiled_2q_depths.index(min(transpiled_2q_depths))
[85, 85, 81, 89, 81, 81, 89, 85, 85]
[10, 10, 10, 10, 10, 10, 10, 10, 10]
Kita melihat bahwa dalam kasus ini, kedalaman dua-qubit hasil transpilasi selalu 10. Ada sedikit variasi pada kedalaman satu-qubit, dan kita akan menggunakan yang paling dangkal. Tapi pada Circuit 36-qubit ini, ini bukan peningkatan yang kritis. Kita bisa memvisualisasikan Circuit yang sudah ditranspilasi ini, meskipun pada skala ini semakin sulit untuk diparsing secara visual.
circuit_ibm = transpiled_qcs[2]
observable_ibm = observable.apply_layout(circuit_ibm.layout)
print(circuit_ibm.decompose().depth())
print(
f"2+ qubit depth: {circuit_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)}"
)
81
2+ qubit depth: 10
Langkah 3 Qiskit Patterns: Eksekusi menggunakan Qiskit Primitives
Untuk membatasi waktu yang digunakan pada komputer kuantum nyata, kita hanya akan melakukan beberapa langkah optimasi di sini, dan melakukannya pada set pelatihan yang sangat kecil. Tapi skalabilitas ini ke lebih banyak langkah optimasi dan set data pengujian yang lebih besar seharusnya sudah jelas dari instruksi di seluruh pelajaran.
# This was run on an Eagle r3 processor on 10-4-24, and took 7 min.
from qiskit_ibm_runtime import EstimatorV2 as Estimator, Session
batch_size = 7
num_epochs = 1
num_samples = len(train_images)
# Globals
circuit = circuit_ibm
observable = observable_ibm
objective_func_vals = []
iter = 0
# Random initial weights for the ansatz
np.random.seed(42)
# weight_params = np.random.rand(len(ansatz.parameters)) * 2 * np.pi
# Or re-load weights from a previous calculation
weight_params = np.array(
[
3.35330497,
5.97351416,
4.59925358,
3.76148219,
0.98029403,
0.98014248,
0.3649501,
6.44234523,
3.77691701,
4.44895122,
0.12933619,
6.09412333,
5.23039137,
1.33416598,
1.14243996,
1.15236452,
1.91161039,
3.2971419,
3.71399059,
1.82984665,
3.84438512,
0.87646578,
1.83559896,
2.30191935,
2.86557222,
4.93340606,
1.25458737,
3.23103027,
3.72225051,
0.29185655,
3.81731689,
1.07143467,
0.40873121,
5.96202367,
6.067245,
5.07931034,
1.91394476,
0.61369199,
4.2991629,
2.76555968,
0.76678884,
3.11128829,
0.21606945,
5.71342859,
1.62596258,
4.16275028,
1.95853845,
3.26768375,
3.43508199,
1.1614748,
6.09207989,
4.87030317,
5.90304595,
5.62236606,
3.75671636,
5.79230665,
0.55601479,
1.23139664,
0.28417144,
2.04411075,
2.44213144,
1.70493625,
5.20711134,
2.24154726,
1.76516358,
3.40986006,
0.88545302,
5.04035228,
0.46841551,
6.2007935,
4.85215699,
1.24856745,
]
)
# Running in a session avoids repeated queuing. This is available to Premium Plan, Flex Plan, and On-Prem (IBM Quantum Platform API) Plan users.
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options={"resilience_level": 1})
for epoch in range(num_epochs):
for i in range((num_samples - 1) // batch_size + 1):
print(f"Epoch: {epoch}, batch: {i}")
start_i = i * batch_size
end_i = start_i + batch_size
train_images_batch = np.array(train_images[start_i:end_i])
train_labels_batch = np.array(train_labels[start_i:end_i])
input_params = train_images_batch
target = train_labels_batch
iter = 0
# We can increase maxiter to do a full optimization.
res = minimize(
mse_loss_weights,
weight_params,
method="COBYLA",
options={"maxiter": 20},
)
weight_params = res["x"]
session.close()
# Open users can carry out the same calculation using a batch, but repeated queuing is possible.
# from qiskit_ibm_runtime import Batch
# with Batch(backend=backend) as batch:
# estimator = Estimator(
# mode=batch, options={"resilience_level": 1}
# )
#
# for epoch in range(num_epochs):
# for i in range((num_samples - 1) // batch_size + 1):
# print(f"Epoch: {epoch}, batch: {i}")
# start_i = i * batch_size
# end_i = start_i + batch_size
# train_images_batch = np.array(train_images[start_i:end_i])
# train_labels_batch = np.array(train_labels[start_i:end_i])
# input_params = train_images_batch
# target = train_labels_batch
# iter = 0
# # We can increase maxiter to do a full optimization.
# res = minimize(
# mse_loss_weights,
# weight_params,
# method="COBYLA",
# options={"maxiter": 20},
# )
# weight_params = res["x"]
# batch.close()
Disarankan untuk menyimpan parameter bobot yang dikembalikan dari komputasi ini, jika kamu memutuskan untuk melakukan iterasi lebih lanjut.
weight_params
array([3.35330497, 6.97351416, 5.59925358, 3.76148219, 0.98029403,
0.98014248, 0.3649501 , 6.44234523, 3.77691701, 4.44895122,
1.12933619, 7.09412333, 5.23039137, 1.33416598, 1.14243996,
1.15236452, 1.91161039, 3.2971419 , 3.71399059, 1.82984665,
3.84438512, 0.87646578, 1.83559896, 2.30191935, 2.86557222,
4.93340606, 1.25458737, 3.23103027, 3.72225051, 0.29185655,
3.81731689, 1.07143467, 0.40873121, 5.96202367, 6.067245 ,
5.07931034, 1.91394476, 0.61369199, 4.2991629 , 2.76555968,
0.76678884, 3.11128829, 0.21606945, 5.71342859, 1.62596258,
4.16275028, 1.95853845, 3.26768375, 3.43508199, 1.1614748 ,
6.09207989, 4.87030317, 5.90304595, 5.62236606, 3.75671636,
5.79230665, 0.55601479, 1.23139664, 0.28417144, 2.04411075,
2.44213144, 1.70493625, 5.20711134, 2.24154726, 1.76516358,
3.40986006, 0.88545302, 5.04035228, 0.46841551, 6.2007935 ,
4.85215699, 1.24856745])
Kita bisa memplot beberapa langkah optimasi pertama ini, meskipun kita tidak mengharapkan konvergensi setelah hanya beberapa langkah optimasi. Kurva-kurva ini relatif datar untuk beberapa langkah pertama, bahkan menggunakan simulator. Namun perlu kita catat bahwa optimasi saat ini memiliki 72 parameter bebas. Ini bisa dikurangi setidaknya faktor 2-3 tanpa mengorbankan hasil dengan, misalnya, memparameterisasi qubit dengan data yang sesuai dengan subset baris dan kolom penuh. Memang, ruang parameter harus dikurangi sebelum menghabiskan lebih banyak waktu komputasi kuantum untuk meminimalkan fungsi loss.
obj_func_vals_qc = objective_func_vals
# import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(obj_func_vals_qc, label="revised ansatz")
plt.xlabel("iteration")
plt.ylabel("loss")
plt.legend()
plt.show()
Penutup
Sebagai rekap, dalam pelajaran ini kita mempelajari alur kerja untuk klasifikasi biner gambar menggunakan jaringan saraf kuantum. Beberapa pertimbangan utama di setiap langkah Qiskit Patterns adalah:
Langkah 1: Memetakan masalah ke Circuit kuantum
- Muat data pelatihan. Ini bisa dilakukan "secara manual" atau menggunakan feature map bawaan seperti
z_feature_map. - Bangun ansatz yang berisi lapisan rotasi dan entanglement yang sesuai untuk masalahmu.
- Pantau kedalaman Circuit untuk memastikan hasil berkualitas pada komputer kuantum.
Langkah 2: Optimalkan masalah untuk eksekusi kuantum
- Pilih Backend, seringkali yang paling tidak sibuk.
- Gunakan pass manager untuk mentranspilasi Circuit dan observable ke arsitektur Backend yang dipilih.
- Untuk Circuit yang sangat dalam atau lebar, lakukan transpilasi beberapa kali, dan pilih Circuit yang paling dangkal.
Langkah 3: Eksekusi menggunakan Qiskit (Runtime) Primitives
- Lakukan percobaan awal pada simulator untuk melakukan debug dan mengoptimalkan ansatz-mu.
- Jalankan pada komputer kuantum IBM®.
Langkah 4: Pasca-proses, kembalikan hasil dalam format klasik
- Hitung akurasi model pada data pelatihan, dan pada data pengujian.
- Pantau konvergensi optimasi klasik.