Qiskit Machine Learning v0.5 Migration Guide#

This tutorial will guide you through the process of migrating your code from Qiskit Machine Learning v0.4 to v0.5.

Introduction#

The main focus of the 0.5 release of Qiskit Machine Learning is the migration of the base computational blocks like quantum kernels and quantum neural networks to the primitives introduced in Qiskit as well as extended support of the primitives in the algorithms.

Contents:

  • Overview of the primitives

  • New quantum kernel

  • New quantum neural networks

  • Other notable deprecation

Overview of the primitives#

The core capability of quantum computers that sets them apart from from classical computers is their ability to generate non-classical probability distributions at their outputs. The native operations that one can do with a probability distribution is to sample from it or to estimate quantities on it. Consequently, these operations of sampling and estimating form the fundamental building blocks of quantum algorithm development. Thus, as it was announced, two basic primitives were introduced, Sampler and Estimator, respectively, that implement these two operations:

Qiskit Terra provides core interfaces and two implementations:

  • The reference implementation that is statevector based. This implementation does require a backend or a simulator, it relies on the classes from the quantum_info package.

  • The backend based primitives are to support provider/backends that do not support primitives directly. This implementation requires an instance of a backend to be passed to a primitive.

More information on the Qiskit Terra primitives can be found in the documentation.

It is worth mentioning other implementations as well:

  • Aer primitives should be used for Aer simulator. They extend corresponding interfaces from Terra and can be used in the same way as primitives from Terra. See documentation for more information.

  • The runtime primitives to be used with IBM devices. This is an implementation that is focused on cloud computing on actual hardware. See here.

Along with the primitives Terra has some primitive-like algorithms that are highly useful in QML and used by the new 0.5 functions:

  • Algorithms to calculate the gradient of a quantum circuit. For each core primitive there’s a corresponding base interface that defines quantum circuit gradient. The documentation on gradients is here.

  • Algorithms that compute the fidelity or “closeness” of pairs of quantum states. Currently, only one implementation is available that requires a sampler primitive and is based on the compute-uncompute method. The documentation is here.

Both two new algorithms are very similar to the core primitives, they share the same method signatures, so they may be called as high level primitives despite they are not in the primitives package.

New quantum kernel#

The previous implementation consisted of a single class QuantumKernel that did everything:

  • Constructed circuits

  • Executed circuits and evaluated overlap between circuits

  • Provided training parameters

  • Kept track of the values assigned to the parameters.

The implementation became sophisticated and inflexible and adding support of the new primitives could be tricky. To address the issues, a new flexible and extendable design of quantum kernels was introduced. The goals of the new design are:

  • Migrate to the primitives and leverage the fidelity algorithm. Now users may plug in their own implementations of fidelity calculations.

  • Extract trainability feature to a dedicated class.

  • Introduce a base class that can be extended by other kernel implementations.

The new design of quantum kernel is shown on the next diagram.

Quantum Kernel Diagram

The new kernels expose the same interface and the same parameters except the quantum_instance parameter. This parameter does not have a direct replacement and instead the fidelity parameter must be used. The backend handling/selection, which was previously done using the quantum_instance, is now taken care of via the Sampler primitive given to the fidelity.

A new hierarchy shown on the diagram introduces:

  • A base and abstract class BaseKernel is introduced. All concrete implementation must inherit this class.

  • A fidelity based quantum kernel FidelityQuantumKernel is added. This is a direct replacement of the previous quantum kernel implementation. The difference is that the new class takes a fidelity instance to estimate overlaps and construct kernel matrix.

  • A new abstract class TrainableKernel is introduced to generalize ability to train quantum kernels.

  • A fidelity-based trainable quantum kernel TrainableFidelityQuantumKernel is introduced. This is a replacement of the previous quantum kernel if a trainable kernel is required. The trainer QuantumKernelTrainer now accepts both quantum kernel implementations, the new one and the previous one.

For convenience, the previous quantum kernel implementation, QuantumKernel, now extends both new abstract classes and thus it is compatible with the new introduced interfaces. This implementation is now pending deprecation, will be deprecated in a future release and subsequently removed after that. New, primitive-based quantum kernels should be used instead.

The existing algorithms such as QSVC, QSVR and other kernel-based algorithms are updated and work with both implementations.

For example a QSVM classifier can be trained as follows.

Create a dataset#

Fixing randomization.

from qiskit.utils import algorithm_globals

algorithm_globals.random_seed = 123456

Generate a simple dataset using scikit-learn.

from sklearn.datasets import make_blobs

features, labels = make_blobs(
    n_samples=20,
    centers=2,
    center_box=(-1, 1),
    cluster_std=0.1,
    random_state=algorithm_globals.random_seed,
)

Previous implementation of quantum kernel#

In the previous implementation we start from creating an instance of QuantumInstance. This class defines where our quantum circuits are executed. In this case we wrap a statevector simulator in the quantum instance.

from qiskit import BasicAer
from qiskit.utils import QuantumInstance

sv_qi = QuantumInstance(
    BasicAer.get_backend("statevector_simulator"),
    seed_simulator=algorithm_globals.random_seed,
    seed_transpiler=algorithm_globals.random_seed,
)

