Langkau ke kandungan utama

Litar Kuantum Varian dan Rangkaian Neural Kuantum

Dalam pelajaran ini, kita melaksanakan beberapa litar kuantum varian untuk tugas pengelasan data, yang dikenali sebagai pengelas kuantum varian (VQC). Pada satu ketika, sebahagian daripada VQC lazim dirujuk sebagai rangkaian neural kuantum (QNN) secara analogi dengan rangkaian neural klasik. Memang ada kes di mana struktur yang dipinjam daripada rangkaian neural klasik, seperti lapisan konvolusi, memainkan peranan penting dalam VQC. Dalam kes di mana analogi ini kuat, QNN mungkin merupakan huraian yang berguna. Namun begitu, litar kuantum berparameter tidak semestinya mengikut struktur am rangkaian neural; contohnya, tidak semua data perlu dimuatkan dalam lapisan pertama (input); kita boleh memuatkan sebahagian data dalam lapisan pertama, menerapkan beberapa gate dan kemudian memuatkan data tambahan (satu proses yang disebut "reuploading" data). Oleh itu, kita harus menganggap QNN sebagai subset daripada litar kuantum berparameter, dan kita tidak harus terbatas dalam penerokaan litar kuantum yang berguna oleh analogi dengan rangkaian neural klasik.

Set data yang ditangani dalam pelajaran ini terdiri daripada imej-imej yang mengandungi jalur mendatar dan menegak, dan matlamat kita adalah untuk melabel imej yang belum dilihat ke dalam salah satu daripada dua kategori bergantung pada orientasi garisnya. Kita akan mencapai ini dengan VQC. Sepanjang perjalanan, kita akan membincangkan cara-cara untuk meningkatkan dan menskalakan pengiraan. Set data di sini amat mudah diklasifikasikan secara klasik. Ia dipilih kerana kesederhanaannya supaya kita dapat fokus pada bahagian kuantum masalah ini, dan melihat bagaimana atribut set data boleh diterjemahkan kepada sebahagian daripada litar kuantum. Adalah tidak munasabah untuk mengharapkan kelajuan kuantum bagi kes-kes mudah sedemikian di mana algoritma klasik sangat cekap.

Pada akhir pelajaran ini, kamu harus dapat:

  • Memuatkan data daripada imej ke dalam litar kuantum
  • Membina ansatz untuk VQC (atau QNN), dan menyesuaikannya dengan masalah kamu
  • Melatih VQC/QNN kamu dan menggunakannya untuk membuat ramalan tepat pada data ujian
  • Menskalakan masalah, dan mengenal pasti had komputer kuantum semasa

Penjanaan data

Kita akan mulakan dengan membina data. Set data selalunya tidak dijana secara eksplisit sebagai sebahagian daripada kerangka corak Qiskit. Tetapi jenis dan persediaan data adalah kritikal untuk berjaya menerapkan pengkomputeran kuantum kepada pembelajaran mesin. Kod di bawah mentakrifkan set data imej-imej dengan dimensi piksel yang ditetapkan. Satu baris atau lajur penuh imej diberi nilai π/2\pi/2, dan piksel yang selebihnya diberi nilai rawak dalam selang (0,π/4)(0,\pi/4). Nilai rawak adalah hingar dalam data kita. Lihat sekilas kod untuk memastikan kamu faham bagaimana imej dijana. Kemudian kita akan menskalakan imej-imej tersebut.

# 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 bahawa kod di atas turut menjana label yang menunjukkan sama ada imej mengandungi garisan menegak (+1) atau mendatar (-1). Kita akan menggunakan sklearn untuk membahagikan set data 100 imej kepada set latihan dan ujian (bersama label yang sepadan). Di sini, kita menggunakan 7070% set data untuk latihan, dengan baki 3030% diketepikan untuk ujian.

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
)

Jom plot beberapa elemen set data kita untuk melihat bagaimana garisan-garisan ini kelihatan:

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)

