Quantum Classifier
In this tutorial, we show how to utilize QNN to handle discriminative tasks based on PennyLane library. Specifically, we aim to implement a quantum binary classifier for the Wine dataset. The primary pipeline includes following steps:
- Load and preprocess the Wine dataset.
- Implement a quantum read-in protocol to load classical data into quantum states.
- Construct a parameterized quantum circuit model to process the input.
- Train the whole circuit and test.
We begin by importing all related libraries:
import sklearn
import sklearn.datasets
import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import AdamOptimizer
import matplotlib.pyplot as plt
We then prepare the Wine dataset for classification task. For simplicity, we focus on the the first two class of the whole Wine dataset. There are 13 attributes determining the origin of wines, where the value of each attribute ranges differently. The normizalization preprocessing is applied to this dataset to re-scale the attribute value to the range of $[0, \pi]$. Meantime, the label is re-mapped from ${0,1}$ to ${-1,1}$ for alignment with value range of the read-out result of quantum circuit model, which will be introduced later. For later generation test of quantum classifier, we fairly split the dataset into training data and test data.
def load_wine(split_ratio = 0.5):
feat, label = sklearn.datasets.load_wine(return_X_y=True)
# normalization
feat = np.pi * (feat - np.min(feat, axis=0, keepdims=True)) / np.ptp(feat, axis=0, keepdims=True)
index_c0 = label == 0
index_c1 = label == 1
label = label * 2 - 1
n_c0 = sum(index_c0)
n_c1 = sum(index_c1)
X_train = np.concatenate((feat[index_c0][:int(split_ratio*n_c0)], feat[index_c1][:int(split_ratio*n_c1)]), axis=0)
y_train = np.concatenate((label[index_c0][:int(split_ratio*n_c0)], label[index_c1][:int(split_ratio*n_c1)]), axis=0)
X_test = np.concatenate((feat[index_c0][int(split_ratio*n_c0):], feat[index_c1][int(split_ratio*n_c1):]), axis=0)
y_test = np.concatenate((label[index_c0][int(split_ratio*n_c0):], label[index_c1][int(split_ratio*n_c1):]), axis=0)
return X_train, y_train, X_test, y_test
X_train, y_train, X_test, y_test = load_wine()
To see what the original distribution of Wine dataset looks like, we employ the t-SNE visualization technique:
def visualize_dataset(X, y):
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
wine_tsne = tsne.fit_transform(X)
for label in np.unique(y):
indices = y == label
plt.scatter(wine_tsne[indices, 0], wine_tsne[indices, 1], edgecolor='black', cmap='coolwarm', s=20, label=f'Class {label}')
# Add labels and legend
plt.title("t-SNE Visualization of Wine dataset (two classes)")
plt.xlabel("t-SNE Dimension 1")
plt.ylabel("t-SNE Dimension 2")
plt.legend()
plt.tight_layout()
plt.show()
visualize_dataset(X_train, y_train)
To encode the 13 attributes in the Wine dataset into a quantum system, we use the angle embedding technique, followed by a layer of CNOT gates acting on neighboring qubits.
def data_encoding(x):
n_qubit = len(x)
qml.AngleEmbedding(features =x , wires = range(n_qubit) , rotation ="X")
for i in range(n_qubit):
if i+1 < n_qubit:
qml.CNOT(wires=[i, i+1])
With the data encoding, we construct a quantum binary classifier. Specifically, the circuit model consists multiple layers, where each layer encomposes parameterized single-qubit rotation gates with trainable angles and a block of non-parametric CNOT gate to introduce entanglement. To read-out the category information of each input sample from the prepared state, we estimate the expectation value of Pauli-Z operator with respect to the first qubit.
def classifier(param, x=None):
data_encoding(x)
n_layer, n_qubit = param.shape[0], param.shape[1]
for i in range(n_layer):
for j in range(n_qubit):
qml.Rot(param[i, j, 0], param[i, j, 1], param[i, j, 2], wires=j)
for j in range(n_qubit):
if j+1 < n_qubit:
qml.CNOT(wires=[j, j+1])
return qml.expval(qml.PauliZ(0))
n_qubit = X_train.shape[1]
dev = qml.device('default.qubit', wires=n_qubit)
circuit = qml.QNode(classifier, dev)
We visualize the whole quantum circuit of 2 layers by drawing the diagram:
fig, ax = qml.draw_mpl(circuit)(np.pi * np.random.randn(2, n_qubit, 3), X_train[0])
fig.show()
With the data and circuit model ready, we now move to the optimization of the quantum classifier. In the framework of supervised learning, we employ the mean square error as the loss function:
def mse_loss(predict, label):
return np.mean((predict - label)**2)
def cost(param, circuit, X, y):
exp = []
for i in range(len(X)):
pred = circuit(param, x=X[i])
exp.append(pred)
return mse_loss(np.array(exp), y)
To measure the classification accuracy of the quantum classifier, we define the metric of accuracy. Concretely, if the sign of the read-out result is the same as the corresponding label, we think the classifier predicts correctly. Otherwise, the classifier fails. The accuracy is counted as the proportion of the correctly classified samples.
def accuracy(predicts, labels):
assert len(predicts) == len(labels)
return np.sum((np.sign(predicts)*labels+1)/2)/len(predicts)
We randomly initialize the parameters of the circuit:
n_layer = 4
param = np.pi * np.random.randn(n_layer, n_qubit, 3)
We next train the quantum classifier with ADAM optimizer
lr = 0.01
opt = AdamOptimizer(lr)
batch_size = 4
n_epoch = 50
cost_train, cost_test, acc_train, acc_test = [], [], [], []
for i in range(n_epoch):
index = np.random.permutation(len(X_train))
feat_train, label_train = X_train[index], y_train[index]
for j in range(0, len(X_train), batch_size):
feat_train_batch = feat_train[j*batch_size:(j+1)*batch_size]
label_train_batch = label_train[j*batch_size:(j+1)*batch_size]
param = opt.step(lambda v: cost(v, circuit, feat_train_batch, label_train_batch), param)
# compute cost
cost_train.append(cost(param, circuit, X_train, y_train))
cost_test.append(cost(param, circuit, X_test, y_test))
# compute accuracy
pred_train = []
for j in range(len(X_train)):
pred_train.append(circuit(param, x=X_train[j]))
acc_train.append(accuracy(np.array(pred_train), y_train))
pred_test = []
for j in range(len(X_test)):
pred_test.append(circuit(param, x=X_test[j]))
acc_test.append(accuracy(np.array(pred_test), y_test))
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
epochs = np.arange(n_epoch) + 1
plt.plot(epochs, cost_train, label='Training Cost', marker='o')
plt.plot(epochs, cost_test, label='Test Cost', marker='o')
plt.title('Cost Over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Cost')
plt.legend()
plt.grid()
# Plot training and test accuracy
plt.subplot(1, 2, 2)
plt.plot(epochs, acc_train, label='Training Accuracy', marker='o')
plt.plot(epochs, acc_test, label='Test Accuracy', marker='o')
plt.title('Accuracy Over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.grid()
plt.tight_layout()
plt.show()