diff --git a/docs/tutorials/barren_plateaus.ipynb b/docs/tutorials/barren_plateaus.ipynb
index 94be3a950..345e910ec 100644
--- a/docs/tutorials/barren_plateaus.ipynb
+++ b/docs/tutorials/barren_plateaus.ipynb
@@ -1,527 +1,528 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "colab": {},
- "colab_type": "code",
- "id": "iiQkM5ZgQ8r2"
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j6331ZSsQGY3"
- },
- "source": [
- "# Barren plateaus"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- "
"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "DyEcfFapraq6"
- },
- "source": [
- "In this example you will explore the result of McClean, 2019 that says not just any quantum neural network structure will do well when it comes to learning. In particular you will see that a certain large family of random quantum circuits do not serve as good quantum neural networks, because they have gradients that vanish almost everywhere. In this example you won't be training any models for a specific learning problem, but instead focusing on the simpler problem of understanding the behaviors of gradients."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "zB_Xw0Y9rVNi"
- },
- "source": [
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "TorxE5tnkvb2"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FxkQA6oblNqI"
- },
- "source": [
- "Install TensorFlow Quantum:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "saFHsRDpkvkH"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J"
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "1PaclXeSrrMW"
- },
- "source": [
- "Now import TensorFlow and the module dependencies:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "enZ300Bflq80"
- },
- "outputs": [],
- "source": [
- "import tensorflow as tf\n",
- "import tensorflow_quantum as tfq\n",
- "\n",
- "import cirq\n",
- "import sympy\n",
- "import numpy as np\n",
- "\n",
- "# visualization tools\n",
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from cirq.contrib.svg import SVGCircuit\n",
- "\n",
- "np.random.seed(1234)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "b08Mmbs8lr81"
- },
- "source": [
- "## 1. Summary\n",
- "\n",
- "Random quantum circuits with many blocks that look like this ($R_{P}(\\theta)$ is a random Pauli rotation): \n",
- " \n",
- "\n",
- "Where if $f(x)$ is defined as the expectation value w.r.t. $Z_{a}Z_{b}$ for any qubits $a$ and $b$, then there is a problem that $f'(x)$ has a mean very close to 0 and does not vary much. You will see this below:"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "y31qSRCczI-L"
- },
- "source": [
- "## 2. Generating random circuits\n",
- "\n",
- "The construction from the paper is straightforward to follow. The following implements a simple function that generates a random quantum circuit—sometimes referred to as a *quantum neural network* (QNN)—with the given depth on a set of qubits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Nh9vrgPBks7O"
- },
- "outputs": [],
- "source": [
- "def generate_random_qnn(qubits, symbol, depth):\n",
- " \"\"\"Generate random QNN's with the same structure from McClean et al.\"\"\"\n",
- " circuit = cirq.Circuit()\n",
- " for qubit in qubits:\n",
- " circuit += cirq.ry(np.pi / 4.0)(qubit)\n",
- "\n",
- " for d in range(depth):\n",
- " # Add a series of single qubit rotations.\n",
- " for i, qubit in enumerate(qubits):\n",
- " random_n = np.random.uniform()\n",
- " random_rot = np.random.uniform(\n",
- " ) * 2.0 * np.pi if i != 0 or d != 0 else symbol\n",
- " if random_n > 2. / 3.:\n",
- " # Add a Z.\n",
- " circuit += cirq.rz(random_rot)(qubit)\n",
- " elif random_n > 1. / 3.:\n",
- " # Add a Y.\n",
- " circuit += cirq.ry(random_rot)(qubit)\n",
- " else:\n",
- " # Add a X.\n",
- " circuit += cirq.rx(random_rot)(qubit)\n",
- "\n",
- " # Add CZ ladder.\n",
- " for src, dest in zip(qubits, qubits[1:]):\n",
- " circuit += cirq.CZ(src, dest)\n",
- "\n",
- " return circuit\n",
- "\n",
- "\n",
- "generate_random_qnn(cirq.GridQubit.rect(1, 3), sympy.Symbol('theta'), 2)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "gUuQfOyrj_Hu"
- },
- "source": [
- "The authors investigate the gradient of a single parameter $\\theta_{1,1}$. Let's follow along by placing a `sympy.Symbol` in the circuit where $\\theta_{1,1}$ would be. Since the authors do not analyze the statistics for any other symbols in the circuit, let's replace them with random values now instead of later."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "lAVDRQ87k3md"
- },
- "source": [
- "## 3. Running the circuits\n",
- "\n",
- "Generate a few of these circuits along with an observable to test the claim that the gradients don't vary much. First, generate a batch of random circuits. Choose a random *ZZ* observable and batch calculate the gradients and variance using TensorFlow Quantum."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "qoDDaHgwj_Hz"
- },
- "source": [
- "### 3.1 Batch variance computation\n",
- "\n",
- "Let's write a helper function that computes the variance of the gradient of a given observable over a batch of circuits:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "OkdndnBKk8B8"
- },
- "outputs": [],
- "source": [
- "def process_batch(circuits, symbol, op):\n",
- " \"\"\"Compute the variance of a batch of expectations w.r.t. op on each circuit that \n",
- " contains `symbol`. Note that this method sets up a new compute graph every time it is\n",
- " called so it isn't as performant as possible.\"\"\"\n",
- "\n",
- " # Setup a simple layer to batch compute the expectation gradients.\n",
- " expectation = tfq.layers.Expectation()\n",
- "\n",
- " # Prep the inputs as tensors\n",
- " circuit_tensor = tfq.convert_to_tensor(circuits)\n",
- " values_tensor = tf.convert_to_tensor(\n",
- " np.random.uniform(0, 2 * np.pi, (n_circuits, 1)).astype(np.float32))\n",
- "\n",
- " # Use TensorFlow GradientTape to track gradients.\n",
- " with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " forward = expectation(circuit_tensor,\n",
- " operators=op,\n",
- " symbol_names=[symbol],\n",
- " symbol_values=values_tensor)\n",
- "\n",
- " # Return variance of gradients across all circuits.\n",
- " grads = g.gradient(forward, values_tensor)\n",
- " grad_var = tf.math.reduce_std(grads, axis=0)\n",
- " return grad_var.numpy()[0]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "JINYTIjDj_H1"
- },
- "source": [
- "### 3.1 Set up and run\n",
- "\n",
- "Choose the number of random circuits to generate along with their depth and the amount of qubits they should act on. Then plot the results."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "xAGBcq9Bj_H3"
- },
- "outputs": [],
- "source": [
- "n_qubits = [2 * i for i in range(2, 7)\n",
- " ] # Ranges studied in paper are between 2 and 24.\n",
- "depth = 50 # Ranges studied in paper are between 50 and 500.\n",
- "n_circuits = 200\n",
- "theta_var = []\n",
- "\n",
- "for n in n_qubits:\n",
- " # Generate the random circuits and observable for the given n.\n",
- " qubits = cirq.GridQubit.rect(1, n)\n",
- " symbol = sympy.Symbol('theta')\n",
- " circuits = [\n",
- " generate_random_qnn(qubits, symbol, depth) for _ in range(n_circuits)\n",
- " ]\n",
- " op = cirq.Z(qubits[0]) * cirq.Z(qubits[1])\n",
- " theta_var.append(process_batch(circuits, symbol, op))\n",
- "\n",
- "plt.semilogy(n_qubits, theta_var)\n",
- "plt.title('Gradient Variance in QNNs')\n",
- "plt.xlabel('n_qubits')\n",
- "plt.xticks(n_qubits)\n",
- "plt.ylabel('$\\\\partial \\\\theta$ variance')\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "qY2E0CFjxRE9"
- },
- "source": [
- "This plot shows that for quantum machine learning problems, you can't simply guess a random QNN ansatz and hope for the best. Some structure must be present in the model circuit in order for gradients to vary to the point where learning can happen."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "4RE_idhmj_H6"
- },
- "source": [
- "## 4. Heuristics\n",
- "\n",
- "An interesting heuristic by Grant, 2019 allows one to start very close to random, but not quite. Using the same circuits as McClean et al., the authors propose a different initialization technique for the classical control parameters to avoid barren plateaus. The initialization technique starts some layers with totally random control parameters—but, in the layers immediately following, choose parameters such that the initial transformation made by the first few layers is undone. The authors call this an *identity block*.\n",
- "\n",
- "The advantage of this heuristic is that by changing just a single parameter, all other blocks outside of the current block will remain the identity—and the gradient signal comes through much stronger than before. This allows the user to pick and choose which variables and blocks to modify to get a strong gradient signal. This heuristic does not prevent the user from falling in to a barren plateau during the training phase (and restricts a fully simultaneous update), it just guarantees that you can start outside of a plateau."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Fofv9hgyj_IB"
- },
- "source": [
- "### 4.1 New QNN construction\n",
- "\n",
- "Now construct a function to generate identity block QNNs. This implementation is slightly different than the one from the paper. For now, look at the behavior of the gradient of a single parameter so it is consistent with McClean et al, so some simplifications can be made.\n",
- "\n",
- "To generate an identity block and train the model, generally you need $U1(\\theta_{1a}) U1(\\theta_{1b})^{\\dagger}$ and not $U1(\\theta_1) U1(\\theta_1)^{\\dagger}$. Initially $\\theta_{1a}$ and $\\theta_{1b}$ are the same angles but they are learned independently. Otherwise, you will always get the identity even after training. The choice for the number of identity blocks is empirical. The deeper the block, the smaller the variance in the middle of the block. But at the start and end of the block, the variance of the parameter gradients should be large. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "PL7mTHEVj_IC"
- },
- "outputs": [],
- "source": [
- "def generate_identity_qnn(qubits, symbol, block_depth, total_depth):\n",
- " \"\"\"Generate random QNN's with the same structure from Grant et al.\"\"\"\n",
- " circuit = cirq.Circuit()\n",
- "\n",
- " # Generate initial block with symbol.\n",
- " prep_and_U = generate_random_qnn(qubits, symbol, block_depth)\n",
- " circuit += prep_and_U\n",
- "\n",
- " # Generate dagger of initial block without symbol.\n",
- " U_dagger = (prep_and_U[1:])**-1\n",
- " circuit += cirq.resolve_parameters(\n",
- " U_dagger, param_resolver={symbol: np.random.uniform() * 2 * np.pi})\n",
- "\n",
- " for d in range(total_depth - 1):\n",
- " # Get a random QNN.\n",
- " prep_and_U_circuit = generate_random_qnn(\n",
- " qubits,\n",
- " np.random.uniform() * 2 * np.pi, block_depth)\n",
- "\n",
- " # Remove the state-prep component\n",
- " U_circuit = prep_and_U_circuit[1:]\n",
- "\n",
- " # Add U\n",
- " circuit += U_circuit\n",
- "\n",
- " # Add U^dagger\n",
- " circuit += U_circuit**-1\n",
- "\n",
- " return circuit\n",
- "\n",
- "\n",
- "generate_identity_qnn(cirq.GridQubit.rect(1, 3), sympy.Symbol('theta'), 2, 2)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "ifWrl19kj_IG"
- },
- "source": [
- "### 4.2 Comparison\n",
- "\n",
- "Here you can see that the heuristic does help to keep the variance of the gradient from vanishing as quickly:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "62kmsVAXj_IH"
- },
- "outputs": [],
- "source": [
- "block_depth = 10\n",
- "total_depth = 5\n",
- "\n",
- "heuristic_theta_var = []\n",
- "\n",
- "for n in n_qubits:\n",
- " # Generate the identity block circuits and observable for the given n.\n",
- " qubits = cirq.GridQubit.rect(1, n)\n",
- " symbol = sympy.Symbol('theta')\n",
- " circuits = [\n",
- " generate_identity_qnn(qubits, symbol, block_depth, total_depth)\n",
- " for _ in range(n_circuits)\n",
- " ]\n",
- " op = cirq.Z(qubits[0]) * cirq.Z(qubits[1])\n",
- " heuristic_theta_var.append(process_batch(circuits, symbol, op))\n",
- "\n",
- "plt.semilogy(n_qubits, theta_var)\n",
- "plt.semilogy(n_qubits, heuristic_theta_var)\n",
- "plt.title('Heuristic vs. Random')\n",
- "plt.xlabel('n_qubits')\n",
- "plt.xticks(n_qubits)\n",
- "plt.ylabel('$\\\\partial \\\\theta$ variance')\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "E0XNSoblj_IK"
- },
- "source": [
- "This is a great improvement in getting stronger gradient signals from (near) random QNNs."
- ]
- }
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "barren_plateaus.ipynb",
- "private_outputs": true,
- "provenance": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "name": "python",
- "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
- },
- "vscode": {
- "interpreter": {
- "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
- }
- }
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
},
- "nbformat": 4,
- "nbformat_minor": 0
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "colab": {},
+ "colab_type": "code",
+ "id": "iiQkM5ZgQ8r2"
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j6331ZSsQGY3"
+ },
+ "source": [
+ "# Barren plateaus"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "DyEcfFapraq6"
+ },
+ "source": [
+ "In this example you will explore the result of McClean, 2019 that says not just any quantum neural network structure will do well when it comes to learning. In particular you will see that a certain large family of random quantum circuits do not serve as good quantum neural networks, because they have gradients that vanish almost everywhere. In this example you won't be training any models for a specific learning problem, but instead focusing on the simpler problem of understanding the behaviors of gradients."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "zB_Xw0Y9rVNi"
+ },
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "TorxE5tnkvb2"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FxkQA6oblNqI"
+ },
+ "source": [
+ "Install TensorFlow Quantum:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "saFHsRDpkvkH"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J"
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "1PaclXeSrrMW"
+ },
+ "source": [
+ "Now import TensorFlow and the module dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "enZ300Bflq80"
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_quantum as tfq\n",
+ "\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import numpy as np\n",
+ "\n",
+ "# visualization tools\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from cirq.contrib.svg import SVGCircuit\n",
+ "\n",
+ "np.random.seed(1234)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "b08Mmbs8lr81"
+ },
+ "source": [
+ "## 1. Summary\n",
+ "\n",
+ "Random quantum circuits with many blocks that look like this ($R_{P}(\\theta)$ is a random Pauli rotation): \n",
+ " \n",
+ "\n",
+ "Where if $f(x)$ is defined as the expectation value w.r.t. $Z_{a}Z_{b}$ for any qubits $a$ and $b$, then there is a problem that $f'(x)$ has a mean very close to 0 and does not vary much. You will see this below:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "y31qSRCczI-L"
+ },
+ "source": [
+ "## 2. Generating random circuits\n",
+ "\n",
+ "The construction from the paper is straightforward to follow. The following implements a simple function that generates a random quantum circuit—sometimes referred to as a *quantum neural network* (QNN)—with the given depth on a set of qubits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Nh9vrgPBks7O"
+ },
+ "outputs": [],
+ "source": [
+ "def generate_random_qnn(qubits, symbol, depth):\n",
+ " \"\"\"Generate random QNN's with the same structure from McClean et al.\"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ " for qubit in qubits:\n",
+ " circuit += cirq.ry(np.pi / 4.0)(qubit)\n",
+ "\n",
+ " for d in range(depth):\n",
+ " # Add a series of single qubit rotations.\n",
+ " for i, qubit in enumerate(qubits):\n",
+ " random_n = np.random.uniform()\n",
+ " random_rot = np.random.uniform(\n",
+ " ) * 2.0 * np.pi if i != 0 or d != 0 else symbol\n",
+ " if random_n > 2. / 3.:\n",
+ " # Add a Z.\n",
+ " circuit += cirq.rz(random_rot)(qubit)\n",
+ " elif random_n > 1. / 3.:\n",
+ " # Add a Y.\n",
+ " circuit += cirq.ry(random_rot)(qubit)\n",
+ " else:\n",
+ " # Add a X.\n",
+ " circuit += cirq.rx(random_rot)(qubit)\n",
+ "\n",
+ " # Add CZ ladder.\n",
+ " for src, dest in zip(qubits, qubits[1:]):\n",
+ " circuit += cirq.CZ(src, dest)\n",
+ "\n",
+ " return circuit\n",
+ "\n",
+ "\n",
+ "generate_random_qnn(cirq.GridQubit.rect(1, 3), sympy.Symbol('theta'), 2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gUuQfOyrj_Hu"
+ },
+ "source": [
+ "The authors investigate the gradient of a single parameter $\\theta_{1,1}$. Let's follow along by placing a `sympy.Symbol` in the circuit where $\\theta_{1,1}$ would be. Since the authors do not analyze the statistics for any other symbols in the circuit, let's replace them with random values now instead of later."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "lAVDRQ87k3md"
+ },
+ "source": [
+ "## 3. Running the circuits\n",
+ "\n",
+ "Generate a few of these circuits along with an observable to test the claim that the gradients don't vary much. First, generate a batch of random circuits. Choose a random *ZZ* observable and batch calculate the gradients and variance using TensorFlow Quantum."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "qoDDaHgwj_Hz"
+ },
+ "source": [
+ "### 3.1 Batch variance computation\n",
+ "\n",
+ "Let's write a helper function that computes the variance of the gradient of a given observable over a batch of circuits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "OkdndnBKk8B8"
+ },
+ "outputs": [],
+ "source": [
+ "def process_batch(circuits, symbol, op):\n",
+ " \"\"\"Compute the variance of a batch of expectations w.r.t. op on each circuit that \n",
+ " contains `symbol`. Note that this method sets up a new compute graph every time it is\n",
+ " called so it isn't as performant as possible.\"\"\"\n",
+ "\n",
+ " # Setup a simple layer to batch compute the expectation gradients.\n",
+ " expectation = tfq.layers.Expectation()\n",
+ "\n",
+ " # Prep the inputs as tensors\n",
+ " circuit_tensor = tfq.convert_to_tensor(circuits)\n",
+ " values_tensor = tf.convert_to_tensor(\n",
+ " np.random.uniform(0, 2 * np.pi, (n_circuits, 1)).astype(np.float32))\n",
+ "\n",
+ " # Use TensorFlow GradientTape to track gradients.\n",
+ " with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " forward = expectation(circuit_tensor,\n",
+ " operators=op,\n",
+ " symbol_names=[symbol],\n",
+ " symbol_values=values_tensor)\n",
+ "\n",
+ " # Return variance of gradients across all circuits.\n",
+ " grads = g.gradient(forward, values_tensor)\n",
+ " grad_var = tf.math.reduce_std(grads, axis=0)\n",
+ " return grad_var.numpy()[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "JINYTIjDj_H1"
+ },
+ "source": [
+ "### 3.1 Set up and run\n",
+ "\n",
+ "Choose the number of random circuits to generate along with their depth and the amount of qubits they should act on. Then plot the results."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "xAGBcq9Bj_H3"
+ },
+ "outputs": [],
+ "source": [
+ "n_qubits = [2 * i for i in range(2, 7)\n",
+ " ] # Ranges studied in paper are between 2 and 24.\n",
+ "depth = 50 # Ranges studied in paper are between 50 and 500.\n",
+ "n_circuits = 200\n",
+ "theta_var = []\n",
+ "\n",
+ "for n in n_qubits:\n",
+ " # Generate the random circuits and observable for the given n.\n",
+ " qubits = cirq.GridQubit.rect(1, n)\n",
+ " symbol = sympy.Symbol('theta')\n",
+ " circuits = [\n",
+ " generate_random_qnn(qubits, symbol, depth) for _ in range(n_circuits)\n",
+ " ]\n",
+ " op = cirq.Z(qubits[0]) * cirq.Z(qubits[1])\n",
+ " theta_var.append(process_batch(circuits, symbol, op))\n",
+ "\n",
+ "plt.semilogy(n_qubits, theta_var)\n",
+ "plt.title('Gradient Variance in QNNs')\n",
+ "plt.xlabel('n_qubits')\n",
+ "plt.xticks(n_qubits)\n",
+ "plt.ylabel('$\\\\partial \\\\theta$ variance')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "qY2E0CFjxRE9"
+ },
+ "source": [
+ "This plot shows that for quantum machine learning problems, you can't simply guess a random QNN ansatz and hope for the best. Some structure must be present in the model circuit in order for gradients to vary to the point where learning can happen."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4RE_idhmj_H6"
+ },
+ "source": [
+ "## 4. Heuristics\n",
+ "\n",
+ "An interesting heuristic by Grant, 2019 allows one to start very close to random, but not quite. Using the same circuits as McClean et al., the authors propose a different initialization technique for the classical control parameters to avoid barren plateaus. The initialization technique starts some layers with totally random control parameters—but, in the layers immediately following, choose parameters such that the initial transformation made by the first few layers is undone. The authors call this an *identity block*.\n",
+ "\n",
+ "The advantage of this heuristic is that by changing just a single parameter, all other blocks outside of the current block will remain the identity—and the gradient signal comes through much stronger than before. This allows the user to pick and choose which variables and blocks to modify to get a strong gradient signal. This heuristic does not prevent the user from falling in to a barren plateau during the training phase (and restricts a fully simultaneous update), it just guarantees that you can start outside of a plateau."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Fofv9hgyj_IB"
+ },
+ "source": [
+ "### 4.1 New QNN construction\n",
+ "\n",
+ "Now construct a function to generate identity block QNNs. This implementation is slightly different than the one from the paper. For now, look at the behavior of the gradient of a single parameter so it is consistent with McClean et al, so some simplifications can be made.\n",
+ "\n",
+ "To generate an identity block and train the model, generally you need $U1(\\theta_{1a}) U1(\\theta_{1b})^{\\dagger}$ and not $U1(\\theta_1) U1(\\theta_1)^{\\dagger}$. Initially $\\theta_{1a}$ and $\\theta_{1b}$ are the same angles but they are learned independently. Otherwise, you will always get the identity even after training. The choice for the number of identity blocks is empirical. The deeper the block, the smaller the variance in the middle of the block. But at the start and end of the block, the variance of the parameter gradients should be large. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "PL7mTHEVj_IC"
+ },
+ "outputs": [],
+ "source": [
+ "def generate_identity_qnn(qubits, symbol, block_depth, total_depth):\n",
+ " \"\"\"Generate random QNN's with the same structure from Grant et al.\"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ "\n",
+ " # Generate initial block with symbol.\n",
+ " prep_and_U = generate_random_qnn(qubits, symbol, block_depth)\n",
+ " circuit += prep_and_U\n",
+ "\n",
+ " # Generate dagger of initial block without symbol.\n",
+ " U_dagger = (prep_and_U[1:])**-1\n",
+ " circuit += cirq.resolve_parameters(\n",
+ " U_dagger, param_resolver={symbol: np.random.uniform() * 2 * np.pi})\n",
+ "\n",
+ " for d in range(total_depth - 1):\n",
+ " # Get a random QNN.\n",
+ " prep_and_U_circuit = generate_random_qnn(\n",
+ " qubits,\n",
+ " np.random.uniform() * 2 * np.pi, block_depth)\n",
+ "\n",
+ " # Remove the state-prep component\n",
+ " U_circuit = prep_and_U_circuit[1:]\n",
+ "\n",
+ " # Add U\n",
+ " circuit += U_circuit\n",
+ "\n",
+ " # Add U^dagger\n",
+ " circuit += U_circuit**-1\n",
+ "\n",
+ " return circuit\n",
+ "\n",
+ "\n",
+ "generate_identity_qnn(cirq.GridQubit.rect(1, 3), sympy.Symbol('theta'), 2, 2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "ifWrl19kj_IG"
+ },
+ "source": [
+ "### 4.2 Comparison\n",
+ "\n",
+ "Here you can see that the heuristic does help to keep the variance of the gradient from vanishing as quickly:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "62kmsVAXj_IH"
+ },
+ "outputs": [],
+ "source": [
+ "block_depth = 10\n",
+ "total_depth = 5\n",
+ "\n",
+ "heuristic_theta_var = []\n",
+ "\n",
+ "for n in n_qubits:\n",
+ " # Generate the identity block circuits and observable for the given n.\n",
+ " qubits = cirq.GridQubit.rect(1, n)\n",
+ " symbol = sympy.Symbol('theta')\n",
+ " circuits = [\n",
+ " generate_identity_qnn(qubits, symbol, block_depth, total_depth)\n",
+ " for _ in range(n_circuits)\n",
+ " ]\n",
+ " op = cirq.Z(qubits[0]) * cirq.Z(qubits[1])\n",
+ " heuristic_theta_var.append(process_batch(circuits, symbol, op))\n",
+ "\n",
+ "plt.semilogy(n_qubits, theta_var)\n",
+ "plt.semilogy(n_qubits, heuristic_theta_var)\n",
+ "plt.title('Heuristic vs. Random')\n",
+ "plt.xlabel('n_qubits')\n",
+ "plt.xticks(n_qubits)\n",
+ "plt.ylabel('$\\\\partial \\\\theta$ variance')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "E0XNSoblj_IK"
+ },
+ "source": [
+ "This is a great improvement in getting stronger gradient signals from (near) random QNNs."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "barren_plateaus.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/gradients.ipynb b/docs/tutorials/gradients.ipynb
index 5eef20cb9..166daf4cb 100644
--- a/docs/tutorials/gradients.ipynb
+++ b/docs/tutorials/gradients.ipynb
@@ -1,828 +1,829 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "colab": {},
- "colab_type": "code",
- "id": "iiQkM5ZgQ8r2"
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j6331ZSsQGY3"
- },
- "source": [
- "# Calculate gradients"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FxkQA6oblNqI"
- },
- "source": [
- "This tutorial explores gradient calculation algorithms for the expectation values of quantum circuits.\n",
- "\n",
- "Calculating the gradient of the expectation value of a certain observable in a quantum circuit is an involved process. Expectation values of observables do not have the luxury of having analytic gradient formulas that are always easy to write down—unlike traditional machine learning transformations such as matrix multiplication or vector addition that have analytic gradient formulas which are easy to write down. As a result, there are different quantum gradient calculation methods that come in handy for different scenarios. This tutorial compares and contrasts two different differentiation schemes."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "pvG0gAJqGYJo"
- },
- "source": [
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "TorxE5tnkvb2"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "OIbP5hklC338"
- },
- "source": [
- "Install TensorFlow Quantum:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "saFHsRDpkvkH"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J"
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "MkTqyoSxGUfB"
- },
- "source": [
- "Now import TensorFlow and the module dependencies:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "enZ300Bflq80"
- },
- "outputs": [],
- "source": [
- "import tensorflow as tf\n",
- "import tensorflow_quantum as tfq\n",
- "\n",
- "import cirq\n",
- "import sympy\n",
- "import numpy as np\n",
- "\n",
- "# visualization tools\n",
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from cirq.contrib.svg import SVGCircuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "b08Mmbs8lr81"
- },
- "source": [
- "## 1. Preliminary\n",
- "\n",
- "Let's make the notion of gradient calculation for quantum circuits a little more concrete. Suppose you have a parameterized circuit like this one:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "YkPYJ_Ak-GKu"
- },
- "outputs": [],
- "source": [
- "qubit = cirq.GridQubit(0, 0)\n",
- "my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha'))\n",
- "SVGCircuit(my_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wgQIlCWy-MVr"
- },
- "source": [
- "Along with an observable:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "xurmJdFy-Jae"
- },
- "outputs": [],
- "source": [
- "pauli_x = cirq.X(qubit)\n",
- "pauli_x"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j3OzKYe5NT_W"
- },
- "source": [
- "Looking at this operator you know that $⟨Y(\\alpha)| X | Y(\\alpha)⟩ = \\sin(\\pi \\alpha)$"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Ps-pd2mndXs7"
- },
- "outputs": [],
- "source": [
- "def my_expectation(op, alpha):\n",
- " \"\"\"Compute ⟨Y(alpha)| `op` | Y(alpha)⟩\"\"\"\n",
- " params = {'alpha': alpha}\n",
- " sim = cirq.Simulator()\n",
- " final_state_vector = sim.simulate(my_circuit, params).final_state_vector\n",
- " return op.expectation_from_state_vector(final_state_vector, {qubit: 0}).real\n",
- "\n",
- "\n",
- "my_alpha = 0.3\n",
- "print(\"Expectation=\", my_expectation(pauli_x, my_alpha))\n",
- "print(\"Sin Formula=\", np.sin(np.pi * my_alpha))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "zcCX109cJUaz"
- },
- "source": [
- " and if you define $f_{1}(\\alpha) = ⟨Y(\\alpha)| X | Y(\\alpha)⟩$ then $f_{1}^{'}(\\alpha) = \\pi \\cos(\\pi \\alpha)$. Let's check this:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "VMq7EayNRyQb"
- },
- "outputs": [],
- "source": [
- "def my_grad(obs, alpha, eps=0.01):\n",
- " grad = 0\n",
- " f_x = my_expectation(obs, alpha)\n",
- " f_x_prime = my_expectation(obs, alpha + eps)\n",
- " return ((f_x_prime - f_x) / eps).real\n",
- "\n",
- "\n",
- "print('Finite difference:', my_grad(pauli_x, my_alpha))\n",
- "print('Cosine formula: ', np.pi * np.cos(np.pi * my_alpha))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-SUlLpXBeicF"
- },
- "source": [
- "## 2. The need for a differentiator\n",
- "\n",
- "With larger circuits, you won't always be so lucky to have a formula that precisely calculates the gradients of a given quantum circuit. In the event that a simple formula isn't enough to calculate the gradient, the `tfq.differentiators.Differentiator` class allows you to define algorithms for computing the gradients of your circuits. For instance you can recreate the above example in TensorFlow Quantum (TFQ) with:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Om76ZLu8NT_i"
- },
- "outputs": [],
- "source": [
- "expectation_calculation = tfq.layers.Expectation(\n",
- " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
- "\n",
- "expectation_calculation(my_circuit,\n",
- " operators=pauli_x,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=[[my_alpha]])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "lx3y2DX9NT_k"
- },
- "source": [
- "However, if you switch to estimating expectation based on sampling (what would happen on a true device) the values can change a little bit. This means you now have an imperfect estimate:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "v27rRyAHNT_l"
- },
- "outputs": [],
- "source": [
- "sampled_expectation_calculation = tfq.layers.SampledExpectation(\n",
- " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
- "\n",
- "sampled_expectation_calculation(my_circuit,\n",
- " operators=pauli_x,\n",
- " repetitions=500,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=[[my_alpha]])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Igwa3EnzNT_p"
- },
- "source": [
- "This can quickly compound into a serious accuracy problem when it comes to gradients:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "StljXH38NT_q"
- },
- "outputs": [],
- "source": [
- "# Make input_points = [batch_size, 1] array.\n",
- "input_points = np.linspace(0, 5, 200)[:, np.newaxis].astype(np.float32)\n",
- "exact_outputs = expectation_calculation(my_circuit,\n",
- " operators=pauli_x,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=input_points)\n",
- "imperfect_outputs = sampled_expectation_calculation(my_circuit,\n",
- " operators=pauli_x,\n",
- " repetitions=500,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=input_points)\n",
- "plt.title('Forward Pass Values')\n",
- "plt.xlabel('$x$')\n",
- "plt.ylabel('$f(x)$')\n",
- "plt.plot(input_points, exact_outputs, label='Analytic')\n",
- "plt.plot(input_points, imperfect_outputs, label='Sampled')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "dfXObk7KNT_t"
- },
- "outputs": [],
- "source": [
- "# Gradients are a much different story.\n",
- "values_tensor = tf.convert_to_tensor(input_points)\n",
- "\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " exact_outputs = expectation_calculation(my_circuit,\n",
- " operators=pauli_x,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=values_tensor)\n",
- "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
- "\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " imperfect_outputs = sampled_expectation_calculation(\n",
- " my_circuit,\n",
- " operators=pauli_x,\n",
- " repetitions=500,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=values_tensor)\n",
- "sampled_finite_diff_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
- "\n",
- "plt.title('Gradient Values')\n",
- "plt.xlabel('$x$')\n",
- "plt.ylabel('$f^{\\'}(x)$')\n",
- "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
- "plt.plot(input_points, sampled_finite_diff_gradients, label='Sampled')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Ld34TJvTNT_w"
- },
- "source": [
- "Here you can see that although the finite difference formula is fast to compute the gradients themselves in the analytical case, when it came to the sampling based methods it was far too noisy. More careful techniques must be used to ensure a good gradient can be calculated. Next you will look at a much slower technique that wouldn't be as well suited for analytical expectation gradient calculations, but does perform much better in the real-world sample based case:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "JsBxH_RaNT_x"
- },
- "outputs": [],
- "source": [
- "# A smarter differentiation scheme.\n",
- "gradient_safe_sampled_expectation = tfq.layers.SampledExpectation(\n",
- " differentiator=tfq.differentiators.ParameterShift())\n",
- "\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " imperfect_outputs = gradient_safe_sampled_expectation(\n",
- " my_circuit,\n",
- " operators=pauli_x,\n",
- " repetitions=500,\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=values_tensor)\n",
- "\n",
- "sampled_param_shift_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
- "\n",
- "plt.title('Gradient Values')\n",
- "plt.xlabel('$x$')\n",
- "plt.ylabel('$f^{\\'}(x)$')\n",
- "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
- "plt.plot(input_points, sampled_param_shift_gradients, label='Sampled')\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "0xlUlh8wNT_z"
- },
- "source": [
- "From the above you can see that certain differentiators are best used for particular research scenarios. In general, the slower sample-based methods that are robust to device noise, etc., are great differentiators when testing or implementing algorithms in a more \"real world\" setting. Faster methods like finite difference are great for analytical calculations and you want higher throughput, but aren't yet concerned with the device viability of your algorithm."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FaijzZ4MNT_0"
- },
- "source": [
- "## 3. Multiple observables\n",
- "\n",
- "Let's introduce a second observable and see how TensorFlow Quantum supports multiple observables for a single circuit."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ytgB_DqDNT_3"
- },
- "outputs": [],
- "source": [
- "pauli_z = cirq.Z(qubit)\n",
- "pauli_z"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "r51TZls4NT_6"
- },
- "source": [
- "If this observable is used with the same circuit as before, then you have $f_{2}(\\alpha) = ⟨Y(\\alpha)| Z | Y(\\alpha)⟩ = \\cos(\\pi \\alpha)$ and $f_{2}^{'}(\\alpha) = -\\pi \\sin(\\pi \\alpha)$. Perform a quick check:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "19FKgu0ANT_7"
- },
- "outputs": [],
- "source": [
- "test_value = 0.\n",
- "\n",
- "print('Finite difference:', my_grad(pauli_z, test_value))\n",
- "print('Sin formula: ', -np.pi * np.sin(np.pi * test_value))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "_33Y5mL0NT_-"
- },
- "source": [
- "It's a match (close enough).\n",
- "\n",
- "Now if you define $g(\\alpha) = f_{1}(\\alpha) + f_{2}(\\alpha)$ then $g'(\\alpha) = f_{1}^{'}(\\alpha) + f^{'}_{2}(\\alpha)$. Defining more than one observable in TensorFlow Quantum to use along with a circuit is equivalent to adding on more terms to $g$.\n",
- "\n",
- "This means that the gradient of a particular symbol in a circuit is equal to the sum of the gradients with regards to each observable for that symbol applied to that circuit. This is compatible with TensorFlow gradient taking and backpropagation (where you give the sum of the gradients over all observables as the gradient for a particular symbol)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "3WFJfFEbNT_-"
- },
- "outputs": [],
- "source": [
- "sum_of_outputs = tfq.layers.Expectation(\n",
- " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
- "\n",
- "sum_of_outputs(my_circuit,\n",
- " operators=[pauli_x, pauli_z],\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=[[test_value]])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-ujQUu3WNUAB"
- },
- "source": [
- "Here you see the first entry is the expectation w.r.t Pauli X, and the second is the expectation w.r.t Pauli Z. Now when you take the gradient:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "jcAQa9l0NUAB"
- },
- "outputs": [],
- "source": [
- "test_value_tensor = tf.convert_to_tensor([[test_value]])\n",
- "\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(test_value_tensor)\n",
- " outputs = sum_of_outputs(my_circuit,\n",
- " operators=[pauli_x, pauli_z],\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=test_value_tensor)\n",
- "\n",
- "sum_of_gradients = g.gradient(outputs, test_value_tensor)\n",
- "\n",
- "print(my_grad(pauli_x, test_value) + my_grad(pauli_z, test_value))\n",
- "print(sum_of_gradients.numpy())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-fZmbYGANUAE"
- },
- "source": [
- "Here you have verified that the sum of the gradients for each observable is indeed the gradient of $\\alpha$. This behavior is supported by all TensorFlow Quantum differentiators and plays a crucial role in the compatibility with the rest of TensorFlow."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "lZsGG7lWNUAF"
- },
- "source": [
- "## 4. Advanced usage\n",
- "All differentiators that exist inside of TensorFlow Quantum subclass `tfq.differentiators.Differentiator`. To implement a differentiator, a user must implement one of two interfaces. The standard is to implement `get_gradient_circuits`, which tells the base class which circuits to measure to obtain an estimate of the gradient. Alternatively, you can overload `differentiate_analytic` and `differentiate_sampled`; the class `tfq.differentiators.Adjoint` takes this route.\n",
- "\n",
- "The following uses TensorFlow Quantum to implement the gradient of a circuit. You will use a small example of parameter shifting."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "J1xN6Ln5mB9N"
- },
- "source": [
- "Recall the circuit you defined above, $|\\alpha⟩ = Y^{\\alpha}|0⟩$. As before, you can define a function as the expectation value of this circuit against the $X$ observable, $f(\\alpha) = ⟨\\alpha|X|\\alpha⟩$. Using [parameter shift rules](https://pennylane.ai/qml/glossary/parameter_shift.html), for this circuit, you can find that the derivative is\n",
- "$$\\frac{\\partial}{\\partial \\alpha} f(\\alpha) = \\frac{\\pi}{2} f\\left(\\alpha + \\frac{1}{2}\\right) - \\frac{ \\pi}{2} f\\left(\\alpha - \\frac{1}{2}\\right)$$\n",
- "The `get_gradient_circuits` function returns the components of this derivative."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "5iY4q6FKNUAG"
- },
- "outputs": [],
- "source": [
- "class MyDifferentiator(tfq.differentiators.Differentiator):\n",
- " \"\"\"A Toy differentiator for .\"\"\"\n",
- "\n",
- " def __init__(self):\n",
- " pass\n",
- "\n",
- " def get_gradient_circuits(self, programs, symbol_names, symbol_values):\n",
- " \"\"\"Return circuits to compute gradients for given forward pass circuits.\n",
- " \n",
- " Every gradient on a quantum computer can be computed via measurements\n",
- " of transformed quantum circuits. Here, you implement a custom gradient\n",
- " for a specific circuit. For a real differentiator, you will need to\n",
- " implement this function in a more general way. See the differentiator\n",
- " implementations in the TFQ library for examples.\n",
- " \"\"\"\n",
- "\n",
- " # The two terms in the derivative are the same circuit...\n",
- " batch_programs = tf.stack([programs, programs], axis=1)\n",
- "\n",
- " # ... with shifted parameter values.\n",
- " shift = tf.constant(1/2)\n",
- " forward = symbol_values + shift\n",
- " backward = symbol_values - shift\n",
- " batch_symbol_values = tf.stack([forward, backward], axis=1)\n",
- " \n",
- " # Weights are the coefficients of the terms in the derivative.\n",
- " num_program_copies = tf.shape(batch_programs)[0]\n",
- " batch_weights = tf.tile(tf.constant([[[np.pi/2, -np.pi/2]]]),\n",
- " [num_program_copies, 1, 1])\n",
- "\n",
- " # The index map simply says which weights go with which circuits.\n",
- " batch_mapper = tf.tile(\n",
- " tf.constant([[[0, 1]]]), [num_program_copies, 1, 1])\n",
- "\n",
- " return (batch_programs, symbol_names, batch_symbol_values,\n",
- " batch_weights, batch_mapper)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "bvEgw2m6NUAI"
- },
- "source": [
- "The `Differentiator` base class uses the components returned from `get_gradient_circuits` to calculate the derivative, as in the parameter shift formula you saw above. This new differentiator can now be used with existing `tfq.layer` objects:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "QrKnkWswNUAJ"
- },
- "outputs": [],
- "source": [
- "custom_dif = MyDifferentiator()\n",
- "custom_grad_expectation = tfq.layers.Expectation(differentiator=custom_dif)\n",
- "\n",
- "# Now let's get the gradients with finite diff.\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " exact_outputs = expectation_calculation(my_circuit,\n",
- " operators=[pauli_x],\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=values_tensor)\n",
- "\n",
- "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
- "\n",
- "# Now let's get the gradients with custom diff.\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(values_tensor)\n",
- " my_outputs = custom_grad_expectation(my_circuit,\n",
- " operators=[pauli_x],\n",
- " symbol_names=['alpha'],\n",
- " symbol_values=values_tensor)\n",
- "\n",
- "my_gradients = g.gradient(my_outputs, values_tensor)\n",
- "\n",
- "plt.subplot(1, 2, 1)\n",
- "plt.title('Exact Gradient')\n",
- "plt.plot(input_points, analytic_finite_diff_gradients.numpy())\n",
- "plt.xlabel('x')\n",
- "plt.ylabel('f(x)')\n",
- "plt.subplot(1, 2, 2)\n",
- "plt.title('My Gradient')\n",
- "plt.plot(input_points, my_gradients.numpy())\n",
- "plt.xlabel('x')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "oXqcJWigNUAL"
- },
- "source": [
- "This new differentiator can now be used to generate differentiable ops.\n",
- "\n",
- "Key Point: A differentiator that has been previously attached to an op must be refreshed before attaching to a new op, because a differentiator may only be attached to one op at a time."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "F_WHcj3bNUAM"
- },
- "outputs": [],
- "source": [
- "# Create a noisy sample based expectation op.\n",
- "expectation_sampled = tfq.get_sampled_expectation_op(\n",
- " cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.01)))\n",
- "\n",
- "# Make it differentiable with your differentiator:\n",
- "# Remember to refresh the differentiator before attaching the new op\n",
- "custom_dif.refresh()\n",
- "differentiable_op = custom_dif.generate_differentiable_op(\n",
- " sampled_op=expectation_sampled)\n",
- "\n",
- "# Prep op inputs.\n",
- "circuit_tensor = tfq.convert_to_tensor([my_circuit])\n",
- "op_tensor = tfq.convert_to_tensor([[pauli_x]])\n",
- "single_value = tf.convert_to_tensor([[my_alpha]])\n",
- "num_samples_tensor = tf.convert_to_tensor([[5000]])\n",
- "\n",
- "with tf.GradientTape() as g:\n",
- " g.watch(single_value)\n",
- " forward_output = differentiable_op(circuit_tensor, ['alpha'], single_value,\n",
- " op_tensor, num_samples_tensor)\n",
- "\n",
- "my_gradients = g.gradient(forward_output, single_value)\n",
- "\n",
- "print('---TFQ---')\n",
- "print('Foward: ', forward_output.numpy())\n",
- "print('Gradient:', my_gradients.numpy())\n",
- "print('---Original---')\n",
- "print('Forward: ', my_expectation(pauli_x, my_alpha))\n",
- "print('Gradient:', my_grad(pauli_x, my_alpha))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "OGWcpqzDNUAP"
- },
- "source": [
- "Success: Now you can use all the differentiators that TensorFlow Quantum has to offer—and define your own."
- ]
- }
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "gradients.ipynb",
- "private_outputs": true,
- "provenance": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "name": "python",
- "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
- },
- "vscode": {
- "interpreter": {
- "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "colab": {},
+ "colab_type": "code",
+ "id": "iiQkM5ZgQ8r2"
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j6331ZSsQGY3"
+ },
+ "source": [
+ "# Calculate gradients"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FxkQA6oblNqI"
+ },
+ "source": [
+ "This tutorial explores gradient calculation algorithms for the expectation values of quantum circuits.\n",
+ "\n",
+ "Calculating the gradient of the expectation value of a certain observable in a quantum circuit is an involved process. Expectation values of observables do not have the luxury of having analytic gradient formulas that are always easy to write down—unlike traditional machine learning transformations such as matrix multiplication or vector addition that have analytic gradient formulas which are easy to write down. As a result, there are different quantum gradient calculation methods that come in handy for different scenarios. This tutorial compares and contrasts two different differentiation schemes."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "pvG0gAJqGYJo"
+ },
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "TorxE5tnkvb2"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OIbP5hklC338"
+ },
+ "source": [
+ "Install TensorFlow Quantum:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "saFHsRDpkvkH"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J"
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "MkTqyoSxGUfB"
+ },
+ "source": [
+ "Now import TensorFlow and the module dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "enZ300Bflq80"
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_quantum as tfq\n",
+ "\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import numpy as np\n",
+ "\n",
+ "# visualization tools\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from cirq.contrib.svg import SVGCircuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "b08Mmbs8lr81"
+ },
+ "source": [
+ "## 1. Preliminary\n",
+ "\n",
+ "Let's make the notion of gradient calculation for quantum circuits a little more concrete. Suppose you have a parameterized circuit like this one:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "YkPYJ_Ak-GKu"
+ },
+ "outputs": [],
+ "source": [
+ "qubit = cirq.GridQubit(0, 0)\n",
+ "my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha'))\n",
+ "SVGCircuit(my_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wgQIlCWy-MVr"
+ },
+ "source": [
+ "Along with an observable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "xurmJdFy-Jae"
+ },
+ "outputs": [],
+ "source": [
+ "pauli_x = cirq.X(qubit)\n",
+ "pauli_x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j3OzKYe5NT_W"
+ },
+ "source": [
+ "Looking at this operator you know that $⟨Y(\\alpha)| X | Y(\\alpha)⟩ = \\sin(\\pi \\alpha)$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Ps-pd2mndXs7"
+ },
+ "outputs": [],
+ "source": [
+ "def my_expectation(op, alpha):\n",
+ " \"\"\"Compute ⟨Y(alpha)| `op` | Y(alpha)⟩\"\"\"\n",
+ " params = {'alpha': alpha}\n",
+ " sim = cirq.Simulator()\n",
+ " final_state_vector = sim.simulate(my_circuit, params).final_state_vector\n",
+ " return op.expectation_from_state_vector(final_state_vector, {qubit: 0}).real\n",
+ "\n",
+ "\n",
+ "my_alpha = 0.3\n",
+ "print(\"Expectation=\", my_expectation(pauli_x, my_alpha))\n",
+ "print(\"Sin Formula=\", np.sin(np.pi * my_alpha))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "zcCX109cJUaz"
+ },
+ "source": [
+ " and if you define $f_{1}(\\alpha) = ⟨Y(\\alpha)| X | Y(\\alpha)⟩$ then $f_{1}^{'}(\\alpha) = \\pi \\cos(\\pi \\alpha)$. Let's check this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "VMq7EayNRyQb"
+ },
+ "outputs": [],
+ "source": [
+ "def my_grad(obs, alpha, eps=0.01):\n",
+ " grad = 0\n",
+ " f_x = my_expectation(obs, alpha)\n",
+ " f_x_prime = my_expectation(obs, alpha + eps)\n",
+ " return ((f_x_prime - f_x) / eps).real\n",
+ "\n",
+ "\n",
+ "print('Finite difference:', my_grad(pauli_x, my_alpha))\n",
+ "print('Cosine formula: ', np.pi * np.cos(np.pi * my_alpha))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "-SUlLpXBeicF"
+ },
+ "source": [
+ "## 2. The need for a differentiator\n",
+ "\n",
+ "With larger circuits, you won't always be so lucky to have a formula that precisely calculates the gradients of a given quantum circuit. In the event that a simple formula isn't enough to calculate the gradient, the `tfq.differentiators.Differentiator` class allows you to define algorithms for computing the gradients of your circuits. For instance you can recreate the above example in TensorFlow Quantum (TFQ) with:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Om76ZLu8NT_i"
+ },
+ "outputs": [],
+ "source": [
+ "expectation_calculation = tfq.layers.Expectation(\n",
+ " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
+ "\n",
+ "expectation_calculation(my_circuit,\n",
+ " operators=pauli_x,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=[[my_alpha]])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "lx3y2DX9NT_k"
+ },
+ "source": [
+ "However, if you switch to estimating expectation based on sampling (what would happen on a true device) the values can change a little bit. This means you now have an imperfect estimate:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "v27rRyAHNT_l"
+ },
+ "outputs": [],
+ "source": [
+ "sampled_expectation_calculation = tfq.layers.SampledExpectation(\n",
+ " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
+ "\n",
+ "sampled_expectation_calculation(my_circuit,\n",
+ " operators=pauli_x,\n",
+ " repetitions=500,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=[[my_alpha]])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Igwa3EnzNT_p"
+ },
+ "source": [
+ "This can quickly compound into a serious accuracy problem when it comes to gradients:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "StljXH38NT_q"
+ },
+ "outputs": [],
+ "source": [
+ "# Make input_points = [batch_size, 1] array.\n",
+ "input_points = np.linspace(0, 5, 200)[:, np.newaxis].astype(np.float32)\n",
+ "exact_outputs = expectation_calculation(my_circuit,\n",
+ " operators=pauli_x,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=input_points)\n",
+ "imperfect_outputs = sampled_expectation_calculation(my_circuit,\n",
+ " operators=pauli_x,\n",
+ " repetitions=500,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=input_points)\n",
+ "plt.title('Forward Pass Values')\n",
+ "plt.xlabel('$x$')\n",
+ "plt.ylabel('$f(x)$')\n",
+ "plt.plot(input_points, exact_outputs, label='Analytic')\n",
+ "plt.plot(input_points, imperfect_outputs, label='Sampled')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "dfXObk7KNT_t"
+ },
+ "outputs": [],
+ "source": [
+ "# Gradients are a much different story.\n",
+ "values_tensor = tf.convert_to_tensor(input_points)\n",
+ "\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " exact_outputs = expectation_calculation(my_circuit,\n",
+ " operators=pauli_x,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=values_tensor)\n",
+ "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
+ "\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " imperfect_outputs = sampled_expectation_calculation(\n",
+ " my_circuit,\n",
+ " operators=pauli_x,\n",
+ " repetitions=500,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=values_tensor)\n",
+ "sampled_finite_diff_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
+ "\n",
+ "plt.title('Gradient Values')\n",
+ "plt.xlabel('$x$')\n",
+ "plt.ylabel('$f^{\\'}(x)$')\n",
+ "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
+ "plt.plot(input_points, sampled_finite_diff_gradients, label='Sampled')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Ld34TJvTNT_w"
+ },
+ "source": [
+ "Here you can see that although the finite difference formula is fast to compute the gradients themselves in the analytical case, when it came to the sampling based methods it was far too noisy. More careful techniques must be used to ensure a good gradient can be calculated. Next you will look at a much slower technique that wouldn't be as well suited for analytical expectation gradient calculations, but does perform much better in the real-world sample based case:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "JsBxH_RaNT_x"
+ },
+ "outputs": [],
+ "source": [
+ "# A smarter differentiation scheme.\n",
+ "gradient_safe_sampled_expectation = tfq.layers.SampledExpectation(\n",
+ " differentiator=tfq.differentiators.ParameterShift())\n",
+ "\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " imperfect_outputs = gradient_safe_sampled_expectation(\n",
+ " my_circuit,\n",
+ " operators=pauli_x,\n",
+ " repetitions=500,\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=values_tensor)\n",
+ "\n",
+ "sampled_param_shift_gradients = g.gradient(imperfect_outputs, values_tensor)\n",
+ "\n",
+ "plt.title('Gradient Values')\n",
+ "plt.xlabel('$x$')\n",
+ "plt.ylabel('$f^{\\'}(x)$')\n",
+ "plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')\n",
+ "plt.plot(input_points, sampled_param_shift_gradients, label='Sampled')\n",
+ "plt.legend()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "0xlUlh8wNT_z"
+ },
+ "source": [
+ "From the above you can see that certain differentiators are best used for particular research scenarios. In general, the slower sample-based methods that are robust to device noise, etc., are great differentiators when testing or implementing algorithms in a more \"real world\" setting. Faster methods like finite difference are great for analytical calculations and you want higher throughput, but aren't yet concerned with the device viability of your algorithm."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FaijzZ4MNT_0"
+ },
+ "source": [
+ "## 3. Multiple observables\n",
+ "\n",
+ "Let's introduce a second observable and see how TensorFlow Quantum supports multiple observables for a single circuit."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "ytgB_DqDNT_3"
+ },
+ "outputs": [],
+ "source": [
+ "pauli_z = cirq.Z(qubit)\n",
+ "pauli_z"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "r51TZls4NT_6"
+ },
+ "source": [
+ "If this observable is used with the same circuit as before, then you have $f_{2}(\\alpha) = ⟨Y(\\alpha)| Z | Y(\\alpha)⟩ = \\cos(\\pi \\alpha)$ and $f_{2}^{'}(\\alpha) = -\\pi \\sin(\\pi \\alpha)$. Perform a quick check:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "19FKgu0ANT_7"
+ },
+ "outputs": [],
+ "source": [
+ "test_value = 0.\n",
+ "\n",
+ "print('Finite difference:', my_grad(pauli_z, test_value))\n",
+ "print('Sin formula: ', -np.pi * np.sin(np.pi * test_value))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_33Y5mL0NT_-"
+ },
+ "source": [
+ "It's a match (close enough).\n",
+ "\n",
+ "Now if you define $g(\\alpha) = f_{1}(\\alpha) + f_{2}(\\alpha)$ then $g'(\\alpha) = f_{1}^{'}(\\alpha) + f^{'}_{2}(\\alpha)$. Defining more than one observable in TensorFlow Quantum to use along with a circuit is equivalent to adding on more terms to $g$.\n",
+ "\n",
+ "This means that the gradient of a particular symbol in a circuit is equal to the sum of the gradients with regards to each observable for that symbol applied to that circuit. This is compatible with TensorFlow gradient taking and backpropagation (where you give the sum of the gradients over all observables as the gradient for a particular symbol)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "3WFJfFEbNT_-"
+ },
+ "outputs": [],
+ "source": [
+ "sum_of_outputs = tfq.layers.Expectation(\n",
+ " differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))\n",
+ "\n",
+ "sum_of_outputs(my_circuit,\n",
+ " operators=[pauli_x, pauli_z],\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=[[test_value]])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "-ujQUu3WNUAB"
+ },
+ "source": [
+ "Here you see the first entry is the expectation w.r.t Pauli X, and the second is the expectation w.r.t Pauli Z. Now when you take the gradient:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "jcAQa9l0NUAB"
+ },
+ "outputs": [],
+ "source": [
+ "test_value_tensor = tf.convert_to_tensor([[test_value]])\n",
+ "\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(test_value_tensor)\n",
+ " outputs = sum_of_outputs(my_circuit,\n",
+ " operators=[pauli_x, pauli_z],\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=test_value_tensor)\n",
+ "\n",
+ "sum_of_gradients = g.gradient(outputs, test_value_tensor)\n",
+ "\n",
+ "print(my_grad(pauli_x, test_value) + my_grad(pauli_z, test_value))\n",
+ "print(sum_of_gradients.numpy())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "-fZmbYGANUAE"
+ },
+ "source": [
+ "Here you have verified that the sum of the gradients for each observable is indeed the gradient of $\\alpha$. This behavior is supported by all TensorFlow Quantum differentiators and plays a crucial role in the compatibility with the rest of TensorFlow."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "lZsGG7lWNUAF"
+ },
+ "source": [
+ "## 4. Advanced usage\n",
+ "All differentiators that exist inside of TensorFlow Quantum subclass `tfq.differentiators.Differentiator`. To implement a differentiator, a user must implement one of two interfaces. The standard is to implement `get_gradient_circuits`, which tells the base class which circuits to measure to obtain an estimate of the gradient. Alternatively, you can overload `differentiate_analytic` and `differentiate_sampled`; the class `tfq.differentiators.Adjoint` takes this route.\n",
+ "\n",
+ "The following uses TensorFlow Quantum to implement the gradient of a circuit. You will use a small example of parameter shifting."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "J1xN6Ln5mB9N"
+ },
+ "source": [
+ "Recall the circuit you defined above, $|\\alpha⟩ = Y^{\\alpha}|0⟩$. As before, you can define a function as the expectation value of this circuit against the $X$ observable, $f(\\alpha) = ⟨\\alpha|X|\\alpha⟩$. Using [parameter shift rules](https://pennylane.ai/qml/glossary/parameter_shift.html), for this circuit, you can find that the derivative is\n",
+ "$$\\frac{\\partial}{\\partial \\alpha} f(\\alpha) = \\frac{\\pi}{2} f\\left(\\alpha + \\frac{1}{2}\\right) - \\frac{ \\pi}{2} f\\left(\\alpha - \\frac{1}{2}\\right)$$\n",
+ "The `get_gradient_circuits` function returns the components of this derivative."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "5iY4q6FKNUAG"
+ },
+ "outputs": [],
+ "source": [
+ "class MyDifferentiator(tfq.differentiators.Differentiator):\n",
+ " \"\"\"A Toy differentiator for .\"\"\"\n",
+ "\n",
+ " def __init__(self):\n",
+ " pass\n",
+ "\n",
+ " def get_gradient_circuits(self, programs, symbol_names, symbol_values):\n",
+ " \"\"\"Return circuits to compute gradients for given forward pass circuits.\n",
+ " \n",
+ " Every gradient on a quantum computer can be computed via measurements\n",
+ " of transformed quantum circuits. Here, you implement a custom gradient\n",
+ " for a specific circuit. For a real differentiator, you will need to\n",
+ " implement this function in a more general way. See the differentiator\n",
+ " implementations in the TFQ library for examples.\n",
+ " \"\"\"\n",
+ "\n",
+ " # The two terms in the derivative are the same circuit...\n",
+ " batch_programs = tf.stack([programs, programs], axis=1)\n",
+ "\n",
+ " # ... with shifted parameter values.\n",
+ " shift = tf.constant(1 / 2)\n",
+ " forward = symbol_values + shift\n",
+ " backward = symbol_values - shift\n",
+ " batch_symbol_values = tf.stack([forward, backward], axis=1)\n",
+ "\n",
+ " # Weights are the coefficients of the terms in the derivative.\n",
+ " num_program_copies = tf.shape(batch_programs)[0]\n",
+ " batch_weights = tf.tile(tf.constant([[[np.pi / 2, -np.pi / 2]]]),\n",
+ " [num_program_copies, 1, 1])\n",
+ "\n",
+ " # The index map simply says which weights go with which circuits.\n",
+ " batch_mapper = tf.tile(tf.constant([[[0, 1]]]),\n",
+ " [num_program_copies, 1, 1])\n",
+ "\n",
+ " return (batch_programs, symbol_names, batch_symbol_values,\n",
+ " batch_weights, batch_mapper)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "bvEgw2m6NUAI"
+ },
+ "source": [
+ "The `Differentiator` base class uses the components returned from `get_gradient_circuits` to calculate the derivative, as in the parameter shift formula you saw above. This new differentiator can now be used with existing `tfq.layer` objects:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "QrKnkWswNUAJ"
+ },
+ "outputs": [],
+ "source": [
+ "custom_dif = MyDifferentiator()\n",
+ "custom_grad_expectation = tfq.layers.Expectation(differentiator=custom_dif)\n",
+ "\n",
+ "# Now let's get the gradients with finite diff.\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " exact_outputs = expectation_calculation(my_circuit,\n",
+ " operators=[pauli_x],\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=values_tensor)\n",
+ "\n",
+ "analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)\n",
+ "\n",
+ "# Now let's get the gradients with custom diff.\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(values_tensor)\n",
+ " my_outputs = custom_grad_expectation(my_circuit,\n",
+ " operators=[pauli_x],\n",
+ " symbol_names=['alpha'],\n",
+ " symbol_values=values_tensor)\n",
+ "\n",
+ "my_gradients = g.gradient(my_outputs, values_tensor)\n",
+ "\n",
+ "plt.subplot(1, 2, 1)\n",
+ "plt.title('Exact Gradient')\n",
+ "plt.plot(input_points, analytic_finite_diff_gradients.numpy())\n",
+ "plt.xlabel('x')\n",
+ "plt.ylabel('f(x)')\n",
+ "plt.subplot(1, 2, 2)\n",
+ "plt.title('My Gradient')\n",
+ "plt.plot(input_points, my_gradients.numpy())\n",
+ "plt.xlabel('x')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "oXqcJWigNUAL"
+ },
+ "source": [
+ "This new differentiator can now be used to generate differentiable ops.\n",
+ "\n",
+ "Key Point: A differentiator that has been previously attached to an op must be refreshed before attaching to a new op, because a differentiator may only be attached to one op at a time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "F_WHcj3bNUAM"
+ },
+ "outputs": [],
+ "source": [
+ "# Create a noisy sample based expectation op.\n",
+ "expectation_sampled = tfq.get_sampled_expectation_op(\n",
+ " cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.01)))\n",
+ "\n",
+ "# Make it differentiable with your differentiator:\n",
+ "# Remember to refresh the differentiator before attaching the new op\n",
+ "custom_dif.refresh()\n",
+ "differentiable_op = custom_dif.generate_differentiable_op(\n",
+ " sampled_op=expectation_sampled)\n",
+ "\n",
+ "# Prep op inputs.\n",
+ "circuit_tensor = tfq.convert_to_tensor([my_circuit])\n",
+ "op_tensor = tfq.convert_to_tensor([[pauli_x]])\n",
+ "single_value = tf.convert_to_tensor([[my_alpha]])\n",
+ "num_samples_tensor = tf.convert_to_tensor([[5000]])\n",
+ "\n",
+ "with tf.GradientTape() as g:\n",
+ " g.watch(single_value)\n",
+ " forward_output = differentiable_op(circuit_tensor, ['alpha'], single_value,\n",
+ " op_tensor, num_samples_tensor)\n",
+ "\n",
+ "my_gradients = g.gradient(forward_output, single_value)\n",
+ "\n",
+ "print('---TFQ---')\n",
+ "print('Foward: ', forward_output.numpy())\n",
+ "print('Gradient:', my_gradients.numpy())\n",
+ "print('---Original---')\n",
+ "print('Forward: ', my_expectation(pauli_x, my_alpha))\n",
+ "print('Gradient:', my_grad(pauli_x, my_alpha))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OGWcpqzDNUAP"
+ },
+ "source": [
+ "Success: Now you can use all the differentiators that TensorFlow Quantum has to offer—and define your own."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "gradients.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/hello_many_worlds.ipynb b/docs/tutorials/hello_many_worlds.ipynb
index 49c88893b..759c8afaf 100644
--- a/docs/tutorials/hello_many_worlds.ipynb
+++ b/docs/tutorials/hello_many_worlds.ipynb
@@ -1,1339 +1,1343 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "colab": {},
- "colab_type": "code",
- "id": "iiQkM5ZgQ8r2",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j6331ZSsQGY3"
- },
- "source": [
- "# Hello, many worlds"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "6tYn2HaAUgH0"
- },
- "source": [
- "This tutorial shows how a classical neural network can learn to correct qubit calibration errors. It introduces Cirq , a Python framework to create, edit, and invoke Noisy Intermediate Scale Quantum (NISQ) circuits, and demonstrates how Cirq interfaces with TensorFlow Quantum."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "sPZoNKvpUaqa"
- },
- "source": [
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "TorxE5tnkvb2",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FxkQA6oblNqI"
- },
- "source": [
- "Install TensorFlow Quantum:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "saFHsRDpkvkH",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "F1L8h1YKUvIO"
- },
- "source": [
- "Now import TensorFlow and the module dependencies:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "enZ300Bflq80",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "import tensorflow as tf\n",
- "import tensorflow_quantum as tfq\n",
- "\n",
- "import cirq\n",
- "import sympy\n",
- "import numpy as np\n",
- "\n",
- "# visualization tools\n",
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from cirq.contrib.svg import SVGCircuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "b08Mmbs8lr81"
- },
- "source": [
- "## 1. The Basics"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "y31qSRCczI-L"
- },
- "source": [
- "### 1.1 Cirq and parameterized quantum circuits\n",
- "\n",
- "Before exploring TensorFlow Quantum (TFQ), let's look at some Cirq basics. Cirq is a Python library for quantum computing from Google. You use it to define circuits, including static and parameterized gates.\n",
- "\n",
- "Cirq uses SymPy symbols to represent free parameters."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "2yQdmhQLCrzQ",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "a, b = sympy.symbols('a b')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "itUlpbKmDYNW"
- },
- "source": [
- "The following code creates a two-qubit circuit using your parameters:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Ps-pd2mndXs7",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Create two qubits\n",
- "q0, q1 = cirq.GridQubit.rect(1, 2)\n",
- "\n",
- "# Create a circuit on these qubits using the parameters you created above.\n",
- "circuit = cirq.Circuit(\n",
- " cirq.rx(a).on(q0),\n",
- " cirq.ry(b).on(q1), cirq.CNOT(q0, q1))\n",
- "\n",
- "SVGCircuit(circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "zcCX109cJUaz"
- },
- "source": [
- "To evaluate circuits, you can use the `cirq.Simulator` interface. You replace free parameters in a circuit with specific numbers by passing in a `cirq.ParamResolver` object. The following code calculates the raw state vector output of your parameterized circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "VMq7EayNRyQb",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Calculate a state vector with a=0.5 and b=-0.5.\n",
- "resolver = cirq.ParamResolver({a: 0.5, b: -0.5})\n",
- "output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state_vector\n",
- "output_state_vector"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-SUlLpXBeicF"
- },
- "source": [
- "State vectors are not directly accessible outside of simulation (notice the complex numbers in the output above). To be physically realistic, you must specify a measurement, which converts a state vector into a real number that classical computers can understand. Cirq specifies measurements using combinations of the Pauli operators $\\hat{X}$, $\\hat{Y}$, and $\\hat{Z}$. As illustration, the following code measures $\\hat{Z}_0$ and $\\frac{1}{2}\\hat{Z}_0 + \\hat{X}_1$ on the state vector you just simulated:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "hrSnOCi3ehr_",
- "scrolled": true,
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "z0 = cirq.Z(q0)\n",
- "\n",
- "qubit_map={q0: 0, q1: 1}\n",
- "\n",
- "z0.expectation_from_state_vector(output_state_vector, qubit_map).real"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "OZ0lWFXv6pII",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "z0x1 = 0.5 * z0 + cirq.X(q1)\n",
- "\n",
- "z0x1.expectation_from_state_vector(output_state_vector, qubit_map).real"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "bkC-yjIolDNr"
- },
- "source": [
- "### 1.2 Quantum circuits as tensors\n",
- "\n",
- "TensorFlow Quantum (TFQ) provides `tfq.convert_to_tensor`, a function that converts Cirq objects into tensors. This allows you to send Cirq objects to our quantum layers and quantum ops . The function can be called on lists or arrays of Cirq Circuits and Cirq Paulis:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1gLQjA02mIyy",
- "scrolled": true,
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Rank 1 tensor containing 1 circuit.\n",
- "circuit_tensor = tfq.convert_to_tensor([circuit])\n",
- "\n",
- "print(circuit_tensor.shape)\n",
- "print(circuit_tensor.dtype)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "SJy6AkbU6pIP"
- },
- "source": [
- "This encodes the Cirq objects as `tf.string` tensors that `tfq` operations decode as needed."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "aX_vEmCKmpQS",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Rank 1 tensor containing 2 Pauli operators.\n",
- "pauli_tensor = tfq.convert_to_tensor([z0, z0x1])\n",
- "pauli_tensor.shape"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FI1JLWe6m8JF"
- },
- "source": [
- "### 1.3 Batching circuit simulation\n",
- "\n",
- "TFQ provides methods for computing expectation values, samples, and state vectors. For now, let's focus on *expectation values*.\n",
- "\n",
- "The highest-level interface for calculating expectation values is the `tfq.layers.Expectation` layer, which is a `tf.keras.Layer`. In its simplest form, this layer is equivalent to simulating a parameterized circuit over many `cirq.ParamResolvers`; however, TFQ allows batching following TensorFlow semantics, and circuits are simulated using efficient C++ code.\n",
- "\n",
- "Create a batch of values to substitute for our `a` and `b` parameters:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1fsVZhF5lIXp",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "batch_vals = np.array(np.random.uniform(0, 2 * np.pi, (5, 2)), dtype=float)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Ip7jlGXIf22u"
- },
- "source": [
- "Batching circuit execution over parameter values in Cirq requires a loop:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "RsfF53UCJtr9",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "cirq_results = []\n",
- "cirq_simulator = cirq.Simulator()\n",
- "\n",
- "for vals in batch_vals:\n",
- " resolver = cirq.ParamResolver({a: vals[0], b: vals[1]})\n",
- " final_state_vector = cirq_simulator.simulate(circuit, resolver).final_state_vector\n",
- " cirq_results.append(\n",
- " [z0.expectation_from_state_vector(final_state_vector, {\n",
- " q0: 0,\n",
- " q1: 1\n",
- " }).real])\n",
- "\n",
- "print('cirq batch results: \\n {}'.format(np.array(cirq_results)))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "W0JlZEu-f9Ac"
- },
- "source": [
- "The same operation is simplified in TFQ:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "kGZVdcZ6y9lC",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "tfq.layers.Expectation()(circuit,\n",
- " symbol_names=[a, b],\n",
- " symbol_values=batch_vals,\n",
- " operators=z0)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wppQ3TJ23mWC"
- },
- "source": [
- "## 2. Hybrid quantum-classical optimization\n",
- "\n",
- "Now that you've seen the basics, let's use TensorFlow Quantum to construct a *hybrid quantum-classical neural net*. You will train a classical neural net to control a single qubit. The control will be optimized to correctly prepare the qubit in the `0` or `1` state, overcoming a simulated systematic calibration error. This figure shows the architecture:\n",
- "\n",
- " \n",
- "\n",
- "Even without a neural network this is a straightforward problem to solve, but the theme is similar to the real quantum control problems you might solve using TFQ. It demonstrates an end-to-end example of a quantum-classical computation using the `tfq.layers.ControlledPQC` (Parametrized Quantum Circuit) layer inside of a `tf.keras.Model`."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "NlyxF3Q-6pIe"
- },
- "source": [
- "For the implementation of this tutorial, this architecture is split into 3 parts:\n",
- "\n",
- "- The *input circuit* or *datapoint circuit*: The first three $R$ gates.\n",
- "- The *controlled circuit*: The other three $R$ gates.\n",
- "- The *controller*: The classical neural-network setting the parameters of the controlled circuit."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "VjDf-nTM6ZSs"
- },
- "source": [
- "### 2.1 The controlled circuit definition\n",
- "\n",
- "Define a learnable single bit rotation, as indicated in the figure above. This will correspond to our controlled circuit."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "N-j7SCl-51-q",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Parameters that the classical NN will feed values into.\n",
- "control_params = sympy.symbols('theta_1 theta_2 theta_3')\n",
- "\n",
- "# Create the parameterized circuit.\n",
- "qubit = cirq.GridQubit(0, 0)\n",
- "model_circuit = cirq.Circuit(\n",
- " cirq.rz(control_params[0])(qubit),\n",
- " cirq.ry(control_params[1])(qubit),\n",
- " cirq.rx(control_params[2])(qubit))\n",
- "\n",
- "SVGCircuit(model_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wfjSbsvb7g9f"
- },
- "source": [
- "### 2.2 The controller\n",
- "\n",
- "Now define controller network: "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1v4CK2jD6pIj",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# The classical neural network layers.\n",
- "controller = tf.keras.Sequential([\n",
- " tf.keras.layers.Dense(10, activation='elu'),\n",
- " tf.keras.layers.Dense(3)\n",
- "])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "QNimbsAt6pIm"
- },
- "source": [
- "Given a batch of commands, the controller outputs a batch of control signals for the controlled circuit. \n",
- "\n",
- "The controller is randomly initialized so these outputs are not useful, yet."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "kZbYRTe16pIm",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "controller(tf.constant([[0.0],[1.0]])).numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "XizLExg56pIp"
- },
- "source": [
- "### 2.3 Connect the controller to the circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "I5Pmy5-V6pIq"
- },
- "source": [
- "Use `tfq` to connect the controller to the controlled circuit, as a single `keras.Model`. \n",
- "\n",
- "See the [Keras Functional API guide](https://www.tensorflow.org/guide/keras/functional) for more about this style of model definition.\n",
- "\n",
- "First define the inputs to the model: "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "UfHF8NNE6pIr",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# This input is the simulated miscalibration that the model will learn to correct.\n",
- "circuits_input = tf.keras.Input(shape=(),\n",
- " # The circuit-tensor has dtype `tf.string` \n",
- " dtype=tf.string,\n",
- " name='circuits_input')\n",
- "\n",
- "# Commands will be either `0` or `1`, specifying the state to set the qubit to.\n",
- "commands_input = tf.keras.Input(shape=(1,),\n",
- " dtype=tf.dtypes.float32,\n",
- " name='commands_input')\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "y9xN2mNl6pIu"
- },
- "source": [
- "Next apply operations to those inputs, to define the computation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Zvt2YGmZ6pIu",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "dense_2 = controller(commands_input)\n",
- "\n",
- "# TFQ layer for classically controlled circuits.\n",
- "expectation_layer = tfq.layers.ControlledPQC(model_circuit,\n",
- " # Observe Z\n",
- " operators = cirq.Z(qubit))\n",
- "expectation = expectation_layer([circuits_input, dense_2])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Ip2jNA9h6pIy"
- },
- "source": [
- "Now package this computation as a `tf.keras.Model`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Xs6EMhah6pIz",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# The full Keras model is built from our layers.\n",
- "model = tf.keras.Model(inputs=[circuits_input, commands_input],\n",
- " outputs=expectation)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "w7kgqm3t6pI3"
- },
- "source": [
- "The network architecture is indicated by the plot of the model below.\n",
- "Compare this model plot to the architecture diagram to verify correctness.\n",
- "\n",
- "Note: May require a system install of the `graphviz` package."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ERXNPe4F6pI4",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "-Pbemgww6pI7"
- },
- "source": [
- "This model takes two inputs: The commands for the controller, and the input-circuit whose output the controller is attempting to correct. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "hpnIBK916pI8"
- },
- "source": [
- "### 2.4 The dataset"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "yJSC9qH76pJA"
- },
- "source": [
- "The model attempts to output the correct correct measurement value of $\\hat{Z}$ for each command. The commands and correct values are defined below."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ciMIJAuH6pJA",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# The command input values to the classical NN.\n",
- "commands = np.array([[0], [1]], dtype=np.float32)\n",
- "\n",
- "# The desired Z expectation value at output of quantum circuit.\n",
- "expected_outputs = np.array([[1], [-1]], dtype=np.float32)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "kV1LM_hZ6pJD"
- },
- "source": [
- "This is not the entire training dataset for this task. \n",
- "Each datapoint in the dataset also needs an input circuit."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "bbiVHvSYVW4H"
- },
- "source": [
- "### 2.4 Input circuit definition\n",
- "\n",
- "The input-circuit below defines the random miscalibration the model will learn to correct."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "_VYfzHffWo7n",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "random_rotations = np.random.uniform(0, 2 * np.pi, 3)\n",
- "noisy_preparation = cirq.Circuit(\n",
- " cirq.rx(random_rotations[0])(qubit),\n",
- " cirq.ry(random_rotations[1])(qubit),\n",
- " cirq.rz(random_rotations[2])(qubit)\n",
- ")\n",
- "datapoint_circuits = tfq.convert_to_tensor([\n",
- " noisy_preparation\n",
- "] * 2) # Make two copied of this circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FvOkMyKI6pJI"
- },
- "source": [
- "There are two copies of the circuit, one for each datapoint."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "6nk2Yr3e6pJJ",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "datapoint_circuits.shape"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "gB--UhZZYgVY"
- },
- "source": [
- "### 2.5 Training"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "jATjqUIv6pJM"
- },
- "source": [
- "With the inputs defined you can test-run the `tfq` model."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Lwphqvs96pJO",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "model([datapoint_circuits, commands]).numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "9gyg5qSL6pJR"
- },
- "source": [
- "Now run a standard training process to adjust these values towards the `expected_outputs`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "dtPYqbNi8zeZ",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)\n",
- "loss = tf.keras.losses.MeanSquaredError()\n",
- "model.compile(optimizer=optimizer, loss=loss)\n",
- "history = model.fit(x=[datapoint_circuits, commands],\n",
- " y=expected_outputs,\n",
- " epochs=30,\n",
- " verbose=0)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "azE-qV0OaC1o",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "plt.plot(history.history['loss'])\n",
- "plt.title(\"Learning to Control a Qubit\")\n",
- "plt.xlabel(\"Iterations\")\n",
- "plt.ylabel(\"Error in Control\")\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "GTd5DGcRmmOK"
- },
- "source": [
- "From this plot you can see that the neural network has learned to overcome the systematic miscalibration."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "C2RfWismj66S"
- },
- "source": [
- "### 2.6 Verify outputs\n",
- "Now use the trained model, to correct the qubit calibration errors. With Cirq:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "RoIlb7r7j5SY",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def check_error(command_values, desired_values):\n",
- " \"\"\"Based on the value in `command_value` see how well you could prepare\n",
- " the full circuit to have `desired_value` when taking expectation w.r.t. Z.\"\"\"\n",
- " params_to_prepare_output = controller(command_values).numpy()\n",
- " full_circuit = noisy_preparation + model_circuit\n",
- "\n",
- " # Test how well you can prepare a state to get expectation the expectation\n",
- " # value in `desired_values`\n",
- " for index in [0, 1]:\n",
- " state = cirq_simulator.simulate(\n",
- " full_circuit,\n",
- " {s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}\n",
- " ).final_state_vector\n",
- " expt = cirq.Z(qubit).expectation_from_state_vector(state, {qubit: 0}).real\n",
- " print(f'For a desired output (expectation) of {desired_values[index]} with'\n",
- " f' noisy preparation, the controller\\nnetwork found the following '\n",
- " f'values for theta: {params_to_prepare_output[index]}\\nWhich gives an'\n",
- " f' actual expectation of: {expt}\\n')\n",
- "\n",
- "\n",
- "check_error(commands, expected_outputs)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wvW_ZDwmsws6"
- },
- "source": [
- "The value of the loss function during training provides a rough idea of how well the model is learning. The lower the loss, the closer the expectation values in the above cell is to `desired_values`. If you aren't as concerned with the parameter values, you can always check the outputs from above using `tfq`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "aYskLTacs8Ku",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "model([datapoint_circuits, commands])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "jNrW0NXR-lDC"
- },
- "source": [
- "## 3 Learning to prepare eigenstates of different operators\n",
- "\n",
- "The choice of the $\\pm \\hat{Z}$ eigenstates corresponding to 1 and 0 was arbitrary. You could have just as easily wanted 1 to correspond to the $+ \\hat{Z}$ eigenstate and 0 to correspond to the $-\\hat{X}$ eigenstate. One way to accomplish this is by specifying a different measurement operator for each command, as indicated in the figure below:\n",
- "\n",
- " \n",
- "\n",
- "This requires use of tfq.layers.Expectation
. Now your input has grown to include three objects: circuit, command, and operator. The output is still the expectation value."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Ci3WMZ9CjEM1"
- },
- "source": [
- "### 3.1 New model definition\n",
- "\n",
- "Lets take a look at the model to accomplish this task:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "hta0G3Nc6pJY",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Define inputs.\n",
- "commands_input = tf.keras.layers.Input(shape=(1),\n",
- " dtype=tf.dtypes.float32,\n",
- " name='commands_input')\n",
- "circuits_input = tf.keras.Input(shape=(),\n",
- " # The circuit-tensor has dtype `tf.string` \n",
- " dtype=tf.dtypes.string,\n",
- " name='circuits_input')\n",
- "operators_input = tf.keras.Input(shape=(1,),\n",
- " dtype=tf.dtypes.string,\n",
- " name='operators_input')"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "dtdnkrZm6pJb"
- },
- "source": [
- "Here is the controller network:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "n_aTG4g3-y0F",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Define classical NN.\n",
- "controller = tf.keras.Sequential([\n",
- " tf.keras.layers.Dense(10, activation='elu'),\n",
- " tf.keras.layers.Dense(3)\n",
- "])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "q9aN2ciy6pJf"
- },
- "source": [
- "Combine the circuit and the controller into a single `keras.Model` using `tfq`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "IMHjiKit6pJg",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "dense_2 = controller(commands_input)\n",
- "\n",
- "# Since you aren't using a PQC or ControlledPQC you must append\n",
- "# your model circuit onto the datapoint circuit tensor manually.\n",
- "full_circuit = tfq.layers.AddCircuit()(circuits_input, append=model_circuit)\n",
- "expectation_output = tfq.layers.Expectation()(full_circuit,\n",
- " symbol_names=control_params,\n",
- " symbol_values=dense_2,\n",
- " operators=operators_input)\n",
- "\n",
- "# Contruct your Keras model.\n",
- "two_axis_control_model = tf.keras.Model(\n",
- " inputs=[circuits_input, commands_input, operators_input],\n",
- " outputs=[expectation_output])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "VQTM6CCiD4gU"
- },
- "source": [
- "### 3.2 The dataset\n",
- "\n",
- "Now you will also include the operators you wish to measure for each datapoint you supply for `model_circuit`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4gw_L3JG0_G0",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# The operators to measure, for each command.\n",
- "operator_data = tfq.convert_to_tensor([[cirq.X(qubit)], [cirq.Z(qubit)]])\n",
- "\n",
- "# The command input values to the classical NN.\n",
- "commands = np.array([[0], [1]], dtype=np.float32)\n",
- "\n",
- "# The desired expectation value at output of quantum circuit.\n",
- "expected_outputs = np.array([[1], [-1]], dtype=np.float32)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "ALCKSvwh0_G2"
- },
- "source": [
- "### 3.3 Training\n",
- "\n",
- "Now that you have your new inputs and outputs you can train once again using keras."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "nFuGA73MAA4p",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)\n",
- "loss = tf.keras.losses.MeanSquaredError()\n",
- "\n",
- "two_axis_control_model.compile(optimizer=optimizer, loss=loss)\n",
- "\n",
- "history = two_axis_control_model.fit(\n",
- " x=[datapoint_circuits, commands, operator_data],\n",
- " y=expected_outputs,\n",
- " epochs=30,\n",
- " verbose=1)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Cf_G-GdturLL",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "plt.plot(history.history['loss'])\n",
- "plt.title(\"Learning to Control a Qubit\")\n",
- "plt.xlabel(\"Iterations\")\n",
- "plt.ylabel(\"Error in Control\")\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "sdCPDH9NlJBl"
- },
- "source": [
- "The loss function has dropped to zero."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "NzY8eSVm6pJs"
- },
- "source": [
- "The `controller` is available as a stand-alone model. Call the controller, and check its response to each command signal. It would take some work to correctly compare these outputs to the contents of `random_rotations`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "uXmH0TQ76pJt",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "controller.predict(np.array([0,1]))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "n2WtXnsxubD2"
- },
- "source": [
- "Success: See if you can adapt the `check_error` function from your first model to work with this new model architecture."
- ]
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "colab": {},
+ "colab_type": "code",
+ "id": "iiQkM5ZgQ8r2",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j6331ZSsQGY3"
+ },
+ "source": [
+ "# Hello, many worlds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6tYn2HaAUgH0"
+ },
+ "source": [
+ "This tutorial shows how a classical neural network can learn to correct qubit calibration errors. It introduces Cirq , a Python framework to create, edit, and invoke Noisy Intermediate Scale Quantum (NISQ) circuits, and demonstrates how Cirq interfaces with TensorFlow Quantum."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "sPZoNKvpUaqa"
+ },
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "TorxE5tnkvb2",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FxkQA6oblNqI"
+ },
+ "source": [
+ "Install TensorFlow Quantum:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "saFHsRDpkvkH",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "F1L8h1YKUvIO"
+ },
+ "source": [
+ "Now import TensorFlow and the module dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "enZ300Bflq80",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_quantum as tfq\n",
+ "\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import numpy as np\n",
+ "\n",
+ "# visualization tools\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from cirq.contrib.svg import SVGCircuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "b08Mmbs8lr81"
+ },
+ "source": [
+ "## 1. The Basics"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "y31qSRCczI-L"
+ },
+ "source": [
+ "### 1.1 Cirq and parameterized quantum circuits\n",
+ "\n",
+ "Before exploring TensorFlow Quantum (TFQ), let's look at some Cirq basics. Cirq is a Python library for quantum computing from Google. You use it to define circuits, including static and parameterized gates.\n",
+ "\n",
+ "Cirq uses SymPy symbols to represent free parameters."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "2yQdmhQLCrzQ",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "a, b = sympy.symbols('a b')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "itUlpbKmDYNW"
+ },
+ "source": [
+ "The following code creates a two-qubit circuit using your parameters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Ps-pd2mndXs7",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Create two qubits\n",
+ "q0, q1 = cirq.GridQubit.rect(1, 2)\n",
+ "\n",
+ "# Create a circuit on these qubits using the parameters you created above.\n",
+ "circuit = cirq.Circuit(cirq.rx(a).on(q0), cirq.ry(b).on(q1), cirq.CNOT(q0, q1))\n",
+ "\n",
+ "SVGCircuit(circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "zcCX109cJUaz"
+ },
+ "source": [
+ "To evaluate circuits, you can use the `cirq.Simulator` interface. You replace free parameters in a circuit with specific numbers by passing in a `cirq.ParamResolver` object. The following code calculates the raw state vector output of your parameterized circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "VMq7EayNRyQb",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Calculate a state vector with a=0.5 and b=-0.5.\n",
+ "resolver = cirq.ParamResolver({a: 0.5, b: -0.5})\n",
+ "output_state_vector = cirq.Simulator().simulate(circuit,\n",
+ " resolver).final_state_vector\n",
+ "output_state_vector"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "-SUlLpXBeicF"
+ },
+ "source": [
+ "State vectors are not directly accessible outside of simulation (notice the complex numbers in the output above). To be physically realistic, you must specify a measurement, which converts a state vector into a real number that classical computers can understand. Cirq specifies measurements using combinations of the Pauli operators $\\hat{X}$, $\\hat{Y}$, and $\\hat{Z}$. As illustration, the following code measures $\\hat{Z}_0$ and $\\frac{1}{2}\\hat{Z}_0 + \\hat{X}_1$ on the state vector you just simulated:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "hrSnOCi3ehr_",
+ "scrolled": true,
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "z0 = cirq.Z(q0)\n",
+ "\n",
+ "qubit_map = {q0: 0, q1: 1}\n",
+ "\n",
+ "z0.expectation_from_state_vector(output_state_vector, qubit_map).real"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "OZ0lWFXv6pII",
+ "vscode": {
+ "languageId": "python"
}
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "hello_many_worlds.ipynb",
- "private_outputs": true,
- "provenance": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
+ },
+ "outputs": [],
+ "source": [
+ "z0x1 = 0.5 * z0 + cirq.X(q1)\n",
+ "\n",
+ "z0x1.expectation_from_state_vector(output_state_vector, qubit_map).real"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "bkC-yjIolDNr"
+ },
+ "source": [
+ "### 1.2 Quantum circuits as tensors\n",
+ "\n",
+ "TensorFlow Quantum (TFQ) provides `tfq.convert_to_tensor`, a function that converts Cirq objects into tensors. This allows you to send Cirq objects to our quantum layers and quantum ops . The function can be called on lists or arrays of Cirq Circuits and Cirq Paulis:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1gLQjA02mIyy",
+ "scrolled": true,
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Rank 1 tensor containing 1 circuit.\n",
+ "circuit_tensor = tfq.convert_to_tensor([circuit])\n",
+ "\n",
+ "print(circuit_tensor.shape)\n",
+ "print(circuit_tensor.dtype)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SJy6AkbU6pIP"
+ },
+ "source": [
+ "This encodes the Cirq objects as `tf.string` tensors that `tfq` operations decode as needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aX_vEmCKmpQS",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Rank 1 tensor containing 2 Pauli operators.\n",
+ "pauli_tensor = tfq.convert_to_tensor([z0, z0x1])\n",
+ "pauli_tensor.shape"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FI1JLWe6m8JF"
+ },
+ "source": [
+ "### 1.3 Batching circuit simulation\n",
+ "\n",
+ "TFQ provides methods for computing expectation values, samples, and state vectors. For now, let's focus on *expectation values*.\n",
+ "\n",
+ "The highest-level interface for calculating expectation values is the `tfq.layers.Expectation` layer, which is a `tf.keras.Layer`. In its simplest form, this layer is equivalent to simulating a parameterized circuit over many `cirq.ParamResolvers`; however, TFQ allows batching following TensorFlow semantics, and circuits are simulated using efficient C++ code.\n",
+ "\n",
+ "Create a batch of values to substitute for our `a` and `b` parameters:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1fsVZhF5lIXp",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "batch_vals = np.array(np.random.uniform(0, 2 * np.pi, (5, 2)), dtype=float)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Ip7jlGXIf22u"
+ },
+ "source": [
+ "Batching circuit execution over parameter values in Cirq requires a loop:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "RsfF53UCJtr9",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "cirq_results = []\n",
+ "cirq_simulator = cirq.Simulator()\n",
+ "\n",
+ "for vals in batch_vals:\n",
+ " resolver = cirq.ParamResolver({a: vals[0], b: vals[1]})\n",
+ " final_state_vector = cirq_simulator.simulate(circuit,\n",
+ " resolver).final_state_vector\n",
+ " cirq_results.append([\n",
+ " z0.expectation_from_state_vector(final_state_vector, {\n",
+ " q0: 0,\n",
+ " q1: 1\n",
+ " }).real\n",
+ " ])\n",
+ "\n",
+ "print('cirq batch results: \\n {}'.format(np.array(cirq_results)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "W0JlZEu-f9Ac"
+ },
+ "source": [
+ "The same operation is simplified in TFQ:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "kGZVdcZ6y9lC",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "tfq.layers.Expectation()(circuit,\n",
+ " symbol_names=[a, b],\n",
+ " symbol_values=batch_vals,\n",
+ " operators=z0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wppQ3TJ23mWC"
+ },
+ "source": [
+ "## 2. Hybrid quantum-classical optimization\n",
+ "\n",
+ "Now that you've seen the basics, let's use TensorFlow Quantum to construct a *hybrid quantum-classical neural net*. You will train a classical neural net to control a single qubit. The control will be optimized to correctly prepare the qubit in the `0` or `1` state, overcoming a simulated systematic calibration error. This figure shows the architecture:\n",
+ "\n",
+ " \n",
+ "\n",
+ "Even without a neural network this is a straightforward problem to solve, but the theme is similar to the real quantum control problems you might solve using TFQ. It demonstrates an end-to-end example of a quantum-classical computation using the `tfq.layers.ControlledPQC` (Parametrized Quantum Circuit) layer inside of a `tf.keras.Model`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "NlyxF3Q-6pIe"
+ },
+ "source": [
+ "For the implementation of this tutorial, this architecture is split into 3 parts:\n",
+ "\n",
+ "- The *input circuit* or *datapoint circuit*: The first three $R$ gates.\n",
+ "- The *controlled circuit*: The other three $R$ gates.\n",
+ "- The *controller*: The classical neural-network setting the parameters of the controlled circuit."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "VjDf-nTM6ZSs"
+ },
+ "source": [
+ "### 2.1 The controlled circuit definition\n",
+ "\n",
+ "Define a learnable single bit rotation, as indicated in the figure above. This will correspond to our controlled circuit."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "N-j7SCl-51-q",
+ "vscode": {
+ "languageId": "python"
}
+ },
+ "outputs": [],
+ "source": [
+ "# Parameters that the classical NN will feed values into.\n",
+ "control_params = sympy.symbols('theta_1 theta_2 theta_3')\n",
+ "\n",
+ "# Create the parameterized circuit.\n",
+ "qubit = cirq.GridQubit(0, 0)\n",
+ "model_circuit = cirq.Circuit(\n",
+ " cirq.rz(control_params[0])(qubit),\n",
+ " cirq.ry(control_params[1])(qubit),\n",
+ " cirq.rx(control_params[2])(qubit))\n",
+ "\n",
+ "SVGCircuit(model_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wfjSbsvb7g9f"
+ },
+ "source": [
+ "### 2.2 The controller\n",
+ "\n",
+ "Now define controller network: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1v4CK2jD6pIj",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# The classical neural network layers.\n",
+ "controller = tf.keras.Sequential(\n",
+ " [tf.keras.layers.Dense(10, activation='elu'),\n",
+ " tf.keras.layers.Dense(3)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "QNimbsAt6pIm"
+ },
+ "source": [
+ "Given a batch of commands, the controller outputs a batch of control signals for the controlled circuit. \n",
+ "\n",
+ "The controller is randomly initialized so these outputs are not useful, yet."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "kZbYRTe16pIm",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "controller(tf.constant([[0.0], [1.0]])).numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "XizLExg56pIp"
+ },
+ "source": [
+ "### 2.3 Connect the controller to the circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "I5Pmy5-V6pIq"
+ },
+ "source": [
+ "Use `tfq` to connect the controller to the controlled circuit, as a single `keras.Model`. \n",
+ "\n",
+ "See the [Keras Functional API guide](https://www.tensorflow.org/guide/keras/functional) for more about this style of model definition.\n",
+ "\n",
+ "First define the inputs to the model: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "UfHF8NNE6pIr",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# This input is the simulated miscalibration that the model will learn to correct.\n",
+ "circuits_input = tf.keras.Input(\n",
+ " shape=(),\n",
+ " # The circuit-tensor has dtype `tf.string`\n",
+ " dtype=tf.string,\n",
+ " name='circuits_input')\n",
+ "\n",
+ "# Commands will be either `0` or `1`, specifying the state to set the qubit to.\n",
+ "commands_input = tf.keras.Input(shape=(1,),\n",
+ " dtype=tf.dtypes.float32,\n",
+ " name='commands_input')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "y9xN2mNl6pIu"
+ },
+ "source": [
+ "Next apply operations to those inputs, to define the computation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Zvt2YGmZ6pIu",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "dense_2 = controller(commands_input)\n",
+ "\n",
+ "# TFQ layer for classically controlled circuits.\n",
+ "expectation_layer = tfq.layers.ControlledPQC(\n",
+ " model_circuit,\n",
+ " # Observe Z\n",
+ " operators=cirq.Z(qubit))\n",
+ "expectation = expectation_layer([circuits_input, dense_2])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Ip2jNA9h6pIy"
+ },
+ "source": [
+ "Now package this computation as a `tf.keras.Model`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xs6EMhah6pIz",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# The full Keras model is built from our layers.\n",
+ "model = tf.keras.Model(inputs=[circuits_input, commands_input],\n",
+ " outputs=expectation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "w7kgqm3t6pI3"
+ },
+ "source": [
+ "The network architecture is indicated by the plot of the model below.\n",
+ "Compare this model plot to the architecture diagram to verify correctness.\n",
+ "\n",
+ "Note: May require a system install of the `graphviz` package."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "ERXNPe4F6pI4",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "-Pbemgww6pI7"
+ },
+ "source": [
+ "This model takes two inputs: The commands for the controller, and the input-circuit whose output the controller is attempting to correct. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "hpnIBK916pI8"
+ },
+ "source": [
+ "### 2.4 The dataset"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "yJSC9qH76pJA"
+ },
+ "source": [
+ "The model attempts to output the correct correct measurement value of $\\hat{Z}$ for each command. The commands and correct values are defined below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "ciMIJAuH6pJA",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# The command input values to the classical NN.\n",
+ "commands = np.array([[0], [1]], dtype=np.float32)\n",
+ "\n",
+ "# The desired Z expectation value at output of quantum circuit.\n",
+ "expected_outputs = np.array([[1], [-1]], dtype=np.float32)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "kV1LM_hZ6pJD"
+ },
+ "source": [
+ "This is not the entire training dataset for this task. \n",
+ "Each datapoint in the dataset also needs an input circuit."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "bbiVHvSYVW4H"
+ },
+ "source": [
+ "### 2.4 Input circuit definition\n",
+ "\n",
+ "The input-circuit below defines the random miscalibration the model will learn to correct."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "_VYfzHffWo7n",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "random_rotations = np.random.uniform(0, 2 * np.pi, 3)\n",
+ "noisy_preparation = cirq.Circuit(\n",
+ " cirq.rx(random_rotations[0])(qubit),\n",
+ " cirq.ry(random_rotations[1])(qubit),\n",
+ " cirq.rz(random_rotations[2])(qubit))\n",
+ "datapoint_circuits = tfq.convert_to_tensor([noisy_preparation] *\n",
+ " 2) # Make two copied of this circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FvOkMyKI6pJI"
+ },
+ "source": [
+ "There are two copies of the circuit, one for each datapoint."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "6nk2Yr3e6pJJ",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "datapoint_circuits.shape"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gB--UhZZYgVY"
+ },
+ "source": [
+ "### 2.5 Training"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "jATjqUIv6pJM"
+ },
+ "source": [
+ "With the inputs defined you can test-run the `tfq` model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Lwphqvs96pJO",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "model([datapoint_circuits, commands]).numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9gyg5qSL6pJR"
+ },
+ "source": [
+ "Now run a standard training process to adjust these values towards the `expected_outputs`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "dtPYqbNi8zeZ",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)\n",
+ "loss = tf.keras.losses.MeanSquaredError()\n",
+ "model.compile(optimizer=optimizer, loss=loss)\n",
+ "history = model.fit(x=[datapoint_circuits, commands],\n",
+ " y=expected_outputs,\n",
+ " epochs=30,\n",
+ " verbose=0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "azE-qV0OaC1o",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "plt.plot(history.history['loss'])\n",
+ "plt.title(\"Learning to Control a Qubit\")\n",
+ "plt.xlabel(\"Iterations\")\n",
+ "plt.ylabel(\"Error in Control\")\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "GTd5DGcRmmOK"
+ },
+ "source": [
+ "From this plot you can see that the neural network has learned to overcome the systematic miscalibration."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "C2RfWismj66S"
+ },
+ "source": [
+ "### 2.6 Verify outputs\n",
+ "Now use the trained model, to correct the qubit calibration errors. With Cirq:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "RoIlb7r7j5SY",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def check_error(command_values, desired_values):\n",
+ " \"\"\"Based on the value in `command_value` see how well you could prepare\n",
+ " the full circuit to have `desired_value` when taking expectation w.r.t. Z.\"\"\"\n",
+ " params_to_prepare_output = controller(command_values).numpy()\n",
+ " full_circuit = noisy_preparation + model_circuit\n",
+ "\n",
+ " # Test how well you can prepare a state to get expectation the expectation\n",
+ " # value in `desired_values`\n",
+ " for index in [0, 1]:\n",
+ " state = cirq_simulator.simulate(full_circuit, {\n",
+ " s: v\n",
+ " for (s, v) in zip(control_params, params_to_prepare_output[index])\n",
+ " }).final_state_vector\n",
+ " expt = cirq.Z(qubit).expectation_from_state_vector(state, {\n",
+ " qubit: 0\n",
+ " }).real\n",
+ " print(\n",
+ " f'For a desired output (expectation) of {desired_values[index]} with'\n",
+ " f' noisy preparation, the controller\\nnetwork found the following '\n",
+ " f'values for theta: {params_to_prepare_output[index]}\\nWhich gives an'\n",
+ " f' actual expectation of: {expt}\\n')\n",
+ "\n",
+ "\n",
+ "check_error(commands, expected_outputs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wvW_ZDwmsws6"
+ },
+ "source": [
+ "The value of the loss function during training provides a rough idea of how well the model is learning. The lower the loss, the closer the expectation values in the above cell is to `desired_values`. If you aren't as concerned with the parameter values, you can always check the outputs from above using `tfq`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aYskLTacs8Ku",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "model([datapoint_circuits, commands])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "jNrW0NXR-lDC"
+ },
+ "source": [
+ "## 3 Learning to prepare eigenstates of different operators\n",
+ "\n",
+ "The choice of the $\\pm \\hat{Z}$ eigenstates corresponding to 1 and 0 was arbitrary. You could have just as easily wanted 1 to correspond to the $+ \\hat{Z}$ eigenstate and 0 to correspond to the $-\\hat{X}$ eigenstate. One way to accomplish this is by specifying a different measurement operator for each command, as indicated in the figure below:\n",
+ "\n",
+ " \n",
+ "\n",
+ "This requires use of tfq.layers.Expectation
. Now your input has grown to include three objects: circuit, command, and operator. The output is still the expectation value."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Ci3WMZ9CjEM1"
+ },
+ "source": [
+ "### 3.1 New model definition\n",
+ "\n",
+ "Lets take a look at the model to accomplish this task:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "hta0G3Nc6pJY",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Define inputs.\n",
+ "commands_input = tf.keras.layers.Input(shape=(1),\n",
+ " dtype=tf.dtypes.float32,\n",
+ " name='commands_input')\n",
+ "circuits_input = tf.keras.Input(\n",
+ " shape=(),\n",
+ " # The circuit-tensor has dtype `tf.string`\n",
+ " dtype=tf.dtypes.string,\n",
+ " name='circuits_input')\n",
+ "operators_input = tf.keras.Input(shape=(1,),\n",
+ " dtype=tf.dtypes.string,\n",
+ " name='operators_input')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "dtdnkrZm6pJb"
+ },
+ "source": [
+ "Here is the controller network:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "n_aTG4g3-y0F",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Define classical NN.\n",
+ "controller = tf.keras.Sequential(\n",
+ " [tf.keras.layers.Dense(10, activation='elu'),\n",
+ " tf.keras.layers.Dense(3)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "q9aN2ciy6pJf"
+ },
+ "source": [
+ "Combine the circuit and the controller into a single `keras.Model` using `tfq`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "IMHjiKit6pJg",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "dense_2 = controller(commands_input)\n",
+ "\n",
+ "# Since you aren't using a PQC or ControlledPQC you must append\n",
+ "# your model circuit onto the datapoint circuit tensor manually.\n",
+ "full_circuit = tfq.layers.AddCircuit()(circuits_input, append=model_circuit)\n",
+ "expectation_output = tfq.layers.Expectation()(full_circuit,\n",
+ " symbol_names=control_params,\n",
+ " symbol_values=dense_2,\n",
+ " operators=operators_input)\n",
+ "\n",
+ "# Contruct your Keras model.\n",
+ "two_axis_control_model = tf.keras.Model(\n",
+ " inputs=[circuits_input, commands_input, operators_input],\n",
+ " outputs=[expectation_output])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "VQTM6CCiD4gU"
+ },
+ "source": [
+ "### 3.2 The dataset\n",
+ "\n",
+ "Now you will also include the operators you wish to measure for each datapoint you supply for `model_circuit`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4gw_L3JG0_G0",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# The operators to measure, for each command.\n",
+ "operator_data = tfq.convert_to_tensor([[cirq.X(qubit)], [cirq.Z(qubit)]])\n",
+ "\n",
+ "# The command input values to the classical NN.\n",
+ "commands = np.array([[0], [1]], dtype=np.float32)\n",
+ "\n",
+ "# The desired expectation value at output of quantum circuit.\n",
+ "expected_outputs = np.array([[1], [-1]], dtype=np.float32)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "ALCKSvwh0_G2"
+ },
+ "source": [
+ "### 3.3 Training\n",
+ "\n",
+ "Now that you have your new inputs and outputs you can train once again using keras."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "nFuGA73MAA4p",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)\n",
+ "loss = tf.keras.losses.MeanSquaredError()\n",
+ "\n",
+ "two_axis_control_model.compile(optimizer=optimizer, loss=loss)\n",
+ "\n",
+ "history = two_axis_control_model.fit(\n",
+ " x=[datapoint_circuits, commands, operator_data],\n",
+ " y=expected_outputs,\n",
+ " epochs=30,\n",
+ " verbose=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Cf_G-GdturLL",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "plt.plot(history.history['loss'])\n",
+ "plt.title(\"Learning to Control a Qubit\")\n",
+ "plt.xlabel(\"Iterations\")\n",
+ "plt.ylabel(\"Error in Control\")\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "sdCPDH9NlJBl"
+ },
+ "source": [
+ "The loss function has dropped to zero."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "NzY8eSVm6pJs"
+ },
+ "source": [
+ "The `controller` is available as a stand-alone model. Call the controller, and check its response to each command signal. It would take some work to correctly compare these outputs to the contents of `random_rotations`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "uXmH0TQ76pJt",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "controller.predict(np.array([0, 1]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "n2WtXnsxubD2"
+ },
+ "source": [
+ "Success: See if you can adapt the `check_error` function from your first model to work with this new model architecture."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "hello_many_worlds.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true
},
- "nbformat": 4,
- "nbformat_minor": 0
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/mnist.ipynb b/docs/tutorials/mnist.ipynb
index 7efe3abe4..e3fc0f8aa 100644
--- a/docs/tutorials/mnist.ipynb
+++ b/docs/tutorials/mnist.ipynb
@@ -1,1133 +1,1139 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "colab": {},
- "colab_type": "code",
- "id": "iiQkM5ZgQ8r2"
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j6331ZSsQGY3"
- },
- "source": [
- "# MNIST classification"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "udLObUVeGfTs"
- },
- "source": [
- "This tutorial builds a quantum neural network (QNN) to classify a simplified version of MNIST, similar to the approach used in Farhi et al . The performance of the quantum neural network on this classical data problem is compared with a classical neural network."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "X35qHdh5Gzqg"
- },
- "source": [
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "TorxE5tnkvb2"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FxkQA6oblNqI"
- },
- "source": [
- "Install TensorFlow Quantum:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "saFHsRDpkvkH"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J"
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "hdgMMZEBGqyl"
- },
- "source": [
- "Now import TensorFlow and the module dependencies:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "enZ300Bflq80"
- },
- "outputs": [],
- "source": [
- "import tensorflow as tf\n",
- "import tensorflow_quantum as tfq\n",
- "\n",
- "import cirq\n",
- "import sympy\n",
- "import numpy as np\n",
- "import seaborn as sns\n",
- "import collections\n",
- "\n",
- "# visualization tools\n",
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from cirq.contrib.svg import SVGCircuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "b08Mmbs8lr81"
- },
- "source": [
- "## 1. Load the data\n",
- "\n",
- "In this tutorial you will build a binary classifier to distinguish between the digits 3 and 6, following Farhi et al. This section covers the data handling that:\n",
- "\n",
- "- Loads the raw data from Keras.\n",
- "- Filters the dataset to only 3s and 6s.\n",
- "- Downscales the images so they fit can fit in a quantum computer.\n",
- "- Removes any contradictory examples.\n",
- "- Converts the binary images to Cirq circuits.\n",
- "- Converts the Cirq circuits to TensorFlow Quantum circuits. "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "pDUdGxn-ojgy"
- },
- "source": [
- "### 1.1 Load the raw data"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xZyGXlaKojgz"
- },
- "source": [
- "Load the MNIST dataset distributed with Keras. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "d9OSExvCojg0"
- },
- "outputs": [],
- "source": [
- "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n",
- "\n",
- "# Rescale the images from [0,255] to the [0.0,1.0] range.\n",
- "x_train, x_test = x_train[..., np.newaxis]/255.0, x_test[..., np.newaxis]/255.0\n",
- "\n",
- "print(\"Number of original training examples:\", len(x_train))\n",
- "print(\"Number of original test examples:\", len(x_test))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "fZpbygdGojg3"
- },
- "source": [
- "Filter the dataset to keep just the 3s and 6s, remove the other classes. At the same time convert the label, `y`, to boolean: `True` for `3` and `False` for 6. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "hOw68cCZojg4"
- },
- "outputs": [],
- "source": [
- "def filter_36(x, y):\n",
- " keep = (y == 3) | (y == 6)\n",
- " x, y = x[keep], y[keep]\n",
- " y = y == 3\n",
- " return x,y"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "p-XEU8egGL6q"
- },
- "outputs": [],
- "source": [
- "x_train, y_train = filter_36(x_train, y_train)\n",
- "x_test, y_test = filter_36(x_test, y_test)\n",
- "\n",
- "print(\"Number of filtered training examples:\", len(x_train))\n",
- "print(\"Number of filtered test examples:\", len(x_test))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "3wyiaP0Xojg_"
- },
- "source": [
- "Show the first example:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "j5STP7MbojhA"
- },
- "outputs": [],
- "source": [
- "print(y_train[0])\n",
- "\n",
- "plt.imshow(x_train[0, :, :, 0])\n",
- "plt.colorbar()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wNS9sVPQojhC"
- },
- "source": [
- "### 1.2 Downscale the images"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "fmmtplIFGL6t"
- },
- "source": [
- "An image size of 28x28 is much too large for current quantum computers. Resize the image down to 4x4:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "lbhUdBFWojhE",
- "scrolled": false
- },
- "outputs": [],
- "source": [
- "x_train_small = tf.image.resize(x_train, (4,4)).numpy()\n",
- "x_test_small = tf.image.resize(x_test, (4,4)).numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "pOMd7zIjGL6x"
- },
- "source": [
- "Again, display the first training example—after resize: "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "YIYOtCRIGL6y",
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "print(y_train[0])\n",
- "\n",
- "plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1)\n",
- "plt.colorbar()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "gGeF1_qtojhK"
- },
- "source": [
- "### 1.3 Remove contradictory examples"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "7ZLkq2yeojhL"
- },
- "source": [
- "From section *3.3 Learning to Distinguish Digits* of Farhi et al. , filter the dataset to remove images that are labeled as belonging to both classes.\n",
- "\n",
- "This is not a standard machine-learning procedure, but is included in the interest of following the paper."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "LqOPW0C7ojhL"
- },
- "outputs": [],
- "source": [
- "def remove_contradicting(xs, ys):\n",
- " mapping = collections.defaultdict(set)\n",
- " orig_x = {}\n",
- " # Determine the set of labels for each unique image:\n",
- " for x,y in zip(xs,ys):\n",
- " orig_x[tuple(x.flatten())] = x\n",
- " mapping[tuple(x.flatten())].add(y)\n",
- " \n",
- " new_x = []\n",
- " new_y = []\n",
- " for flatten_x in mapping:\n",
- " x = orig_x[flatten_x]\n",
- " labels = mapping[flatten_x]\n",
- " if len(labels) == 1:\n",
- " new_x.append(x)\n",
- " new_y.append(next(iter(labels)))\n",
- " else:\n",
- " # Throw out images that match more than one label.\n",
- " pass\n",
- " \n",
- " num_uniq_3 = sum(1 for value in mapping.values() if len(value) == 1 and True in value)\n",
- " num_uniq_6 = sum(1 for value in mapping.values() if len(value) == 1 and False in value)\n",
- " num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2)\n",
- "\n",
- " print(\"Number of unique images:\", len(mapping.values()))\n",
- " print(\"Number of unique 3s: \", num_uniq_3)\n",
- " print(\"Number of unique 6s: \", num_uniq_6)\n",
- " print(\"Number of unique contradicting labels (both 3 and 6): \", num_uniq_both)\n",
- " print()\n",
- " print(\"Initial number of images: \", len(xs))\n",
- " print(\"Remaining non-contradicting unique images: \", len(new_x))\n",
- " \n",
- " return np.array(new_x), np.array(new_y)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "VMOiJfz_ojhP"
- },
- "source": [
- "The resulting counts do not closely match the reported values, but the exact procedure is not specified.\n",
- "\n",
- "It is also worth noting here that applying filtering contradictory examples at this point does not totally prevent the model from receiving contradictory training examples: the next step binarizes the data which will cause more collisions. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "zpnsAssWojhP",
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "SlJ5NVaPojhT"
- },
- "source": [
- "### 1.4 Encode the data as quantum circuits\n",
- "\n",
- "To process images using a quantum computer, Farhi et al. proposed representing each pixel with a qubit, with the state depending on the value of the pixel. The first step is to convert to a binary encoding."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1z8J7OyDojhV"
- },
- "outputs": [],
- "source": [
- "THRESHOLD = 0.5\n",
- "\n",
- "x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32)\n",
- "x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "SlJ5NVaPojhU"
- },
- "source": [
- "If you were to remove contradictory images at this point you would be left with only 193, likely not enough for effective training."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1z8J7OyDojhW"
- },
- "outputs": [],
- "source": [
- "_ = remove_contradicting(x_train_bin, y_train_nocon)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "oLyxS9KlojhZ"
- },
- "source": [
- "The qubits at pixel indices with values that exceed a threshold, are rotated through an $X$ gate."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "aOu_3-3ZGL61"
- },
- "outputs": [],
- "source": [
- "def convert_to_circuit(image):\n",
- " \"\"\"Encode truncated classical image into quantum datapoint.\"\"\"\n",
- " values = np.ndarray.flatten(image)\n",
- " qubits = cirq.GridQubit.rect(4, 4)\n",
- " circuit = cirq.Circuit()\n",
- " for i, value in enumerate(values):\n",
- " if value:\n",
- " circuit.append(cirq.X(qubits[i]))\n",
- " return circuit\n",
- "\n",
- "\n",
- "x_train_circ = [convert_to_circuit(x) for x in x_train_bin]\n",
- "x_test_circ = [convert_to_circuit(x) for x in x_test_bin]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "zSCXqzOzojhd"
- },
- "source": [
- "Here is the circuit created for the first example (circuit diagrams do not show qubits with zero gates):"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "w3POmUEUojhe",
- "scrolled": false
- },
- "outputs": [],
- "source": [
- "SVGCircuit(x_train_circ[0])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "AEQMxCcBojhg"
- },
- "source": [
- "Compare this circuit to the indices where the image value exceeds the threshold:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "TBIsiXdtojhh"
- },
- "outputs": [],
- "source": [
- "bin_img = x_train_bin[0,:,:,0]\n",
- "indices = np.array(np.where(bin_img)).T\n",
- "indices"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "mWZ24w1Oojhk"
- },
- "source": [
- "Convert these `Cirq` circuits to tensors for `tfq`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "IZStEMk4ojhk"
- },
- "outputs": [],
- "source": [
- "x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)\n",
- "x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "4USiqeOqGL67"
- },
- "source": [
- "## 2. Quantum neural network\n",
- "\n",
- "There is little guidance for a quantum circuit structure that classifies images. Since the classification is based on the expectation of the readout qubit, Farhi et al. propose using two qubit gates, with the readout qubit always acted upon. This is similar in some ways to running small a Unitary RNN across the pixels."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "knIzawEeojho"
- },
- "source": [
- "### 2.1 Build the model circuit\n",
- "\n",
- "This following example shows this layered approach. Each layer uses *n* instances of the same gate, with each of the data qubits acting on the readout qubit.\n",
- "\n",
- "Start with a simple class that will add a layer of these gates to a circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "-hjxxgU5ojho"
- },
- "outputs": [],
- "source": [
- "class CircuitLayerBuilder():\n",
- " def __init__(self, data_qubits, readout):\n",
- " self.data_qubits = data_qubits\n",
- " self.readout = readout\n",
- " \n",
- " def add_layer(self, circuit, gate, prefix):\n",
- " for i, qubit in enumerate(self.data_qubits):\n",
- " symbol = sympy.Symbol(prefix + '-' + str(i))\n",
- " circuit.append(gate(qubit, self.readout)**symbol)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Sjo5hANFojhr"
- },
- "source": [
- "Build an example circuit layer to see how it looks:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "SzXWOpUGojhs"
- },
- "outputs": [],
- "source": [
- "demo_builder = CircuitLayerBuilder(data_qubits = cirq.GridQubit.rect(4,1),\n",
- " readout=cirq.GridQubit(-1,-1))\n",
- "\n",
- "circuit = cirq.Circuit()\n",
- "demo_builder.add_layer(circuit, gate = cirq.XX, prefix='xx')\n",
- "SVGCircuit(circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "T-QhPE1pojhu"
- },
- "source": [
- "Now build a two-layered model, matching the data-circuit size, and include the preparation and readout operations."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "JiALbpwRGL69"
- },
- "outputs": [],
- "source": [
- "def create_quantum_model():\n",
- " \"\"\"Create a QNN model circuit and readout operation to go along with it.\"\"\"\n",
- " data_qubits = cirq.GridQubit.rect(4, 4) # a 4x4 grid.\n",
- " readout = cirq.GridQubit(-1, -1) # a single qubit at [-1,-1]\n",
- " circuit = cirq.Circuit()\n",
- " \n",
- " # Prepare the readout qubit.\n",
- " circuit.append(cirq.X(readout))\n",
- " circuit.append(cirq.H(readout))\n",
- " \n",
- " builder = CircuitLayerBuilder(\n",
- " data_qubits = data_qubits,\n",
- " readout=readout)\n",
- "\n",
- " # Then add layers (experiment by adding more).\n",
- " builder.add_layer(circuit, cirq.XX, \"xx1\")\n",
- " builder.add_layer(circuit, cirq.ZZ, \"zz1\")\n",
- "\n",
- " # Finally, prepare the readout qubit.\n",
- " circuit.append(cirq.H(readout))\n",
- "\n",
- " return circuit, cirq.Z(readout)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "2QZvVh7vojhx"
- },
- "outputs": [],
- "source": [
- "model_circuit, model_readout = create_quantum_model()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "LY7vbY6yfABE"
- },
- "source": [
- "### 2.2 Wrap the model-circuit in a tfq-keras model\n",
- "\n",
- "Build the Keras model with the quantum components. This model is fed the \"quantum data\", from `x_train_circ`, that encodes the classical data. It uses a *Parametrized Quantum Circuit* layer, `tfq.layers.PQC`, to train the model circuit, on the quantum data.\n",
- "\n",
- "To classify these images, Farhi et al. proposed taking the expectation of a readout qubit in a parameterized circuit. The expectation returns a value between 1 and -1."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ZYdf_KOxojh0"
- },
- "outputs": [],
- "source": [
- "# Build the Keras model.\n",
- "model = tf.keras.Sequential([\n",
- " # The input is the data-circuit, encoded as a tf.string\n",
- " tf.keras.layers.Input(shape=(), dtype=tf.string),\n",
- " # The PQC layer returns the expected value of the readout gate, range [-1,1].\n",
- " tfq.layers.PQC(model_circuit, model_readout),\n",
- "])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "jz-FbVc9ojh3"
- },
- "source": [
- "Next, describe the training procedure to the model, using the `compile` method.\n",
- "\n",
- "Since the the expected readout is in the range `[-1,1]`, optimizing the hinge loss is a somewhat natural fit. \n",
- "\n",
- "Note: Another valid approach would be to shift the output range to `[0,1]`, and treat it as the probability the model assigns to class `3`. This could be used with a standard a `tf.losses.BinaryCrossentropy` loss.\n",
- "\n",
- "To use the hinge loss here you need to make two small adjustments. First convert the labels, `y_train_nocon`, from boolean to `[-1,1]`, as expected by the hinge loss."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "CgMNkC1Fojh5"
- },
- "outputs": [],
- "source": [
- "y_train_hinge = 2.0*y_train_nocon-1.0\n",
- "y_test_hinge = 2.0*y_test-1.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "5nwnveDiojh7"
- },
- "source": [
- "Second, use a custiom `hinge_accuracy` metric that correctly handles `[-1, 1]` as the `y_true` labels argument. \n",
- "`tf.losses.BinaryAccuracy(threshold=0.0)` expects `y_true` to be a boolean, and so can't be used with hinge loss)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "3XKtZ_TEojh8"
- },
- "outputs": [],
- "source": [
- "def hinge_accuracy(y_true, y_pred):\n",
- " y_true = tf.squeeze(y_true) > 0.0\n",
- " y_pred = tf.squeeze(y_pred) > 0.0\n",
- " result = tf.cast(y_true == y_pred, tf.float32)\n",
- "\n",
- " return tf.reduce_mean(result)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "FlpETlLRojiA"
- },
- "outputs": [],
- "source": [
- "model.compile(\n",
- " loss=tf.keras.losses.Hinge(),\n",
- " optimizer=tf.keras.optimizers.Adam(),\n",
- " metrics=[hinge_accuracy])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "jkHq2RstojiC"
- },
- "outputs": [],
- "source": [
- "print(model.summary())"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "lsuOzDYblA9s"
- },
- "source": [
- "### Train the quantum model\n",
- "\n",
- "Now train the model—this takes about 45 min. If you don't want to wait that long, use a small subset of the data (set `NUM_EXAMPLES=500`, below). This doesn't really affect the model's progress during training (it only has 32 parameters, and doesn't need much data to constrain these). Using fewer examples just ends training earlier (5min), but runs long enough to show that it is making progress in the validation logs."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "n8vuQpSLlBV2"
- },
- "outputs": [],
- "source": [
- "EPOCHS = 3\n",
- "BATCH_SIZE = 32\n",
- "\n",
- "NUM_EXAMPLES = len(x_train_tfcirc)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "qJnNG-3JojiI"
- },
- "outputs": [],
- "source": [
- "x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]\n",
- "y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "QMSdgGC1GL7D"
- },
- "source": [
- "Training this model to convergence should achieve >85% accuracy on the test set."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Ya9qP3KkojiM"
- },
- "outputs": [],
- "source": [
- "qnn_history = model.fit(\n",
- " x_train_tfcirc_sub, y_train_hinge_sub,\n",
- " batch_size=32,\n",
- " epochs=EPOCHS,\n",
- " verbose=1,\n",
- " validation_data=(x_test_tfcirc, y_test_hinge))\n",
- "\n",
- "qnn_results = model.evaluate(x_test_tfcirc, y_test)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "3ER7B7aaojiP"
- },
- "source": [
- "Note: The training accuracy reports the average over the epoch. The validation accuracy is evaluated at the end of each epoch."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "8952YvuWGL7J"
- },
- "source": [
- "## 3. Classical neural network\n",
- "\n",
- "While the quantum neural network works for this simplified MNIST problem, a basic classical neural network can easily outperform a QNN on this task. After a single epoch, a classical neural network can achieve >98% accuracy on the holdout set.\n",
- "\n",
- "In the following example, a classical neural network is used for for the 3-6 classification problem using the entire 28x28 image instead of subsampling the image. This easily converges to nearly 100% accuracy of the test set."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "pZofEHhLGL7L"
- },
- "outputs": [],
- "source": [
- "def create_classical_model():\n",
- " # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/\n",
- " model = tf.keras.Sequential()\n",
- " model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu', input_shape=(28,28,1)))\n",
- " model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))\n",
- " model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))\n",
- " model.add(tf.keras.layers.Dropout(0.25))\n",
- " model.add(tf.keras.layers.Flatten())\n",
- " model.add(tf.keras.layers.Dense(128, activation='relu'))\n",
- " model.add(tf.keras.layers.Dropout(0.5))\n",
- " model.add(tf.keras.layers.Dense(1))\n",
- " return model\n",
- "\n",
- "\n",
- "model = create_classical_model()\n",
- "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
- " optimizer=tf.keras.optimizers.Adam(),\n",
- " metrics=['accuracy'])\n",
- "\n",
- "model.summary()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "CiAJl7sZojiU"
- },
- "outputs": [],
- "source": [
- "model.fit(x_train,\n",
- " y_train,\n",
- " batch_size=128,\n",
- " epochs=1,\n",
- " verbose=1,\n",
- " validation_data=(x_test, y_test))\n",
- "\n",
- "cnn_results = model.evaluate(x_test, y_test)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "X5-5BVJaojiZ"
- },
- "source": [
- "The above model has nearly 1.2M parameters. For a more fair comparison, try a 37-parameter model, on the subsampled images:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "70TOM6r-ojiZ",
- "scrolled": false
- },
- "outputs": [],
- "source": [
- "def create_fair_classical_model():\n",
- " # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/\n",
- " model = tf.keras.Sequential()\n",
- " model.add(tf.keras.layers.Flatten(input_shape=(4,4,1)))\n",
- " model.add(tf.keras.layers.Dense(2, activation='relu'))\n",
- " model.add(tf.keras.layers.Dense(1))\n",
- " return model\n",
- "\n",
- "\n",
- "model = create_fair_classical_model()\n",
- "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
- " optimizer=tf.keras.optimizers.Adam(),\n",
- " metrics=['accuracy'])\n",
- "\n",
- "model.summary()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "lA_Fx-8gojid"
- },
- "outputs": [],
- "source": [
- "model.fit(x_train_bin,\n",
- " y_train_nocon,\n",
- " batch_size=128,\n",
- " epochs=20,\n",
- " verbose=2,\n",
- " validation_data=(x_test_bin, y_test))\n",
- "\n",
- "fair_nn_results = model.evaluate(x_test_bin, y_test)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "RH3mam7EGL7N"
- },
- "source": [
- "## 4. Comparison\n",
- "\n",
- "Higher resolution input and a more powerful model make this problem easy for the CNN. While a classical model of similar power (~32 parameters) trains to a similar accuracy in a fraction of the time. One way or the other, the classical neural network easily outperforms the quantum neural network. For classical data, it is difficult to beat a classical neural network."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "NOMeN7pMGL7P"
- },
- "outputs": [],
- "source": [
- "qnn_accuracy = qnn_results[1]\n",
- "cnn_accuracy = cnn_results[1]\n",
- "fair_nn_accuracy = fair_nn_results[1]\n",
- "\n",
- "sns.barplot(x=[\"Quantum\", \"Classical, full\", \"Classical, fair\"],\n",
- " y=[qnn_accuracy, cnn_accuracy, fair_nn_accuracy])"
- ]
- }
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "mnist.ipynb",
- "private_outputs": true,
- "provenance": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "name": "python",
- "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
- },
- "vscode": {
- "interpreter": {
- "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "colab": {},
+ "colab_type": "code",
+ "id": "iiQkM5ZgQ8r2"
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j6331ZSsQGY3"
+ },
+ "source": [
+ "# MNIST classification"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "udLObUVeGfTs"
+ },
+ "source": [
+ "This tutorial builds a quantum neural network (QNN) to classify a simplified version of MNIST, similar to the approach used in Farhi et al . The performance of the quantum neural network on this classical data problem is compared with a classical neural network."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "X35qHdh5Gzqg"
+ },
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "TorxE5tnkvb2"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FxkQA6oblNqI"
+ },
+ "source": [
+ "Install TensorFlow Quantum:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "saFHsRDpkvkH"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J"
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "hdgMMZEBGqyl"
+ },
+ "source": [
+ "Now import TensorFlow and the module dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "enZ300Bflq80"
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_quantum as tfq\n",
+ "\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import numpy as np\n",
+ "import seaborn as sns\n",
+ "import collections\n",
+ "\n",
+ "# visualization tools\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from cirq.contrib.svg import SVGCircuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "b08Mmbs8lr81"
+ },
+ "source": [
+ "## 1. Load the data\n",
+ "\n",
+ "In this tutorial you will build a binary classifier to distinguish between the digits 3 and 6, following Farhi et al. This section covers the data handling that:\n",
+ "\n",
+ "- Loads the raw data from Keras.\n",
+ "- Filters the dataset to only 3s and 6s.\n",
+ "- Downscales the images so they fit can fit in a quantum computer.\n",
+ "- Removes any contradictory examples.\n",
+ "- Converts the binary images to Cirq circuits.\n",
+ "- Converts the Cirq circuits to TensorFlow Quantum circuits. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "pDUdGxn-ojgy"
+ },
+ "source": [
+ "### 1.1 Load the raw data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xZyGXlaKojgz"
+ },
+ "source": [
+ "Load the MNIST dataset distributed with Keras. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "d9OSExvCojg0"
+ },
+ "outputs": [],
+ "source": [
+ "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n",
+ "\n",
+ "# Rescale the images from [0,255] to the [0.0,1.0] range.\n",
+ "x_train, x_test = x_train[..., np.newaxis] / 255.0, x_test[...,\n",
+ " np.newaxis] / 255.0\n",
+ "\n",
+ "print(\"Number of original training examples:\", len(x_train))\n",
+ "print(\"Number of original test examples:\", len(x_test))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "fZpbygdGojg3"
+ },
+ "source": [
+ "Filter the dataset to keep just the 3s and 6s, remove the other classes. At the same time convert the label, `y`, to boolean: `True` for `3` and `False` for 6. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "hOw68cCZojg4"
+ },
+ "outputs": [],
+ "source": [
+ "def filter_36(x, y):\n",
+ " keep = (y == 3) | (y == 6)\n",
+ " x, y = x[keep], y[keep]\n",
+ " y = y == 3\n",
+ " return x, y"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "p-XEU8egGL6q"
+ },
+ "outputs": [],
+ "source": [
+ "x_train, y_train = filter_36(x_train, y_train)\n",
+ "x_test, y_test = filter_36(x_test, y_test)\n",
+ "\n",
+ "print(\"Number of filtered training examples:\", len(x_train))\n",
+ "print(\"Number of filtered test examples:\", len(x_test))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "3wyiaP0Xojg_"
+ },
+ "source": [
+ "Show the first example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "j5STP7MbojhA"
+ },
+ "outputs": [],
+ "source": [
+ "print(y_train[0])\n",
+ "\n",
+ "plt.imshow(x_train[0, :, :, 0])\n",
+ "plt.colorbar()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wNS9sVPQojhC"
+ },
+ "source": [
+ "### 1.2 Downscale the images"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "fmmtplIFGL6t"
+ },
+ "source": [
+ "An image size of 28x28 is much too large for current quantum computers. Resize the image down to 4x4:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "lbhUdBFWojhE",
+ "scrolled": false
+ },
+ "outputs": [],
+ "source": [
+ "x_train_small = tf.image.resize(x_train, (4, 4)).numpy()\n",
+ "x_test_small = tf.image.resize(x_test, (4, 4)).numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "pOMd7zIjGL6x"
+ },
+ "source": [
+ "Again, display the first training example—after resize: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "YIYOtCRIGL6y",
+ "scrolled": true
+ },
+ "outputs": [],
+ "source": [
+ "print(y_train[0])\n",
+ "\n",
+ "plt.imshow(x_train_small[0, :, :, 0], vmin=0, vmax=1)\n",
+ "plt.colorbar()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gGeF1_qtojhK"
+ },
+ "source": [
+ "### 1.3 Remove contradictory examples"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "7ZLkq2yeojhL"
+ },
+ "source": [
+ "From section *3.3 Learning to Distinguish Digits* of Farhi et al. , filter the dataset to remove images that are labeled as belonging to both classes.\n",
+ "\n",
+ "This is not a standard machine-learning procedure, but is included in the interest of following the paper."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "LqOPW0C7ojhL"
+ },
+ "outputs": [],
+ "source": [
+ "def remove_contradicting(xs, ys):\n",
+ " mapping = collections.defaultdict(set)\n",
+ " orig_x = {}\n",
+ " # Determine the set of labels for each unique image:\n",
+ " for x, y in zip(xs, ys):\n",
+ " orig_x[tuple(x.flatten())] = x\n",
+ " mapping[tuple(x.flatten())].add(y)\n",
+ "\n",
+ " new_x = []\n",
+ " new_y = []\n",
+ " for flatten_x in mapping:\n",
+ " x = orig_x[flatten_x]\n",
+ " labels = mapping[flatten_x]\n",
+ " if len(labels) == 1:\n",
+ " new_x.append(x)\n",
+ " new_y.append(next(iter(labels)))\n",
+ " else:\n",
+ " # Throw out images that match more than one label.\n",
+ " pass\n",
+ "\n",
+ " num_uniq_3 = sum(\n",
+ " 1 for value in mapping.values() if len(value) == 1 and True in value)\n",
+ " num_uniq_6 = sum(\n",
+ " 1 for value in mapping.values() if len(value) == 1 and False in value)\n",
+ " num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2)\n",
+ "\n",
+ " print(\"Number of unique images:\", len(mapping.values()))\n",
+ " print(\"Number of unique 3s: \", num_uniq_3)\n",
+ " print(\"Number of unique 6s: \", num_uniq_6)\n",
+ " print(\"Number of unique contradicting labels (both 3 and 6): \",\n",
+ " num_uniq_both)\n",
+ " print()\n",
+ " print(\"Initial number of images: \", len(xs))\n",
+ " print(\"Remaining non-contradicting unique images: \", len(new_x))\n",
+ "\n",
+ " return np.array(new_x), np.array(new_y)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "VMOiJfz_ojhP"
+ },
+ "source": [
+ "The resulting counts do not closely match the reported values, but the exact procedure is not specified.\n",
+ "\n",
+ "It is also worth noting here that applying filtering contradictory examples at this point does not totally prevent the model from receiving contradictory training examples: the next step binarizes the data which will cause more collisions. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "zpnsAssWojhP",
+ "scrolled": true
+ },
+ "outputs": [],
+ "source": [
+ "x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SlJ5NVaPojhT"
+ },
+ "source": [
+ "### 1.4 Encode the data as quantum circuits\n",
+ "\n",
+ "To process images using a quantum computer, Farhi et al. proposed representing each pixel with a qubit, with the state depending on the value of the pixel. The first step is to convert to a binary encoding."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1z8J7OyDojhV"
+ },
+ "outputs": [],
+ "source": [
+ "THRESHOLD = 0.5\n",
+ "\n",
+ "x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32)\n",
+ "x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SlJ5NVaPojhU"
+ },
+ "source": [
+ "If you were to remove contradictory images at this point you would be left with only 193, likely not enough for effective training."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1z8J7OyDojhW"
+ },
+ "outputs": [],
+ "source": [
+ "_ = remove_contradicting(x_train_bin, y_train_nocon)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "oLyxS9KlojhZ"
+ },
+ "source": [
+ "The qubits at pixel indices with values that exceed a threshold, are rotated through an $X$ gate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aOu_3-3ZGL61"
+ },
+ "outputs": [],
+ "source": [
+ "def convert_to_circuit(image):\n",
+ " \"\"\"Encode truncated classical image into quantum datapoint.\"\"\"\n",
+ " values = np.ndarray.flatten(image)\n",
+ " qubits = cirq.GridQubit.rect(4, 4)\n",
+ " circuit = cirq.Circuit()\n",
+ " for i, value in enumerate(values):\n",
+ " if value:\n",
+ " circuit.append(cirq.X(qubits[i]))\n",
+ " return circuit\n",
+ "\n",
+ "\n",
+ "x_train_circ = [convert_to_circuit(x) for x in x_train_bin]\n",
+ "x_test_circ = [convert_to_circuit(x) for x in x_test_bin]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "zSCXqzOzojhd"
+ },
+ "source": [
+ "Here is the circuit created for the first example (circuit diagrams do not show qubits with zero gates):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "w3POmUEUojhe",
+ "scrolled": false
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(x_train_circ[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "AEQMxCcBojhg"
+ },
+ "source": [
+ "Compare this circuit to the indices where the image value exceeds the threshold:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "TBIsiXdtojhh"
+ },
+ "outputs": [],
+ "source": [
+ "bin_img = x_train_bin[0, :, :, 0]\n",
+ "indices = np.array(np.where(bin_img)).T\n",
+ "indices"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "mWZ24w1Oojhk"
+ },
+ "source": [
+ "Convert these `Cirq` circuits to tensors for `tfq`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "IZStEMk4ojhk"
+ },
+ "outputs": [],
+ "source": [
+ "x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)\n",
+ "x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4USiqeOqGL67"
+ },
+ "source": [
+ "## 2. Quantum neural network\n",
+ "\n",
+ "There is little guidance for a quantum circuit structure that classifies images. Since the classification is based on the expectation of the readout qubit, Farhi et al. propose using two qubit gates, with the readout qubit always acted upon. This is similar in some ways to running small a Unitary RNN across the pixels."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "knIzawEeojho"
+ },
+ "source": [
+ "### 2.1 Build the model circuit\n",
+ "\n",
+ "This following example shows this layered approach. Each layer uses *n* instances of the same gate, with each of the data qubits acting on the readout qubit.\n",
+ "\n",
+ "Start with a simple class that will add a layer of these gates to a circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "-hjxxgU5ojho"
+ },
+ "outputs": [],
+ "source": [
+ "class CircuitLayerBuilder():\n",
+ "\n",
+ " def __init__(self, data_qubits, readout):\n",
+ " self.data_qubits = data_qubits\n",
+ " self.readout = readout\n",
+ "\n",
+ " def add_layer(self, circuit, gate, prefix):\n",
+ " for i, qubit in enumerate(self.data_qubits):\n",
+ " symbol = sympy.Symbol(prefix + '-' + str(i))\n",
+ " circuit.append(gate(qubit, self.readout)**symbol)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Sjo5hANFojhr"
+ },
+ "source": [
+ "Build an example circuit layer to see how it looks:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "SzXWOpUGojhs"
+ },
+ "outputs": [],
+ "source": [
+ "demo_builder = CircuitLayerBuilder(data_qubits=cirq.GridQubit.rect(4, 1),\n",
+ " readout=cirq.GridQubit(-1, -1))\n",
+ "\n",
+ "circuit = cirq.Circuit()\n",
+ "demo_builder.add_layer(circuit, gate=cirq.XX, prefix='xx')\n",
+ "SVGCircuit(circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "T-QhPE1pojhu"
+ },
+ "source": [
+ "Now build a two-layered model, matching the data-circuit size, and include the preparation and readout operations."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "JiALbpwRGL69"
+ },
+ "outputs": [],
+ "source": [
+ "def create_quantum_model():\n",
+ " \"\"\"Create a QNN model circuit and readout operation to go along with it.\"\"\"\n",
+ " data_qubits = cirq.GridQubit.rect(4, 4) # a 4x4 grid.\n",
+ " readout = cirq.GridQubit(-1, -1) # a single qubit at [-1,-1]\n",
+ " circuit = cirq.Circuit()\n",
+ "\n",
+ " # Prepare the readout qubit.\n",
+ " circuit.append(cirq.X(readout))\n",
+ " circuit.append(cirq.H(readout))\n",
+ "\n",
+ " builder = CircuitLayerBuilder(data_qubits=data_qubits, readout=readout)\n",
+ "\n",
+ " # Then add layers (experiment by adding more).\n",
+ " builder.add_layer(circuit, cirq.XX, \"xx1\")\n",
+ " builder.add_layer(circuit, cirq.ZZ, \"zz1\")\n",
+ "\n",
+ " # Finally, prepare the readout qubit.\n",
+ " circuit.append(cirq.H(readout))\n",
+ "\n",
+ " return circuit, cirq.Z(readout)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "2QZvVh7vojhx"
+ },
+ "outputs": [],
+ "source": [
+ "model_circuit, model_readout = create_quantum_model()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "LY7vbY6yfABE"
+ },
+ "source": [
+ "### 2.2 Wrap the model-circuit in a tfq-keras model\n",
+ "\n",
+ "Build the Keras model with the quantum components. This model is fed the \"quantum data\", from `x_train_circ`, that encodes the classical data. It uses a *Parametrized Quantum Circuit* layer, `tfq.layers.PQC`, to train the model circuit, on the quantum data.\n",
+ "\n",
+ "To classify these images, Farhi et al. proposed taking the expectation of a readout qubit in a parameterized circuit. The expectation returns a value between 1 and -1."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "ZYdf_KOxojh0"
+ },
+ "outputs": [],
+ "source": [
+ "# Build the Keras model.\n",
+ "model = tf.keras.Sequential([\n",
+ " # The input is the data-circuit, encoded as a tf.string\n",
+ " tf.keras.layers.Input(shape=(), dtype=tf.string),\n",
+ " # The PQC layer returns the expected value of the readout gate, range [-1,1].\n",
+ " tfq.layers.PQC(model_circuit, model_readout),\n",
+ "])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "jz-FbVc9ojh3"
+ },
+ "source": [
+ "Next, describe the training procedure to the model, using the `compile` method.\n",
+ "\n",
+ "Since the the expected readout is in the range `[-1,1]`, optimizing the hinge loss is a somewhat natural fit. \n",
+ "\n",
+ "Note: Another valid approach would be to shift the output range to `[0,1]`, and treat it as the probability the model assigns to class `3`. This could be used with a standard a `tf.losses.BinaryCrossentropy` loss.\n",
+ "\n",
+ "To use the hinge loss here you need to make two small adjustments. First convert the labels, `y_train_nocon`, from boolean to `[-1,1]`, as expected by the hinge loss."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "CgMNkC1Fojh5"
+ },
+ "outputs": [],
+ "source": [
+ "y_train_hinge = 2.0 * y_train_nocon - 1.0\n",
+ "y_test_hinge = 2.0 * y_test - 1.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "5nwnveDiojh7"
+ },
+ "source": [
+ "Second, use a custiom `hinge_accuracy` metric that correctly handles `[-1, 1]` as the `y_true` labels argument. \n",
+ "`tf.losses.BinaryAccuracy(threshold=0.0)` expects `y_true` to be a boolean, and so can't be used with hinge loss)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "3XKtZ_TEojh8"
+ },
+ "outputs": [],
+ "source": [
+ "def hinge_accuracy(y_true, y_pred):\n",
+ " y_true = tf.squeeze(y_true) > 0.0\n",
+ " y_pred = tf.squeeze(y_pred) > 0.0\n",
+ " result = tf.cast(y_true == y_pred, tf.float32)\n",
+ "\n",
+ " return tf.reduce_mean(result)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "FlpETlLRojiA"
+ },
+ "outputs": [],
+ "source": [
+ "model.compile(loss=tf.keras.losses.Hinge(),\n",
+ " optimizer=tf.keras.optimizers.Adam(),\n",
+ " metrics=[hinge_accuracy])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "jkHq2RstojiC"
+ },
+ "outputs": [],
+ "source": [
+ "print(model.summary())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "lsuOzDYblA9s"
+ },
+ "source": [
+ "### Train the quantum model\n",
+ "\n",
+ "Now train the model—this takes about 45 min. If you don't want to wait that long, use a small subset of the data (set `NUM_EXAMPLES=500`, below). This doesn't really affect the model's progress during training (it only has 32 parameters, and doesn't need much data to constrain these). Using fewer examples just ends training earlier (5min), but runs long enough to show that it is making progress in the validation logs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "n8vuQpSLlBV2"
+ },
+ "outputs": [],
+ "source": [
+ "EPOCHS = 3\n",
+ "BATCH_SIZE = 32\n",
+ "\n",
+ "NUM_EXAMPLES = len(x_train_tfcirc)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "qJnNG-3JojiI"
+ },
+ "outputs": [],
+ "source": [
+ "x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]\n",
+ "y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "QMSdgGC1GL7D"
+ },
+ "source": [
+ "Training this model to convergence should achieve >85% accuracy on the test set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Ya9qP3KkojiM"
+ },
+ "outputs": [],
+ "source": [
+ "qnn_history = model.fit(x_train_tfcirc_sub,\n",
+ " y_train_hinge_sub,\n",
+ " batch_size=32,\n",
+ " epochs=EPOCHS,\n",
+ " verbose=1,\n",
+ " validation_data=(x_test_tfcirc, y_test_hinge))\n",
+ "\n",
+ "qnn_results = model.evaluate(x_test_tfcirc, y_test)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "3ER7B7aaojiP"
+ },
+ "source": [
+ "Note: The training accuracy reports the average over the epoch. The validation accuracy is evaluated at the end of each epoch."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "8952YvuWGL7J"
+ },
+ "source": [
+ "## 3. Classical neural network\n",
+ "\n",
+ "While the quantum neural network works for this simplified MNIST problem, a basic classical neural network can easily outperform a QNN on this task. After a single epoch, a classical neural network can achieve >98% accuracy on the holdout set.\n",
+ "\n",
+ "In the following example, a classical neural network is used for for the 3-6 classification problem using the entire 28x28 image instead of subsampling the image. This easily converges to nearly 100% accuracy of the test set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "pZofEHhLGL7L"
+ },
+ "outputs": [],
+ "source": [
+ "def create_classical_model():\n",
+ " # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/\n",
+ " model = tf.keras.Sequential()\n",
+ " model.add(\n",
+ " tf.keras.layers.Conv2D(32, [3, 3],\n",
+ " activation='relu',\n",
+ " input_shape=(28, 28, 1)))\n",
+ " model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))\n",
+ " model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))\n",
+ " model.add(tf.keras.layers.Dropout(0.25))\n",
+ " model.add(tf.keras.layers.Flatten())\n",
+ " model.add(tf.keras.layers.Dense(128, activation='relu'))\n",
+ " model.add(tf.keras.layers.Dropout(0.5))\n",
+ " model.add(tf.keras.layers.Dense(1))\n",
+ " return model\n",
+ "\n",
+ "\n",
+ "model = create_classical_model()\n",
+ "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
+ " optimizer=tf.keras.optimizers.Adam(),\n",
+ " metrics=['accuracy'])\n",
+ "\n",
+ "model.summary()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "CiAJl7sZojiU"
+ },
+ "outputs": [],
+ "source": [
+ "model.fit(x_train,\n",
+ " y_train,\n",
+ " batch_size=128,\n",
+ " epochs=1,\n",
+ " verbose=1,\n",
+ " validation_data=(x_test, y_test))\n",
+ "\n",
+ "cnn_results = model.evaluate(x_test, y_test)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "X5-5BVJaojiZ"
+ },
+ "source": [
+ "The above model has nearly 1.2M parameters. For a more fair comparison, try a 37-parameter model, on the subsampled images:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "70TOM6r-ojiZ",
+ "scrolled": false
+ },
+ "outputs": [],
+ "source": [
+ "def create_fair_classical_model():\n",
+ " # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/\n",
+ " model = tf.keras.Sequential()\n",
+ " model.add(tf.keras.layers.Flatten(input_shape=(4, 4, 1)))\n",
+ " model.add(tf.keras.layers.Dense(2, activation='relu'))\n",
+ " model.add(tf.keras.layers.Dense(1))\n",
+ " return model\n",
+ "\n",
+ "\n",
+ "model = create_fair_classical_model()\n",
+ "model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
+ " optimizer=tf.keras.optimizers.Adam(),\n",
+ " metrics=['accuracy'])\n",
+ "\n",
+ "model.summary()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "lA_Fx-8gojid"
+ },
+ "outputs": [],
+ "source": [
+ "model.fit(x_train_bin,\n",
+ " y_train_nocon,\n",
+ " batch_size=128,\n",
+ " epochs=20,\n",
+ " verbose=2,\n",
+ " validation_data=(x_test_bin, y_test))\n",
+ "\n",
+ "fair_nn_results = model.evaluate(x_test_bin, y_test)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "RH3mam7EGL7N"
+ },
+ "source": [
+ "## 4. Comparison\n",
+ "\n",
+ "Higher resolution input and a more powerful model make this problem easy for the CNN. While a classical model of similar power (~32 parameters) trains to a similar accuracy in a fraction of the time. One way or the other, the classical neural network easily outperforms the quantum neural network. For classical data, it is difficult to beat a classical neural network."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "NOMeN7pMGL7P"
+ },
+ "outputs": [],
+ "source": [
+ "qnn_accuracy = qnn_results[1]\n",
+ "cnn_accuracy = cnn_results[1]\n",
+ "fair_nn_accuracy = fair_nn_results[1]\n",
+ "\n",
+ "sns.barplot(x=[\"Quantum\", \"Classical, full\", \"Classical, fair\"],\n",
+ " y=[qnn_accuracy, cnn_accuracy, fair_nn_accuracy])"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "mnist.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.10.9 (main, Dec 7 2022, 13:47:07) [GCC 12.2.0]"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/noise.ipynb b/docs/tutorials/noise.ipynb
index 4e40e72a3..997b2a0ee 100644
--- a/docs/tutorials/noise.ipynb
+++ b/docs/tutorials/noise.ipynb
@@ -1,805 +1,836 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "id": "iiQkM5ZgQ8r2"
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "UndbWF_UpN-X"
- },
- "source": [
- "# Noise"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "fHHaKIG06Iv_"
- },
- "source": [
- "Noise is present in modern day quantum computers. Qubits are susceptible to interference from the surrounding environment, imperfect fabrication, TLS and sometimes even [gamma rays](https://arxiv.org/abs/2104.05219). Until large scale error correction is reached, the algorithms of today must be able to remain functional in the presence of noise. This makes testing algorithms under noise an important step for validating quantum algorithms / models will function on the quantum computers of today.\n",
- "\n",
- "In this tutorial you will explore the basics of noisy circuit simulation in TFQ via the high level `tfq.layers` API.\n",
- "\n",
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "J2CRbYRqrLdt"
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0 tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "QStNslxBwgte"
- },
- "outputs": [],
- "source": [
- "!pip install -q git+https://github.com/tensorflow/docs"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J"
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "iRU07S4o8B52"
- },
- "outputs": [],
- "source": [
- "import random\n",
- "import cirq\n",
- "import sympy\n",
- "import tensorflow_quantum as tfq\n",
- "import tensorflow as tf\n",
- "import numpy as np\n",
- "# Plotting\n",
- "import matplotlib.pyplot as plt\n",
- "import tensorflow_docs as tfdocs\n",
- "import tensorflow_docs.plots"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "CVnAGxZyruv8"
- },
- "source": [
- "## 1. Understanding quantum noise\n",
- "\n",
- "### 1.1 Basic circuit noise\n",
- "\n",
- "Noise on a quantum computer impacts the bitstring samples you are able to measure from it. One intuitive way you can start to think about this is that a noisy quantum computer will \"insert\", \"delete\" or \"replace\" gates in random places like the diagram below:\n",
- "\n",
- " \n",
- "\n",
- "Building off of this intuition, when dealing with noise, you are no longer using a single pure state $|\\psi \\rangle$ but instead dealing with an *ensemble* of all possible noisy realizations of your desired circuit: $\\rho = \\sum_j p_j |\\psi_j \\rangle \\langle \\psi_j |$ . Where $p_j$ gives the probability that the system is in $|\\psi_j \\rangle$ .\n",
- "\n",
- "Revisiting the above picture, if we knew beforehand that 90% of the time our system executed perfectly, or errored 10% of the time with just this one mode of failure, then our ensemble would be: \n",
- "\n",
- "$\\rho = 0.9 |\\psi_\\text{desired} \\rangle \\langle \\psi_\\text{desired}| + 0.1 |\\psi_\\text{noisy} \\rangle \\langle \\psi_\\text{noisy}| $\n",
- "\n",
- "If there was more than just one way that our circuit could error, then the ensemble $\\rho$ would contain more than just two terms (one for each new noisy realization that could happen). $\\rho$ is referred to as the [density matrix](https://en.wikipedia.org/wiki/Density_matrix) describing your noisy system.\n",
- "\n",
- "### 1.2 Using channels to model circuit noise\n",
- "\n",
- "Unfortunately in practice it's nearly impossible to know all the ways your circuit might error and their exact probabilities. A simplifying assumption you can make is that after each operation in your circuit there is some kind of [channel](https://quantumai.google/cirq/noise) that roughly captures how that operation might error. You can quickly create a circuit with some noise:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "Eu_vpHbfrQKQ"
- },
- "outputs": [],
- "source": [
- "def x_circuit(qubits):\n",
- " \"\"\"Produces an X wall circuit on `qubits`.\"\"\"\n",
- " return cirq.Circuit(cirq.X.on_each(*qubits))\n",
- "\n",
- "def make_noisy(circuit, p):\n",
- " \"\"\"Add a depolarization channel to all qubits in `circuit` before measurement.\"\"\"\n",
- " return circuit + cirq.Circuit(cirq.depolarize(p).on_each(*circuit.all_qubits()))\n",
- "\n",
- "my_qubits = cirq.GridQubit.rect(1, 2)\n",
- "my_circuit = x_circuit(my_qubits)\n",
- "my_noisy_circuit = make_noisy(my_circuit, 0.5)\n",
- "my_circuit"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "1B7vmyPm_TQ7"
- },
- "outputs": [],
- "source": [
- "my_noisy_circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "EejhXc2e9Cl8"
- },
- "source": [
- "You can examine the noiseless density matrix $\\rho$ with:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "0QN9W69U8v_V"
- },
- "outputs": [],
- "source": [
- "rho = cirq.final_density_matrix(my_circuit)\n",
- "np.round(rho, 3)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "RHHBeizr-DEo"
- },
- "source": [
- "And the noisy density matrix $\\rho$ with:\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "zSD9H8SC9IJ1"
- },
- "outputs": [],
- "source": [
- "rho = cirq.final_density_matrix(my_noisy_circuit)\n",
- "np.round(rho, 3)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "2YWiejLl-a0Z"
- },
- "source": [
- "Comparing the two different $ \\rho $ 's you can see that the noise has impacted the amplitudes of the state (and consequently sampling probabilities). In the noiseless case you would always expect to sample the $ |11\\rangle $ state. But in the noisy state there is now a nonzero probability of sampling $ |00\\rangle $ or $ |01\\rangle $ or $ |10\\rangle $ as well:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "Z4uj-Zs0AE3n"
- },
- "outputs": [],
- "source": [
- "\"\"\"Sample from my_noisy_circuit.\"\"\"\n",
- "def plot_samples(circuit):\n",
- " samples = cirq.sample(circuit + cirq.measure(*circuit.all_qubits(), key='bits'), repetitions=1000)\n",
- " freqs, _ = np.histogram(samples.data['bits'], bins=[i+0.01 for i in range(-1,2** len(my_qubits))])\n",
- " plt.figure(figsize=(10,5))\n",
- " plt.title('Noisy Circuit Sampling')\n",
- " plt.xlabel('Bitstring')\n",
- " plt.ylabel('Frequency')\n",
- " plt.bar([i for i in range(2** len(my_qubits))], freqs, tick_label=['00','01','10','11'])\n",
- "\n",
- "plot_samples(my_noisy_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "IpPh1Y0HEOWs"
- },
- "source": [
- "Without any noise you will always get $|11\\rangle$:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "NRCOhTVpEJzz"
- },
- "outputs": [],
- "source": [
- "\"\"\"Sample from my_circuit.\"\"\"\n",
- "plot_samples(my_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "EMbJBXAiT9GH"
- },
- "source": [
- "If you increase the noise a little further it will become harder and harder to distinguish the desired behavior (sampling $|11\\rangle$ ) from the noise:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "D2Fg-FUdUJQx"
- },
- "outputs": [],
- "source": [
- "my_really_noisy_circuit = make_noisy(my_circuit, 0.75)\n",
- "plot_samples(my_really_noisy_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "oV-0WV5Z7FQ8"
- },
- "source": [
- "Note: Try experimenting with different channels in your circuit to generate noise. Common channels supported in both Cirq and TFQ can be found [here](https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/ops/common_channels.py)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "atzsYj5qScn0"
- },
- "source": [
- "## 2. Basic noise in TFQ\n",
- "With this understanding of how noise can impact circuit execution, you can explore how noise works in TFQ. TensorFlow Quantum uses monte-carlo / trajectory based simulation as an alternative to density matrix simulation. This is because the memory complexity of density matrix simulation limits large simulations to being <= 20 qubits with traditional full density matrix simulation methods. Monte-carlo / trajectory trades this cost in memory for additional cost in time. The `backend='noisy'` option available to all `tfq.layers.Sample`, `tfq.layers.SampledExpectation` and `tfq.layers.Expectation` (In the case of `Expectation` this does add a required `repetitions` parameter).\n",
- "\n",
- "### 2.1 Noisy sampling in TFQ\n",
- "To recreate the above plots using TFQ and trajectory simulation you can use `tfq.layers.Sample`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "byVI5nbNQ4_b"
- },
- "outputs": [],
- "source": [
- "\"\"\"Draw bitstring samples from `my_noisy_circuit`\"\"\"\n",
- "bitstrings = tfq.layers.Sample(backend='noisy')(my_noisy_circuit, repetitions=1000)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "ncl0ruCZrd2s"
- },
- "outputs": [],
- "source": [
- "numeric_values = np.einsum('ijk,k->ij', bitstrings.to_tensor().numpy(), [1, 2])[0]\n",
- "freqs, _ = np.histogram(numeric_values, bins=[i+0.01 for i in range(-1,2** len(my_qubits))])\n",
- "plt.figure(figsize=(10,5))\n",
- "plt.title('Noisy Circuit Sampling')\n",
- "plt.xlabel('Bitstring')\n",
- "plt.ylabel('Frequency')\n",
- "plt.bar([i for i in range(2** len(my_qubits))], freqs, tick_label=['00','01','10','11'])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "QfHq13RwuLlF"
- },
- "source": [
- "### 2.2 Noisy sample based expectation\n",
- "To do noisy sample based expectation calculation you can use `tfq.layers.SampleExpectation`:\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "ep45G-09rfrA"
- },
- "outputs": [],
- "source": [
- "some_observables = [cirq.X(my_qubits[0]), cirq.Z(my_qubits[0]), 3.0 * cirq.Y(my_qubits[1]) + 1]\n",
- "some_observables"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "ur4iF_PGv0Xf"
- },
- "source": [
- "Compute the noiseless expectation estimates via sampling from the circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "jL6wJ3LCvNcn"
- },
- "outputs": [],
- "source": [
- "noiseless_sampled_expectation = tfq.layers.SampledExpectation(backend='noiseless')(\n",
- " my_circuit, operators=some_observables, repetitions=10000\n",
- ")\n",
- "noiseless_sampled_expectation.numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "c6hHgNtEv40i"
- },
- "source": [
- "Compare those with the noisy versions:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "8U4Gm-LGvYqa"
- },
- "outputs": [],
- "source": [
- "noisy_sampled_expectation = tfq.layers.SampledExpectation(backend='noisy')(\n",
- " [my_noisy_circuit, my_really_noisy_circuit], operators=some_observables, repetitions=10000\n",
- ")\n",
- "noisy_sampled_expectation.numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "CqQ_2c7XwMku"
- },
- "source": [
- "You can see that the noise has particularly impacted the $\\langle \\psi | Z | \\psi \\rangle$ accuracy, with `my_really_noisy_circuit` concentrating very quickly towards 0.\n",
- "\n",
- "### 2.3 Noisy analytic expectation calculation\n",
- "Doing noisy analytic expectation calculations is nearly identical to above:\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "pGXKlyCywAfj"
- },
- "outputs": [],
- "source": [
- "noiseless_analytic_expectation = tfq.layers.Expectation(backend='noiseless')(\n",
- " my_circuit, operators=some_observables\n",
- ")\n",
- "noiseless_analytic_expectation.numpy()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "6FUkJ7aOyTlI"
- },
- "outputs": [],
- "source": [
- "noisy_analytic_expectation = tfq.layers.Expectation(backend='noisy')(\n",
- " [my_noisy_circuit, my_really_noisy_circuit], operators=some_observables, repetitions=10000\n",
- ")\n",
- "noisy_analytic_expectation.numpy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "5KHvORT42XFV"
- },
- "source": [
- "## 3. Hybrid models and quantum data noise\n",
- "Now that you have implemented some noisy circuit simulations in TFQ, you can experiment with how noise impacts quantum and hybrid quantum classical models, by comparing and contrasting their noisy vs noiseless performance. A good first check to see if a model or algorithm is robust to noise is to test under a circuit wide depolarizing model which looks something like this:\n",
- "\n",
- " \n",
- "\n",
- "Where each time slice of the circuit (sometimes referred to as moment) has a depolarizing channel appended after each gate operation in that time slice. The depolarizing channel with apply one of $\\{X, Y, Z \\}$ with probability $p$ or apply nothing (keep the original operation) with probability $1-p$.\n",
- "\n",
- "### 3.1 Data\n",
- "For this example you can use some prepared circuits in the `tfq.datasets` module as training data:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "_ZqVLEji2WUx"
- },
- "outputs": [],
- "source": [
- "qubits = cirq.GridQubit.rect(1, 8)\n",
- "circuits, labels, pauli_sums, _ = tfq.datasets.xxz_chain(qubits, 'closed')\n",
- "circuits[0]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "MFgNU_nBGeTm"
- },
- "source": [
- "Writing a small helper function will help to generate the data for the noisy vs noiseless case:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "zkQofAqqGibQ"
- },
- "outputs": [],
- "source": [
- "def get_data(qubits, depolarize_p=0.):\n",
- " \"\"\"Return quantum data circuits and labels in `tf.Tensor` form.\"\"\"\n",
- " circuits, labels, pauli_sums, _ = tfq.datasets.xxz_chain(qubits, 'closed')\n",
- " if depolarize_p >= 1e-5:\n",
- " circuits = [circuit.with_noise(cirq.depolarize(depolarize_p)) for circuit in circuits]\n",
- " tmp = list(zip(circuits, labels))\n",
- " random.shuffle(tmp)\n",
- " circuits_tensor = tfq.convert_to_tensor([x[0] for x in tmp])\n",
- " labels_tensor = tf.convert_to_tensor([x[1] for x in tmp])\n",
- "\n",
- " return circuits_tensor, labels_tensor"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "FtJrfsLCF9Z3"
- },
- "source": [
- "### 3.2 Define a model circuit\n",
- "Now that you have quantum data in the form of circuits, you will need a circuit to model this data, like with the data you can write a helper function to generate this circuit optionally containing noise:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "TwryFaFIG2Ya"
- },
- "outputs": [],
- "source": [
- "def modelling_circuit(qubits, depth, depolarize_p=0.):\n",
- " \"\"\"A simple classifier circuit.\"\"\"\n",
- " dim = len(qubits)\n",
- " ret = cirq.Circuit(cirq.H.on_each(*qubits))\n",
- "\n",
- " for i in range(depth):\n",
- " # Entangle layer.\n",
- " ret += cirq.Circuit(cirq.CX(q1, q2) for (q1, q2) in zip(qubits[::2], qubits[1::2]))\n",
- " ret += cirq.Circuit(cirq.CX(q1, q2) for (q1, q2) in zip(qubits[1::2], qubits[2::2]))\n",
- " # Learnable rotation layer.\n",
- " # i_params = sympy.symbols(f'layer-{i}-0:{dim}')\n",
- " param = sympy.Symbol(f'layer-{i}')\n",
- " single_qb = cirq.X\n",
- " if i % 2 == 1:\n",
- " single_qb = cirq.Y\n",
- " ret += cirq.Circuit(single_qb(q) ** param for q in qubits)\n",
- " \n",
- " if depolarize_p >= 1e-5:\n",
- " ret = ret.with_noise(cirq.depolarize(depolarize_p))\n",
- "\n",
- " return ret, [op(q) for q in qubits for op in [cirq.X, cirq.Y, cirq.Z]]\n",
- "\n",
- "modelling_circuit(qubits, 3)[0]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "U-ZMaCpJI9TH"
- },
- "source": [
- "### 3.3 Model building and training\n",
- "With your data and model circuit built, the final helper function you will need is one that can assemble both a noisy or a noiseless hybrid quantum `tf.keras.Model`:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "r09CT5N9DWa_"
- },
- "outputs": [],
- "source": [
- "def build_keras_model(qubits, depolarize_p=0.):\n",
- " \"\"\"Prepare a noisy hybrid quantum classical Keras model.\"\"\"\n",
- " spin_input = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
- "\n",
- " circuit_and_readout = modelling_circuit(qubits, 4, depolarize_p)\n",
- " if depolarize_p >= 1e-5:\n",
- " quantum_model = tfq.layers.NoisyPQC(*circuit_and_readout, sample_based=False, repetitions=10)(spin_input)\n",
- " else:\n",
- " quantum_model = tfq.layers.PQC(*circuit_and_readout)(spin_input)\n",
- "\n",
- " intermediate = tf.keras.layers.Dense(4, activation='sigmoid')(quantum_model)\n",
- " post_process = tf.keras.layers.Dense(1)(intermediate)\n",
- "\n",
- " return tf.keras.Model(inputs=[spin_input], outputs=[post_process])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "QbMtT7BZmhfm"
- },
- "source": [
- "## 4. Compare performance\n",
- "\n",
- "### 4.1 Noiseless baseline\n",
- "\n",
- "With your data generation and model building code, you can now compare and contrast model performance in the noiseless and noisy settings, first you can run a reference noiseless training:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "QAgpq9c-EakW"
- },
- "outputs": [],
- "source": [
- "training_histories = dict()\n",
- "depolarize_p = 0.\n",
- "n_epochs = 50\n",
- "phase_classifier = build_keras_model(qubits, depolarize_p)\n",
- "\n",
- "phase_classifier.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
- " loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
- " metrics=['accuracy'])\n",
- "\n",
- "\n",
- "# Show the keras plot of the model\n",
- "tf.keras.utils.plot_model(phase_classifier, show_shapes=True, dpi=70)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "9tKimWRMlVfL"
- },
- "outputs": [],
- "source": [
- "noiseless_data, noiseless_labels = get_data(qubits, depolarize_p)\n",
- "training_histories['noiseless'] = phase_classifier.fit(x=noiseless_data,\n",
- " y=noiseless_labels,\n",
- " batch_size=16,\n",
- " epochs=n_epochs,\n",
- " validation_split=0.15,\n",
- " verbose=1)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "A9oql6Synv3f"
- },
- "source": [
- "And explore the results and accuracy:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "TG87YNUWKKLY"
- },
- "outputs": [],
- "source": [
- "loss_plotter = tfdocs.plots.HistoryPlotter(metric = 'loss', smoothing_std=10)\n",
- "loss_plotter.plot(training_histories)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "O2ZwM18YUxxm"
- },
- "outputs": [],
- "source": [
- "acc_plotter = tfdocs.plots.HistoryPlotter(metric = 'accuracy', smoothing_std=10)\n",
- "acc_plotter.plot(training_histories)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "JlOwBxvSnzid"
- },
- "source": [
- "### 4.2 Noisy comparison\n",
- "Now you can build a new model with noisy structure and compare to the above, the code is nearly identical:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "0jy54uWpgwhi"
- },
- "outputs": [],
- "source": [
- "depolarize_p = 0.001\n",
- "n_epochs = 50\n",
- "noisy_phase_classifier = build_keras_model(qubits, depolarize_p)\n",
- "\n",
- "noisy_phase_classifier.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
- " loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
- " metrics=['accuracy'])\n",
- "\n",
- "\n",
- "# Show the keras plot of the model\n",
- "tf.keras.utils.plot_model(noisy_phase_classifier, show_shapes=True, dpi=70)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "r-vYU6S3oN-J"
- },
- "source": [
- "Note: in the model diagram there is now a `tfq.layers.NoisyPQC` instead of a `tfq.layers.PQC` since the depolarization probability is no longer zero. Training will take significantly longer since noisy simulation is far more expensive than noiseless."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "210cLP5AoClJ"
- },
- "outputs": [],
- "source": [
- "noisy_data, noisy_labels = get_data(qubits, depolarize_p)\n",
- "training_histories['noisy'] = noisy_phase_classifier.fit(x=noisy_data,\n",
- " y=noisy_labels,\n",
- " batch_size=16,\n",
- " epochs=n_epochs,\n",
- " validation_split=0.15,\n",
- " verbose=1)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "eQ8pknNdohzy"
- },
- "outputs": [],
- "source": [
- "loss_plotter.plot(training_histories)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "id": "nBtgnKWtuWRR"
- },
- "outputs": [],
- "source": [
- "acc_plotter.plot(training_histories)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "id": "r86TeFxlubls"
- },
- "source": [
- "Success: The noisy model still managed to train under some mild depolarization noise. Try experimenting with different noise models to see how and when training might fail. Also look out for noisy functionality under `tfq.layers` and `tfq.noise`."
- ]
- }
- ],
- "metadata": {
- "colab": {
- "name": "noise.ipynb",
- "provenance": []
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.8"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 0
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "id": "iiQkM5ZgQ8r2"
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "UndbWF_UpN-X"
+ },
+ "source": [
+ "# Noise"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "fHHaKIG06Iv_"
+ },
+ "source": [
+ "Noise is present in modern day quantum computers. Qubits are susceptible to interference from the surrounding environment, imperfect fabrication, TLS and sometimes even [gamma rays](https://arxiv.org/abs/2104.05219). Until large scale error correction is reached, the algorithms of today must be able to remain functional in the presence of noise. This makes testing algorithms under noise an important step for validating quantum algorithms / models will function on the quantum computers of today.\n",
+ "\n",
+ "In this tutorial you will explore the basics of noisy circuit simulation in TFQ via the high level `tfq.layers` API.\n",
+ "\n",
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "J2CRbYRqrLdt"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0 tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "QStNslxBwgte"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install -q git+https://github.com/tensorflow/docs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J"
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "iRU07S4o8B52"
+ },
+ "outputs": [],
+ "source": [
+ "import random\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import tensorflow_quantum as tfq\n",
+ "import tensorflow as tf\n",
+ "import numpy as np\n",
+ "# Plotting\n",
+ "import matplotlib.pyplot as plt\n",
+ "import tensorflow_docs as tfdocs\n",
+ "import tensorflow_docs.plots"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CVnAGxZyruv8"
+ },
+ "source": [
+ "## 1. Understanding quantum noise\n",
+ "\n",
+ "### 1.1 Basic circuit noise\n",
+ "\n",
+ "Noise on a quantum computer impacts the bitstring samples you are able to measure from it. One intuitive way you can start to think about this is that a noisy quantum computer will \"insert\", \"delete\" or \"replace\" gates in random places like the diagram below:\n",
+ "\n",
+ " \n",
+ "\n",
+ "Building off of this intuition, when dealing with noise, you are no longer using a single pure state $|\\psi \\rangle$ but instead dealing with an *ensemble* of all possible noisy realizations of your desired circuit: $\\rho = \\sum_j p_j |\\psi_j \\rangle \\langle \\psi_j |$ . Where $p_j$ gives the probability that the system is in $|\\psi_j \\rangle$ .\n",
+ "\n",
+ "Revisiting the above picture, if we knew beforehand that 90% of the time our system executed perfectly, or errored 10% of the time with just this one mode of failure, then our ensemble would be: \n",
+ "\n",
+ "$\\rho = 0.9 |\\psi_\\text{desired} \\rangle \\langle \\psi_\\text{desired}| + 0.1 |\\psi_\\text{noisy} \\rangle \\langle \\psi_\\text{noisy}| $\n",
+ "\n",
+ "If there was more than just one way that our circuit could error, then the ensemble $\\rho$ would contain more than just two terms (one for each new noisy realization that could happen). $\\rho$ is referred to as the [density matrix](https://en.wikipedia.org/wiki/Density_matrix) describing your noisy system.\n",
+ "\n",
+ "### 1.2 Using channels to model circuit noise\n",
+ "\n",
+ "Unfortunately in practice it's nearly impossible to know all the ways your circuit might error and their exact probabilities. A simplifying assumption you can make is that after each operation in your circuit there is some kind of [channel](https://quantumai.google/cirq/noise) that roughly captures how that operation might error. You can quickly create a circuit with some noise:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Eu_vpHbfrQKQ"
+ },
+ "outputs": [],
+ "source": [
+ "def x_circuit(qubits):\n",
+ " \"\"\"Produces an X wall circuit on `qubits`.\"\"\"\n",
+ " return cirq.Circuit(cirq.X.on_each(*qubits))\n",
+ "\n",
+ "\n",
+ "def make_noisy(circuit, p):\n",
+ " \"\"\"Add a depolarization channel to all qubits in `circuit` before measurement.\"\"\"\n",
+ " return circuit + cirq.Circuit(\n",
+ " cirq.depolarize(p).on_each(*circuit.all_qubits()))\n",
+ "\n",
+ "\n",
+ "my_qubits = cirq.GridQubit.rect(1, 2)\n",
+ "my_circuit = x_circuit(my_qubits)\n",
+ "my_noisy_circuit = make_noisy(my_circuit, 0.5)\n",
+ "my_circuit"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "1B7vmyPm_TQ7"
+ },
+ "outputs": [],
+ "source": [
+ "my_noisy_circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "EejhXc2e9Cl8"
+ },
+ "source": [
+ "You can examine the noiseless density matrix $\\rho$ with:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "0QN9W69U8v_V"
+ },
+ "outputs": [],
+ "source": [
+ "rho = cirq.final_density_matrix(my_circuit)\n",
+ "np.round(rho, 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "RHHBeizr-DEo"
+ },
+ "source": [
+ "And the noisy density matrix $\\rho$ with:\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "zSD9H8SC9IJ1"
+ },
+ "outputs": [],
+ "source": [
+ "rho = cirq.final_density_matrix(my_noisy_circuit)\n",
+ "np.round(rho, 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "2YWiejLl-a0Z"
+ },
+ "source": [
+ "Comparing the two different $ \\rho $ 's you can see that the noise has impacted the amplitudes of the state (and consequently sampling probabilities). In the noiseless case you would always expect to sample the $ |11\\rangle $ state. But in the noisy state there is now a nonzero probability of sampling $ |00\\rangle $ or $ |01\\rangle $ or $ |10\\rangle $ as well:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "Z4uj-Zs0AE3n"
+ },
+ "outputs": [],
+ "source": [
+ "\"\"\"Sample from my_noisy_circuit.\"\"\"\n",
+ "\n",
+ "\n",
+ "def plot_samples(circuit):\n",
+ " samples = cirq.sample(circuit +\n",
+ " cirq.measure(*circuit.all_qubits(), key='bits'),\n",
+ " repetitions=1000)\n",
+ " freqs, _ = np.histogram(\n",
+ " samples.data['bits'],\n",
+ " bins=[i + 0.01 for i in range(-1, 2**len(my_qubits))])\n",
+ " plt.figure(figsize=(10, 5))\n",
+ " plt.title('Noisy Circuit Sampling')\n",
+ " plt.xlabel('Bitstring')\n",
+ " plt.ylabel('Frequency')\n",
+ " plt.bar([i for i in range(2**len(my_qubits))],\n",
+ " freqs,\n",
+ " tick_label=['00', '01', '10', '11'])\n",
+ "\n",
+ "\n",
+ "plot_samples(my_noisy_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "IpPh1Y0HEOWs"
+ },
+ "source": [
+ "Without any noise you will always get $|11\\rangle$:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "NRCOhTVpEJzz"
+ },
+ "outputs": [],
+ "source": [
+ "\"\"\"Sample from my_circuit.\"\"\"\n",
+ "plot_samples(my_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "EMbJBXAiT9GH"
+ },
+ "source": [
+ "If you increase the noise a little further it will become harder and harder to distinguish the desired behavior (sampling $|11\\rangle$ ) from the noise:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "D2Fg-FUdUJQx"
+ },
+ "outputs": [],
+ "source": [
+ "my_really_noisy_circuit = make_noisy(my_circuit, 0.75)\n",
+ "plot_samples(my_really_noisy_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "oV-0WV5Z7FQ8"
+ },
+ "source": [
+ "Note: Try experimenting with different channels in your circuit to generate noise. Common channels supported in both Cirq and TFQ can be found [here](https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/ops/common_channels.py)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "atzsYj5qScn0"
+ },
+ "source": [
+ "## 2. Basic noise in TFQ\n",
+ "With this understanding of how noise can impact circuit execution, you can explore how noise works in TFQ. TensorFlow Quantum uses monte-carlo / trajectory based simulation as an alternative to density matrix simulation. This is because the memory complexity of density matrix simulation limits large simulations to being <= 20 qubits with traditional full density matrix simulation methods. Monte-carlo / trajectory trades this cost in memory for additional cost in time. The `backend='noisy'` option available to all `tfq.layers.Sample`, `tfq.layers.SampledExpectation` and `tfq.layers.Expectation` (In the case of `Expectation` this does add a required `repetitions` parameter).\n",
+ "\n",
+ "### 2.1 Noisy sampling in TFQ\n",
+ "To recreate the above plots using TFQ and trajectory simulation you can use `tfq.layers.Sample`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "byVI5nbNQ4_b"
+ },
+ "outputs": [],
+ "source": [
+ "\"\"\"Draw bitstring samples from `my_noisy_circuit`\"\"\"\n",
+ "bitstrings = tfq.layers.Sample(backend='noisy')(my_noisy_circuit,\n",
+ " repetitions=1000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ncl0ruCZrd2s"
+ },
+ "outputs": [],
+ "source": [
+ "numeric_values = np.einsum('ijk,k->ij',\n",
+ " bitstrings.to_tensor().numpy(), [1, 2])[0]\n",
+ "freqs, _ = np.histogram(numeric_values,\n",
+ " bins=[i + 0.01 for i in range(-1, 2**len(my_qubits))])\n",
+ "plt.figure(figsize=(10, 5))\n",
+ "plt.title('Noisy Circuit Sampling')\n",
+ "plt.xlabel('Bitstring')\n",
+ "plt.ylabel('Frequency')\n",
+ "plt.bar([i for i in range(2**len(my_qubits))],\n",
+ " freqs,\n",
+ " tick_label=['00', '01', '10', '11'])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QfHq13RwuLlF"
+ },
+ "source": [
+ "### 2.2 Noisy sample based expectation\n",
+ "To do noisy sample based expectation calculation you can use `tfq.layers.SampleExpectation`:\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "ep45G-09rfrA"
+ },
+ "outputs": [],
+ "source": [
+ "some_observables = [\n",
+ " cirq.X(my_qubits[0]),\n",
+ " cirq.Z(my_qubits[0]), 3.0 * cirq.Y(my_qubits[1]) + 1\n",
+ "]\n",
+ "some_observables"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "ur4iF_PGv0Xf"
+ },
+ "source": [
+ "Compute the noiseless expectation estimates via sampling from the circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "jL6wJ3LCvNcn"
+ },
+ "outputs": [],
+ "source": [
+ "noiseless_sampled_expectation = tfq.layers.SampledExpectation(\n",
+ " backend='noiseless')(my_circuit,\n",
+ " operators=some_observables,\n",
+ " repetitions=10000)\n",
+ "noiseless_sampled_expectation.numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "c6hHgNtEv40i"
+ },
+ "source": [
+ "Compare those with the noisy versions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "8U4Gm-LGvYqa"
+ },
+ "outputs": [],
+ "source": [
+ "noisy_sampled_expectation = tfq.layers.SampledExpectation(backend='noisy')(\n",
+ " [my_noisy_circuit, my_really_noisy_circuit],\n",
+ " operators=some_observables,\n",
+ " repetitions=10000)\n",
+ "noisy_sampled_expectation.numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "CqQ_2c7XwMku"
+ },
+ "source": [
+ "You can see that the noise has particularly impacted the $\\langle \\psi | Z | \\psi \\rangle$ accuracy, with `my_really_noisy_circuit` concentrating very quickly towards 0.\n",
+ "\n",
+ "### 2.3 Noisy analytic expectation calculation\n",
+ "Doing noisy analytic expectation calculations is nearly identical to above:\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "pGXKlyCywAfj"
+ },
+ "outputs": [],
+ "source": [
+ "noiseless_analytic_expectation = tfq.layers.Expectation(backend='noiseless')(\n",
+ " my_circuit, operators=some_observables)\n",
+ "noiseless_analytic_expectation.numpy()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "6FUkJ7aOyTlI"
+ },
+ "outputs": [],
+ "source": [
+ "noisy_analytic_expectation = tfq.layers.Expectation(backend='noisy')(\n",
+ " [my_noisy_circuit, my_really_noisy_circuit],\n",
+ " operators=some_observables,\n",
+ " repetitions=10000)\n",
+ "noisy_analytic_expectation.numpy()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "5KHvORT42XFV"
+ },
+ "source": [
+ "## 3. Hybrid models and quantum data noise\n",
+ "Now that you have implemented some noisy circuit simulations in TFQ, you can experiment with how noise impacts quantum and hybrid quantum classical models, by comparing and contrasting their noisy vs noiseless performance. A good first check to see if a model or algorithm is robust to noise is to test under a circuit wide depolarizing model which looks something like this:\n",
+ "\n",
+ " \n",
+ "\n",
+ "Where each time slice of the circuit (sometimes referred to as moment) has a depolarizing channel appended after each gate operation in that time slice. The depolarizing channel with apply one of $\\{X, Y, Z \\}$ with probability $p$ or apply nothing (keep the original operation) with probability $1-p$.\n",
+ "\n",
+ "### 3.1 Data\n",
+ "For this example you can use some prepared circuits in the `tfq.datasets` module as training data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "_ZqVLEji2WUx"
+ },
+ "outputs": [],
+ "source": [
+ "qubits = cirq.GridQubit.rect(1, 8)\n",
+ "circuits, labels, pauli_sums, _ = tfq.datasets.xxz_chain(qubits, 'closed')\n",
+ "circuits[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "MFgNU_nBGeTm"
+ },
+ "source": [
+ "Writing a small helper function will help to generate the data for the noisy vs noiseless case:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "zkQofAqqGibQ"
+ },
+ "outputs": [],
+ "source": [
+ "def get_data(qubits, depolarize_p=0.):\n",
+ " \"\"\"Return quantum data circuits and labels in `tf.Tensor` form.\"\"\"\n",
+ " circuits, labels, pauli_sums, _ = tfq.datasets.xxz_chain(qubits, 'closed')\n",
+ " if depolarize_p >= 1e-5:\n",
+ " circuits = [\n",
+ " circuit.with_noise(cirq.depolarize(depolarize_p))\n",
+ " for circuit in circuits\n",
+ " ]\n",
+ " tmp = list(zip(circuits, labels))\n",
+ " random.shuffle(tmp)\n",
+ " circuits_tensor = tfq.convert_to_tensor([x[0] for x in tmp])\n",
+ " labels_tensor = tf.convert_to_tensor([x[1] for x in tmp])\n",
+ "\n",
+ " return circuits_tensor, labels_tensor"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "FtJrfsLCF9Z3"
+ },
+ "source": [
+ "### 3.2 Define a model circuit\n",
+ "Now that you have quantum data in the form of circuits, you will need a circuit to model this data, like with the data you can write a helper function to generate this circuit optionally containing noise:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "TwryFaFIG2Ya"
+ },
+ "outputs": [],
+ "source": [
+ "def modelling_circuit(qubits, depth, depolarize_p=0.):\n",
+ " \"\"\"A simple classifier circuit.\"\"\"\n",
+ " dim = len(qubits)\n",
+ " ret = cirq.Circuit(cirq.H.on_each(*qubits))\n",
+ "\n",
+ " for i in range(depth):\n",
+ " # Entangle layer.\n",
+ " ret += cirq.Circuit(\n",
+ " cirq.CX(q1, q2) for (q1, q2) in zip(qubits[::2], qubits[1::2]))\n",
+ " ret += cirq.Circuit(\n",
+ " cirq.CX(q1, q2) for (q1, q2) in zip(qubits[1::2], qubits[2::2]))\n",
+ " # Learnable rotation layer.\n",
+ " # i_params = sympy.symbols(f'layer-{i}-0:{dim}')\n",
+ " param = sympy.Symbol(f'layer-{i}')\n",
+ " single_qb = cirq.X\n",
+ " if i % 2 == 1:\n",
+ " single_qb = cirq.Y\n",
+ " ret += cirq.Circuit(single_qb(q)**param for q in qubits)\n",
+ "\n",
+ " if depolarize_p >= 1e-5:\n",
+ " ret = ret.with_noise(cirq.depolarize(depolarize_p))\n",
+ "\n",
+ " return ret, [op(q) for q in qubits for op in [cirq.X, cirq.Y, cirq.Z]]\n",
+ "\n",
+ "\n",
+ "modelling_circuit(qubits, 3)[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "U-ZMaCpJI9TH"
+ },
+ "source": [
+ "### 3.3 Model building and training\n",
+ "With your data and model circuit built, the final helper function you will need is one that can assemble both a noisy or a noiseless hybrid quantum `tf.keras.Model`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "r09CT5N9DWa_"
+ },
+ "outputs": [],
+ "source": [
+ "def build_keras_model(qubits, depolarize_p=0.):\n",
+ " \"\"\"Prepare a noisy hybrid quantum classical Keras model.\"\"\"\n",
+ " spin_input = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
+ "\n",
+ " circuit_and_readout = modelling_circuit(qubits, 4, depolarize_p)\n",
+ " if depolarize_p >= 1e-5:\n",
+ " quantum_model = tfq.layers.NoisyPQC(*circuit_and_readout,\n",
+ " sample_based=False,\n",
+ " repetitions=10)(spin_input)\n",
+ " else:\n",
+ " quantum_model = tfq.layers.PQC(*circuit_and_readout)(spin_input)\n",
+ "\n",
+ " intermediate = tf.keras.layers.Dense(4, activation='sigmoid')(quantum_model)\n",
+ " post_process = tf.keras.layers.Dense(1)(intermediate)\n",
+ "\n",
+ " return tf.keras.Model(inputs=[spin_input], outputs=[post_process])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "QbMtT7BZmhfm"
+ },
+ "source": [
+ "## 4. Compare performance\n",
+ "\n",
+ "### 4.1 Noiseless baseline\n",
+ "\n",
+ "With your data generation and model building code, you can now compare and contrast model performance in the noiseless and noisy settings, first you can run a reference noiseless training:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "QAgpq9c-EakW"
+ },
+ "outputs": [],
+ "source": [
+ "training_histories = dict()\n",
+ "depolarize_p = 0.\n",
+ "n_epochs = 50\n",
+ "phase_classifier = build_keras_model(qubits, depolarize_p)\n",
+ "\n",
+ "phase_classifier.compile(\n",
+ " optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
+ " loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
+ " metrics=['accuracy'])\n",
+ "\n",
+ "# Show the keras plot of the model\n",
+ "tf.keras.utils.plot_model(phase_classifier, show_shapes=True, dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "9tKimWRMlVfL"
+ },
+ "outputs": [],
+ "source": [
+ "noiseless_data, noiseless_labels = get_data(qubits, depolarize_p)\n",
+ "training_histories['noiseless'] = phase_classifier.fit(x=noiseless_data,\n",
+ " y=noiseless_labels,\n",
+ " batch_size=16,\n",
+ " epochs=n_epochs,\n",
+ " validation_split=0.15,\n",
+ " verbose=1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "A9oql6Synv3f"
+ },
+ "source": [
+ "And explore the results and accuracy:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "TG87YNUWKKLY"
+ },
+ "outputs": [],
+ "source": [
+ "loss_plotter = tfdocs.plots.HistoryPlotter(metric='loss', smoothing_std=10)\n",
+ "loss_plotter.plot(training_histories)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "O2ZwM18YUxxm"
+ },
+ "outputs": [],
+ "source": [
+ "acc_plotter = tfdocs.plots.HistoryPlotter(metric='accuracy', smoothing_std=10)\n",
+ "acc_plotter.plot(training_histories)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "JlOwBxvSnzid"
+ },
+ "source": [
+ "### 4.2 Noisy comparison\n",
+ "Now you can build a new model with noisy structure and compare to the above, the code is nearly identical:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "0jy54uWpgwhi"
+ },
+ "outputs": [],
+ "source": [
+ "depolarize_p = 0.001\n",
+ "n_epochs = 50\n",
+ "noisy_phase_classifier = build_keras_model(qubits, depolarize_p)\n",
+ "\n",
+ "noisy_phase_classifier.compile(\n",
+ " optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
+ " loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
+ " metrics=['accuracy'])\n",
+ "\n",
+ "# Show the keras plot of the model\n",
+ "tf.keras.utils.plot_model(noisy_phase_classifier, show_shapes=True, dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "r-vYU6S3oN-J"
+ },
+ "source": [
+ "Note: in the model diagram there is now a `tfq.layers.NoisyPQC` instead of a `tfq.layers.PQC` since the depolarization probability is no longer zero. Training will take significantly longer since noisy simulation is far more expensive than noiseless."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "210cLP5AoClJ"
+ },
+ "outputs": [],
+ "source": [
+ "noisy_data, noisy_labels = get_data(qubits, depolarize_p)\n",
+ "training_histories['noisy'] = noisy_phase_classifier.fit(x=noisy_data,\n",
+ " y=noisy_labels,\n",
+ " batch_size=16,\n",
+ " epochs=n_epochs,\n",
+ " validation_split=0.15,\n",
+ " verbose=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "eQ8pknNdohzy"
+ },
+ "outputs": [],
+ "source": [
+ "loss_plotter.plot(training_histories)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "id": "nBtgnKWtuWRR"
+ },
+ "outputs": [],
+ "source": [
+ "acc_plotter.plot(training_histories)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "r86TeFxlubls"
+ },
+ "source": [
+ "Success: The noisy model still managed to train under some mild depolarization noise. Try experimenting with different noise models to see how and when training might fail. Also look out for noisy functionality under `tfq.layers` and `tfq.noise`."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "name": "noise.ipynb",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/qcnn.ipynb b/docs/tutorials/qcnn.ipynb
index 7b566a5b0..f30bacce7 100644
--- a/docs/tutorials/qcnn.ipynb
+++ b/docs/tutorials/qcnn.ipynb
@@ -1,1215 +1,1216 @@
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xLOXFOT5Q40E"
- },
- "source": [
- "##### Copyright 2020 The TensorFlow Authors."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "cellView": "form",
- "colab": {},
- "colab_type": "code",
- "id": "iiQkM5ZgQ8r2",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
- "# you may not use this file except in compliance with the License.\n",
- "# You may obtain a copy of the License at\n",
- "#\n",
- "# https://www.apache.org/licenses/LICENSE-2.0\n",
- "#\n",
- "# Unless required by applicable law or agreed to in writing, software\n",
- "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
- "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
- "# See the License for the specific language governing permissions and\n",
- "# limitations under the License."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "uLeF5Nmdef0V"
- },
- "source": [
- "# Quantum Convolutional Neural Network"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "i9Jcnb8bQQyd"
- },
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "4D3xaWBHOIVg"
- },
- "source": [
- "This tutorial implements a simplified Quantum Convolutional Neural Network (QCNN), a proposed quantum analogue to a classical convolutional neural network that is also *translationally invariant*.\n",
- "\n",
- "This example demonstrates how to detect certain properties of a quantum data source, such as a quantum sensor or a complex simulation from a device. The quantum data source being a cluster state that may or may not have an excitation—what the QCNN will learn to detect (The dataset used in the paper was SPT phase classification)."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "FnjolLuz8o5C"
- },
- "source": [
- "## Setup"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Aquwcz-0aHqz",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow==2.15.0"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "e_ZuLN_N8yhT"
- },
- "source": [
- "Install TensorFlow Quantum:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "3Pl5PW-ACO9J",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "!pip install tensorflow-quantum==0.7.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "TL_LvHXzPNjW"
- },
- "source": [
- "Now import TensorFlow and the module dependencies:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "QytLEAtoejW5",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "import tensorflow as tf\n",
- "import tensorflow_quantum as tfq\n",
- "\n",
- "import cirq\n",
- "import sympy\n",
- "import numpy as np\n",
- "\n",
- "# visualization tools\n",
- "%matplotlib inline\n",
- "import matplotlib.pyplot as plt\n",
- "from cirq.contrib.svg import SVGCircuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "j6331ZSsQGY3"
- },
- "source": [
- "## 1. Build a QCNN"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Qg85u3G--CGq"
- },
- "source": [
- "### 1.1 Assemble circuits in a TensorFlow graph\n",
- "\n",
- "TensorFlow Quantum (TFQ) provides layer classes designed for in-graph circuit construction. One example is the `tfq.layers.AddCircuit` layer that inherits from `tf.keras.Layer`. This layer can either prepend or append to the input batch of circuits, as shown in the following figure.\n",
- "\n",
- " \n",
- "\n",
- "The following snippet uses this layer:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "FhNf0G_OPLqZ",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "qubit = cirq.GridQubit(0, 0)\n",
- "\n",
- "# Define some circuits.\n",
- "circuit1 = cirq.Circuit(cirq.X(qubit))\n",
- "circuit2 = cirq.Circuit(cirq.H(qubit))\n",
- "\n",
- "# Convert to a tensor.\n",
- "input_circuit_tensor = tfq.convert_to_tensor([circuit1, circuit2])\n",
- "\n",
- "# Define a circuit that we want to append\n",
- "y_circuit = cirq.Circuit(cirq.Y(qubit))\n",
- "\n",
- "# Instantiate our layer\n",
- "y_appender = tfq.layers.AddCircuit()\n",
- "\n",
- "# Run our circuit tensor through the layer and save the output.\n",
- "output_circuit_tensor = y_appender(input_circuit_tensor, append=y_circuit)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "ShZbRZCXkvk5"
- },
- "source": [
- "Examine the input tensor:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "ImRynsUN4BSG",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "print(tfq.from_tensor(input_circuit_tensor))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "xkGU4ZTUk4gf"
- },
- "source": [
- "And examine the output tensor:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "tfff6dJp39Fg",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "print(tfq.from_tensor(output_circuit_tensor))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "23JeZ7Ns5qy5"
- },
- "source": [
- "While it is possible to run the examples below without using `tfq.layers.AddCircuit`, it's a good opportunity to understand how complex functionality can be embedded into TensorFlow compute graphs."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "GcVplt9455Hi"
- },
- "source": [
- "### 1.2 Problem overview\n",
- "\n",
- "You will prepare a *cluster state* and train a quantum classifier to detect if it is \"excited\" or not. The cluster state is highly entangled but not necessarily difficult for a classical computer. For clarity, this is a simpler dataset than the one used in the paper.\n",
- "\n",
- "For this classification task you will implement a deep MERA -like QCNN architecture since:\n",
- "\n",
- "1. Like the QCNN, the cluster state on a ring is translationally invariant.\n",
- "2. The cluster state is highly entangled.\n",
- "\n",
- "This architecture should be effective at reducing entanglement, obtaining the classification by reading out a single qubit.\n",
- "\n",
- " \n",
- "\n",
- "An \"excited\" cluster state is defined as a cluster state that had a `cirq.rx` gate applied to any of its qubits. Qconv and QPool are discussed later in this tutorial."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "jpqtsGJH_I1d"
- },
- "source": [
- "### 1.3 Building blocks for TensorFlow\n",
- "\n",
- " \n",
- "\n",
- "One way to solve this problem with TensorFlow Quantum is to implement the following:\n",
- "\n",
- "1. The input to the model is a circuit tensor—either an empty circuit or an X gate on a particular qubit indicating an excitation.\n",
- "2. The rest of the model's quantum components are constructed with `tfq.layers.AddCircuit` layers.\n",
- "3. For inference a `tfq.layers.PQC` layer is used. This reads $\\langle \\hat{Z} \\rangle$ and compares it to a label of 1 for an excited state, or -1 for a non-excited state."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "oa7Q3m_ThDgO"
- },
- "source": [
- "### 1.4 Data\n",
- "Before building your model, you can generate your data. In this case it's going to be excitations to the cluster state (The original paper uses a more complicated dataset). Excitations are represented with `cirq.rx` gates. A large enough rotation is deemed an excitation and is labeled `1` and a rotation that isn't large enough is labeled `-1` and deemed not an excitation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "iUrvTCU1hDgP",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def generate_data(qubits):\n",
- " \"\"\"Generate training and testing data.\"\"\"\n",
- " n_rounds = 20 # Produces n_rounds * n_qubits datapoints.\n",
- " excitations = []\n",
- " labels = []\n",
- " for n in range(n_rounds):\n",
- " for bit in qubits:\n",
- " rng = np.random.uniform(-np.pi, np.pi)\n",
- " excitations.append(cirq.Circuit(cirq.rx(rng)(bit)))\n",
- " labels.append(1 if (-np.pi / 2) <= rng <= (np.pi / 2) else -1)\n",
- "\n",
- " split_ind = int(len(excitations) * 0.7)\n",
- " train_excitations = excitations[:split_ind]\n",
- " test_excitations = excitations[split_ind:]\n",
- "\n",
- " train_labels = labels[:split_ind]\n",
- " test_labels = labels[split_ind:]\n",
- "\n",
- " return tfq.convert_to_tensor(train_excitations), np.array(train_labels), \\\n",
- " tfq.convert_to_tensor(test_excitations), np.array(test_labels)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "wGsDkZnrhDgS"
- },
- "source": [
- "You can see that just like with regular machine learning you create a training and testing set to use to benchmark the model. You can quickly look at some datapoints with:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "eLJ-JHOihDgT",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "sample_points, sample_labels, _, __ = generate_data(cirq.GridQubit.rect(1, 4))\n",
- "print('Input:', tfq.from_tensor(sample_points)[0], 'Output:', sample_labels[0])\n",
- "print('Input:', tfq.from_tensor(sample_points)[1], 'Output:', sample_labels[1])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "sFiRlDt_0-DL"
- },
- "source": [
- "### 1.5 Define layers\n",
- "\n",
- "Now define the layers shown in the figure above in TensorFlow."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "s2B9geIqLWHK"
- },
- "source": [
- "#### 1.5.1 Cluster state\n",
- "\n",
- "The first step is to define the cluster state using Cirq , a Google-provided framework for programming quantum circuits. Since this is a static part of the model, embed it using the `tfq.layers.AddCircuit` functionality."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "qpQwVWKazU8g",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def cluster_state_circuit(bits):\n",
- " \"\"\"Return a cluster state on the qubits in `bits`.\"\"\"\n",
- " circuit = cirq.Circuit()\n",
- " circuit.append(cirq.H.on_each(bits))\n",
- " for this_bit, next_bit in zip(bits, bits[1:] + [bits[0]]):\n",
- " circuit.append(cirq.CZ(this_bit, next_bit))\n",
- " return circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "e9qX1uN740vJ"
- },
- "source": [
- "Display a cluster state circuit for a rectangle of cirq.GridQubit
s:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "9tZt0aAO4r4F",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "SVGCircuit(cluster_state_circuit(cirq.GridQubit.rect(1, 4)))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "4xElWnRf1ZC7"
- },
- "source": [
- "#### 1.5.2 QCNN layers\n",
- "\n",
- "Define the layers that make up the model using the Cong and Lukin QCNN paper . There are a few prerequisites:\n",
- "\n",
- "* The one- and two-qubit parameterized unitary matrices from the Tucci paper .\n",
- "* A general parameterized two-qubit pooling operation."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "oNRGOqky2exY",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def one_qubit_unitary(bit, symbols):\n",
- " \"\"\"Make a Cirq circuit enacting a rotation of the bloch sphere about the X,\n",
- " Y and Z axis, that depends on the values in `symbols`.\n",
- " \"\"\"\n",
- " return cirq.Circuit(\n",
- " cirq.X(bit)**symbols[0],\n",
- " cirq.Y(bit)**symbols[1],\n",
- " cirq.Z(bit)**symbols[2])\n",
- "\n",
- "\n",
- "def two_qubit_unitary(bits, symbols):\n",
- " \"\"\"Make a Cirq circuit that creates an arbitrary two qubit unitary.\"\"\"\n",
- " circuit = cirq.Circuit()\n",
- " circuit += one_qubit_unitary(bits[0], symbols[0:3])\n",
- " circuit += one_qubit_unitary(bits[1], symbols[3:6])\n",
- " circuit += [cirq.ZZ(*bits)**symbols[6]]\n",
- " circuit += [cirq.YY(*bits)**symbols[7]]\n",
- " circuit += [cirq.XX(*bits)**symbols[8]]\n",
- " circuit += one_qubit_unitary(bits[0], symbols[9:12])\n",
- " circuit += one_qubit_unitary(bits[1], symbols[12:])\n",
- " return circuit\n",
- "\n",
- "\n",
- "def two_qubit_pool(source_qubit, sink_qubit, symbols):\n",
- " \"\"\"Make a Cirq circuit to do a parameterized 'pooling' operation, which\n",
- " attempts to reduce entanglement down from two qubits to just one.\"\"\"\n",
- " pool_circuit = cirq.Circuit()\n",
- " sink_basis_selector = one_qubit_unitary(sink_qubit, symbols[0:3])\n",
- " source_basis_selector = one_qubit_unitary(source_qubit, symbols[3:6])\n",
- " pool_circuit.append(sink_basis_selector)\n",
- " pool_circuit.append(source_basis_selector)\n",
- " pool_circuit.append(cirq.CNOT(source_qubit, sink_qubit))\n",
- " pool_circuit.append(sink_basis_selector**-1)\n",
- " return pool_circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "LoG0a3U_2qGA"
- },
- "source": [
- "To see what you created, print out the one-qubit unitary circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "T5uhvF-g2rpZ",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "SVGCircuit(one_qubit_unitary(cirq.GridQubit(0, 0), sympy.symbols('x0:3')))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "NWuMb_Us8ar2"
- },
- "source": [
- "And the two-qubit unitary circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "aJTdRrfS2uIo",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "SVGCircuit(two_qubit_unitary(cirq.GridQubit.rect(1, 2), sympy.symbols('x0:15')))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "EXQD1R_V8jyk"
- },
- "source": [
- "And the two-qubit pooling circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "DOHRbkvH2xGK",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "SVGCircuit(two_qubit_pool(*cirq.GridQubit.rect(1, 2), sympy.symbols('x0:6')))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "AzVauXWD3v8C"
- },
- "source": [
- "##### 1.5.2.1 Quantum convolution\n",
- "\n",
- "As in the Cong and Lukin paper, define the 1D quantum convolution as the application of a two-qubit parameterized unitary to every pair of adjacent qubits with a stride of one."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "1Fa19Lzb3wnR",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def quantum_conv_circuit(bits, symbols):\n",
- " \"\"\"Quantum Convolution Layer following the above diagram.\n",
- " Return a Cirq circuit with the cascade of `two_qubit_unitary` applied\n",
- " to all pairs of qubits in `bits` as in the diagram above.\n",
- " \"\"\"\n",
- " circuit = cirq.Circuit()\n",
- " for first, second in zip(bits[0::2], bits[1::2]):\n",
- " circuit += two_qubit_unitary([first, second], symbols)\n",
- " for first, second in zip(bits[1::2], bits[2::2] + [bits[0]]):\n",
- " circuit += two_qubit_unitary([first, second], symbols)\n",
- " return circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "fTzOm_t394Gj"
- },
- "source": [
- "Display the (very horizontal) circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Bi6q2nmY3z_U",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "SVGCircuit(\n",
- " quantum_conv_circuit(cirq.GridQubit.rect(1, 8), sympy.symbols('x0:15')))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "3svBAfap4xhP"
- },
- "source": [
- "##### 1.5.2.2 Quantum pooling\n",
- "\n",
- "A quantum pooling layer pools from $N$ qubits to $\\frac{N}{2}$ qubits using the two-qubit pool defined above."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "jD3fgcWO4yEU",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def quantum_pool_circuit(source_bits, sink_bits, symbols):\n",
- " \"\"\"A layer that specifies a quantum pooling operation.\n",
- " A Quantum pool tries to learn to pool the relevant information from two\n",
- " qubits onto 1.\n",
- " \"\"\"\n",
- " circuit = cirq.Circuit()\n",
- " for source, sink in zip(source_bits, sink_bits):\n",
- " circuit += two_qubit_pool(source, sink, symbols)\n",
- " return circuit"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "NX83NHDP_Q_Z"
- },
- "source": [
- "Examine a pooling component circuit:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "pFXow2OX47O5",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "test_bits = cirq.GridQubit.rect(1, 8)\n",
- "\n",
- "SVGCircuit(\n",
- " quantum_pool_circuit(test_bits[:4], test_bits[4:], sympy.symbols('x0:6')))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "23VcPLT45Lg7"
- },
- "source": [
- "### 1.6 Model definition\n",
- "\n",
- "Now use the defined layers to construct a purely quantum CNN. Start with eight qubits, pool down to one, then measure $\\langle \\hat{Z} \\rangle$."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "vzEsY6-n5NR0",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "def create_model_circuit(qubits):\n",
- " \"\"\"Create sequence of alternating convolution and pooling operators \n",
- " which gradually shrink over time.\"\"\"\n",
- " model_circuit = cirq.Circuit()\n",
- " symbols = sympy.symbols('qconv0:63')\n",
- " # Cirq uses sympy.Symbols to map learnable variables. TensorFlow Quantum\n",
- " # scans incoming circuits and replaces these with TensorFlow variables.\n",
- " model_circuit += quantum_conv_circuit(qubits, symbols[0:15])\n",
- " model_circuit += quantum_pool_circuit(qubits[:4], qubits[4:],\n",
- " symbols[15:21])\n",
- " model_circuit += quantum_conv_circuit(qubits[4:], symbols[21:36])\n",
- " model_circuit += quantum_pool_circuit(qubits[4:6], qubits[6:],\n",
- " symbols[36:42])\n",
- " model_circuit += quantum_conv_circuit(qubits[6:], symbols[42:57])\n",
- " model_circuit += quantum_pool_circuit([qubits[6]], [qubits[7]],\n",
- " symbols[57:63])\n",
- " return model_circuit\n",
- "\n",
- "\n",
- "# Create our qubits and readout operators in Cirq.\n",
- "cluster_state_bits = cirq.GridQubit.rect(1, 8)\n",
- "readout_operators = cirq.Z(cluster_state_bits[-1])\n",
- "\n",
- "# Build a sequential model enacting the logic in 1.3 of this notebook.\n",
- "# Here you are making the static cluster state prep as a part of the AddCircuit and the\n",
- "# \"quantum datapoints\" are coming in the form of excitation\n",
- "excitation_input = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
- "cluster_state = tfq.layers.AddCircuit()(\n",
- " excitation_input, prepend=cluster_state_circuit(cluster_state_bits))\n",
- "\n",
- "quantum_model = tfq.layers.PQC(create_model_circuit(cluster_state_bits),\n",
- " readout_operators)(cluster_state)\n",
- "\n",
- "qcnn_model = tf.keras.Model(inputs=[excitation_input], outputs=[quantum_model])\n",
- "\n",
- "# Show the keras plot of the model\n",
- "tf.keras.utils.plot_model(qcnn_model,\n",
- " show_shapes=True,\n",
- " show_layer_names=False,\n",
- " dpi=70)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "9jqTEe5VSbug"
- },
- "source": [
- "### 1.7 Train the model\n",
- "\n",
- "Train the model over the full batch to simplify this example."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "_TFkAm1sQZEN",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# Generate some training data.\n",
- "train_excitations, train_labels, test_excitations, test_labels = generate_data(\n",
- " cluster_state_bits)\n",
- "\n",
- "\n",
- "# Custom accuracy metric.\n",
- "@tf.function\n",
- "def custom_accuracy(y_true, y_pred):\n",
- " y_true = tf.squeeze(y_true)\n",
- " y_pred = tf.map_fn(lambda x: 1.0 if x >= 0 else -1.0, y_pred)\n",
- " return tf.keras.backend.mean(tf.keras.backend.equal(y_true, y_pred))\n",
- "\n",
- "\n",
- "qcnn_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
- " loss=tf.losses.mse,\n",
- " metrics=[custom_accuracy])\n",
- "\n",
- "history = qcnn_model.fit(x=train_excitations,\n",
- " y=train_labels,\n",
- " batch_size=16,\n",
- " epochs=25,\n",
- " verbose=1,\n",
- " validation_data=(test_excitations, test_labels))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "2tiCJOb5Qzcr",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "plt.plot(history.history['loss'][1:], label='Training')\n",
- "plt.plot(history.history['val_loss'][1:], label='Validation')\n",
- "plt.title('Training a Quantum CNN to Detect Excited Cluster States')\n",
- "plt.xlabel('Epochs')\n",
- "plt.ylabel('Loss')\n",
- "plt.legend()\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "GyrkcEReQ5Bc"
- },
- "source": [
- "## 2. Hybrid models\n",
- "\n",
- "You don't have to go from eight qubits to one qubit using quantum convolution—you could have done one or two rounds of quantum convolution and fed the results into a classical neural network. This section explores quantum-classical hybrid models."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "A2tOK22t7Kjm"
- },
- "source": [
- "### 2.1 Hybrid model with a single quantum filter\n",
- "\n",
- "Apply one layer of quantum convolution, reading out $\\langle \\hat{Z}_n \\rangle$ on all bits, followed by a densely-connected neural network.\n",
- "\n",
- " "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "lKXuOApgWYFa"
- },
- "source": [
- "#### 2.1.1 Model definition"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "Ut-U1hBkQ8Fs",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "# 1-local operators to read out\n",
- "readouts = [cirq.Z(bit) for bit in cluster_state_bits[4:]]\n",
- "\n",
- "\n",
- "def multi_readout_model_circuit(qubits):\n",
- " \"\"\"Make a model circuit with less quantum pool and conv operations.\"\"\"\n",
- " model_circuit = cirq.Circuit()\n",
- " symbols = sympy.symbols('qconv0:21')\n",
- " model_circuit += quantum_conv_circuit(qubits, symbols[0:15])\n",
- " model_circuit += quantum_pool_circuit(qubits[:4], qubits[4:],\n",
- " symbols[15:21])\n",
- " return model_circuit\n",
- "\n",
- "\n",
- "# Build a model enacting the logic in 2.1 of this notebook.\n",
- "excitation_input_dual = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
- "\n",
- "cluster_state_dual = tfq.layers.AddCircuit()(\n",
- " excitation_input_dual, prepend=cluster_state_circuit(cluster_state_bits))\n",
- "\n",
- "quantum_model_dual = tfq.layers.PQC(\n",
- " multi_readout_model_circuit(cluster_state_bits),\n",
- " readouts)(cluster_state_dual)\n",
- "\n",
- "d1_dual = tf.keras.layers.Dense(8)(quantum_model_dual)\n",
- "\n",
- "d2_dual = tf.keras.layers.Dense(1)(d1_dual)\n",
- "\n",
- "hybrid_model = tf.keras.Model(inputs=[excitation_input_dual], outputs=[d2_dual])\n",
- "\n",
- "# Display the model architecture\n",
- "tf.keras.utils.plot_model(hybrid_model,\n",
- " show_shapes=True,\n",
- " show_layer_names=False,\n",
- " dpi=70)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "qDqoLZJuWcgH"
- },
- "source": [
- "#### 2.1.2 Train the model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "EyYw9kYIRCE7",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "hybrid_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
- " loss=tf.losses.mse,\n",
- " metrics=[custom_accuracy])\n",
- "\n",
- "hybrid_history = hybrid_model.fit(x=train_excitations,\n",
- " y=train_labels,\n",
- " batch_size=16,\n",
- " epochs=25,\n",
- " verbose=1,\n",
- " validation_data=(test_excitations,\n",
- " test_labels))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "yL3jhGiBRJHt",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "plt.plot(history.history['val_custom_accuracy'], label='QCNN')\n",
- "plt.plot(hybrid_history.history['val_custom_accuracy'], label='Hybrid CNN')\n",
- "plt.title('Quantum vs Hybrid CNN performance')\n",
- "plt.xlabel('Epochs')\n",
- "plt.legend()\n",
- "plt.ylabel('Validation Accuracy')\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "NCNiNvheRNzq"
- },
- "source": [
- "As you can see, with very modest classical assistance, the hybrid model will usually converge faster than the purely quantum version."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "nVUtWLZnRRDE"
- },
- "source": [
- "### 2.2 Hybrid convolution with multiple quantum filters\n",
- "\n",
- "Now let's try an architecture that uses multiple quantum convolutions and a classical neural network to combine them.\n",
- "\n",
- " "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "Ldo_m5P3YBV7"
- },
- "source": [
- "#### 2.2.1 Model definition"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "W3TkNVm9RTBj",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "excitation_input_multi = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
- "\n",
- "cluster_state_multi = tfq.layers.AddCircuit()(\n",
- " excitation_input_multi, prepend=cluster_state_circuit(cluster_state_bits))\n",
- "\n",
- "# apply 3 different filters and measure expectation values\n",
- "\n",
- "quantum_model_multi1 = tfq.layers.PQC(\n",
- " multi_readout_model_circuit(cluster_state_bits),\n",
- " readouts)(cluster_state_multi)\n",
- "\n",
- "quantum_model_multi2 = tfq.layers.PQC(\n",
- " multi_readout_model_circuit(cluster_state_bits),\n",
- " readouts)(cluster_state_multi)\n",
- "\n",
- "quantum_model_multi3 = tfq.layers.PQC(\n",
- " multi_readout_model_circuit(cluster_state_bits),\n",
- " readouts)(cluster_state_multi)\n",
- "\n",
- "# concatenate outputs and feed into a small classical NN\n",
- "concat_out = tf.keras.layers.concatenate(\n",
- " [quantum_model_multi1, quantum_model_multi2, quantum_model_multi3])\n",
- "\n",
- "dense_1 = tf.keras.layers.Dense(8)(concat_out)\n",
- "\n",
- "dense_2 = tf.keras.layers.Dense(1)(dense_1)\n",
- "\n",
- "multi_qconv_model = tf.keras.Model(inputs=[excitation_input_multi],\n",
- " outputs=[dense_2])\n",
- "\n",
- "# Display the model architecture\n",
- "tf.keras.utils.plot_model(multi_qconv_model,\n",
- " show_shapes=True,\n",
- " show_layer_names=True,\n",
- " dpi=70)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {
- "colab_type": "text",
- "id": "2eNhDWwKY9N4"
- },
- "source": [
- "#### 2.2.2 Train the model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "suRvxcAKRZK6",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "multi_qconv_model.compile(\n",
- " optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
- " loss=tf.losses.mse,\n",
- " metrics=[custom_accuracy])\n",
- "\n",
- "multi_qconv_history = multi_qconv_model.fit(x=train_excitations,\n",
- " y=train_labels,\n",
- " batch_size=16,\n",
- " epochs=25,\n",
- " verbose=1,\n",
- " validation_data=(test_excitations,\n",
- " test_labels))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "-6NR7yAQRmOU",
- "vscode": {
- "languageId": "python"
- }
- },
- "outputs": [],
- "source": [
- "plt.plot(history.history['val_custom_accuracy'][:25], label='QCNN')\n",
- "plt.plot(hybrid_history.history['val_custom_accuracy'][:25], label='Hybrid CNN')\n",
- "plt.plot(multi_qconv_history.history['val_custom_accuracy'][:25],\n",
- " label='Hybrid CNN \\n Multiple Quantum Filters')\n",
- "plt.title('Quantum vs Hybrid CNN performance')\n",
- "plt.xlabel('Epochs')\n",
- "plt.legend()\n",
- "plt.ylabel('Validation Accuracy')\n",
- "plt.show()"
- ]
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xLOXFOT5Q40E"
+ },
+ "source": [
+ "##### Copyright 2020 The TensorFlow Authors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "cellView": "form",
+ "colab": {},
+ "colab_type": "code",
+ "id": "iiQkM5ZgQ8r2",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "uLeF5Nmdef0V"
+ },
+ "source": [
+ "# Quantum Convolutional Neural Network"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "i9Jcnb8bQQyd"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4D3xaWBHOIVg"
+ },
+ "source": [
+ "This tutorial implements a simplified Quantum Convolutional Neural Network (QCNN), a proposed quantum analogue to a classical convolutional neural network that is also *translationally invariant*.\n",
+ "\n",
+ "This example demonstrates how to detect certain properties of a quantum data source, such as a quantum sensor or a complex simulation from a device. The quantum data source being a cluster state that may or may not have an excitation—what the QCNN will learn to detect (The dataset used in the paper was SPT phase classification)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FnjolLuz8o5C"
+ },
+ "source": [
+ "## Setup"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Aquwcz-0aHqz",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow==2.15.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "e_ZuLN_N8yhT"
+ },
+ "source": [
+ "Install TensorFlow Quantum:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "3Pl5PW-ACO9J",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "!pip install tensorflow-quantum==0.7.3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "TL_LvHXzPNjW"
+ },
+ "source": [
+ "Now import TensorFlow and the module dependencies:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "QytLEAtoejW5",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "import tensorflow_quantum as tfq\n",
+ "\n",
+ "import cirq\n",
+ "import sympy\n",
+ "import numpy as np\n",
+ "\n",
+ "# visualization tools\n",
+ "%matplotlib inline\n",
+ "import matplotlib.pyplot as plt\n",
+ "from cirq.contrib.svg import SVGCircuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "j6331ZSsQGY3"
+ },
+ "source": [
+ "## 1. Build a QCNN"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Qg85u3G--CGq"
+ },
+ "source": [
+ "### 1.1 Assemble circuits in a TensorFlow graph\n",
+ "\n",
+ "TensorFlow Quantum (TFQ) provides layer classes designed for in-graph circuit construction. One example is the `tfq.layers.AddCircuit` layer that inherits from `tf.keras.Layer`. This layer can either prepend or append to the input batch of circuits, as shown in the following figure.\n",
+ "\n",
+ " \n",
+ "\n",
+ "The following snippet uses this layer:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "FhNf0G_OPLqZ",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "qubit = cirq.GridQubit(0, 0)\n",
+ "\n",
+ "# Define some circuits.\n",
+ "circuit1 = cirq.Circuit(cirq.X(qubit))\n",
+ "circuit2 = cirq.Circuit(cirq.H(qubit))\n",
+ "\n",
+ "# Convert to a tensor.\n",
+ "input_circuit_tensor = tfq.convert_to_tensor([circuit1, circuit2])\n",
+ "\n",
+ "# Define a circuit that we want to append\n",
+ "y_circuit = cirq.Circuit(cirq.Y(qubit))\n",
+ "\n",
+ "# Instantiate our layer\n",
+ "y_appender = tfq.layers.AddCircuit()\n",
+ "\n",
+ "# Run our circuit tensor through the layer and save the output.\n",
+ "output_circuit_tensor = y_appender(input_circuit_tensor, append=y_circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "ShZbRZCXkvk5"
+ },
+ "source": [
+ "Examine the input tensor:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "ImRynsUN4BSG",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "print(tfq.from_tensor(input_circuit_tensor))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "xkGU4ZTUk4gf"
+ },
+ "source": [
+ "And examine the output tensor:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "tfff6dJp39Fg",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "print(tfq.from_tensor(output_circuit_tensor))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "23JeZ7Ns5qy5"
+ },
+ "source": [
+ "While it is possible to run the examples below without using `tfq.layers.AddCircuit`, it's a good opportunity to understand how complex functionality can be embedded into TensorFlow compute graphs."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "GcVplt9455Hi"
+ },
+ "source": [
+ "### 1.2 Problem overview\n",
+ "\n",
+ "You will prepare a *cluster state* and train a quantum classifier to detect if it is \"excited\" or not. The cluster state is highly entangled but not necessarily difficult for a classical computer. For clarity, this is a simpler dataset than the one used in the paper.\n",
+ "\n",
+ "For this classification task you will implement a deep MERA -like QCNN architecture since:\n",
+ "\n",
+ "1. Like the QCNN, the cluster state on a ring is translationally invariant.\n",
+ "2. The cluster state is highly entangled.\n",
+ "\n",
+ "This architecture should be effective at reducing entanglement, obtaining the classification by reading out a single qubit.\n",
+ "\n",
+ " \n",
+ "\n",
+ "An \"excited\" cluster state is defined as a cluster state that had a `cirq.rx` gate applied to any of its qubits. Qconv and QPool are discussed later in this tutorial."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "jpqtsGJH_I1d"
+ },
+ "source": [
+ "### 1.3 Building blocks for TensorFlow\n",
+ "\n",
+ " \n",
+ "\n",
+ "One way to solve this problem with TensorFlow Quantum is to implement the following:\n",
+ "\n",
+ "1. The input to the model is a circuit tensor—either an empty circuit or an X gate on a particular qubit indicating an excitation.\n",
+ "2. The rest of the model's quantum components are constructed with `tfq.layers.AddCircuit` layers.\n",
+ "3. For inference a `tfq.layers.PQC` layer is used. This reads $\\langle \\hat{Z} \\rangle$ and compares it to a label of 1 for an excited state, or -1 for a non-excited state."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "oa7Q3m_ThDgO"
+ },
+ "source": [
+ "### 1.4 Data\n",
+ "Before building your model, you can generate your data. In this case it's going to be excitations to the cluster state (The original paper uses a more complicated dataset). Excitations are represented with `cirq.rx` gates. A large enough rotation is deemed an excitation and is labeled `1` and a rotation that isn't large enough is labeled `-1` and deemed not an excitation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "iUrvTCU1hDgP",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def generate_data(qubits):\n",
+ " \"\"\"Generate training and testing data.\"\"\"\n",
+ " n_rounds = 20 # Produces n_rounds * n_qubits datapoints.\n",
+ " excitations = []\n",
+ " labels = []\n",
+ " for n in range(n_rounds):\n",
+ " for bit in qubits:\n",
+ " rng = np.random.uniform(-np.pi, np.pi)\n",
+ " excitations.append(cirq.Circuit(cirq.rx(rng)(bit)))\n",
+ " labels.append(1 if (-np.pi / 2) <= rng <= (np.pi / 2) else -1)\n",
+ "\n",
+ " split_ind = int(len(excitations) * 0.7)\n",
+ " train_excitations = excitations[:split_ind]\n",
+ " test_excitations = excitations[split_ind:]\n",
+ "\n",
+ " train_labels = labels[:split_ind]\n",
+ " test_labels = labels[split_ind:]\n",
+ "\n",
+ " return tfq.convert_to_tensor(train_excitations), np.array(train_labels), \\\n",
+ " tfq.convert_to_tensor(test_excitations), np.array(test_labels)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "wGsDkZnrhDgS"
+ },
+ "source": [
+ "You can see that just like with regular machine learning you create a training and testing set to use to benchmark the model. You can quickly look at some datapoints with:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "eLJ-JHOihDgT",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "sample_points, sample_labels, _, __ = generate_data(cirq.GridQubit.rect(1, 4))\n",
+ "print('Input:', tfq.from_tensor(sample_points)[0], 'Output:', sample_labels[0])\n",
+ "print('Input:', tfq.from_tensor(sample_points)[1], 'Output:', sample_labels[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "sFiRlDt_0-DL"
+ },
+ "source": [
+ "### 1.5 Define layers\n",
+ "\n",
+ "Now define the layers shown in the figure above in TensorFlow."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "s2B9geIqLWHK"
+ },
+ "source": [
+ "#### 1.5.1 Cluster state\n",
+ "\n",
+ "The first step is to define the cluster state using Cirq , a Google-provided framework for programming quantum circuits. Since this is a static part of the model, embed it using the `tfq.layers.AddCircuit` functionality."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "qpQwVWKazU8g",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def cluster_state_circuit(bits):\n",
+ " \"\"\"Return a cluster state on the qubits in `bits`.\"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ " circuit.append(cirq.H.on_each(bits))\n",
+ " for this_bit, next_bit in zip(bits, bits[1:] + [bits[0]]):\n",
+ " circuit.append(cirq.CZ(this_bit, next_bit))\n",
+ " return circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "e9qX1uN740vJ"
+ },
+ "source": [
+ "Display a cluster state circuit for a rectangle of cirq.GridQubit
s:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "9tZt0aAO4r4F",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(cluster_state_circuit(cirq.GridQubit.rect(1, 4)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4xElWnRf1ZC7"
+ },
+ "source": [
+ "#### 1.5.2 QCNN layers\n",
+ "\n",
+ "Define the layers that make up the model using the Cong and Lukin QCNN paper . There are a few prerequisites:\n",
+ "\n",
+ "* The one- and two-qubit parameterized unitary matrices from the Tucci paper .\n",
+ "* A general parameterized two-qubit pooling operation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "oNRGOqky2exY",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def one_qubit_unitary(bit, symbols):\n",
+ " \"\"\"Make a Cirq circuit enacting a rotation of the bloch sphere about the X,\n",
+ " Y and Z axis, that depends on the values in `symbols`.\n",
+ " \"\"\"\n",
+ " return cirq.Circuit(\n",
+ " cirq.X(bit)**symbols[0],\n",
+ " cirq.Y(bit)**symbols[1],\n",
+ " cirq.Z(bit)**symbols[2])\n",
+ "\n",
+ "\n",
+ "def two_qubit_unitary(bits, symbols):\n",
+ " \"\"\"Make a Cirq circuit that creates an arbitrary two qubit unitary.\"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ " circuit += one_qubit_unitary(bits[0], symbols[0:3])\n",
+ " circuit += one_qubit_unitary(bits[1], symbols[3:6])\n",
+ " circuit += [cirq.ZZ(*bits)**symbols[6]]\n",
+ " circuit += [cirq.YY(*bits)**symbols[7]]\n",
+ " circuit += [cirq.XX(*bits)**symbols[8]]\n",
+ " circuit += one_qubit_unitary(bits[0], symbols[9:12])\n",
+ " circuit += one_qubit_unitary(bits[1], symbols[12:])\n",
+ " return circuit\n",
+ "\n",
+ "\n",
+ "def two_qubit_pool(source_qubit, sink_qubit, symbols):\n",
+ " \"\"\"Make a Cirq circuit to do a parameterized 'pooling' operation, which\n",
+ " attempts to reduce entanglement down from two qubits to just one.\"\"\"\n",
+ " pool_circuit = cirq.Circuit()\n",
+ " sink_basis_selector = one_qubit_unitary(sink_qubit, symbols[0:3])\n",
+ " source_basis_selector = one_qubit_unitary(source_qubit, symbols[3:6])\n",
+ " pool_circuit.append(sink_basis_selector)\n",
+ " pool_circuit.append(source_basis_selector)\n",
+ " pool_circuit.append(cirq.CNOT(source_qubit, sink_qubit))\n",
+ " pool_circuit.append(sink_basis_selector**-1)\n",
+ " return pool_circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "LoG0a3U_2qGA"
+ },
+ "source": [
+ "To see what you created, print out the one-qubit unitary circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "T5uhvF-g2rpZ",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(one_qubit_unitary(cirq.GridQubit(0, 0), sympy.symbols('x0:3')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "NWuMb_Us8ar2"
+ },
+ "source": [
+ "And the two-qubit unitary circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "aJTdRrfS2uIo",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(two_qubit_unitary(cirq.GridQubit.rect(1, 2), sympy.symbols('x0:15')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "EXQD1R_V8jyk"
+ },
+ "source": [
+ "And the two-qubit pooling circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "DOHRbkvH2xGK",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(two_qubit_pool(*cirq.GridQubit.rect(1, 2), sympy.symbols('x0:6')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "AzVauXWD3v8C"
+ },
+ "source": [
+ "##### 1.5.2.1 Quantum convolution\n",
+ "\n",
+ "As in the Cong and Lukin paper, define the 1D quantum convolution as the application of a two-qubit parameterized unitary to every pair of adjacent qubits with a stride of one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "1Fa19Lzb3wnR",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def quantum_conv_circuit(bits, symbols):\n",
+ " \"\"\"Quantum Convolution Layer following the above diagram.\n",
+ " Return a Cirq circuit with the cascade of `two_qubit_unitary` applied\n",
+ " to all pairs of qubits in `bits` as in the diagram above.\n",
+ " \"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ " for first, second in zip(bits[0::2], bits[1::2]):\n",
+ " circuit += two_qubit_unitary([first, second], symbols)\n",
+ " for first, second in zip(bits[1::2], bits[2::2] + [bits[0]]):\n",
+ " circuit += two_qubit_unitary([first, second], symbols)\n",
+ " return circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "fTzOm_t394Gj"
+ },
+ "source": [
+ "Display the (very horizontal) circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Bi6q2nmY3z_U",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "SVGCircuit(\n",
+ " quantum_conv_circuit(cirq.GridQubit.rect(1, 8), sympy.symbols('x0:15')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "3svBAfap4xhP"
+ },
+ "source": [
+ "##### 1.5.2.2 Quantum pooling\n",
+ "\n",
+ "A quantum pooling layer pools from $N$ qubits to $\\frac{N}{2}$ qubits using the two-qubit pool defined above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "jD3fgcWO4yEU",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def quantum_pool_circuit(source_bits, sink_bits, symbols):\n",
+ " \"\"\"A layer that specifies a quantum pooling operation.\n",
+ " A Quantum pool tries to learn to pool the relevant information from two\n",
+ " qubits onto 1.\n",
+ " \"\"\"\n",
+ " circuit = cirq.Circuit()\n",
+ " for source, sink in zip(source_bits, sink_bits):\n",
+ " circuit += two_qubit_pool(source, sink, symbols)\n",
+ " return circuit"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "NX83NHDP_Q_Z"
+ },
+ "source": [
+ "Examine a pooling component circuit:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "pFXow2OX47O5",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "test_bits = cirq.GridQubit.rect(1, 8)\n",
+ "\n",
+ "SVGCircuit(\n",
+ " quantum_pool_circuit(test_bits[:4], test_bits[4:], sympy.symbols('x0:6')))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "23VcPLT45Lg7"
+ },
+ "source": [
+ "### 1.6 Model definition\n",
+ "\n",
+ "Now use the defined layers to construct a purely quantum CNN. Start with eight qubits, pool down to one, then measure $\\langle \\hat{Z} \\rangle$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "vzEsY6-n5NR0",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def create_model_circuit(qubits):\n",
+ " \"\"\"Create sequence of alternating convolution and pooling operators \n",
+ " which gradually shrink over time.\"\"\"\n",
+ " model_circuit = cirq.Circuit()\n",
+ " symbols = sympy.symbols('qconv0:63')\n",
+ " # Cirq uses sympy.Symbols to map learnable variables. TensorFlow Quantum\n",
+ " # scans incoming circuits and replaces these with TensorFlow variables.\n",
+ " model_circuit += quantum_conv_circuit(qubits, symbols[0:15])\n",
+ " model_circuit += quantum_pool_circuit(qubits[:4], qubits[4:],\n",
+ " symbols[15:21])\n",
+ " model_circuit += quantum_conv_circuit(qubits[4:], symbols[21:36])\n",
+ " model_circuit += quantum_pool_circuit(qubits[4:6], qubits[6:],\n",
+ " symbols[36:42])\n",
+ " model_circuit += quantum_conv_circuit(qubits[6:], symbols[42:57])\n",
+ " model_circuit += quantum_pool_circuit([qubits[6]], [qubits[7]],\n",
+ " symbols[57:63])\n",
+ " return model_circuit\n",
+ "\n",
+ "\n",
+ "# Create our qubits and readout operators in Cirq.\n",
+ "cluster_state_bits = cirq.GridQubit.rect(1, 8)\n",
+ "readout_operators = cirq.Z(cluster_state_bits[-1])\n",
+ "\n",
+ "# Build a sequential model enacting the logic in 1.3 of this notebook.\n",
+ "# Here you are making the static cluster state prep as a part of the AddCircuit and the\n",
+ "# \"quantum datapoints\" are coming in the form of excitation\n",
+ "excitation_input = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
+ "cluster_state = tfq.layers.AddCircuit()(\n",
+ " excitation_input, prepend=cluster_state_circuit(cluster_state_bits))\n",
+ "\n",
+ "quantum_model = tfq.layers.PQC(create_model_circuit(cluster_state_bits),\n",
+ " readout_operators)(cluster_state)\n",
+ "\n",
+ "qcnn_model = tf.keras.Model(inputs=[excitation_input], outputs=[quantum_model])\n",
+ "\n",
+ "# Show the keras plot of the model\n",
+ "tf.keras.utils.plot_model(qcnn_model,\n",
+ " show_shapes=True,\n",
+ " show_layer_names=False,\n",
+ " dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9jqTEe5VSbug"
+ },
+ "source": [
+ "### 1.7 Train the model\n",
+ "\n",
+ "Train the model over the full batch to simplify this example."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "_TFkAm1sQZEN",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# Generate some training data.\n",
+ "train_excitations, train_labels, test_excitations, test_labels = generate_data(\n",
+ " cluster_state_bits)\n",
+ "\n",
+ "\n",
+ "# Custom accuracy metric.\n",
+ "@tf.function\n",
+ "def custom_accuracy(y_true, y_pred):\n",
+ " y_true = tf.squeeze(y_true)\n",
+ " y_pred = tf.map_fn(lambda x: 1.0 if x >= 0 else -1.0, y_pred)\n",
+ " return tf.keras.backend.mean(tf.keras.backend.equal(y_true, y_pred))\n",
+ "\n",
+ "\n",
+ "qcnn_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
+ " loss=tf.losses.mse,\n",
+ " metrics=[custom_accuracy])\n",
+ "\n",
+ "history = qcnn_model.fit(x=train_excitations,\n",
+ " y=train_labels,\n",
+ " batch_size=16,\n",
+ " epochs=25,\n",
+ " verbose=1,\n",
+ " validation_data=(test_excitations, test_labels))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "2tiCJOb5Qzcr",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "plt.plot(history.history['loss'][1:], label='Training')\n",
+ "plt.plot(history.history['val_loss'][1:], label='Validation')\n",
+ "plt.title('Training a Quantum CNN to Detect Excited Cluster States')\n",
+ "plt.xlabel('Epochs')\n",
+ "plt.ylabel('Loss')\n",
+ "plt.legend()\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "GyrkcEReQ5Bc"
+ },
+ "source": [
+ "## 2. Hybrid models\n",
+ "\n",
+ "You don't have to go from eight qubits to one qubit using quantum convolution—you could have done one or two rounds of quantum convolution and fed the results into a classical neural network. This section explores quantum-classical hybrid models."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "A2tOK22t7Kjm"
+ },
+ "source": [
+ "### 2.1 Hybrid model with a single quantum filter\n",
+ "\n",
+ "Apply one layer of quantum convolution, reading out $\\langle \\hat{Z}_n \\rangle$ on all bits, followed by a densely-connected neural network.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "lKXuOApgWYFa"
+ },
+ "source": [
+ "#### 2.1.1 Model definition"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Ut-U1hBkQ8Fs",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# 1-local operators to read out\n",
+ "readouts = [cirq.Z(bit) for bit in cluster_state_bits[4:]]\n",
+ "\n",
+ "\n",
+ "def multi_readout_model_circuit(qubits):\n",
+ " \"\"\"Make a model circuit with less quantum pool and conv operations.\"\"\"\n",
+ " model_circuit = cirq.Circuit()\n",
+ " symbols = sympy.symbols('qconv0:21')\n",
+ " model_circuit += quantum_conv_circuit(qubits, symbols[0:15])\n",
+ " model_circuit += quantum_pool_circuit(qubits[:4], qubits[4:],\n",
+ " symbols[15:21])\n",
+ " return model_circuit\n",
+ "\n",
+ "\n",
+ "# Build a model enacting the logic in 2.1 of this notebook.\n",
+ "excitation_input_dual = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
+ "\n",
+ "cluster_state_dual = tfq.layers.AddCircuit()(\n",
+ " excitation_input_dual, prepend=cluster_state_circuit(cluster_state_bits))\n",
+ "\n",
+ "quantum_model_dual = tfq.layers.PQC(\n",
+ " multi_readout_model_circuit(cluster_state_bits),\n",
+ " readouts)(cluster_state_dual)\n",
+ "\n",
+ "d1_dual = tf.keras.layers.Dense(8)(quantum_model_dual)\n",
+ "\n",
+ "d2_dual = tf.keras.layers.Dense(1)(d1_dual)\n",
+ "\n",
+ "hybrid_model = tf.keras.Model(inputs=[excitation_input_dual], outputs=[d2_dual])\n",
+ "\n",
+ "# Display the model architecture\n",
+ "tf.keras.utils.plot_model(hybrid_model,\n",
+ " show_shapes=True,\n",
+ " show_layer_names=False,\n",
+ " dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "qDqoLZJuWcgH"
+ },
+ "source": [
+ "#### 2.1.2 Train the model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "EyYw9kYIRCE7",
+ "vscode": {
+ "languageId": "python"
}
- ],
- "metadata": {
- "colab": {
- "collapsed_sections": [],
- "name": "qcnn.ipynb",
- "private_outputs": true,
- "provenance": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
+ },
+ "outputs": [],
+ "source": [
+ "hybrid_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
+ " loss=tf.losses.mse,\n",
+ " metrics=[custom_accuracy])\n",
+ "\n",
+ "hybrid_history = hybrid_model.fit(x=train_excitations,\n",
+ " y=train_labels,\n",
+ " batch_size=16,\n",
+ " epochs=25,\n",
+ " verbose=1,\n",
+ " validation_data=(test_excitations,\n",
+ " test_labels))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "yL3jhGiBRJHt",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "plt.plot(history.history['val_custom_accuracy'], label='QCNN')\n",
+ "plt.plot(hybrid_history.history['val_custom_accuracy'], label='Hybrid CNN')\n",
+ "plt.title('Quantum vs Hybrid CNN performance')\n",
+ "plt.xlabel('Epochs')\n",
+ "plt.legend()\n",
+ "plt.ylabel('Validation Accuracy')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "NCNiNvheRNzq"
+ },
+ "source": [
+ "As you can see, with very modest classical assistance, the hybrid model will usually converge faster than the purely quantum version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "nVUtWLZnRRDE"
+ },
+ "source": [
+ "### 2.2 Hybrid convolution with multiple quantum filters\n",
+ "\n",
+ "Now let's try an architecture that uses multiple quantum convolutions and a classical neural network to combine them.\n",
+ "\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "Ldo_m5P3YBV7"
+ },
+ "source": [
+ "#### 2.2.1 Model definition"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "W3TkNVm9RTBj",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "excitation_input_multi = tf.keras.Input(shape=(), dtype=tf.dtypes.string)\n",
+ "\n",
+ "cluster_state_multi = tfq.layers.AddCircuit()(\n",
+ " excitation_input_multi, prepend=cluster_state_circuit(cluster_state_bits))\n",
+ "\n",
+ "# apply 3 different filters and measure expectation values\n",
+ "\n",
+ "quantum_model_multi1 = tfq.layers.PQC(\n",
+ " multi_readout_model_circuit(cluster_state_bits),\n",
+ " readouts)(cluster_state_multi)\n",
+ "\n",
+ "quantum_model_multi2 = tfq.layers.PQC(\n",
+ " multi_readout_model_circuit(cluster_state_bits),\n",
+ " readouts)(cluster_state_multi)\n",
+ "\n",
+ "quantum_model_multi3 = tfq.layers.PQC(\n",
+ " multi_readout_model_circuit(cluster_state_bits),\n",
+ " readouts)(cluster_state_multi)\n",
+ "\n",
+ "# concatenate outputs and feed into a small classical NN\n",
+ "concat_out = tf.keras.layers.concatenate(\n",
+ " [quantum_model_multi1, quantum_model_multi2, quantum_model_multi3])\n",
+ "\n",
+ "dense_1 = tf.keras.layers.Dense(8)(concat_out)\n",
+ "\n",
+ "dense_2 = tf.keras.layers.Dense(1)(dense_1)\n",
+ "\n",
+ "multi_qconv_model = tf.keras.Model(inputs=[excitation_input_multi],\n",
+ " outputs=[dense_2])\n",
+ "\n",
+ "# Display the model architecture\n",
+ "tf.keras.utils.plot_model(multi_qconv_model,\n",
+ " show_shapes=True,\n",
+ " show_layer_names=True,\n",
+ " dpi=70)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "2eNhDWwKY9N4"
+ },
+ "source": [
+ "#### 2.2.2 Train the model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "suRvxcAKRZK6",
+ "vscode": {
+ "languageId": "python"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "multi_qconv_model.compile(\n",
+ " optimizer=tf.keras.optimizers.Adam(learning_rate=0.02),\n",
+ " loss=tf.losses.mse,\n",
+ " metrics=[custom_accuracy])\n",
+ "\n",
+ "multi_qconv_history = multi_qconv_model.fit(x=train_excitations,\n",
+ " y=train_labels,\n",
+ " batch_size=16,\n",
+ " epochs=25,\n",
+ " verbose=1,\n",
+ " validation_data=(test_excitations,\n",
+ " test_labels))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "-6NR7yAQRmOU",
+ "vscode": {
+ "languageId": "python"
}
+ },
+ "outputs": [],
+ "source": [
+ "plt.plot(history.history['val_custom_accuracy'][:25], label='QCNN')\n",
+ "plt.plot(hybrid_history.history['val_custom_accuracy'][:25], label='Hybrid CNN')\n",
+ "plt.plot(multi_qconv_history.history['val_custom_accuracy'][:25],\n",
+ " label='Hybrid CNN \\n Multiple Quantum Filters')\n",
+ "plt.title('Quantum vs Hybrid CNN performance')\n",
+ "plt.xlabel('Epochs')\n",
+ "plt.legend()\n",
+ "plt.ylabel('Validation Accuracy')\n",
+ "plt.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "name": "qcnn.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true
},
- "nbformat": 4,
- "nbformat_minor": 0
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/docs/tutorials/quantum_data.ipynb b/docs/tutorials/quantum_data.ipynb
index 8877807dc..727f9384e 100644
--- a/docs/tutorials/quantum_data.ipynb
+++ b/docs/tutorials/quantum_data.ipynb
@@ -126,6 +126,7 @@
"source": [
"# Update package resources to account for version changes.\n",
"import importlib, pkg_resources\n",
+ "\n",
"importlib.reload(pkg_resources)"
]
},
@@ -193,10 +194,11 @@
}
],
"source": [
- "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()\n",
+ "(x_train, y_train), (x_test,\n",
+ " y_test) = tf.keras.datasets.fashion_mnist.load_data()\n",
"\n",
"# Rescale the images from [0,255] to the [0.0,1.0] range.\n",
- "x_train, x_test = x_train/255.0, x_test/255.0\n",
+ "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
"\n",
"print(\"Number of original training examples:\", len(x_train))\n",
"print(\"Number of original test examples:\", len(x_test))"
@@ -223,7 +225,7 @@
" keep = (y == 0) | (y == 3)\n",
" x, y = x[keep], y[keep]\n",
" y = y == 0\n",
- " return x,y"
+ " return x, y"
]
},
{
@@ -323,24 +325,24 @@
"outputs": [],
"source": [
"def truncate_x(x_train, x_test, n_components=10):\n",
- " \"\"\"Perform PCA on image dataset keeping the top `n_components` components.\"\"\"\n",
- " n_points_train = tf.gather(tf.shape(x_train), 0)\n",
- " n_points_test = tf.gather(tf.shape(x_test), 0)\n",
+ " \"\"\"Perform PCA on image dataset keeping the top `n_components` components.\"\"\"\n",
+ " n_points_train = tf.gather(tf.shape(x_train), 0)\n",
+ " n_points_test = tf.gather(tf.shape(x_test), 0)\n",
"\n",
- " # Flatten to 1D\n",
- " x_train = tf.reshape(x_train, [n_points_train, -1])\n",
- " x_test = tf.reshape(x_test, [n_points_test, -1])\n",
+ " # Flatten to 1D\n",
+ " x_train = tf.reshape(x_train, [n_points_train, -1])\n",
+ " x_test = tf.reshape(x_test, [n_points_test, -1])\n",
"\n",
- " # Normalize.\n",
- " feature_mean = tf.reduce_mean(x_train, axis=0)\n",
- " x_train_normalized = x_train - feature_mean\n",
- " x_test_normalized = x_test - feature_mean\n",
+ " # Normalize.\n",
+ " feature_mean = tf.reduce_mean(x_train, axis=0)\n",
+ " x_train_normalized = x_train - feature_mean\n",
+ " x_test_normalized = x_test - feature_mean\n",
"\n",
- " # Truncate.\n",
- " e_values, e_vectors = tf.linalg.eigh(\n",
- " tf.einsum('ji,jk->ik', x_train_normalized, x_train_normalized))\n",
- " return tf.einsum('ij,jk->ik', x_train_normalized, e_vectors[:,-n_components:]), \\\n",
- " tf.einsum('ij,jk->ik', x_test_normalized, e_vectors[:, -n_components:])"
+ " # Truncate.\n",
+ " e_values, e_vectors = tf.linalg.eigh(\n",
+ " tf.einsum('ji,jk->ik', x_train_normalized, x_train_normalized))\n",
+ " return tf.einsum('ij,jk->ik', x_train_normalized, e_vectors[:,-n_components:]), \\\n",
+ " tf.einsum('ij,jk->ik', x_test_normalized, e_vectors[:, -n_components:])"
]
},
{
@@ -452,13 +454,13 @@
"outputs": [],
"source": [
"def single_qubit_wall(qubits, rotations):\n",
- " \"\"\"Prepare a single qubit X,Y,Z rotation wall on `qubits`.\"\"\"\n",
- " wall_circuit = cirq.Circuit()\n",
- " for i, qubit in enumerate(qubits):\n",
- " for j, gate in enumerate([cirq.X, cirq.Y, cirq.Z]):\n",
- " wall_circuit.append(gate(qubit) ** rotations[i][j])\n",
+ " \"\"\"Prepare a single qubit X,Y,Z rotation wall on `qubits`.\"\"\"\n",
+ " wall_circuit = cirq.Circuit()\n",
+ " for i, qubit in enumerate(qubits):\n",
+ " for j, gate in enumerate([cirq.X, cirq.Y, cirq.Z]):\n",
+ " wall_circuit.append(gate(qubit)**rotations[i][j])\n",
"\n",
- " return wall_circuit"
+ " return wall_circuit"
]
},
{
@@ -484,7 +486,9 @@
"outputs": [
{
"data": {
- "image/svg+xml": "(0, 0): (0, 1): (0, 2): (0, 3): X^0.192 X^(11/14) X^0.276 X^0.876 Y^0.622 Y^0.78 Y^0.802 Y^(5/14) Z^(7/16) Z^(3/11) Z^0.958 Z^0.501 ",
+ "image/svg+xml": [
+ "(0, 0): (0, 1): (0, 2): (0, 3): X^0.192 X^(11/14) X^0.276 X^0.876 Y^0.622 Y^0.78 Y^0.802 Y^(5/14) Z^(7/16) Z^(3/11) Z^0.958 Z^0.501 "
+ ],
"text/plain": [
""
]
@@ -495,8 +499,9 @@
}
],
"source": [
- "SVGCircuit(single_qubit_wall(\n",
- " cirq.GridQubit.rect(1,4), np.random.uniform(size=(4, 3))))"
+ "SVGCircuit(\n",
+ " single_qubit_wall(cirq.GridQubit.rect(1, 4),\n",
+ " np.random.uniform(size=(4, 3))))"
]
},
{
@@ -517,14 +522,14 @@
"outputs": [],
"source": [
"def v_theta(qubits):\n",
- " \"\"\"Prepares a circuit that generates V(\\theta).\"\"\"\n",
- " ref_paulis = [\n",
- " cirq.X(q0) * cirq.X(q1) + \\\n",
- " cirq.Y(q0) * cirq.Y(q1) + \\\n",
- " cirq.Z(q0) * cirq.Z(q1) for q0, q1 in zip(qubits, qubits[1:])\n",
- " ]\n",
- " exp_symbols = list(sympy.symbols('ref_0:'+str(len(ref_paulis))))\n",
- " return tfq.util.exponential(ref_paulis, exp_symbols), exp_symbols"
+ " \"\"\"Prepares a circuit that generates V(\\theta).\"\"\"\n",
+ " ref_paulis = [\n",
+ " cirq.X(q0) * cirq.X(q1) + \\\n",
+ " cirq.Y(q0) * cirq.Y(q1) + \\\n",
+ " cirq.Z(q0) * cirq.Z(q1) for q0, q1 in zip(qubits, qubits[1:])\n",
+ " ]\n",
+ " exp_symbols = list(sympy.symbols('ref_0:' + str(len(ref_paulis))))\n",
+ " return tfq.util.exponential(ref_paulis, exp_symbols), exp_symbols"
]
},
{
@@ -557,7 +562,9 @@
},
{
"data": {
- "image/svg+xml": "(0, 0): (0, 1): H H X Rz(2.0*ref_0) X H H Rx(0.5π) Rx(0.5π) X Rz(2.0*ref_0) X Rx(-0.5π) Rx(-0.5π) X Rz(2.0*ref_0) X ",
+ "image/svg+xml": [
+ "(0, 0): (0, 1): H H X Rz(2.0*ref_0) X H H Rx(0.5π) Rx(0.5π) X Rz(2.0*ref_0) X Rx(-0.5π) Rx(-0.5π) X Rz(2.0*ref_0) X "
+ ],
"text/plain": [
""
]
@@ -591,28 +598,27 @@
"outputs": [],
"source": [
"def prepare_pqk_circuits(qubits, classical_source, n_trotter=10):\n",
- " \"\"\"Prepare the pqk feature circuits around a dataset.\"\"\"\n",
- " n_qubits = len(qubits)\n",
- " n_points = len(classical_source)\n",
+ " \"\"\"Prepare the pqk feature circuits around a dataset.\"\"\"\n",
+ " n_qubits = len(qubits)\n",
+ " n_points = len(classical_source)\n",
+ "\n",
+ " # Prepare random single qubit rotation wall.\n",
+ " random_rots = np.random.uniform(-2, 2, size=(n_qubits, 3))\n",
+ " initial_U = single_qubit_wall(qubits, random_rots)\n",
"\n",
- " # Prepare random single qubit rotation wall.\n",
- " random_rots = np.random.uniform(-2, 2, size=(n_qubits, 3))\n",
- " initial_U = single_qubit_wall(qubits, random_rots)\n",
+ " # Prepare parametrized V\n",
+ " V_circuit, symbols = v_theta(qubits)\n",
+ " exp_circuit = cirq.Circuit(V_circuit for t in range(n_trotter))\n",
"\n",
- " # Prepare parametrized V\n",
- " V_circuit, symbols = v_theta(qubits)\n",
- " exp_circuit = cirq.Circuit(V_circuit for t in range(n_trotter))\n",
- " \n",
- " # Convert to `tf.Tensor`\n",
- " initial_U_tensor = tfq.convert_to_tensor([initial_U])\n",
- " initial_U_splat = tf.tile(initial_U_tensor, [n_points])\n",
+ " # Convert to `tf.Tensor`\n",
+ " initial_U_tensor = tfq.convert_to_tensor([initial_U])\n",
+ " initial_U_splat = tf.tile(initial_U_tensor, [n_points])\n",
"\n",
- " full_circuits = tfq.layers.AddCircuit()(\n",
- " initial_U_splat, append=exp_circuit)\n",
- " # Replace placeholders in circuits with values from `classical_source`.\n",
- " return tfq.resolve_parameters(\n",
- " full_circuits, tf.convert_to_tensor([str(x) for x in symbols]),\n",
- " tf.convert_to_tensor(classical_source*(n_qubits/3)/n_trotter))"
+ " full_circuits = tfq.layers.AddCircuit()(initial_U_splat, append=exp_circuit)\n",
+ " # Replace placeholders in circuits with values from `classical_source`.\n",
+ " return tfq.resolve_parameters(\n",
+ " full_circuits, tf.convert_to_tensor([str(x) for x in symbols]),\n",
+ " tf.convert_to_tensor(classical_source * (n_qubits / 3) / n_trotter))"
]
},
{
@@ -655,14 +661,14 @@
"outputs": [],
"source": [
"def get_pqk_features(qubits, data_batch):\n",
- " \"\"\"Get PQK features based on above construction.\"\"\"\n",
- " ops = [[cirq.X(q), cirq.Y(q), cirq.Z(q)] for q in qubits]\n",
- " ops_tensor = tf.expand_dims(tf.reshape(tfq.convert_to_tensor(ops), -1), 0)\n",
- " batch_dim = tf.gather(tf.shape(data_batch), 0)\n",
- " ops_splat = tf.tile(ops_tensor, [batch_dim, 1])\n",
- " exp_vals = tfq.layers.Expectation()(data_batch, operators=ops_splat)\n",
- " rdm = tf.reshape(exp_vals, [batch_dim, len(qubits), -1])\n",
- " return rdm"
+ " \"\"\"Get PQK features based on above construction.\"\"\"\n",
+ " ops = [[cirq.X(q), cirq.Y(q), cirq.Z(q)] for q in qubits]\n",
+ " ops_tensor = tf.expand_dims(tf.reshape(tfq.convert_to_tensor(ops), -1), 0)\n",
+ " batch_dim = tf.gather(tf.shape(data_batch), 0)\n",
+ " ops_splat = tf.tile(ops_tensor, [batch_dim, 1])\n",
+ " exp_vals = tfq.layers.Expectation()(data_batch, operators=ops_splat)\n",
+ " rdm = tf.reshape(exp_vals, [batch_dim, len(qubits), -1])\n",
+ " return rdm"
]
},
{
@@ -720,17 +726,18 @@
"outputs": [],
"source": [
"def compute_kernel_matrix(vecs, gamma):\n",
- " \"\"\"Computes d[i][j] = e^ -gamma * (vecs[i] - vecs[j]) ** 2 \"\"\"\n",
- " scaled_gamma = gamma / (\n",
- " tf.cast(tf.gather(tf.shape(vecs), 1), tf.float32) * tf.math.reduce_std(vecs))\n",
- " return scaled_gamma * tf.einsum('ijk->ij',(vecs[:,None,:] - vecs) ** 2)\n",
+ " \"\"\"Computes d[i][j] = e^ -gamma * (vecs[i] - vecs[j]) ** 2 \"\"\"\n",
+ " scaled_gamma = gamma / (tf.cast(tf.gather(tf.shape(vecs), 1), tf.float32) *\n",
+ " tf.math.reduce_std(vecs))\n",
+ " return scaled_gamma * tf.einsum('ijk->ij', (vecs[:, None, :] - vecs)**2)\n",
+ "\n",
"\n",
"def get_spectrum(datapoints, gamma=1.0):\n",
- " \"\"\"Compute the eigenvalues and eigenvectors of the kernel of datapoints.\"\"\"\n",
- " KC_qs = compute_kernel_matrix(datapoints, gamma)\n",
- " S, V = tf.linalg.eigh(KC_qs)\n",
- " S = tf.math.abs(S)\n",
- " return S, V"
+ " \"\"\"Compute the eigenvalues and eigenvectors of the kernel of datapoints.\"\"\"\n",
+ " KC_qs = compute_kernel_matrix(datapoints, gamma)\n",
+ " S, V = tf.linalg.eigh(KC_qs)\n",
+ " S = tf.math.abs(S)\n",
+ " return S, V"
]
},
{
@@ -781,10 +788,12 @@
],
"source": [
"S_pqk, V_pqk = get_spectrum(\n",
- " tf.reshape(tf.concat([x_train_pqk, x_test_pqk], 0), [-1, len(qubits) * 3]))\n",
+ " tf.reshape(tf.concat([x_train_pqk, x_test_pqk], 0),\n",
+ " [-1, len(qubits) * 3]))\n",
"\n",
- "S_original, V_original = get_spectrum(\n",
- " tf.cast(tf.concat([x_train, x_test], 0), tf.float32), gamma=0.005)\n",
+ "S_original, V_original = get_spectrum(tf.cast(tf.concat([x_train, x_test], 0),\n",
+ " tf.float32),\n",
+ " gamma=0.005)\n",
"\n",
"print('Eigenvectors of pqk kernel matrix:', V_pqk)\n",
"print('Eigenvectors of original kernel matrix:', V_original)"
@@ -819,21 +828,22 @@
"outputs": [],
"source": [
"def get_stilted_dataset(S, V, S_2, V_2, lambdav=1.1):\n",
- " \"\"\"Prepare new labels that maximize geometric distance between kernels.\"\"\"\n",
- " S_diag = tf.linalg.diag(S ** 0.5)\n",
- " S_2_diag = tf.linalg.diag(S_2 / (S_2 + lambdav) ** 2)\n",
- " scaling = S_diag @ tf.transpose(V) @ \\\n",
- " V_2 @ S_2_diag @ tf.transpose(V_2) @ \\\n",
- " V @ S_diag\n",
+ " \"\"\"Prepare new labels that maximize geometric distance between kernels.\"\"\"\n",
+ " S_diag = tf.linalg.diag(S**0.5)\n",
+ " S_2_diag = tf.linalg.diag(S_2 / (S_2 + lambdav)**2)\n",
+ " scaling = S_diag @ tf.transpose(V) @ \\\n",
+ " V_2 @ S_2_diag @ tf.transpose(V_2) @ \\\n",
+ " V @ S_diag\n",
"\n",
- " # Generate new lables using the largest eigenvector.\n",
- " _, vecs = tf.linalg.eig(scaling)\n",
- " new_labels = tf.math.real(\n",
- " tf.einsum('ij,j->i', tf.cast(V @ S_diag, tf.complex64), vecs[-1])).numpy()\n",
- " # Create new labels and add some small amount of noise.\n",
- " final_y = new_labels > np.median(new_labels)\n",
- " noisy_y = (final_y ^ (np.random.uniform(size=final_y.shape) > 0.95))\n",
- " return noisy_y"
+ " # Generate new lables using the largest eigenvector.\n",
+ " _, vecs = tf.linalg.eig(scaling)\n",
+ " new_labels = tf.math.real(\n",
+ " tf.einsum('ij,j->i', tf.cast(V @ S_diag, tf.complex64),\n",
+ " vecs[-1])).numpy()\n",
+ " # Create new labels and add some small amount of noise.\n",
+ " final_y = new_labels > np.median(new_labels)\n",
+ " noisy_y = (final_y ^ (np.random.uniform(size=final_y.shape) > 0.95))\n",
+ " return noisy_y"
]
},
{
@@ -904,15 +914,21 @@
"#docs_infra: no_execute\n",
"def create_pqk_model():\n",
" model = tf.keras.Sequential()\n",
- " model.add(tf.keras.layers.Dense(32, activation='sigmoid', input_shape=[len(qubits) * 3,]))\n",
+ " model.add(\n",
+ " tf.keras.layers.Dense(32,\n",
+ " activation='sigmoid',\n",
+ " input_shape=[\n",
+ " len(qubits) * 3,\n",
+ " ]))\n",
" model.add(tf.keras.layers.Dense(16, activation='sigmoid'))\n",
" model.add(tf.keras.layers.Dense(1))\n",
" return model\n",
"\n",
+ "\n",
"pqk_model = create_pqk_model()\n",
"pqk_model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
- " optimizer=tf.keras.optimizers.Adam(learning_rate=0.003),\n",
- " metrics=['accuracy'])\n",
+ " optimizer=tf.keras.optimizers.Adam(learning_rate=0.003),\n",
+ " metrics=['accuracy'])\n",
"\n",
"pqk_model.summary()"
]
@@ -927,11 +943,13 @@
"source": [
"#docs_infra: no_execute\n",
"pqk_history = pqk_model.fit(tf.reshape(x_train_pqk, [N_TRAIN, -1]),\n",
- " y_train_new,\n",
- " batch_size=32,\n",
- " epochs=1000,\n",
- " verbose=0,\n",
- " validation_data=(tf.reshape(x_test_pqk, [N_TEST, -1]), y_test_new))"
+ " y_train_new,\n",
+ " batch_size=32,\n",
+ " epochs=1000,\n",
+ " verbose=0,\n",
+ " validation_data=(tf.reshape(x_test_pqk,\n",
+ " [N_TEST, -1]),\n",
+ " y_test_new))"
]
},
{
@@ -980,11 +998,17 @@
"#docs_infra: no_execute\n",
"def create_fair_classical_model():\n",
" model = tf.keras.Sequential()\n",
- " model.add(tf.keras.layers.Dense(32, activation='sigmoid', input_shape=[DATASET_DIM,]))\n",
+ " model.add(\n",
+ " tf.keras.layers.Dense(32,\n",
+ " activation='sigmoid',\n",
+ " input_shape=[\n",
+ " DATASET_DIM,\n",
+ " ]))\n",
" model.add(tf.keras.layers.Dense(16, activation='sigmoid'))\n",
" model.add(tf.keras.layers.Dense(1))\n",
" return model\n",
"\n",
+ "\n",
"model = create_fair_classical_model()\n",
"model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),\n",
" optimizer=tf.keras.optimizers.Adam(learning_rate=0.03),\n",
@@ -1003,11 +1027,11 @@
"source": [
"#docs_infra: no_execute\n",
"classical_history = model.fit(x_train,\n",
- " y_train_new,\n",
- " batch_size=32,\n",
- " epochs=1000,\n",
- " verbose=0,\n",
- " validation_data=(x_test, y_test_new))"
+ " y_train_new,\n",
+ " batch_size=32,\n",
+ " epochs=1000,\n",
+ " verbose=0,\n",
+ " validation_data=(x_test, y_test_new))"
]
},
{
@@ -1056,9 +1080,10 @@
],
"source": [
"#docs_infra: no_execute\n",
- "plt.figure(figsize=(10,5))\n",
+ "plt.figure(figsize=(10, 5))\n",
"plt.plot(classical_history.history['accuracy'], label='accuracy_classical')\n",
- "plt.plot(classical_history.history['val_accuracy'], label='val_accuracy_classical')\n",
+ "plt.plot(classical_history.history['val_accuracy'],\n",
+ " label='val_accuracy_classical')\n",
"plt.plot(pqk_history.history['accuracy'], label='accuracy_quantum')\n",
"plt.plot(pqk_history.history['val_accuracy'], label='val_accuracy_quantum')\n",
"plt.xlabel('Epoch')\n",
diff --git a/docs/tutorials/quantum_reinforcement_learning.ipynb b/docs/tutorials/quantum_reinforcement_learning.ipynb
index bc7644883..caf09c8fe 100644
--- a/docs/tutorials/quantum_reinforcement_learning.ipynb
+++ b/docs/tutorials/quantum_reinforcement_learning.ipynb
@@ -187,6 +187,7 @@
"source": [
"# Update package resources to account for version changes.\n",
"import importlib, pkg_resources\n",
+ "\n",
"importlib.reload(pkg_resources)"
]
},
@@ -207,6 +208,7 @@
"from collections import deque, defaultdict\n",
"import matplotlib.pyplot as plt\n",
"from cirq.contrib.svg import SVGCircuit\n",
+ "\n",
"tf.get_logger().setLevel('ERROR')"
]
},
@@ -272,9 +274,12 @@
" Returns Cirq gates that apply a rotation of the bloch sphere about the X,\n",
" Y and Z axis, specified by the values in `symbols`.\n",
" \"\"\"\n",
- " return [cirq.rx(symbols[0])(qubit),\n",
- " cirq.ry(symbols[1])(qubit),\n",
- " cirq.rz(symbols[2])(qubit)]\n",
+ " return [\n",
+ " cirq.rx(symbols[0])(qubit),\n",
+ " cirq.ry(symbols[1])(qubit),\n",
+ " cirq.rz(symbols[2])(qubit)\n",
+ " ]\n",
+ "\n",
"\n",
"def entangling_layer(qubits):\n",
" \"\"\"\n",
@@ -306,27 +311,31 @@
" \"\"\"Prepares a data re-uploading circuit on `qubits` with `n_layers` layers.\"\"\"\n",
" # Number of qubits\n",
" n_qubits = len(qubits)\n",
- " \n",
+ "\n",
" # Sympy symbols for variational angles\n",
" params = sympy.symbols(f'theta(0:{3*(n_layers+1)*n_qubits})')\n",
" params = np.asarray(params).reshape((n_layers + 1, n_qubits, 3))\n",
- " \n",
+ "\n",
" # Sympy symbols for encoding angles\n",
- " inputs = sympy.symbols(f'x(0:{n_layers})'+f'_(0:{n_qubits})')\n",
+ " inputs = sympy.symbols(f'x(0:{n_layers})' + f'_(0:{n_qubits})')\n",
" inputs = np.asarray(inputs).reshape((n_layers, n_qubits))\n",
- " \n",
+ "\n",
" # Define circuit\n",
" circuit = cirq.Circuit()\n",
" for l in range(n_layers):\n",
" # Variational layer\n",
- " circuit += cirq.Circuit(one_qubit_rotation(q, params[l, i]) for i, q in enumerate(qubits))\n",
+ " circuit += cirq.Circuit(\n",
+ " one_qubit_rotation(q, params[l, i]) for i, q in enumerate(qubits))\n",
" circuit += entangling_layer(qubits)\n",
" # Encoding layer\n",
- " circuit += cirq.Circuit(cirq.rx(inputs[l, i])(q) for i, q in enumerate(qubits))\n",
+ " circuit += cirq.Circuit(\n",
+ " cirq.rx(inputs[l, i])(q) for i, q in enumerate(qubits))\n",
"\n",
" # Last varitional layer\n",
- " circuit += cirq.Circuit(one_qubit_rotation(q, params[n_layers, i]) for i,q in enumerate(qubits))\n",
- " \n",
+ " circuit += cirq.Circuit(\n",
+ " one_qubit_rotation(q, params[n_layers, i])\n",
+ " for i, q in enumerate(qubits))\n",
+ "\n",
" return circuit, list(params.flat), list(inputs.flat)"
]
},
@@ -360,7 +369,9 @@
},
{
"data": {
- "image/svg+xml": "(0, 0): (0, 1): (0, 2): Rx(theta0) Rx(theta3) Rx(theta6) Ry(theta1) Ry(theta4) Ry(theta7) Rz(theta2) Rz(theta5) Rz(theta8) Rx(x0_0) Rx(x0_1) Rx(x0_2) Rx(theta9) Rx(theta12) Rx(theta15) Ry(theta10) Ry(theta13) Ry(theta16) Rz(theta11) Rz(theta14) Rz(theta17) ",
+ "image/svg+xml": [
+ "(0, 0): (0, 1): (0, 2): Rx(theta0) Rx(theta3) Rx(theta6) Ry(theta1) Ry(theta4) Ry(theta7) Rz(theta2) Rz(theta5) Rz(theta8) Rx(x0_0) Rx(x0_1) Rx(x0_2) Rx(theta9) Rx(theta12) Rx(theta15) Ry(theta10) Ry(theta13) Ry(theta16) Rz(theta11) Rz(theta14) Rz(theta17) "
+ ],
"text/plain": [
""
]
@@ -406,31 +417,38 @@
" by the ControlledPQC.\n",
" \"\"\"\n",
"\n",
- " def __init__(self, qubits, n_layers, observables, activation=\"linear\", name=\"re-uploading_PQC\"):\n",
+ " def __init__(self,\n",
+ " qubits,\n",
+ " n_layers,\n",
+ " observables,\n",
+ " activation=\"linear\",\n",
+ " name=\"re-uploading_PQC\"):\n",
" super(ReUploadingPQC, self).__init__(name=name)\n",
" self.n_layers = n_layers\n",
" self.n_qubits = len(qubits)\n",
"\n",
- " circuit, theta_symbols, input_symbols = generate_circuit(qubits, n_layers)\n",
+ " circuit, theta_symbols, input_symbols = generate_circuit(\n",
+ " qubits, n_layers)\n",
"\n",
" theta_init = tf.random_uniform_initializer(minval=0.0, maxval=np.pi)\n",
- " self.theta = tf.Variable(\n",
- " initial_value=theta_init(shape=(1, len(theta_symbols)), dtype=\"float32\"),\n",
- " trainable=True, name=\"thetas\"\n",
- " )\n",
- " \n",
+ " self.theta = tf.Variable(initial_value=theta_init(\n",
+ " shape=(1, len(theta_symbols)), dtype=\"float32\"),\n",
+ " trainable=True,\n",
+ " name=\"thetas\")\n",
+ "\n",
" lmbd_init = tf.ones(shape=(self.n_qubits * self.n_layers,))\n",
- " self.lmbd = tf.Variable(\n",
- " initial_value=lmbd_init, dtype=\"float32\", trainable=True, name=\"lambdas\"\n",
- " )\n",
- " \n",
+ " self.lmbd = tf.Variable(initial_value=lmbd_init,\n",
+ " dtype=\"float32\",\n",
+ " trainable=True,\n",
+ " name=\"lambdas\")\n",
+ "\n",
" # Define explicit symbol order.\n",
" symbols = [str(symb) for symb in theta_symbols + input_symbols]\n",
" self.indices = tf.constant([symbols.index(a) for a in sorted(symbols)])\n",
- " \n",
+ "\n",
" self.activation = activation\n",
" self.empty_circuit = tfq.convert_to_tensor([cirq.Circuit()])\n",
- " self.computation_layer = tfq.layers.ControlledPQC(circuit, observables) \n",
+ " self.computation_layer = tfq.layers.ControlledPQC(circuit, observables)\n",
"\n",
" def call(self, inputs):\n",
" # inputs[0] = encoding data for the state.\n",
@@ -439,11 +457,12 @@
" tiled_up_thetas = tf.tile(self.theta, multiples=[batch_dim, 1])\n",
" tiled_up_inputs = tf.tile(inputs[0], multiples=[1, self.n_layers])\n",
" scaled_inputs = tf.einsum(\"i,ji->ji\", self.lmbd, tiled_up_inputs)\n",
- " squashed_inputs = tf.keras.layers.Activation(self.activation)(scaled_inputs)\n",
+ " squashed_inputs = tf.keras.layers.Activation(\n",
+ " self.activation)(scaled_inputs)\n",
"\n",
" joined_vars = tf.concat([tiled_up_thetas, squashed_inputs], axis=1)\n",
" joined_vars = tf.gather(joined_vars, self.indices, axis=1)\n",
- " \n",
+ "\n",
" return self.computation_layer([tiled_up_circuits, joined_vars])"
]
},
@@ -485,11 +504,15 @@
"outputs": [],
"source": [
"class Alternating(tf.keras.layers.Layer):\n",
+ "\n",
" def __init__(self, output_dim):\n",
" super(Alternating, self).__init__()\n",
- " self.w = tf.Variable(\n",
- " initial_value=tf.constant([[(-1.)**i for i in range(output_dim)]]), dtype=\"float32\",\n",
- " trainable=True, name=\"obs-weights\")\n",
+ " self.w = tf.Variable(initial_value=tf.constant([[\n",
+ " (-1.)**i for i in range(output_dim)\n",
+ " ]]),\n",
+ " dtype=\"float32\",\n",
+ " trainable=True,\n",
+ " name=\"obs-weights\")\n",
"\n",
" def call(self, inputs):\n",
" return tf.matmul(inputs, self.w)"
@@ -512,9 +535,9 @@
},
"outputs": [],
"source": [
- "n_qubits = 4 # Dimension of the state vectors in CartPole\n",
- "n_layers = 5 # Number of layers in the PQC\n",
- "n_actions = 2 # Number of actions in CartPole\n",
+ "n_qubits = 4 # Dimension of the state vectors in CartPole\n",
+ "n_layers = 5 # Number of layers in the PQC\n",
+ "n_actions = 2 # Number of actions in CartPole\n",
"\n",
"qubits = cirq.GridQubit.rect(1, n_qubits)"
]
@@ -537,7 +560,7 @@
"outputs": [],
"source": [
"ops = [cirq.Z(q) for q in qubits]\n",
- "observables = [reduce((lambda x, y: x * y), ops)] # Z_0*Z_1*Z_2*Z_3"
+ "observables = [reduce((lambda x, y: x * y), ops)] # Z_0*Z_1*Z_2*Z_3"
]
},
{
@@ -560,18 +583,23 @@
"def generate_model_policy(qubits, n_layers, n_actions, beta, observables):\n",
" \"\"\"Generates a Keras model for a data re-uploading PQC policy.\"\"\"\n",
"\n",
- " input_tensor = tf.keras.Input(shape=(len(qubits), ), dtype=tf.dtypes.float32, name='input')\n",
- " re_uploading_pqc = ReUploadingPQC(qubits, n_layers, observables)([input_tensor])\n",
+ " input_tensor = tf.keras.Input(shape=(len(qubits),),\n",
+ " dtype=tf.dtypes.float32,\n",
+ " name='input')\n",
+ " re_uploading_pqc = ReUploadingPQC(qubits, n_layers,\n",
+ " observables)([input_tensor])\n",
" process = tf.keras.Sequential([\n",
" Alternating(n_actions),\n",
" tf.keras.layers.Lambda(lambda x: x * beta),\n",
" tf.keras.layers.Softmax()\n",
- " ], name=\"observables-policy\")\n",
+ " ],\n",
+ " name=\"observables-policy\")\n",
" policy = process(re_uploading_pqc)\n",
" model = tf.keras.Model(inputs=[input_tensor], outputs=policy)\n",
"\n",
" return model\n",
"\n",
+ "\n",
"model = generate_model_policy(qubits, n_layers, n_actions, 1.0, observables)"
]
},
@@ -646,7 +674,9 @@
"\n",
" while not all(done):\n",
" unfinished_ids = [i for i in range(n_episodes) if not done[i]]\n",
- " normalized_states = [s/state_bounds for i, s in enumerate(states) if not done[i]]\n",
+ " normalized_states = [\n",
+ " s / state_bounds for i, s in enumerate(states) if not done[i]\n",
+ " ]\n",
"\n",
" for i, state in zip(unfinished_ids, normalized_states):\n",
" trajectories[i]['states'].append(state)\n",
@@ -695,7 +725,7 @@
" returns = np.array(returns)\n",
" returns = (returns - np.mean(returns)) / (np.std(returns) + 1e-8)\n",
" returns = returns.tolist()\n",
- " \n",
+ "\n",
" return returns"
]
},
@@ -777,7 +807,8 @@
" log_probs = tf.math.log(p_actions)\n",
" loss = tf.math.reduce_sum(-log_probs * returns) / batch_size\n",
" grads = tape.gradient(loss, model.trainable_variables)\n",
- " for optimizer, w in zip([optimizer_in, optimizer_var, optimizer_out], [w_in, w_var, w_out]):\n",
+ " for optimizer, w in zip([optimizer_in, optimizer_var, optimizer_out],\n",
+ " [w_in, w_var, w_out]):\n",
" optimizer.apply_gradients([(grads[w], model.trainable_variables[w])])"
]
},
@@ -855,29 +886,31 @@
"episode_reward_history = []\n",
"for batch in range(n_episodes // batch_size):\n",
" # Gather episodes\n",
- " episodes = gather_episodes(state_bounds, n_actions, model, batch_size, env_name)\n",
- " \n",
+ " episodes = gather_episodes(state_bounds, n_actions, model, batch_size,\n",
+ " env_name)\n",
+ "\n",
" # Group states, actions and returns in numpy arrays\n",
" states = np.concatenate([ep['states'] for ep in episodes])\n",
" actions = np.concatenate([ep['actions'] for ep in episodes])\n",
" rewards = [ep['rewards'] for ep in episodes]\n",
- " returns = np.concatenate([compute_returns(ep_rwds, gamma) for ep_rwds in rewards])\n",
+ " returns = np.concatenate(\n",
+ " [compute_returns(ep_rwds, gamma) for ep_rwds in rewards])\n",
" returns = np.array(returns, dtype=np.float32)\n",
"\n",
" id_action_pairs = np.array([[i, a] for i, a in enumerate(actions)])\n",
- " \n",
+ "\n",
" # Update model parameters.\n",
" reinforce_update(states, id_action_pairs, returns, model)\n",
"\n",
" # Store collected rewards\n",
" for ep_rwds in rewards:\n",
" episode_reward_history.append(np.sum(ep_rwds))\n",
- " \n",
+ "\n",
" avg_rewards = np.mean(episode_reward_history[-10:])\n",
"\n",
- " print('Finished episode', (batch + 1) * batch_size,\n",
- " 'Average rewards: ', avg_rewards)\n",
- " \n",
+ " print('Finished episode', (batch + 1) * batch_size, 'Average rewards: ',\n",
+ " avg_rewards)\n",
+ "\n",
" if avg_rewards >= 500.0:\n",
" break"
]
@@ -919,7 +952,7 @@
}
],
"source": [
- "plt.figure(figsize=(10,5))\n",
+ "plt.figure(figsize=(10, 5))\n",
"plt.plot(episode_reward_history)\n",
"plt.xlabel('Epsiode')\n",
"plt.ylabel('Collected rewards')\n",
@@ -1021,15 +1054,20 @@
"outputs": [],
"source": [
"class Rescaling(tf.keras.layers.Layer):\n",
+ "\n",
" def __init__(self, input_dim):\n",
" super(Rescaling, self).__init__()\n",
" self.input_dim = input_dim\n",
- " self.w = tf.Variable(\n",
- " initial_value=tf.ones(shape=(1,input_dim)), dtype=\"float32\",\n",
- " trainable=True, name=\"obs-weights\")\n",
+ " self.w = tf.Variable(initial_value=tf.ones(shape=(1, input_dim)),\n",
+ " dtype=\"float32\",\n",
+ " trainable=True,\n",
+ " name=\"obs-weights\")\n",
"\n",
" def call(self, inputs):\n",
- " return tf.math.multiply((inputs+1)/2, tf.repeat(self.w,repeats=tf.shape(inputs)[0],axis=0))"
+ " return tf.math.multiply((inputs + 1) / 2,\n",
+ " tf.repeat(self.w,\n",
+ " repeats=tf.shape(inputs)[0],\n",
+ " axis=0))"
]
},
{
@@ -1049,13 +1087,14 @@
},
"outputs": [],
"source": [
- "n_qubits = 4 # Dimension of the state vectors in CartPole\n",
- "n_layers = 5 # Number of layers in the PQC\n",
- "n_actions = 2 # Number of actions in CartPole\n",
+ "n_qubits = 4 # Dimension of the state vectors in CartPole\n",
+ "n_layers = 5 # Number of layers in the PQC\n",
+ "n_actions = 2 # Number of actions in CartPole\n",
"\n",
"qubits = cirq.GridQubit.rect(1, n_qubits)\n",
"ops = [cirq.Z(q) for q in qubits]\n",
- "observables = [ops[0]*ops[1], ops[2]*ops[3]] # Z_0*Z_1 for action 0 and Z_2*Z_3 for action 1"
+ "observables = [ops[0] * ops[1],\n",
+ " ops[2] * ops[3]] # Z_0*Z_1 for action 0 and Z_2*Z_3 for action 1"
]
},
{
@@ -1078,16 +1117,25 @@
"def generate_model_Qlearning(qubits, n_layers, n_actions, observables, target):\n",
" \"\"\"Generates a Keras model for a data re-uploading PQC Q-function approximator.\"\"\"\n",
"\n",
- " input_tensor = tf.keras.Input(shape=(len(qubits), ), dtype=tf.dtypes.float32, name='input')\n",
- " re_uploading_pqc = ReUploadingPQC(qubits, n_layers, observables, activation='tanh')([input_tensor])\n",
- " process = tf.keras.Sequential([Rescaling(len(observables))], name=target*\"Target\"+\"Q-values\")\n",
+ " input_tensor = tf.keras.Input(shape=(len(qubits),),\n",
+ " dtype=tf.dtypes.float32,\n",
+ " name='input')\n",
+ " re_uploading_pqc = ReUploadingPQC(qubits,\n",
+ " n_layers,\n",
+ " observables,\n",
+ " activation='tanh')([input_tensor])\n",
+ " process = tf.keras.Sequential([Rescaling(len(observables))],\n",
+ " name=target * \"Target\" + \"Q-values\")\n",
" Q_values = process(re_uploading_pqc)\n",
" model = tf.keras.Model(inputs=[input_tensor], outputs=Q_values)\n",
"\n",
" return model\n",
"\n",
- "model = generate_model_Qlearning(qubits, n_layers, n_actions, observables, False)\n",
- "model_target = generate_model_Qlearning(qubits, n_layers, n_actions, observables, True)\n",
+ "\n",
+ "model = generate_model_Qlearning(qubits, n_layers, n_actions, observables,\n",
+ " False)\n",
+ "model_target = generate_model_Qlearning(qubits, n_layers, n_actions,\n",
+ " observables, True)\n",
"\n",
"model_target.set_weights(model.get_weights())"
]
@@ -1186,7 +1234,7 @@
"source": [
"def interact_env(state, model, epsilon, n_actions, env):\n",
" # Preprocess state\n",
- " state_array = np.array(state) \n",
+ " state_array = np.array(state)\n",
" state = tf.convert_to_tensor([state_array])\n",
"\n",
" # Sample action\n",
@@ -1199,10 +1247,15 @@
"\n",
" # Apply sampled action in the environment, receive reward and next state\n",
" next_state, reward, done, _ = env.step(action)\n",
- " \n",
- " interaction = {'state': state_array, 'action': action, 'next_state': next_state.copy(),\n",
- " 'reward': reward, 'done':np.float32(done)}\n",
- " \n",
+ "\n",
+ " interaction = {\n",
+ " 'state': state_array,\n",
+ " 'action': action,\n",
+ " 'next_state': next_state.copy(),\n",
+ " 'reward': reward,\n",
+ " 'done': np.float32(done)\n",
+ " }\n",
+ "\n",
" return interaction"
]
},
@@ -1224,7 +1277,8 @@
"outputs": [],
"source": [
"@tf.function\n",
- "def Q_learning_update(states, actions, rewards, next_states, done, model, gamma, n_actions):\n",
+ "def Q_learning_update(states, actions, rewards, next_states, done, model, gamma,\n",
+ " n_actions):\n",
" states = tf.convert_to_tensor(states)\n",
" actions = tf.convert_to_tensor(actions)\n",
" rewards = tf.convert_to_tensor(rewards)\n",
@@ -1233,8 +1287,8 @@
"\n",
" # Compute their target q_values and the masks on sampled actions\n",
" future_rewards = model_target([next_states])\n",
- " target_q_values = rewards + (gamma * tf.reduce_max(future_rewards, axis=1)\n",
- " * (1.0 - done))\n",
+ " target_q_values = rewards + (gamma * tf.reduce_max(future_rewards, axis=1) *\n",
+ " (1.0 - done))\n",
" masks = tf.one_hot(actions, n_actions)\n",
"\n",
" # Train the model on the states and target Q-values\n",
@@ -1246,7 +1300,8 @@
"\n",
" # Backpropagation\n",
" grads = tape.gradient(loss, model.trainable_variables)\n",
- " for optimizer, w in zip([optimizer_in, optimizer_var, optimizer_out], [w_in, w_var, w_out]):\n",
+ " for optimizer, w in zip([optimizer_in, optimizer_var, optimizer_out],\n",
+ " [w_in, w_var, w_out]):\n",
" optimizer.apply_gradients([(grads[w], model.trainable_variables[w])])"
]
},
@@ -1271,15 +1326,15 @@
"n_episodes = 2000\n",
"\n",
"# Define replay memory\n",
- "max_memory_length = 10000 # Maximum replay length\n",
+ "max_memory_length = 10000 # Maximum replay length\n",
"replay_memory = deque(maxlen=max_memory_length)\n",
"\n",
"epsilon = 1.0 # Epsilon greedy parameter\n",
"epsilon_min = 0.01 # Minimum epsilon greedy parameter\n",
- "decay_epsilon = 0.99 # Decay rate of epsilon greedy parameter\n",
+ "decay_epsilon = 0.99 # Decay rate of epsilon greedy parameter\n",
"batch_size = 16\n",
- "steps_per_update = 10 # Train the model every x steps\n",
- "steps_per_target_update = 30 # Update the target model every x steps"
+ "steps_per_update = 10 # Train the model every x steps\n",
+ "steps_per_target_update = 30 # Update the target model every x steps"
]
},
{
@@ -1430,39 +1485,41 @@
],
"source": [
"env = gym.make(\"CartPole-v1\")\n",
- " \n",
+ "\n",
"episode_reward_history = []\n",
"step_count = 0\n",
"for episode in range(n_episodes):\n",
" episode_reward = 0\n",
" state = env.reset()\n",
- " \n",
+ "\n",
" while True:\n",
" # Interact with env\n",
" interaction = interact_env(state, model, epsilon, n_actions, env)\n",
- " \n",
+ "\n",
" # Store interaction in the replay memory\n",
" replay_memory.append(interaction)\n",
- " \n",
+ "\n",
" state = interaction['next_state']\n",
" episode_reward += interaction['reward']\n",
" step_count += 1\n",
- " \n",
+ "\n",
" # Update model\n",
" if step_count % steps_per_update == 0:\n",
" # Sample a batch of interactions and update Q_function\n",
" training_batch = np.random.choice(replay_memory, size=batch_size)\n",
- " Q_learning_update(np.asarray([x['state'] for x in training_batch]),\n",
- " np.asarray([x['action'] for x in training_batch]),\n",
- " np.asarray([x['reward'] for x in training_batch], dtype=np.float32),\n",
- " np.asarray([x['next_state'] for x in training_batch]),\n",
- " np.asarray([x['done'] for x in training_batch], dtype=np.float32),\n",
- " model, gamma, n_actions)\n",
- " \n",
+ " Q_learning_update(\n",
+ " np.asarray([x['state'] for x in training_batch]),\n",
+ " np.asarray([x['action'] for x in training_batch]),\n",
+ " np.asarray([x['reward'] for x in training_batch],\n",
+ " dtype=np.float32),\n",
+ " np.asarray([x['next_state'] for x in training_batch]),\n",
+ " np.asarray([x['done'] for x in training_batch],\n",
+ " dtype=np.float32), model, gamma, n_actions)\n",
+ "\n",
" # Update target model\n",
" if step_count % steps_per_target_update == 0:\n",
" model_target.set_weights(model.get_weights())\n",
- " \n",
+ "\n",
" # Check if the episode is finished\n",
" if interaction['done']:\n",
" break\n",
@@ -1470,10 +1527,10 @@
" # Decay epsilon\n",
" epsilon = max(epsilon * decay_epsilon, epsilon_min)\n",
" episode_reward_history.append(episode_reward)\n",
- " if (episode+1)%10 == 0:\n",
+ " if (episode + 1) % 10 == 0:\n",
" avg_rewards = np.mean(episode_reward_history[-10:])\n",
" print(\"Episode {}/{}, average last 10 rewards {}\".format(\n",
- " episode+1, n_episodes, avg_rewards))\n",
+ " episode + 1, n_episodes, avg_rewards))\n",
" if avg_rewards >= 500.0:\n",
" break"
]
@@ -1514,7 +1571,7 @@
}
],
"source": [
- "plt.figure(figsize=(10,5))\n",
+ "plt.figure(figsize=(10, 5))\n",
"plt.plot(episode_reward_history)\n",
"plt.xlabel('Epsiode')\n",
"plt.ylabel('Collected rewards')\n",
diff --git a/docs/tutorials/research_tools.ipynb b/docs/tutorials/research_tools.ipynb
index 8f091f14c..fcb39d3c4 100644
--- a/docs/tutorials/research_tools.ipynb
+++ b/docs/tutorials/research_tools.ipynb
@@ -86,22 +86,23 @@
"!pip install tensorflow==2.15.0 tensorflow-quantum==0.7.3 tensorboard_plugin_profile==2.15.0"
]
},
- {
- "cell_type": "code",
- "execution_count": 0,
- "metadata": {
- "colab": {},
- "colab_type": "code",
- "id": "4Ql5PW-ACO0J"
- },
- "outputs": [],
- "source": [
- "# Update package resources to account for version changes.\n",
- "import importlib, pkg_resources\n",
- "importlib.reload(pkg_resources)"
- ]
- },
- {
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "4Ql5PW-ACO0J"
+ },
+ "outputs": [],
+ "source": [
+ "# Update package resources to account for version changes.\n",
+ "import importlib, pkg_resources\n",
+ "\n",
+ "importlib.reload(pkg_resources)"
+ ]
+ },
+ {
"cell_type": "code",
"execution_count": null,
"metadata": {
@@ -159,9 +160,11 @@
" qubits, depth=2)\n",
" return random_circuit\n",
"\n",
+ "\n",
"def generate_data(circuit, n_samples):\n",
" \"\"\"Draw n_samples samples from circuit into a tf.Tensor.\"\"\"\n",
- " return tf.squeeze(tfq.layers.Sample()(circuit, repetitions=n_samples).to_tensor())"
+ " return tf.squeeze(tfq.layers.Sample()(circuit,\n",
+ " repetitions=n_samples).to_tensor())"
]
},
{
@@ -270,16 +273,20 @@
" \"\"\"Convert tensor of bitstrings to tensor of ints.\"\"\"\n",
" sigs = tf.constant([1 << i for i in range(N_QUBITS)], dtype=tf.int32)\n",
" rounded_bits = tf.clip_by_value(tf.math.round(\n",
- " tf.cast(bits, dtype=tf.dtypes.float32)), clip_value_min=0, clip_value_max=1)\n",
- " return tf.einsum('jk,k->j', tf.cast(rounded_bits, dtype=tf.dtypes.int32), sigs)\n",
+ " tf.cast(bits, dtype=tf.dtypes.float32)),\n",
+ " clip_value_min=0,\n",
+ " clip_value_max=1)\n",
+ " return tf.einsum('jk,k->j', tf.cast(rounded_bits, dtype=tf.dtypes.int32),\n",
+ " sigs)\n",
+ "\n",
"\n",
"@tf.function\n",
"def xeb_fid(bits):\n",
" \"\"\"Compute linear XEB fidelity of bitstrings.\"\"\"\n",
" final_probs = tf.squeeze(\n",
- " tf.abs(tfq.layers.State()(REFERENCE_CIRCUIT).to_tensor()) ** 2)\n",
+ " tf.abs(tfq.layers.State()(REFERENCE_CIRCUIT).to_tensor())**2)\n",
" nums = bits_to_ints(bits)\n",
- " return (2 ** N_QUBITS) * tf.reduce_mean(tf.gather(final_probs, nums)) - 1.0"
+ " return (2**N_QUBITS) * tf.reduce_mean(tf.gather(final_probs, nums)) - 1.0"
]
},
{
@@ -334,6 +341,8 @@
"outputs": [],
"source": [
"LATENT_DIM = 100\n",
+ "\n",
+ "\n",
"def make_generator_model():\n",
" \"\"\"Construct generator model.\"\"\"\n",
" model = tf.keras.Sequential()\n",
@@ -345,6 +354,7 @@
"\n",
" return model\n",
"\n",
+ "\n",
"def make_discriminator_model():\n",
" \"\"\"Constrcut discriminator model.\"\"\"\n",
" model = tf.keras.Sequential()\n",
@@ -387,6 +397,8 @@
"outputs": [],
"source": [
"cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n",
+ "\n",
+ "\n",
"def discriminator_loss(real_output, fake_output):\n",
" \"\"\"Compute discriminator loss.\"\"\"\n",
" real_loss = cross_entropy(tf.ones_like(real_output), real_output)\n",
@@ -394,10 +406,12 @@
" total_loss = real_loss + fake_loss\n",
" return total_loss\n",
"\n",
+ "\n",
"def generator_loss(fake_output):\n",
" \"\"\"Compute generator loss.\"\"\"\n",
" return cross_entropy(tf.ones_like(fake_output), fake_output)\n",
"\n",
+ "\n",
"generator_optimizer = tf.keras.optimizers.Adam(1e-4)\n",
"discriminator_optimizer = tf.keras.optimizers.Adam(1e-4)"
]
@@ -410,7 +424,8 @@
},
"outputs": [],
"source": [
- "BATCH_SIZE=256\n",
+ "BATCH_SIZE = 256\n",
+ "\n",
"\n",
"@tf.function\n",
"def train_step(images):\n",
@@ -425,8 +440,8 @@
" gen_loss = generator_loss(fake_output)\n",
" disc_loss = discriminator_loss(real_output, fake_output)\n",
"\n",
- " gradients_of_generator = gen_tape.gradient(\n",
- " gen_loss, generator.trainable_variables)\n",
+ " gradients_of_generator = gen_tape.gradient(gen_loss,\n",
+ " generator.trainable_variables)\n",
" gradients_of_discriminator = disc_tape.gradient(\n",
" disc_loss, discriminator.trainable_variables)\n",
"\n",
@@ -480,29 +495,37 @@
"def train(dataset, epochs, start_epoch=1):\n",
" \"\"\"Launch full training run for the given number of epochs.\"\"\"\n",
" # Log original training distribution.\n",
- " tf.summary.histogram('Training Distribution', data=bits_to_ints(dataset), step=0)\n",
+ " tf.summary.histogram('Training Distribution',\n",
+ " data=bits_to_ints(dataset),\n",
+ " step=0)\n",
"\n",
- " batched_data = tf.data.Dataset.from_tensor_slices(dataset).shuffle(N_SAMPLES).batch(512)\n",
+ " batched_data = tf.data.Dataset.from_tensor_slices(dataset).shuffle(\n",
+ " N_SAMPLES).batch(512)\n",
" t = time.time()\n",
" for epoch in range(start_epoch, start_epoch + epochs):\n",
" for i, image_batch in enumerate(batched_data):\n",
" # Log batch-wise loss.\n",
" gl, dl = train_step(image_batch)\n",
- " tf.summary.scalar(\n",
- " 'Generator loss', data=gl, step=epoch * len(batched_data) + i)\n",
- " tf.summary.scalar(\n",
- " 'Discriminator loss', data=dl, step=epoch * len(batched_data) + i)\n",
+ " tf.summary.scalar('Generator loss',\n",
+ " data=gl,\n",
+ " step=epoch * len(batched_data) + i)\n",
+ " tf.summary.scalar('Discriminator loss',\n",
+ " data=dl,\n",
+ " step=epoch * len(batched_data) + i)\n",
"\n",
" # Log full dataset XEB Fidelity and generated distribution.\n",
" generated_samples = generator(tf.random.normal([N_SAMPLES, 100]))\n",
- " tf.summary.scalar(\n",
- " 'Generator XEB Fidelity Estimate', data=xeb_fid(generated_samples), step=epoch)\n",
- " tf.summary.histogram(\n",
- " 'Generator distribution', data=bits_to_ints(generated_samples), step=epoch)\n",
+ " tf.summary.scalar('Generator XEB Fidelity Estimate',\n",
+ " data=xeb_fid(generated_samples),\n",
+ " step=epoch)\n",
+ " tf.summary.histogram('Generator distribution',\n",
+ " data=bits_to_ints(generated_samples),\n",
+ " step=epoch)\n",
" # Log new samples drawn from this particular random circuit.\n",
" random_new_distribution = generate_data(REFERENCE_CIRCUIT, N_SAMPLES)\n",
- " tf.summary.histogram(\n",
- " 'New round of True samples', data=bits_to_ints(random_new_distribution), step=epoch)\n",
+ " tf.summary.histogram('New round of True samples',\n",
+ " data=bits_to_ints(random_new_distribution),\n",
+ " step=epoch)\n",
"\n",
" if epoch % 10 == 0:\n",
" print('Epoch {}, took {}(s)'.format(epoch, time.time() - t))\n",