Then create a quantum kernel.

from qiskit.circuit.library import ZZFeatureMap
from qiskit_machine_learning.kernels import QuantumKernel

feature_map = ZZFeatureMap(2)
previous_kernel = QuantumKernel(feature_map=feature_map, quantum_instance=sv_qi)

And finally we fit an SVM classifier.

from qiskit_machine_learning.algorithms import QSVC

qsvc = QSVC(quantum_kernel=previous_kernel)
qsvc.fit(features, labels)
qsvc.score(features, labels)
0.95

New implementation of quantum kernel#

In the new implementation we start from creating a Fidelity instance. Fidelity is optional and quantum kernel will create it automatically if none is passed. But here, we create it manually for illustrative purposes. To create a fidelity instance we pass a sampler. The sampler is the reference implementation and defines where our quantum circuits are executed. You may create a sampler instance from QiskitRuntimeService to leverage Qiskit runtime services.

from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.primitives import Sampler

fidelity = ComputeUncompute(sampler=Sampler())

Next, we create a new quantum kernel with the fidelity instance.

from qiskit_machine_learning.kernels import FidelityQuantumKernel

feature_map = ZZFeatureMap(2)
new_kernel = FidelityQuantumKernel(feature_map=feature_map, fidelity=fidelity)

Then we fit an SVM classifier the same way as before.

from qiskit_machine_learning.algorithms import QSVC

qsvc = QSVC(quantum_kernel=new_kernel)
qsvc.fit(features, labels)
qsvc.score(features, labels)
0.95

New quantum neural networks#

Changes in the quantum neural networks are not as dramatic as in quantum kernels. In addition, and as a replacement to the existing neural networks, two new networks are introduced. The new networks introduced are SamplerQNN and EstimatorQNN which are detailed below and are replacements for the pre-existing CircuitQNN, OpflowQNN and TwoLayerQNN which are now pending deprecated.

SamplerQNN#

A new Sampler Quantum Neural Network leverages the sampler primitive, sampler gradients and is a direct replacement of CircuitQNN.

The new SamplerQNN exposes a similar interface to the existing CircuitQNN, with a few differences. One is the quantum_instance parameter. This parameter does not have a direct replacement, and instead the sampler parameter must be used. The gradient parameter keeps the same name as in the CircuitQNN implementation, but it no longer accepts Opflow gradient classes as inputs; instead, this parameter expects an (optionally custom) primitive gradient. The sampling option has been removed for the time being, as this information is not currently exposed by the sampler, and might correspond to future lower-level primitives.

The existing training algorithms such as VQC that were based on CircuitQNN, are updated to accept both implementations. The implementation of NeuralNetworkClassifier has not changed.

The existing CircuitQNN is now pending deprecation, will be deprecated in a future release and subsequently removed after that.

We’ll show how to train a variational quantum classifier using both networks. For this purposes we re-use the dataset generated for the quantum kernel. For both quantum neural networks we still have to construct a feature map, an ansatz and combine them into a single quantum circuit.

from qiskit import QuantumCircuit
from qiskit.circuit.library import RealAmplitudes

num_inputs = 2
feature_map = ZZFeatureMap(num_inputs)
ansatz = RealAmplitudes(num_inputs, reps=1)

circuit = QuantumCircuit(num_inputs)
circuit.compose(feature_map, inplace=True)
circuit.compose(ansatz, inplace=True)

We need an interpret function as well. We define our usual parity function that maps bitstrings either to \(0\) or \(1\).

def parity(x):
    return "{:b}".format(x).count("1") % 2

We fix the initial point to get the same results from both networks.

initial_point = algorithm_globals.random.random(ansatz.num_parameters)

Building a classifier using CircuitQNN#

We create a CircuitQNN instance and re-use the quantum instance created for the quantum kernel.

from qiskit_machine_learning.neural_networks import CircuitQNN

circuit_qnn = CircuitQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    interpret=parity,
    output_shape=2,
    quantum_instance=sv_qi,
)

Construct a classifier out of the network, train it and score it. We are not aiming for good results, so the number of iterations is set to a small number to reduce overall execution time.

from qiskit.algorithms.optimizers import COBYLA
from qiskit_machine_learning.algorithms import NeuralNetworkClassifier

classifier = NeuralNetworkClassifier(
    neural_network=circuit_qnn,
    loss="cross_entropy",
    one_hot=True,
    optimizer=COBYLA(maxiter=40),
    initial_point=initial_point,
)
classifier.fit(features, labels)
classifier.score(features, labels)
0.6

Building a classifier using SamplerQNN#

Instead of QuantumInstance create an instance of the reference Sampler.

from qiskit.primitives import Sampler

sampler = Sampler()

Now, we create a instance of SamplerQNN. The difference with CircuitQNN is that we pass a sampler instead of a quantum instance.

from qiskit_machine_learning.neural_networks import SamplerQNN

sampler_qnn = SamplerQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    interpret=parity,
    output_shape=2,
    sampler=sampler,
)

