Quantum Convolutional Neural Networks

How well can QML classify classical datasets? In this paper[1], we use various Quantum Convolutional Neural Networks (QCNN) for image data classification tasks. We benchmarked various QCNN models differentiated by structures of parameterized quantum circuits, quantum data encoding methods, classical data pre-processing methods, cost functions and optimizers on MNIST and Fashion MNIST datasets. This is a introductory tutorial for the paper and the full code can be found in this Github repo.

QCNN is a local and translationally invariant variational quantum model suggested by Cong et al.[2]. One of the key feature of QCNN is it reduces the number of qubit each layer. Due to its similarity with classical convolutional neural networks, people often use the term convolutional and pooling layers. The original work uses QCNN to classify quantum data in quantum phase recognition problem. Here, we will focus on classifying classical data.

This tutorial was tested under following environment:

python==3.10
pennylane==0.27.0
tensorflow==2.19.0

Alt text for the image

0. Data Loading

Code
import pennylane as qml
from pennylane import numpy as np
import tensorflow as tf

np.random.seed(42)
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = x_train[..., np.newaxis] / 255.0, x_test[..., np.newaxis] / 255.0 
/Users/tak/anaconda3/envs/new/lib/python3.10/site-packages/requests/__init__.py:86: RequestsDependencyWarning: Unable to find acceptable character detection dependency (chardet or charset_normalizer).
  warnings.warn(

Filter class 0 and 1 of MNIST dataset for binary classification tasks.

Code
x_train_filter_01 = np.where((y_train == 0) | (y_train == 1))
x_test_filter_01 = np.where((y_test == 0) | (y_test == 1))

X_train, X_test = x_train[x_train_filter_01], x_test[x_test_filter_01]
Y_train, Y_test = y_train[x_train_filter_01], y_test[x_test_filter_01]

Resize 28 by 28 image into 256 dimensional vectors so we can fit into 8 qubit QCNN model.

Code
X_train = tf.image.resize(X_train[:], (256, 1)).numpy()
X_test = tf.image.resize(X_test[:], (256, 1)).numpy()
X_train, X_test = tf.squeeze(X_train).numpy(), tf.squeeze(X_test).numpy()

1. Quantum Embedding

In order to process classical data with quantum hardware, we must first map classical data in a quantum state. Here, we will use Amplitude Embedding scheme.

Code
def data_embedding(X):
    qml.AmplitudeEmbedding(X, wires=range(8), normalize=True)

2. Unitary Ansatz

QCNN model utilizes local trainable unitaries. Here, we are interested in 2-local architecture. Below is parameterized circuit that can express general SU4 unitaries with 15 parameters.

Code
def U_SU4(params, wires): # 15 params
    qml.U3(params[0], params[1], params[2], wires=wires[0])
    qml.U3(params[3], params[4], params[5], wires=wires[1])
    qml.CNOT(wires=[wires[0], wires[1]])
    qml.RY(params[6], wires=wires[0])
    qml.RZ(params[7], wires=wires[1])
    qml.CNOT(wires=[wires[1], wires[0]])
    qml.RY(params[8], wires=wires[0])
    qml.CNOT(wires=[wires[0], wires[1]])
    qml.U3(params[9], params[10], params[11], wires=wires[0])
    qml.U3(params[12], params[13], params[14], wires=wires[1])

3. QCNN Circuit

Here, we will define a QCNN structure. For convolutional layer, we will consider all two nearest neighbour two qubit unitaries. We will assume periodic boundary condition where the first qubit and the last qubits are connected as well. For the pooling layer, we will trace out the qubits without applying any additional gates.

Code
def conv_layer1(U, params):
    U(params, wires=[0, 7])
    for i in range(0, 8, 2):
        U(params, wires=[i, i + 1])
    for i in range(1, 7, 2):
        U(params, wires=[i, i + 1])
def conv_layer2(U, params):
    U(params, wires=[0, 6])
    U(params, wires=[0, 2])
    U(params, wires=[4, 6])
    U(params, wires=[2, 4])
def conv_layer3(U, params):
    U(params, wires=[0,4])
Code
def QCNN_structure_without_pooling(U, params):
    param1 = params[0:15]
    param2 = params[15:30]
    param3 = params[30:45]

    conv_layer1(U, param1)
    conv_layer2(U, param2)
    conv_layer3(U, param3)

Full QCNN model can be constructed by concatenating data embedding, QCNN ansatz, and measurement.

Code
dev = qml.device('default.qubit', wires = 8)
@qml.qnode(dev)
def QCNN(X, params):

    data_embedding(X)
    QCNN_structure_without_pooling(U_SU4, params)
    result = qml.expval(qml.PauliZ(4))
    return result

5. Training

We train QCNN model with MSE loss fucntion. Here, we will train for 100 steps, with batch size 25 and learning rate 0.01.

Code
def square_loss(labels, predictions):
    loss = 0
    for l, p in zip(labels, predictions):
        loss = loss + (l - p) ** 2
    loss = loss / len(labels)
    return loss
Code
def cost(params, X, Y):
    predictions = [QCNN(x, params) for x in X]
    loss = square_loss(Y, predictions)
    return loss
Code
steps = 100
learning_rate = 0.01
batch_size = 25


params = np.random.randn(45, requires_grad=True)
opt = qml.NesterovMomentumOptimizer(stepsize=learning_rate)
loss_history = []

for it in range(steps):
    batch_index = np.random.randint(0, len(X_train), (batch_size,))
    X_batch = [X_train[i] for i in batch_index]
    Y_batch = [Y_train[i] for i in batch_index]
    params, cost_new = opt.step_and_cost(lambda v: cost(v, X_batch, Y_batch), params)
    loss_history.append(cost_new)
    if it % 10 == 0:
        print("iteration: ", it, " cost: ", cost_new)
/Users/tak/anaconda3/envs/new/lib/python3.10/site-packages/autograd/numpy/numpy_vjps.py:943: ComplexWarning: Casting complex values to real discards the imaginary part
  onp.add.at(A, idx, x)
iteration:  0  cost:  0.44686799204947114
iteration:  10  cost:  0.29208977354527255
iteration:  20  cost:  0.37411009764285796
iteration:  30  cost:  0.27463692530358796
iteration:  40  cost:  0.18627018190756262
iteration:  50  cost:  0.0803754591165053
iteration:  60  cost:  0.1137624847960277
iteration:  70  cost:  0.08537221852104945
iteration:  80  cost:  0.06860804023485788
iteration:  90  cost:  0.0806990055170192

6. Accuracy Test

As we trained QCNN models, let’s see how well it can classify the images.

Code
def accuracy_test(predictions, labels):
    acc = 0
    for l, p in zip(labels, predictions):
        if np.abs(l - p) < 0.5:
            acc = acc + 1
    return acc / len(labels)
Code
predictions = [QCNN(x, params) for x in X_test]
accuracy = accuracy_test(predictions, Y_test)
accuracy = accuracy * 100
Code
print(f"Test data accuracy with QCNN model: {accuracy:.3}%")
Test data accuracy with QCNN model: 98.4%

Our simple QCNN model classifies the MNIST data with 98.4% accuracy!

In this introductory tutorial, we only looked at QCNN with resizing (interpolation) classical preprocessing, amplitude embedding, SU4 ansatz without pooling layer, MSE loss function applied on MNIST datasets. If you want to see more of the results, look at the original paper and code!

References

  1. Tak Hur, Leeseok Kim, and Daniel K Park. Quantum convolutional neural network for classical data classification. Quantum Machine Intelligence (2022).
  2. Iris Cong, Soonwon Choi, and Mikhail D. Lukin. Quantum convolutional neural networks. Nature Physics (2019).

Citation

If you use this code in research, please cite:

@article{hur2022quantum,
  title={Quantum convolutional neural network for classical data classification},
  author={Hur, Tak and Kim, Leeseok and Park, Daniel K},
  journal={Quantum Machine Intelligence},
  volume={4},
  number={1},
  pages={3},
  year={2022},
  publisher={Springer}
}