Building a Variational Quantum Classifier from Scratch: A Step-by-Step Guide
As quantum computing steadily advances from a theoretical curiosity toward practical utility, the fusion of quantum devices with machine learning techniques often referred to as Quantum Machine Learning (QML) has emerged as a promising frontier. While classical machine learning has already revolutionized fields like computer vision, natural language processing, and recommender systems, quantum computing may provide new avenues to tackle problems that are computationally hard for conventional computers.
In this article, we’ll walk through the process of building and training a simple Variational Quantum Classifier (VQC) using Python and PennyLane. We’ll start with a basic, two-dimensional classification problem and then move on to encoding this classical data into a quantum state. From there, we’ll apply a parameterized quantum circuit and use classical optimization techniques to find the best parameters for classification. Finally, we’ll visualize our training progress and discuss how you can expand on this foundational work.
What is a Variational Quantum Classifier?
A Variational Quantum Classifier (VQC) is a hybrid model that blends the strengths of quantum and classical computation. In a VQC:
- Classical Data Encoding: We start with classical inputs, think of them as points on a 2D plane or general n-dimensional features and encode them into the states of quantum bits (qubits).
- Parameterized Quantum Circuit (Ansatz): We design a quantum circuit whose gates depend on a set of parameters (real numbers). Adjusting these parameters changes how the quantum state transforms, ultimately shaping the decision boundary the model learns.
- Measurement & Prediction: By measuring certain qubits (often looking at expectation values of Pauli operators), we obtain a number that we treat as the model’s raw prediction. For binary classification tasks, this output can be interpreted as a label after a suitable transformation, like taking the sign.
- Classical Optimization: Although the model is quantum, we still rely on classical optimization methods (like gradient descent) to tweak the parameters. PennyLane makes it possible to compute gradients of quantum circuits automatically, so we can train the model just as we would a neural network.
Why Explore a VQC?
While classical models are powerful and often simpler, exploring quantum approaches could reveal scenarios where quantum effects speed up learning or model certain distributions more naturally. Even if we’re not at the point of “quantum advantage” today, learning how these models work is an investment in the future, as better quantum hardware and new quantum algorithms emerge.
Step-by-Step Implementation
Prerequisites:
Generating and Preparing Data
We’ll start with a small synthetic dataset. Using scikit-learn’s make_classification
, we can create a simple binary classification problem with two features. We normalize the data and split it into training and test sets. Our labels will be converted from {0, 1} to {-1, +1}, which aligns nicely with the range of the quantum circuit’s output.
import pennylane as qml
from pennylane import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
# Generate a 2D dataset
X, y = make_classification(n_samples=100, n_features=2, n_informative=2,
n_redundant=0, n_clusters_per_class=1, random_state=42)
# Normalize features
X = (X - np.mean(X, axis=0)) / np.std(X, axis=0)
# Split into train/test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Convert labels from {0,1} to {-1,+1}
y_train = 2*y_train - 1
y_test = 2*y_test - 1
Setting Up the Quantum Device
We’ll use PennyLane’s built-in simulator, default.qubit
, and choose 2 qubits. This is often enough for a toy problem, though you can scale up as needed.
n_qubits = 2
dev = qml.device("default.qubit", wires=n_qubits)
Defining the Quantum Circuit
Our circuit has two parts: (1) Encoding the input features into qubit states, and (2) applying a variational ansatz with adjustable parameters.
- Feature Encoding: We use
AngleEmbedding
to rotate qubits based on input features. For a two-dimensional input (x1,x2), each qubit gets rotated by one of these values, preparing a state dependent on the input. - Ansatz: We’ll stack a few layers of single-qubit rotations (RX, RY, RZ) and CNOT gates to entangle the qubits. Each rotation has parameters we’ll optimize.
def feature_encoding(x):
qml.AngleEmbedding(x, wires=range(n_qubits), rotation='Y')
def variational_circuit(params, x):
feature_encoding(x)
for layer_params in params:
for i, wire_params in enumerate(layer_params):
qml.RX(wire_params[0], wires=i)
qml.RY(wire_params[1], wires=i)
qml.RZ(wire_params[2], wires=i)
# Entangle qubits
for i in range(n_qubits - 1):
qml.CNOT(wires=[i, i+1])
# Measure expectation value on the first qubit’s Z-axis
return qml.expval(qml.PauliZ(0))
@qml.qnode(dev, interface='autograd')
def quantum_model(params, x):
return variational_circuit(params, x)
The measured expectation value will be in [-1, +1]. After training, values close to +1 might correspond to one class, and values close to -1 to the other.
Defining Loss and Accuracy
We use a mean squared error (MSE) loss. If the prediction matches the label exactly, the error is zero. Accuracy is computed by taking the sign of the prediction and comparing it to the true label.
def loss(params, X, Y):
predictions = [quantum_model(params, x) for x in X]
predictions = np.stack(predictions)
return np.mean((predictions - Y)**2)
def accuracy(params, X, Y):
predictions = [quantum_model(params, x) for x in X]
predictions = np.sign(np.stack(predictions))
return np.mean(predictions == Y)
Parameter Initialization and Training
We’ll start with small random parameters. PennyLane can compute gradients automatically, allowing us to use standard optimization loops.
num_layers = 2
params = 0.01 * np.random.randn(num_layers, n_qubits, 3)
opt = qml.GradientDescentOptimizer(stepsize=0.1)
num_epochs = 50
epoch_list = []
loss_list = []
train_acc_list = []
test_acc_list = []
for epoch in range(num_epochs):
params = opt.step(lambda v: loss(v, X_train, y_train), params)
current_loss = loss(params, X_train, y_train)
train_acc = accuracy(params, X_train, y_train)
test_acc = accuracy(params, X_test, y_test)
epoch_list.append(epoch+1)
loss_list.append(current_loss)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print(f"Epoch {epoch+1}/{num_epochs}: Loss = {current_loss:.4f}, "
f"Train Acc = {train_acc:.2f}, Test Acc = {test_acc:.2f}")
Here, each iteration:
- Computes the gradient of the loss w.r.t. parameters,
- Updates the parameters in the direction that reduces the loss,
- Measures the current performance on both training and test sets.
Results and Visualization
After training, we print final accuracies and plot the metrics:
final_train_acc = accuracy(params, X_train, y_train)
final_test_acc = accuracy(params, X_test, y_test)
print(f"Final Training Accuracy: {final_train_acc:.2f}")
print(f"Final Test Accuracy: {final_test_acc:.2f}")
plt.figure(figsize=(10,4))
# Loss plot
plt.subplot(1, 2, 1)
plt.plot(epoch_list, loss_list, marker='o', color='blue')
plt.title("Training Loss over Epochs")
plt.xlabel("Epoch")
plt.ylabel("Loss")
# Accuracy plot
plt.subplot(1, 2, 2)
plt.plot(epoch_list, train_acc_list, marker='o', label="Train Acc", color='green')
plt.plot(epoch_list, test_acc_list, marker='o', label="Test Acc", color='red')
plt.title("Accuracy over Epochs")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.tight_layout()
plt.show()
- Loss over Epochs: Ideally, it decreases as the classifier learns.
- Training and Test Accuracy: Training accuracy typically improves faster. Test accuracy shows how well the model generalizes. If both improve, that’s a good sign. If the test accuracy lags behind or flatlines, you might need more layers, more data, or different optimization strategies.
Potential Next Steps:
- More Complex Ansatz: Adding more layers or different gate patterns may improve accuracy.
- Advanced Optimization Techniques: Try Adam or other adaptive optimizers to handle tricky landscapes.
- Noise and Real Hardware: Simulate realistic noise models or run on actual quantum hardware via cloud services to see how robust your model is.
- Alternative Encodings: Explore different feature encoding schemes (like amplitude encoding) to represent classical data as quantum states more efficiently.
Why This Matters
While current quantum devices are noisy and limited in scale, exploring QML now positions you to leverage future hardware improvements. Variational approaches like this one could eventually surpass classical methods for certain tasks, offering glimpses into the power of quantum-enhanced intelligence. Even now, understanding these methods and building prototypes enriches your skill set and expands the horizons of what’s computationally possible.
Conclusion
We started with a straightforward binary classification task and implemented a Variational Quantum Classifier with PennyLane. This hands-on approach serves as a foundation for exploring more advanced QML topics. As quantum hardware matures, these foundational skills will position you to take advantage of quantum speed-ups, better understand the quantum learning landscape, and contribute to the growing field of quantum machine learning.
Whether you’re a quantum enthusiast, a machine learning researcher, or just curious about the cutting edge of computation, experimenting with VQCs provides a glimpse into a possible future where quantum computers and classical ML systems work hand in hand to solve previously intractable problems.
You can find entire code here: https://github.com/neuralsorcerer/variational-quantum-classifier
Check out this awesome video on Quantum Machine Learning from Qiskit Summer School!
Thank you for reading! If you found this article helpful, feel free to share it with others.