Quantum machine learning for Classification

High-level intro to classical machine learning engineer

Xin Cheng
8 min readNov 26, 2024

On high-level, classical machine learning is similar to optimization problem, for which the target is to optimize some loss function by adjusting parameters. Quantum machine learning is similar in this sense (especially with quantum-classical hybrid approach). Let’s build some intuitions first.

In classical machine learning, to train ML model, generally steps look like

  1. Data Preprocessing: clean, normalize, data imbalance, missing value, outliers, one-hot encoding, etc.
  2. Algorithm selection and setup: decision tree, regression, svm, neural network. You can also build your own model.
  3. Define loss function: provide target you try to optimize
  4. Training loop: fit training data, use loss function to direct parameter update and repeat until convergence (or for a set number of epochs)
  5. Model evaluation: use test dataset to evaluate trained model

Similarly, steps for training quantum machine learning model look like

  1. Data Preprocessing: data needs to be encoded in quantum states
  2. Algorithm selection and setup: qsvm, qnn are provided as quantum-version of svm, neural network, etc. At backend, they are mapped to quantum circuits. You can also design your quantum circuits.
  3. Define loss function: in quantum machine learning, generally first define observable which can be measured after training input is passed to quantum circuits, then the objective function can direct how to update parameters
  4. Training loop: pass training inputs to quantum circuit, measure quantum state, compute loss function classically and use that to update quantum circuit’s parameters using classical optimizer, repeat until convergence (or for a set number of epochs)
  5. Model evaluation: use test dataset to evaluate trained model (pass test inputs to quantum circuit and measure result quantum state, evaluate performance using classical metrics (e.g. accuracy, precision, recall, F1 score for classification).)

Data encoding

To use a quantum algorithm, classical data must somehow be brought into a quantum circuit. This is usually referred to as data encoding, but is also called data loading.

Basis encoding

To encode a vector (4,8,5)/3 features, first find the maximum value 8. Then convert to binary: 8 in binary is 1000. Count the Bits: There are 4 bits in 1000. Assign qubits: 4 qubits is needed to represent numbers up to 8. Then prepare an array to set those positions (in this case index 4, 5, 8)to value 1/sqrt(number of features, which is 3), e.g.

import math
from qiskit import QuantumCircuit
desired_state = [
0,
0,
0,
0,
1 / math.sqrt(3),
1 / math.sqrt(3),
0,
0,
1 / math.sqrt(3),
0,
0,
0,
0,
0,
0,
0]
qc = QuantumCircuit(4)
qc.initialize(desired_state, [0,1,2,3])
qc.decompose(reps = 7).draw(output = 'mpl')

Amplitude encoding: benefit is that it requires only log2(N) qubits to encode. However, subsequent algorithms must operate on the amplitudes of a quantum state, and methods to prepare and measure the quantum states tend not to be efficient.

First determine how many features, for same vector (4,8,5)/3 features, use 2 qubits (²²=4>3). Find normalization value by sqrt(sum of (each value)²) = sqrt(105)

desired_state = [
1 / math.sqrt(105) * 4,
1 / math.sqrt(105) * 8,
1 / math.sqrt(105) * 5,
1 / math.sqrt(105) * 0]
qc = QuantumCircuit(2)
qc.initialize(desired_state, [0,1])
qc.decompose(reps = 5).draw(output = 'mpl')

Angle encoding/rotation encoding: for QML models using Pauli feature maps such as quantum support vector machines (QSVMs) and variational quantum circuits (VQCs). Each feature value is mapped to a corresponding qubit. Rotates the qubit state around X, Y (common in the literature), or Z axis by an angle θ. Vector (0,π/4,π/2) encoded as

qc = QuantumCircuit(3)
qc.ry(0, 0)
qc.ry(2*math.pi/4, 1)
qc.ry(2*math.pi/2, 2)
qc.draw(output = 'mpl')

Phase encoding: similar to angle encoding, but shifts the phase of the qubit’s state by angle θ using phase shift gate P. Is used in many quantum feature maps, particularly Z and ZZ feature maps, and general Pauli feature maps.

qc = QuantumCircuit(1)
qc.h(0) # Hadamard gate rotates state down to Bloch equator
state1 = Statevector.from_instruction(qc)
qc.p(pi/2, 0) # Phase gate rotates by an angle pi/2
state2 = Statevector.from_instruction(qc)
states = state1, state2
qc.draw('mpl', scale=1)

Dense encoding: a combination of angle encoding and phase encoding. DAE allows two feature values to be encoded in a single qubit: one angle with a Y-axis rotation angle, and the other with a z-axis rotation angle

qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(3*pi/8, 0)
state2 = Statevector.from_instruction(qc)
qc.rz(7*pi/4, 0)
state3 = Statevector.from_instruction(qc)
states = state1, state2, state3
plot_Nstates(states, axis=None, plot_trace_points=True)

Encoding with built-in feature maps