Output of the previous code cell

Setiap imej ini masih dipasangkan dengan labelnya dalam train_labels dalam bentuk senarai mudah:

print(train_labels[:8])
[1, 1, 1, 1, -1, 1, 1, 1]

Pengelas kuantum varian: percubaan pertama

Langkah corak Qiskit 1: Petakan masalah ke litar kuantum

Matlamatnya adalah untuk mencari fungsi ff dengan parameter θ\theta yang memetakan vektor data / imej x\vec{x} ke kategori yang betul: fθ(x)±1f_\theta(\vec{x}) \rightarrow \pm1. Ini akan dicapai menggunakan VQC dengan beberapa lapisan yang boleh dikenal pasti oleh tujuan masing-masing yang berbeza:

fθ(x)=0U(x)W(θ)OW(θ)U(x)0f_\theta(\vec{x}) = \langle 0|U^{\dagger}(\vec{x})W^\dagger(\theta)OW(\theta)U(\vec{x})|0\rangle

Di sini, U(x)U(\vec{x}) adalah litar pengekodan, yang mana kita mempunyai banyak pilihan seperti yang dilihat dalam pelajaran sebelumnya. W(θ)W(\theta) adalah blok litar varian, atau boleh dilatih, dan θ\theta adalah set parameter yang perlu dilatih. Parameter tersebut akan diubah oleh algoritma pengoptimuman klasik untuk mencari set parameter yang menghasilkan pengelasan imej terbaik oleh litar kuantum. Litar varian ini kadang-kadang disebut "ansatz". Akhirnya, OO adalah beberapa boleh-cerap yang akan dianggarkan menggunakan primitif Estimator. Tiada kekangan yang memaksa lapisan-lapisan datang dalam susunan ini, atau bahkan sepenuhnya terpisah. Seseorang boleh mempunyai beberapa lapisan varian dan/atau pengekodan dalam sebarang susunan yang dimotivasikan secara teknikal.

Kita mulakan dengan memilih peta ciri untuk mengekod data kita. Kita akan menggunakan z_feature_map, kerana ia memastikan kedalaman litar rendah berbanding dengan beberapa pemetaan ciri lain.

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 sekarang perlu memutuskan ansatz untuk dilatih. Terdapat banyak pertimbangan semasa memilih ansatz. Penerangan lengkap adalah di luar skop pengenalan ini; di sini kita hanya menyatakan beberapa kategori pertimbangan.

  1. Perkakasan: Semua komputer kuantum moden lebih terdedah kepada ralat dan lebih mudah terkena hingar berbanding rakan sejawat klasik mereka. Menggunakan ansatz yang terlalu dalam (terutamanya dalam kedalaman dua-Qubit yang ditranspil) tidak akan menghasilkan keputusan yang baik. Isu berkaitan ialah komputer kuantum mempunyai susun atur Qubit tertentu, bermakna sebahagian Qubit fizikal bersebelahan pada komputer kuantum, dan yang lain mungkin sangat jauh antara satu sama lain. Menghubungkan Qubit bersebelahan tidak meningkatkan kedalaman terlalu banyak, tetapi menghubungkan Qubit yang sangat jauh boleh meningkatkan kedalaman dengan ketara, kerana kita perlu memasukkan gate swap untuk memindahkan maklumat ke atas Qubit yang bersebelahan supaya ia boleh dihubungkan.
  2. Masalah: Apabila kamu mempunyai maklumat tentang masalah kamu yang boleh membimbing ansatz kamu, gunakan ia. Contohnya, data dalam pelajaran ini terdiri daripada imej garisan mendatar dan menegak. Seseorang boleh mempertimbangkan apakah korelasi antara warna/nilai bersebelahan yang mengenal pasti imej garisan mendatar atau menegak. Apakah atribut ansatz yang sepadan dengan korelasi antara piksel bersebelahan ini? Kita akan kembali semula ke titik ini secara lebih teknikal kemudian dalam pelajaran ini. Tetapi buat masa ini, mari kita katakan bahawa memasukkan gate CNOT antara Qubit yang sepadan dengan piksel bersebelahan kelihatan seperti idea yang baik. Dalam gambaran yang lebih besar, pertimbangkan sama ada masalah ini sebenarnya paling baik diselesaikan menggunakan litar kuantum, atau sama ada algoritma klasik mungkin wujud yang boleh melakukan kerja yang sama baik.
  3. Bilangan parameter: Setiap gate kuantum berparameter bebas dalam litar meningkatkan ruang yang perlu dioptimumkan secara klasik, dan ini mengakibatkan penumpuan yang lebih perlahan. Tetapi apabila masalah berskala naik, seseorang mungkin menghadapi dataran tandus. Istilah ini merujuk kepada fenomena di mana landskap pengoptimuman algoritma kuantum varian menjadi rata dan tidak berkesan secara eksponen apabila saiz masalah meningkat. Ini menyebabkan kecerunan yang hilang, menjadikannya sukar untuk melatih algoritma dengan berkesan[1]. Dataran tandus berkaitan dengan algoritma kuantum varian seperti VQC/QNN. Perlu diperhatikan bahawa peningkatan bilangan parameter bukan satu-satunya pertimbangan dalam mengelakkan dataran tandus; pertimbangan lain termasuk fungsi kos global dan permulaan parameter rawak.

