2. The polyadicQML
In the previous tutorial we learned how to define a runnable parametric
quantum circuit using
Here, we will integrate a parametric circuit in a classification model,
train its parameters and predict the classes of new samples.
All of this is easily done using a
For people familiar with the popular python package scikit-learn, the
syntax will be very familar, as we rely on two methods:
To explain each step of this tutorial (Creating a Classifier,
Training the parameters, Predicting on new points), we rely on a simple
example: the binary classification of points generated from two
gaussians on the plane, which we show in figure.
2.1. Creating a Classifier¶
To define a classifier, we need two ingredients: a
a list of bitstrings.
The former is the circuit we intend to use in our quantum classifier.
The list of bistrings specifies on which bitstring we want to read the classes.
Imagine we want to use the simple circuit represented in figure, to
classify the binary dataset.
Again, encodes for the input vector
represents the models parameters.
The first step requires to create a
circuitML, as seen in previous tutorial.
We use the
# We define the circuit structure def simple_circuit(bdr, x, params): bdr.allin(x).cz(0,1).allin(params[:2]) bdr.cz(0,1).allin(params[2:4]) return bdr # We instantiate the circuit from polyadicqml.manyq import mqCircuitML nbqbits = 2 nbparams = 4 qc = mqCircuitML(make_circuit=simple_circuit, nbqbits=nbqbits, nbparams=nbparams)
Now, we have to choose on which bitstrings to read the classes.
Since we are using two qubits, the circuit can generate four different
outputs, as the number of possible bitstrings is
The bitstring choice is arbitrary and different readouts creates
different decision boundaries.
Nonetheless, we can guide our choice using heuristic arguments.
For instance, choosing to assign
01 to class 0 and 1
respectively, would mean that our model output should always keep the
first qubit ‘inactive’, while using the second one to discriminate the classes.
A more thoughtful choice, in this case, could be to choose a pair of
reversed bitstrings, such as
00,11, so that a change in
the class can be interpreted as a flip of state on the output qubit.
We choose to go with
11 and we instantiate the classifier.
By default, the classifier uses the exact quantum-state probabilities, to
use shots-based probabilites, one should specify the
argument in the constructor.
from polyadicqml import Classifier bitstr = ['00', '11'] model = Classifier(qc, bitstr)
The number of shots and the circuitML implementation can be changed
at any time through the
attribute and the
This is very useful to train models on simulators and then testing
them on real hardware.
2.2. Training the parameters¶
Parameter training is straightforward and only requires a call to the
fit method, by providing the design
X and the corresponding labels
Parameters are optimized to minimize the negative loglikelihood.
On top of that,
fit accepts many different
The first one, which can be positional, is the
tells how many samples, picked at random, should be used at each
iteration to compute the loss.
The most important keyword argument is
method, a string that
specifies the numerical optimization method to be used.
For now, polyadicQML relies on the SciPy optimization module and
therefore, supports all methods provided by scipy.optimize.minimize;
nevertheless, we mainly rely on
COBYLA, the former being
the default method.
BFGS is a gradient based method, which we compute using finite differences. With this approach, there is no reliable way to approximate the gradient with a limited number of shots. Therefore, to train a quantum model which do not use the bitstring probabilities from the quantum state, but estimates them through shots, we must resort to a gradient-free method, such as COBYLA.
For instance, we can choose to use shots by specifying it in the
In this case, we should be aware of the optimization method.
model = Classifier(qc, bitstr, nbshots=300) model.fit(X, y, method='COBYLA')
2.3. Predicting on new points¶
The model being trained, we can use it to predict the label of any new
point from the same space as the original dataset.
Supposing we have a matrix
X_test, we can predict its labels by
labels_test = model.predict(X_test)
An equivalent, but shorter, version consists of directly calling the model.
labels_test = model(X_test)
For instance, the figure shows the predicted label for each point in .
We can also predict the probabilities of the bitstring associated to each
probas_test = model.predict_proba(X_test)
If we want to compute the circuit output, full and raw, we can use the
It returns the counts – or probabilities – of all bitstrings.
For example, we can compute a realization with 300 shots over the
X, to se how the model fits the data.
# Changing nbshots from None changes from exact probabilities # to shots-based estimation model.nbshots = 300 # We get the counts from each bitstring full_output = model.run_circuit(X)
We plot the counts for each point of the two classes, along with their box plot.