"*Note*: The jupyter notebooks for this course are _adapted_ from Peter Witteks course on <a href=\"https://www.edx.org/course/quantum-machine-learning-2\">**Quantum Machine Learning**</a>, and illustration from <a href=\"https://pennylane.ai/\">**Pennylane**</a>."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before you begin, execute this cell to import numpy and packages from the D-Wave Ocean suite, and all necessary functions for the gate-model framework you are going to use, whether that is the Forest SDK or Qiskit. In the case of Forest SDK, it also starts the qvm and quilc servers."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"nbgrader": {
"grade": false,
"locked": true,
"solution": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Available frameworks:\n",
"Qiskit\n",
"D-Wave Ocean\n"
]
}
],
"source": [
"%run -i \"assignment_helper_QML.py\"\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quantum Neural Networks #\n",
"\n",
"\n",
"In this tutorial, we show how to use PennyLane to implement variational quantum classifiers - quantum circuits that can be trained from labelled data to classify new data samples. The architecture is inspired by <a href=\"https://arxiv.org/abs/1802.06002\">**Farhi and Neven (2018)**</a> and <a href=\"https://arxiv.org/abs/1804.00633\">**Schuld et al. (2018)**</a>.\n",
"This optimization example demonstrates how to encode binary inputs into the initial state of the variational circuit, which is simply a computational basis state.\n",
"\n",
"We then show how to encode real vectors as amplitude vectors (*amplitude encoding*) and train the model to recognize the first two classes of\n",
"flowers in the Iris dataset.\n",
"\n",
"# 1. Fitting the parity function #\n",
"\n",
"**Imports**\n",
"\n",
"As before, we import PennyLane, the PennyLane-provided version of NumPy,and an optimizer."
"We create a quantum device with four “wires” (or qubits)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"dev = qml.device(\"default.qubit\", wires=4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Variational classifiers usually define a “layer” or “block”, which is an elementary circuit architecture that gets repeated to build the\n",
"variational circuit.\n",
"\n",
"Our circuit layer consists of an arbitrary rotation on every qubit, as well as CNOTs that entangle each qubit with its neighbour."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def layer(W):\n",
"\n",
" qml.Rot(W[0, 0], W[0, 1], W[0, 2], wires=0)\n",
" qml.Rot(W[1, 0], W[1, 1], W[1, 2], wires=1)\n",
" qml.Rot(W[2, 0], W[2, 1], W[2, 2], wires=2)\n",
" qml.Rot(W[3, 0], W[3, 1], W[3, 2], wires=3)\n",
"\n",
" qml.CNOT(wires=[0, 1])\n",
" qml.CNOT(wires=[1, 2])\n",
" qml.CNOT(wires=[2, 3])\n",
" qml.CNOT(wires=[3, 0])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We also need a way to encode data inputs $x$ into the circuit, so that the measured output depends on the inputs. In this first example, the inputs are bitstrings, which we encode into the state of the qubits. The quantum state $\\psi$ after state preparation is a computational basis state that has 1s where $x$ has 1s, for example\n",
"We use the ``BasisState`` function provided by PennyLane, which expects ``x`` to be a list of zeros and ones, i.e. ``[0,1,0,1]``.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def statepreparation(x):\n",
" qml.BasisState(x, wires=[0, 1, 2, 3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we define the quantum node as a state preparation routine, followed\n",
"by a repetition of the layer structure. Borrowing from machine learning,\n",
"we call the parameters ``weights``.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"@qml.qnode(dev)\n",
"def circuit(weights, x=None):\n",
"\n",
" statepreparation(x)\n",
"\n",
" for W in weights:\n",
" layer(W)\n",
"\n",
" return qml.expval(qml.PauliZ(0))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Different from previous examples, the quantum node takes the data as a\n",
"keyword argument ``x`` (with the default value ``None``). Keyword\n",
"arguments of a quantum node are considered as fixed when calculating a\n",
"gradient; they are never trained.\n",
"\n",
"If we want to add a “classical” bias parameter, the variational quantum\n",
"classifer also needs some post-processing. We define the final model by\n",
"a classical node that uses the first variable, and feeds the remainder\n",
"into the quantum node. Before this, we reshape the list of remaining\n",
"variables for easy use in the quantum node.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def variational_classifier(var, x=None):\n",
" weights = var[0]\n",
" bias = var[1]\n",
" return circuit(weights, x=x) + bias"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Cost**\n",
"\n",
"\n",
"In supervised learning, the cost function is usually the sum of a loss function and a regularizer. We use the standard square loss that measures the distance between target labels and model predictions."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def square_loss(labels, predictions):\n",
" loss = 0\n",
" for l, p in zip(labels, predictions):\n",
" loss = loss + (l - p) ** 2\n",
"\n",
" loss = loss / len(labels)\n",
" return loss"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To monitor how many inputs the current classifier predicted correctly,\n",
"we also define the accuracy given target labels and model predictions.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def accuracy(labels, predictions):\n",
"\n",
" loss = 0\n",
" for l, p in zip(labels, predictions):\n",
" if abs(l - p) < 1e-5:\n",
" loss = loss + 1\n",
" loss = loss / len(labels)\n",
"\n",
" return loss"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For learning tasks, the cost depends on the data - here the features and\n",
"labels considered in the iteration of the optimization routine.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [],
"source": [
"def cost(var, X, Y):\n",
" predictions = [variational_classifier(var, x=x) for x in X]\n",
" return square_loss(Y, predictions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Optimization**\n",
"\n",
"\n",
"Let’s now load and preprocess some data."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false,
"jupyter": {
"outputs_hidden": false
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"X = [0. 0. 0. 0.], Y = -1\n",
"X = [0. 0. 0. 1.], Y = 1\n",
"X = [0. 0. 1. 0.], Y = 1\n",
"X = [0. 0. 1. 1.], Y = -1\n",
"X = [0. 1. 0. 0.], Y = 1\n",
"...\n"
]
}
],
"source": [
"data = np.loadtxt(\"data/parity.txt\")\n",
"X = data[:, :-1]\n",
"Y = data[:, -1]\n",
"Y = Y * 2 - np.ones(len(Y)) # shift label from {0, 1} to {-1, 1}\n",
"\n",
"for i in range(5):\n",
" print(\"X = {}, Y = {: d}\".format(X[i], int(Y[i])))\n",
"\n",
"print(\"...\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We initialize the variables randomly (but fix a seed for\n",
"reproducability). The first variable in the list is used as a bias,\n",
"while the rest is fed into the gates of the variational circuit.\n",