Dalam pelajaran ini kita akan melihat beberapa contoh mudah amalan baik dalam pembinaan ansatz. Mari kita cuba ansatz di bawah dahulu. Kita akan kembali untuk menyemaknya kemudian.

# 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 pengekodan data dan litar varian yang telah disediakan, kita boleh menggabungkannya untuk membentuk ansatz penuh kita. Dalam kes ini, komponen litar kuantum kita cukup serupa dengan komponen dalam rangkaian neural, dengan U(x)U(\vec{x}) paling mirip dengan lapisan yang memuatkan nilai input daripada imej, dan W(θ)W(\theta) seperti lapisan "berat" pemboleh ubah. Oleh kerana analogi ini berlaku dalam kes ini, kita menggunakan "qnn" dalam beberapa konvensyen penamaan kita; tetapi analogi ini tidak seharusnya membataskan penerokaan VQC kamu.

QML_CR_background_QNN_circuit-2.png

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

Output of the previous code cell

Kita sekarang perlu mentakrifkan boleh-cerap, supaya kita boleh menggunakannya dalam fungsi kos kita. Kita akan mendapatkan nilai jangkaan untuk boleh-cerap ini menggunakan Estimator. Jika kita telah memilih ansatz yang baik dan bermotivasi masalah, maka setiap Qubit akan mengandungi maklumat yang berkaitan dengan pengelasan. Seseorang boleh menambah lapisan untuk menggabungkan maklumat ke atas lebih sedikit Qubit (dipanggil lapisan konvolusi), supaya pengukuran hanya diperlukan pada subset Qubit dalam litar (seperti dalam rangkaian neural konvolusi). Atau seseorang boleh mengukur beberapa atribut daripada setiap Qubit. Di sini kita akan memilih yang kedua, jadi kita sertakan operator Z untuk setiap Qubit. Tiada yang unik tentang memilih ZZ, tetapi ia bermotivasi baik:

  • Ini adalah tugas pengelasan binari, dan pengukuran ZZ boleh menghasilkan dua kemungkinan hasil.
  • Nilai eigen ZZ (±1\pm 1) dipisahkan dengan munasabah, dan menghasilkan hasil estimator dalam selang [-1, +1], di mana 0 boleh digunakan sebagai nilai sempadan.
  • Adalah mudah untuk mengukur dalam asas Pauli Z tanpa overhead gate tambahan.

Jadi, Z adalah pilihan yang sangat semulajadi.

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp.from_list([("Z" * (num_qubits), 1)])