EfficientSU2: template circuit for variational algorithm and efficient on quantum hardware. Interestingly, because 1 qubit can be represented by notation ∣ψ⟩=α∣0⟩+β∣1⟩ and further, α=αre​+iαim​,β=βre​+iβim​, 4 parameters can theoretically encode 4 “features” (however, Pauli-Y gate is needed to create imaginary part).

x = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2]
circuit = EfficientSU2(num_qubits=3, reps=1, insert_barriers=True)
encode = circuit.assign_parameters(x)
encode.decompose().draw(output = 'mpl')

ZFeatureMap: natural extension of phase encoding. The ZFM consists of alternating layers of single-qubit gates: Hadamard gate layers and phase gate layers. Good for independent features.

from qiskit.circuit.library import ZFeatureMap
zfeature_map = ZFeatureMap(feature_dimension=2, reps=3)
zfeature_map = zfeature_map.assign_parameters([(1/2)*pi/2, (1/2)*pi/3])
zfeature_map.decompose().draw('mpl')

Generally Pauli and ZZ feature maps will result in greater circuit depth and higher numbers of 2-qubit gates than EfficientSU2 and Z feature maps.

ZZFeatureMap: built on top of ZFeatureMap, adding entangling gates to capture relationships between qubits, good for feature correlation. (increasing reps parameter would cause more complex feature interaction, because of more combinations of the input features x0 and x1 to be multiplied together)

from qiskit.circuit.library import ZZFeatureMap
feature_dim=2
zzfeature_map = ZZFeatureMap(feature_dimension=feature_dim, entanglement='linear', reps=1)
zzfeature_map.decompose(reps = 1).draw('mpl', scale=1)

PauliFeatureMap: generalization of the ZFM and ZZFM to use arbitrary Pauli gates. Entangled qubits also makes it good for interacting features.

from qiskit.circuit.library import PauliFeatureMap
feature_dim=3
pauli_feature_map = PauliFeatureMap(feature_dimension=feature_dim,
entanglement='linear',
reps=1,
paulis=['Y','XX'])
pauli_feature_map.decompose().draw('mpl', scale=1.5)

Feature mapping, a mapping of data features from one space to another (usually higher-dimensions).

Machine learning

Variational quantum classifier: The variational quantum classifier (VQC) is a variational algorithm where the measured bitstrings are interpreted as the output of a classifier.

The goal is to find a function ff with parameters θ that maps a data vector / image x to the correct category: ​(x)→±1. This will be accomplished using a VQC with few layers that can be identified by their distinct purposes:

​(x)=⟨0∣U†(x)W†(θ)OW(θ)U(x)∣0⟩

Below is the explanation for above function (thanks to ChatGPT).

Components Explained:

  1. Initial State ∣0⟩: This represents the starting point of the quantum system, where all qubits are initialized to the zero state.
  2. Encoding Circuit U(x):
  • Purpose: Transforms the initial quantum state to encode the data vector x into the quantum system.
  • Action: Applies quantum gates based on the values in x to prepare the system for further processing.

3. Variational Circuit W(θ):

  • Purpose: Processes the encoded data using trainable parameters θ.
  • Action: Applies a series of quantum gates whose effects depend on θ. These parameters are adjusted during training to improve classification accuracy.
  • Also Known As: The “ansatz”, which is a proposed form for the quantum circuit that is refined during optimization.

4. Observable O:

  • Purpose: Represents a measurable quantity in the quantum system whose expected value helps determine the classification.
  • Action: After processing by U(x) and W(θ), the system is measured using O.

5. Adjoint Operations U†(x) and W†(θ):

  • Purpose: Reverse the effects of the encoding and variational circuits before measurement.
  • Action: Apply the inverse operations of W(θ) and U(x) to the system.

Putting It All Together:

  • Preparation: Start with the initial state ∣0⟩.
  • Encoding: Apply U(x) to encode the data into the quantum state.
  • Variational Processing: Apply W(θ) to transform the state based on trainable parameters.
  • Measurement Setup: Reverse the transformations by applying W†(θ) and U†(x). This prepares the system for measurement in a specific basis.
  • Measurement: Measure the observable O in this prepared state.
  • Result: The expectation value ⟨0∣U†(x)W†(θ)OW(θ)U(x)∣0⟩ yields a numerical value that is used to classify x as +1 or −1.

Similar to classical machine learning, it still has a forward pass accepting inputs and outputting result, which is used to calculate loss function, then optimizer uses loss function value to adjust parameters.

Run qiskit machine learning, it has some builtin wrappers to simplify machine learning setup, e.g. VQC. By default, it runs on simulator. However, to run on a real quantum computer, you need to do some modifications, VQC uses SamplerQNN and NeuralNetworkClassifier. We need to replace sampler or estimator (which accepts backend) somewhere. With this migration guide. Very slow on real quantum computer vs. simulator