Construct a classifier and fit it as usual. As neural_network we pass a created SamplerQNN and this is the only difference.

classifier = NeuralNetworkClassifier(
    neural_network=sampler_qnn,
    loss="cross_entropy",
    one_hot=True,
    optimizer=COBYLA(maxiter=40),
    initial_point=initial_point,
)
classifier.fit(features, labels)
classifier.score(features, labels)
0.6

Instead of constructing a quantum neural network manually, you may train VQC. It takes either a quantum instance or a sampler, depending on what is passed it automatically constructs either CircuitQNN or SamplerQNN respectively.

EstimatorQNN#

A new Estimator quantum neural network leverages the estimator primitive, estimator gradients and is a direct replacement of OpflowQNN.

The new EstimatorQNN exposes a similar interface to the existing OpflowQNN, with a few differences. One is the quantum_instance parameter. This parameter does not have a direct replacement, and instead the estimator parameter must be used. The gradient parameter keeps the same name as in the OpflowQNN implementation, but it no longer accepts Opflow gradient classes as inputs; instead, this parameter expects an (optionally custom) primitive gradient.

The existing training algorithms such as VQR that were based on the TwoLayerQNN, are updated to accept both implementations. The implementation of NeuralNetworkRegressor has not changed.

The existing OpflowQNN is now pending deprecation, will be deprecated in a future release and subsequently removed after that.

We’ll show how to train a variational quantum regressor using both networks. We start from generating a simple regression dataset.

import numpy as np

num_samples = 20
eps = 0.2
lb, ub = -np.pi, np.pi
features = (ub - lb) * np.random.rand(num_samples, 1) + lb
labels = np.sin(features[:, 0]) + eps * (2 * np.random.rand(num_samples) - 1)

We still have to construct a feature map, an ansatz and combine them into a single quantum circuit for both quantum neural networks.

from qiskit.circuit import Parameter

num_inputs = 1
feature_map = QuantumCircuit(1)
feature_map.ry(Parameter("input"), 0)

ansatz = QuantumCircuit(1)
ansatz.ry(Parameter("weight"), 0)

circuit = QuantumCircuit(num_inputs)
circuit.compose(feature_map, inplace=True)
circuit.compose(ansatz, inplace=True)

We fix the initial point to get the same results from both networks.

initial_point = algorithm_globals.random.random(ansatz.num_parameters)

Building a regressor using OpflowQNN#

We create an OpflowQNN instance and re-use the quantum instance created for the quantum kernel.

from qiskit.opflow import PauliSumOp, StateFn
from qiskit_machine_learning.neural_networks import OpflowQNN

observable = PauliSumOp.from_list([("Z", 1)])
operator = StateFn(observable, is_measurement=True) @ StateFn(circuit)

opflow_qnn = OpflowQNN(
    operator=operator,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    quantum_instance=sv_qi,
)

Construct a regressor out of the network, train it and score it. In this case we use a gradient based optimizer, thus the network makes use of the gradient framework and due to nature of the dataset converges very quickly.

from qiskit.algorithms.optimizers import L_BFGS_B
from qiskit_machine_learning.algorithms import NeuralNetworkRegressor

regressor = NeuralNetworkRegressor(
    neural_network=opflow_qnn,
    optimizer=L_BFGS_B(maxiter=5),
    initial_point=initial_point,
)
regressor.fit(features, labels)
regressor.score(features, labels)
0.9681198723451012

Building a regressor using EstimatorQNN#

Create an instance of the reference Estimator. You may create an estimator instance from QiskitRuntimeService to leverage Qiskit runtime services.

from qiskit.primitives import Estimator

estimator = Estimator()

Now, we create a instance of EstimatorQNN. The network creates an observable as \(Z^{\otimes n}\), where \(n\) is the number of qubit, if it is not specified.

from qiskit_machine_learning.neural_networks import EstimatorQNN

estimator_qnn = EstimatorQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    estimator=estimator,
)

Construct a variational quantum regressor and fit it. In this case we use a gradient based optimizer, thus the network makes use of the default estimator gradient that is created automatically.

from qiskit.algorithms.optimizers import L_BFGS_B
from qiskit_machine_learning.algorithms import VQR

regressor = NeuralNetworkRegressor(
    neural_network=estimator_qnn,
    optimizer=L_BFGS_B(maxiter=5),
    initial_point=initial_point,
)
regressor.fit(features, labels)
regressor.score(features, labels)
0.9681198723451012

Instead of constructing a quantum neural network manually, you may train VQR. It takes either a quantum instance or an estimator, depending on what is passed it automatically constructs either TwoLayerQNN or EstimatorQNN respectively.

Other notable deprecation#

A few other components, not mentioned explicitly above, are also deprecated or pending deprecation:

import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.25.0
qiskit-aer0.13.0
qiskit-machine-learning0.7.0
System information
Python version3.8.13
Python compilerClang 12.0.0
Python builddefault, Oct 19 2022 17:54:22
OSDarwin
CPUs10
Memory (Gb)64.0
Thu Sep 14 13:57:31 2023 IST

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.