Kita mempunyai litar kuantum dan boleh-cerap yang ingin kita anggarkan. Sekarang kita perlukan beberapa perkara untuk menjalankan dan mengoptimumkan litar ini. Pertama, kita perlukan fungsi untuk menjalankan lulus ke hadapan. Perhatikan bahawa fungsi di bawah mengambil input_params dan weight_params secara berasingan. Yang pertama adalah set parameter statik yang menerangkan data dalam imej, dan yang kedua adalah set parameter pemboleh ubah yang perlu dioptimumkan.

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 kerugian

Seterusnya, kita memerlukan fungsi kerugian untuk mengira perbezaan antara nilai label yang diramal dan yang dikira. Fungsi ini akan mengambil label yang diramal oleh algoritma dan label yang betul, dan mengembalikan perbezaan kuasa dua min. Terdapat banyak fungsi kerugian yang berbeza. 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 takrifkan fungsi kerugian yang sedikit berbeza yang merupakan fungsi parameter pemboleh ubah (berat), untuk digunakan oleh pengoptimum klasik. Fungsi ini hanya mengambil parameter ansatz sebagai input; pemboleh ubah lain untuk lulus ke hadapan dan kerugian ditetapkan sebagai parameter global. Pengoptimum akan melatih model dengan mengambil berat yang berbeza dan cuba untuk menurunkan output fungsi kos/kerugian.

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 menyebut penggunaan pengoptimum klasik. Apabila kita perlu mencari berat untuk meminimumkan fungsi kos, kita akan menggunakan pengoptimum COBYLA:

from scipy.optimize import minimize

Kita akan menetapkan beberapa pemboleh ubah global awal untuk fungsi kos.

# Globals
circuit = full_circuit
observables = observable
# input_params = train_images_batch
# target = train_labels_batch
objective_func_vals = []
iter = 0

Langkah Corak Qiskit 2: Optimumkan masalah untuk pelaksanaan kuantum

Kita mulakan dengan memilih Backend untuk pelaksanaan. Dalam kes ini, kita akan menggunakan Backend yang paling kurang 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 mengoptimumkan litar untuk dijalankan pada Backend nyata dengan menetapkan optimization_level dan menambah decoupling dinamik. Kod di bawah menjana pengurus laluan menggunakan pengurus laluan praset daripada 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 pengurus laluan pada litar. Perubahan susun atur yang terhasil mesti digunakan pada boleh-cerap juga. Untuk litar yang sangat besar, heuristik yang digunakan dalam pengoptimuman litar mungkin tidak sentiasa menghasilkan litar yang terbaik dan paling cetek. Dalam kes tersebut, masuk akal untuk menjalankan pengurus laluan sedemikian beberapa kali dan menggunakan litar yang terbaik. Kita akan melihat ini kemudian apabila kita menskalakan pengiraan kita.

circuit_ibm = pm.run(full_circuit)
observable_ibm = observable.apply_layout(circuit_ibm.layout)

Langkah Corak Qiskit 3: Laksanakan menggunakan Primitif Qiskit

Gelung ke atas set data dalam kelompok dan epok

Kita mula-mula melaksanakan algoritma penuh menggunakan simulator untuk penyahpepijatan sekilas dan untuk anggaran ralat. Kita kini boleh melalui keseluruhan set data dalam kelompok dalam bilangan epok yang dikehendaki untuk melatih rangkaian neural kuantum 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 Corak Qiskit 4: Pasca-proses, kembalikan hasil dalam format klasik

Ujian dan ketepatan

Kita kini mentafsir keputusan daripada latihan. Kita mula-mula menguji ketepatan latihan ke atas set latihan.

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%