from qiskit.circuit.library import TwoLocal, ZZFeatureMap
from qiskit_machine_learning.utils import algorithm_globals
from qiskit_machine_learning.datasets import ad_hoc_data
# from qiskit_machine_learning.neural_networks import SamplerQNN
from qiskit_machine_learning.neural_networks import EstimatorQNN
from qiskit_machine_learning.algorithms.classifiers import NeuralNetworkClassifier
# from qiskit.primitives import Sampler
from qiskit_ibm_runtime import SamplerV2 as Sampler, EstimatorV2 as Estimator
from qiskit_machine_learning.optimizers import COBYLA
from qiskit import QuantumCircuit, transpile
from qiskit_aer import Aer
import numpy as np
from qiskit_algorithms.gradients import ParamShiftEstimatorGradient
from qiskit_ibm_runtime import QiskitRuntimeService
# Set the random seed for reproducibility
seed = 1376
algorithm_globals.random_seed = seed
# Use ad hoc data set for training and test data
feature_dim = 2 # dimension of each data point
training_size = 2
test_size = 1
# training features, training labels, test features, test labels as np.ndarray,
# one hot encoding for labels
training_features, training_labels, test_features, test_labels = ad_hoc_data(
training_size=training_size, test_size=test_size, n=feature_dim, gap=0.3
)
# Convert one-hot encoded labels to class indices to resolve "QiskitMachineLearningError: "Shapes don't match, predict: (40,), target: (40, 2)!""
training_labels = np.argmax(training_labels, axis=1)
test_labels = np.argmax(test_labels, axis=1)
# Define the feature map and ansatz circuits
feature_map = ZZFeatureMap(feature_dimension=feature_dim, reps=2, entanglement="linear")
# ry and rz rotation gates to each qubit, cz gates to entangle qubits.
ansatz = TwoLocal(feature_map.num_qubits, ["ry", "rz"], "cz", reps=3)
# Combine the feature map and ansatz into a single circuit
qc = QuantumCircuit(feature_map.num_qubits)
qc.append(feature_map.to_instruction(), range(feature_map.num_qubits))
qc.append(ansatz.to_instruction(), range(ansatz.num_qubits))
# Transpile the circuit to the basis gates of the backend
# backend = Aer.get_backend('aer_simulator')
service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=feature_map.num_qubits
)

# ValueError: Maximum allowed dimension exceeded error
# sampler = Sampler(mode=backend)
estimator = Estimator(mode=backend)
qc = transpile(qc, backend)
# # Define the SamplerQNN
# qnn = SamplerQNN(
# circuit=qc,
# input_params=feature_map.parameters,
# weight_params=ansatz.parameters,
# sampler=sampler,
# )
# QiskitMachineLearningError: 'Please provide a gradient with pass manager initialised.'
gradient = ParamShiftEstimatorGradient(estimator=estimator)
# Define the EstimatorQNN
qnn = EstimatorQNN(
circuit=qc,
input_params=feature_map.parameters,
weight_params=ansatz.parameters,
estimator=estimator,
gradient=gradient, # Pass the gradient here
)
# Set up the NeuralNetworkClassifier with the COBYLA optimizer
classifier = NeuralNetworkClassifier(
neural_network=qnn,
optimizer=COBYLA(maxiter=2),
one_hot=False, # Set to True if labels are one-hot encoded
)
# Train the classifier
classifier.fit(training_features, training_labels)
# Evaluate the classifier on the test set
score = classifier.score(test_features, test_labels)
print(f"Testing accuracy: {score:0.2f}")

Original code

from qiskit.circuit.library import TwoLocal, ZZFeatureMap
from qiskit_machine_learning.optimizers import COBYLA
from qiskit_machine_learning.utils import algorithm_globals
from qiskit_machine_learning.algorithms import VQC
from qiskit_machine_learning.datasets import ad_hoc_data
seed = 1376
algorithm_globals.random_seed = seed
# Use ad hoc data set for training and test data
feature_dim = 2 # dimension of each data point
training_size = 20
test_size = 10
# training features, training labels, test features, test labels as np.ndarray,
# one hot encoding for labels
training_features, training_labels, test_features, test_labels = ad_hoc_data(
training_size=training_size, test_size=test_size, n=feature_dim, gap=0.3
)
feature_map = ZZFeatureMap(feature_dimension=feature_dim, reps=2, entanglement="linear")
ansatz = TwoLocal(feature_map.num_qubits, ["ry", "rz"], "cz", reps=3)
vqc = VQC(
feature_map=feature_map,
ansatz=ansatz,
optimizer=COBYLA(maxiter=100),
)
vqc.fit(training_features, training_labels)
score = vqc.score(test_features, test_labels)
print(f"Testing accuracy: {score:0.2f}")

Appendix

https://ieeexplore.ieee.org/document/9274431

--

--

Xin Cheng
Xin Cheng

Written by Xin Cheng

Multi/Hybrid-cloud, Kubernetes, cloud-native, big data, machine learning, IoT developer/architect, 3x Azure-certified, 3x AWS-certified, 2x GCP-certified

No responses yet