量子卷積神經網絡
卷積和池化:卷積神經網絡的必備成分
卷積神經網絡被廣泛應用于圖像和音頻的識別當中,關鍵在于“卷積”操作賦予神經網絡統籌學習數據的能力。
執行卷積操作需要輸入數據與卷積核,卷積核首先與輸入數據左上角對齊,隨后逐個計算對應位置處兩個數字之積。當對卷積核中所有的元素均執行該乘積操作后,將結果全部加起來作為新的輸出。在下圖中,我們可以看到卷積核最多只能輸出9個數據(就像游戲2048一樣)。
下面的動態圖能更直觀的展現這一過程
每一次池化操作都需要輸入數據和池化核,池化核會根據某一要求挑選出符合條件的元素作為代表。
如果我們執行最大池化,那么池化核會首先與輸入數據左上角對齊,挑選出范圍內最大的元素作為輸出。如果我們執行平均池化,那么池化核會首先與輸入數據左上角對齊,挑選出范圍內所有元素的平均值作為輸出。
一般而言,一個卷積神經網絡會間隔包含卷積層和池化層,多次重復后,再加上一個全連接層(Fully Connected),就能得到最終數據了。我們一般使用經過卷積神經網絡處理后的圖片或音頻進行機器學習,這樣學習的參數更少,速度更快,同時保留了原先數據的特點。
量子卷積與量子池化
在量子電路中,我們不能隨心所欲的創建和使用數據。在經典卷積神經網絡中,輸出和輸入或許是分離存儲的,但是在量子電路中,我們只能一直使用若干條量子電路,不能在執行過程中將其切開。
根據相關論文結果,量子中的卷積操作如下
最一般的量子卷積包含了15個參數,但是過多的參數會嚴重延長訓練時間,出于教學目的,我們使用這一簡化的卷積操作。
根據相關論文結果,量子中的池化操作如下
我們像經典池化操作一樣拋棄一些數據,這里我們拋棄位于上方的量子比特,保留下方的量子比特。
使用Qiskit將卷積和池化寫成代碼形式
def conv_circuit(params):target = QuantumCircuit(2)target.rz(-np.pi / 2, 1)target.cx(1, 0)target.rz(params[0], 0)target.ry(params[1], 1)target.cx(0, 1)target.ry(params[2], 1)target.cx(1, 0)target.rz(np.pi / 2, 0)return target
def pool_circuit(params):target = QuantumCircuit(2)target.rz(-np.pi / 2, 1)target.cx(1, 0)target.rz(params[0], 0)target.ry(params[1], 1)target.cx(0, 1)target.ry(params[2], 1)return target
而在一個大型量子電路中,我們不能只在某兩個量子比特上使用卷積和池化,所以我們需要編寫一套適用于大型量子電路的卷積和池化函數
def conv_layer(num_qubits, param_prefix):qc = QuantumCircuit(num_qubits, name="Convolutional Layer")qubits = list(range(num_qubits))param_index = 0params = ParameterVector(param_prefix, length=num_qubits * 3)for q1, q2 in zip(qubits[0::2], qubits[1::2]):qc = qc.compose(conv_circuit(params[param_index : (param_index + 3)]), [q1, q2])qc.barrier()param_index += 3for q1, q2 in zip(qubits[1::2], qubits[2::2] + [0]):qc = qc.compose(conv_circuit(params[param_index : (param_index + 3)]), [q1, q2])qc.barrier()param_index += 3qc_inst = qc.to_instruction()qc = QuantumCircuit(num_qubits)qc.append(qc_inst, qubits)return qc
對于四個量子比特的電路,結果 如下
容易看出該函數的意義即對電路中任意兩個不同量子比特施加卷積操作。
同樣地,我們也可以定義適用于大型量子電路的池化函數
def pool_layer(sources, sinks, param_prefix):num_qubits = len(sources) + len(sinks)qc = QuantumCircuit(num_qubits, name="Pooling Layer")param_index = 0params = ParameterVector(param_prefix, length=num_qubits // 2 * 3)for source, sink in zip(sources, sinks):qc = qc.compose(pool_circuit(params[param_index : (param_index + 3)]), [source, sink])qc.barrier()param_index += 3qc_inst = qc.to_instruction()qc = QuantumCircuit(num_qubits)qc.append(qc_inst, range(num_qubits))return qc
可以看出,池化操作先把整個電路分成若干個量子比特對,對與對之間相互不干擾,每個對內再執行池化操作。
實戰演練:水平線與豎直線的分類
首先我們導入本次用到的所有庫
import json
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import clear_output
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import ZFeatureMap
from qiskit.quantum_info import SparsePauliOp
from qiskit_algorithms.optimizers import COBYLA
from qiskit_algorithms.utils import algorithm_globals
from qiskit_machine_learning.algorithms.classifiers import NeuralNetworkClassifier
from qiskit_machine_learning.neural_networks import EstimatorQNN
from sklearn.model_selection import train_test_splitalgorithm_globals.random_seed = 12345
進一步,手動生成數據。我們會編寫函數生成大量包含水平線或豎直線的圖片。
def generate_dataset(num_images):images = []labels = []hor_array = np.zeros((6, 8))ver_array = np.zeros((4, 8))j = 0for i in range(0, 7):if i != 3:hor_array[j][i] = np.pi / 2hor_array[j][i + 1] = np.pi / 2j += 1j = 0for i in range(0, 4):ver_array[j][i] = np.pi / 2ver_array[j][i + 4] = np.pi / 2j += 1for n in range(num_images):rng = algorithm_globals.random.integers(0, 2)if rng == 0:labels.append(-1)random_image = algorithm_globals.random.integers(0, 6)images.append(np.array(hor_array[random_image]))elif rng == 1:labels.append(1)random_image = algorithm_globals.random.integers(0, 4)images.append(np.array(ver_array[random_image]))# Create noisefor i in range(8):if images[-1][i] == 0:images[-1][i] = algorithm_globals.random.uniform(0, np.pi / 4)return images, labels
進行訓練集和測試集分割
images, labels = generate_dataset(50)train_images, test_images, train_labels, test_labels = train_test_split(images, labels, test_size=0.3
)
為了將經典數據編碼到量子電路中,我們還是需要一個特征映射。這里我們使用ZFeatureMao而不是ZZFeatureMap。
feature_map = ZFeatureMap(8)
feature_map.decompose().draw("mpl")
組合卷積層和池化層,創建量子卷積神經網絡
feature_map = ZFeatureMap(8)ansatz = QuantumCircuit(8, name="Ansatz")# First Convolutional Layer
ansatz.compose(conv_layer(8, "с1"), list(range(8)), inplace=True)# First Pooling Layer
ansatz.compose(pool_layer([0, 1, 2, 3], [4, 5, 6, 7], "p1"), list(range(8)), inplace=True)# Second Convolutional Layer
ansatz.compose(conv_layer(4, "c2"), list(range(4, 8)), inplace=True)# Second Pooling Layer
ansatz.compose(pool_layer([0, 1], [2, 3], "p2"), list(range(4, 8)), inplace=True)# Third Convolutional Layer
ansatz.compose(conv_layer(2, "c3"), list(range(6, 8)), inplace=True)# Third Pooling Layer
ansatz.compose(pool_layer([0], [1], "p3"), list(range(6, 8)), inplace=True)# Combining the feature map and ansatz
circuit = QuantumCircuit(8)
circuit.compose(feature_map, range(8), inplace=True)
circuit.compose(ansatz, range(8), inplace=True)observable = SparsePauliOp.from_list([("Z" + "I" * 7, 1)])# we decompose the circuit for the QNN to avoid additional data copying
qnn = EstimatorQNN(circuit=circuit.decompose(),observables=observable,input_params=feature_map.parameters,weight_params=ansatz.parameters,
)
根據量子卷積神經網絡創建分類器
classifier = NeuralNetworkClassifier(qnn,optimizer=COBYLA(maxiter=200), # Set max iterations here
)
擬合數據,測試結果
x = np.asarray(train_images)
y = np.asarray(train_labels)
classifier.fit(x, y)y_predict = classifier.predict(test_images)
x = np.asarray(test_images)
y = np.asarray(test_labels)
print(f"Accuracy from the test data : {np.round(100 * classifier.score(x, y), 2)}%")
如果將結果可視化的話(詳見Qiskit量子卷積神經網絡),應如下圖