Ketepatan latihan hanya 6060%, yang jelas tidak bagus. Sukar untuk dibayangkan bahawa prestasi model pada set ujian boleh lebih baik. Mari kita sahkan.

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 tidak mengkelaskan data ini dengan baik. Kita harus bertanya mengapa demikian, dan khususnya, kita perlu semak:

  • Adakah kita berhenti melatih terlalu awal? Adakah lebih banyak langkah pengoptimuman diperlukan?
  • Adakah kita membina ansatz yang buruk? Ini boleh bermakna banyak perkara. Apabila kita bekerja pada komputer kuantum sebenar, kedalaman litar akan menjadi pertimbangan utama. Bilangan parameter juga berpotensi penting, begitu juga dengan penghubungan antara Qubit.
  • Menggabungkan kedua-dua di atas, adakah kita membina ansatz dengan terlalu banyak parameter untuk boleh dilatih?

Kita boleh mulakan dengan menyemak penumpuan dalam pengoptimuman:

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()

Output of the previous code cell

Kita mungkin cuba melanjutkan langkah pengoptimuman untuk memastikan pengoptimum tidak terperangkap dalam minimum setempat dalam ruang parameter. Tetapi kelihatan agak menumpu. Mari kita lihat lebih dekat imej yang tidak diklasifikasikan dengan betul, dan cuba memahami apa yang berlaku.

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)

Output of the previous code cell

Di sini kita dapat melihat bahawa sebahagian besar imej yang salah diklasifikasikan mempunyai garisan menegak. Ada sesuatu tentang model kita yang gagal menangkap maklumat tentang itu. Mungkin kamu sudah dapat menjangkanya, berdasarkan litar varian pertama. Mari kita lihat lebih dekat.

Memperbaiki model

Langkah 1 disemak semula

Dalam memetakan masalah kita ke litar kuantum, kita seharusnya secara eksplisit memikirkan bagaimana maklumat dalam piksel bersebelahan menentukan kelas. Untuk mengenal pasti garisan mendatar, kita ingin tahu "jika piksel ii berwarna kuning, adakah piksel i+1i+1 berwarna kuning" untuk semua piksel di sepanjang setiap baris. Kita juga ingin tahu tentang garisan menegak. Tetapi oleh kerana pengelasan adalah binari, seseorang boleh membayangkan dengan mudah mengatakan bahawa jika garisan mendatar tidak dikesan, maka ia adalah garisan menegak. Litar varian kita sebelumnya mengandungi gate CNOT antara Qubit (dan oleh itu piksel) 0 dan 1, 1 dan 2, dan 2 dan 3. Itu merangkumi sebarang garisan mendatar di bahagian atas imej, tetapi ia tidak mengesan garisan menegak secara langsung, dan ia juga tidak mengesan garisan mendatar sepenuhnya, kerana ia mengabaikan baris bawah. Untuk mengesan semua garisan mendatar sepenuhnya, kita ingin mempunyai set gate CNOT yang serupa antara Qubit (piksel) 4 dan 5, 5 dan 6, dan 6 dan 7. Kita boleh ingat bahawa menambah gate CNOT antara Qubit yang sepadan dengan garisan menegak (seperti 0 dan 4, atau 2 dan 6) mungkin juga berguna. Tetapi kita akan semak dahulu sama ada sudah mencukupi untuk mengesan bahawa garisan mendatar ada atau tidak ada.

# 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

Output of the previous code cell

Kita tidak meningkatkan kedalaman litar. Mari kita lihat sama ada kita telah meningkatkan keupayaannya untuk memodelkan imej kita.

Langkah 2 disemak semula

Kita perlu mentranspil litar baharu ini untuk dijalankan pada Backend kuantum sebenar. Mari kita langkau langkah ini buat masa ini untuk melihat sama ada semakan litar varian kita memberi kesan yang dikehendaki pada simulator. Kita akan menyelami lebih dalam tentang transpilasi dalam subseksyen seterusnya.

Langkah 3 disemak semula

Kita kini menerapkan model yang dikemas kini kepada data latihan 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 disemak semula

