The general idea of machine learning protocols is to *learn* some
function — for example, a classification function — from the information
provided by a data set consisting of inputs and outputs. Usually, a
parameterized function — in the quantum world often referred to as a circuit — is used as an ansatz.
This function may have an arbitrarily large number of parameters
that need to be tuned to *learn* the appropriate function. A way to
quantify how *close* the parameterized function is to the target one (in the case of supervised learning) is
by using a custom *loss function* that depends on the difference
between the output of the *circuit* and the real output. The way we tune
the parameters of our model is by *moving* them in the high dimensional
space towards a region where the loss function is lower.

In quantum machine learning the parameterized function is substituted by the expectation value of some observables in the state resulting from one parameterized circuit. If you’re new to quantum machine learning we encourage you to also check out our blog post on how to start learning quantum machine learning. Usually, we need to evaluate this circuit using different input parameters before actually computing a loss function. In a typical supervised-learning scenario, we will have a set of \(M\) input data \(\vec{x}\) and labels \(\vec{y}\) or target outputs. Then, we can define a cost function through the mean squared error between the outputs of the circuit evaluated at each \(\vec{x}_{m}\) and the labels \(\vec{y}_{m}\), that is

where \(\theta\) is a parameter that defines the model.

To evaluate batches the naive approach would be to run a for loop to sequentially run the circuit on each sample in the batch. Yet, in PennyLane it is possible to do this by using built-in methods. Those methods are available for all PennyLane devices. This means they can be used either in the context of qubit-based circuits (using for instance the default.qubit) device or in the evaluation of optical continuous-variable circuits (like the default.gaussian device).

In this how-to, we will learn about the best practices in PennyLane to deal
with batches and collections of circuits. For that, we will consider
first a simple two-qubit circuit. Then, in a second moment, we will use
two circuits to form a basic *collection*, which is useful when
more complicated classification tasks have to be accomplished.

## Basic example

We will build a basic example to show how to run batches of circuits. For that, let’s start by importing the relevant packages.

```
import pennylane as qml
from pennylane import numpy as np
```

Let’s now create a very simple circuit to play with. The idea with this circuit is to encode the information from the input into the state of the qubit by using a rotation around the X-axis (RX gate). Then we will perform a parametrized rotation around the Y-axis (RY gate), using an angle defined as a parameter of the circuit.

```
# We create our data and labels
x_train = np.array([0.2, 0.1, 0.2, 0.14, 0.11, 0.41, 0.55, 0.3, 0.31, 0.6])
y_train = np.array([1,1,1,1,1,-1,-1,-1,-1,-1])
# We define the circuit we are going to use
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev)
def simple_qubit_circuit(theta, input_value):
qml.RX(input_value, wires=0)
qml.RY(theta, wires=0)
return qml.expval(qml.PauliZ(0))
```

Now, we have created all we need to execute a batch of circuits. The goal is to take the training data set and run the circuit on each of its samples to compute the error function as described above. This can be done by running a for loop over all the samples, as shown below.

```
err = 0
theta=0.2
for x, y in zip(x_train, y_train):
err += (simple_qubit_circuit(theta, x)-y)**2
```

```
>>> print(err)
17.73917311951324
```

Nevertheless, in PennyLane this can be done efficiently by using a feature called quantum parameter broadcasting. The idea is as simple as passing parameters as arrays with one added dimension that runs over the batch.

```
>>> print(sum((simple_qubit_circuit(theta,x_train)-y_train)**2))
17.73917311951324
```

And that’s it, we’ve run a batched evaluation of a circuit! As you can see, in this case we have sent to the circuit the whole dataset instead of sending the elements one by one.

## Executing collections of quantum circuits

A limitation of the above evaluation is that it is only valid for a single fixed qnode operating with different parameters. That is the same circuit and the same device. Yet, more generally, we may want to execute collections of different quantum circuits (or devices) and simultaneously use their output to compute a loss function (see, for example, this demo). To do this we can use the QNodeCollection() PennyLane class. One of the most attractive features of this class is that it allows for the evaluation of different parameterized circuits in different devices.

Below, we show a simple example in which we evaluate circuits in different devices. With their outputs, we could create a classification function, and then define a customized cost function, yet, we will not distract ourselves here by doing that. We will consider two circuits, one qubit-based circuit and one continuous-variable circuit, to make the versatility of this functionality clear.

```
dev0 = qml.device("default.qubit", wires=1)
dev1 = qml.device("default.gaussian", wires=1)
```

Now we define the two circuits

```
# for the first circuit (qubit one) we will use the same circuit defined above
def qubit_circuit(params, input_value):
qml.RX(input_value, wires=0)
qml.RY(params[0], wires=0)
return qml.expval(qml.PauliZ(0))
def gaussian_circuit(params, input_value):
qml.Displacement(input_value, 0.0, wires=0)
qml.Rotation(params[1], wires=0)
return qml.expval(qml.X(0))
# create the quantum nodes
qnode0 = qml.QNode(qubit_circuit, dev0)
qnode1 = qml.QNode(gaussian_circuit, dev1)
```

The gaussian circuit is given by a composition of a displacement and a rotation in the phase space of an optical mode. A subtle but important point to keep in mind is that even if the parameters that define the circuits are independent the input parameters are the same, which implies that we have to carefully specify which parameters are passed to each gate.

```
params_qubit = np.random.rand()
params_gaussian = np.random.rand()
params = np.array([params_qubit, params_gaussian], dtype=object)
```

Now we can create an instance of the class QNodeCollection().

```
qnode_collection = qml.QNodeCollection([qnode0, qnode1])
```

Let’s run them! Let PennyLane do it in the most optimal way.

```
>>> input_value=0.2
>>> qnode_collection(params, input_value)
array([0.76468227, 0.29916979])
```

And it’s done! In this how-to, we have learned how to evaluate circuits in batches and collections. Now you just have to bring it to the next level and run your own machine learning algorithms. With collections, you will be able to exploit the potential of the different devices PennyLane gives access to, and create your own custom models!