Mari kita mulakan dengan menyemak sama ada pengoptimum kita telah menumpu sepenuhnya.

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()

Output of the previous code cell

Ini kelihatan tidak menumpu sepenuhnya, kerana fungsi kerugian tidak kekal kira-kira pada paras yang sama untuk banyak langkah. Tetapi fungsi kerugian sudah ~60% lebih rendah berbanding ketika menggunakan litar varian sebelumnya. Jika ini adalah projek penyelidikan, kita ingin memastikan penumpuan penuh. Tetapi untuk tujuan penerokaan, ini sudah mencukupi. Mari kita semak ketepatan pada data latihan dan ujian 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

Ketepatan $100\%$ pada kedua-dua set! Syak kita bahawa pengesanan tepat garisan mendatar sudah mencukupi ternyata betul! Selain itu, pemetaan kita daripada maklumat yang diperlukan tentang piksel kepada gate CNOT dalam litar kuantum adalah berkesan. Mari kita lihat sekarang bagaimana proses ini berskala untuk dijalankan pada komputer kuantum sebenar.
## Penskalaan dan menjalankan pada komputer kuantum sebenar \{#scaling-and-running-on-real-quantum-computers}

### Data \{#data}

Mari kita mulakan dengan meningkatkan saiz imej kita. Tiada yang istimewa tentang pilihan grid 6x6, kecuali ia melebihi bilangan Qubit (32) yang boleh kita simulasikan untuk litar menggunakan gate bukan-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)

Oleh kerana masa pengkomputeran kuantum adalah komoditi berharga, kita akan menggunakan set latihan yang sangat kecil, dan sangat sedikit langkah pengoptimuman. Ini sudah cukup untuk menunjukkan aliran 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)

Output of the previous code cell

Langkah 1: Petakan masalah ke litar 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 munasabah. Kita sepatutnya dapat mendapatkan keputusan berkualiti tinggi daripada komputer kuantum sebenar.

# 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

Oleh kerana kita menggunakan z_feature_map, yang tidak mempunyai gate CNOT, menambah lapisan pengekodan tidak meningkatkan kedalaman dua-Qubit kita. Kita boleh memvisualisasikan litar penuh di sini.

full_circuit.decompose().draw("mpl", style="clifford", idle_wires=False, fold=-1)

Output of the previous code cell

Kamu mungkin perasan bahawa jika meminimumkan kedalaman dua-Qubit adalah sangat penting, kita sebenarnya boleh mengurangkannya sedikit dengan mengubah susunan CNOT. Contohnya, CNOT pada q35q_{35} dan q34q_{34} boleh dialihkan ke kiri dalam rajah litar di atas, dan boleh diletakkan terus di bawah CNOT pada q30q_{30} dan q31q_{31}, sebagai contoh. Untuk kedalaman gate dua-Qubit sebanyak 5, tidak jelas bahawa ini akan membuat perbezaan selepas transpilasi, tetapi ia adalah sesuatu yang perlu diingat. Jika susunan gate CNOT adalah penting untuk pemodelan logik struktur data dalam imej kita, kedalaman di sini adalah baik. Jika susunan CNOT tidak kritikal untuk memodelkan struktur data dalam imej kita, maka kita boleh menulis skrip untuk menyusun semula gate CNOT ini bagi meminimumkan kedalaman.

Kita juga perlu mentakrifkan semula boleh-cerap kita dengan imej yang lebih besar:

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp.from_list([("Z" * (num_qubits), 1)])

Langkah Corak Qiskit 2: Optimumkan masalah untuk pelaksanaan kuantum

Kita mulakan dengan memilih Backend untuk pelaksanaan. Dalam kes ini, kita akan menggunakan Backend yang paling kurang 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 mentakrifkan pengurus laluan, dengan tahap pengoptimuman ditetapkan kepada 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 pengurus laluan beberapa kali. Untuk litar yang sangat lebar atau sangat dalam, terdapat kebolehubahan yang besar dalam kedalaman dua-Qubit yang ditranspil. Untuk litar sedemikian, adalah penting untuk mencuba pengurus laluan berkali-kali dan menggunakan hasil yang terbaik (paling cetek).

# 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 nampak bahawa dalam kes ini, kedalaman dua-Qubit yang ditranspil sentiasa 10. Terdapat variasi kecil dalam kedalaman satu-Qubit, dan kita akan menggunakan yang paling cetek. Tetapi pada litar 36-Qubit ini, ini bukan peningkatan yang kritikal. Kita boleh memvisualisasikan litar yang ditranspil ini, walaupun pada skala ini ia semakin sukar untuk dianalisis 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 Corak Qiskit 3: Laksanakan menggunakan Primitif Qiskit

Untuk mengehadkan masa yang digunakan pada komputer kuantum sebenar, kita hanya akan menjalankan beberapa langkah pengoptimuman di sini, dan kita melakukannya pada set latihan yang sangat kecil. Tetapi penskalaan ini kepada lebih banyak langkah pengoptimuman dan set data ujian yang lebih besar seharusnya jelas daripada arahan 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()

Adalah disyorkan untuk menyimpan parameter berat yang dikembalikan daripada pengiraan ini, sekiranya kamu memutuskan untuk melakukan iterasi selanjutnya.

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 boleh memplot beberapa langkah pengoptimuman pertama ini, walaupun kita tidak menjangkakan sebarang penumpuan selepas hanya beberapa langkah pengoptimuman. Keluk-keluk ini agak rata untuk beberapa langkah pertama, walaupun menggunakan simulator. Kita perlu perhatikan, bagaimanapun, bahawa pengoptimuman pada masa ini mempunyai 72 parameter bebas. Ini boleh dikurangkan sekurang-kurangnya 2-3 kali tanpa menjejaskan keputusan, misalnya dengan memparameterkan Qubit dengan data yang sepadan dengan subset baris dan lajur penuh. Memang, ruang parameter harus dikurangkan sebelum membelanjakan lebih banyak masa pengkomputeran kuantum untuk meminimumkan fungsi kerugian.

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()

Output of the previous code cell

Penutup

Untuk merumuskan, dalam pelajaran ini kita mempelajari aliran kerja untuk pengelasan binari imej menggunakan rangkaian neural kuantum. Beberapa pertimbangan utama dalam setiap langkah corak Qiskit ialah:

Langkah 1: Petakan masalah ke litar kuantum

  • Muatkan data latihan. Ini boleh dilakukan "secara manual" atau menggunakan peta ciri prabina seperti z_feature_map.
  • Bina ansatz yang mengandungi lapisan putaran dan penghubungan yang sesuai untuk masalah kamu.
  • Pantau kedalaman litar untuk memastikan keputusan berkualiti pada komputer kuantum.

Langkah 2: Optimumkan masalah untuk pelaksanaan kuantum

  • Pilih Backend, kerap kali yang paling kurang sibuk.
  • Gunakan pengurus laluan untuk mentranspil litar dan boleh-cerap kepada seni bina Backend yang dipilih.
  • Untuk litar yang sangat dalam atau lebar, transpil beberapa kali, dan pilih litar yang paling cetek.

Langkah 3: Laksanakan menggunakan Primitif Qiskit (Runtime)

  • Jalankan ujian awal pada simulator untuk menyahpepijat dan mengoptimumkan ansatz kamu.
  • Laksanakan pada komputer kuantum IBM®.

Langkah 4: Pasca-proses, kembalikan hasil dalam format klasik

  • Kira ketepatan model pada data latihan, dan pada data ujian.
  • Pantau penumpuan pengoptimuman klasik.

Rujukan

[1] https://arxiv.org/abs/2405.00781

Source: IBM Quantum docs — updated 15 Jan 2026
English version on doQumentation — updated 7 Mei 2026
This translation based on the English version of approx. 26 Mac 2026