--- a +++ b/Code/All PennyLane QML Demos/36 GANs Cirq TensorFlow 29.5s kkawchak.ipynb @@ -0,0 +1,714 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "URenBt8iB4_G", + "outputId": "50111bc4-f5da-428b-c7c6-6d08fd86841d" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Time in seconds since beginning of run: 1693409355.3239555\n", + "Wed Aug 30 15:29:15 2023\n" + ] + } + ], + "source": [ + "# This cell is added by sphinx-gallery\n", + "# It can be customized to whatever you like\n", + "%matplotlib inline\n", + "# !pip install pennylane\n", + "# !pip install pennylane-cirq\n", + "# !pip install tensorflow==2.8.1\n", + "# !pip install qsimcirq\n", + "import time\n", + "seconds = time.time()\n", + "print(\"Time in seconds since beginning of run:\", seconds)\n", + "local_time = time.ctime(seconds)\n", + "print(local_time)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1xodSdeDB4_G" + }, + "source": [ + "Quantum generative adversarial networks with Cirq + TensorFlow {#quantum_GAN}\n", + "==============================================================\n", + "\n", + "::: {.meta}\n", + ":property=\\\"og:description\\\": This demo constructs and trains a Quantum\n", + "Generative Adversarial Network (QGAN) using PennyLane, Cirq, and\n", + "TensorFlow. :property=\\\"og:image\\\":\n", + "<https://pennylane.ai/qml/_images/qgan3.png>\n", + ":::\n", + "\n", + "*Author: Nathan Killoran --- Posted: 11 October 2019. Last updated: 30\n", + "January 2023.*\n", + "\n", + "This demo constructs a Quantum Generative Adversarial Network (QGAN)\n", + "([Lloyd and Weedbrook\n", + "(2018)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.121.040502),\n", + "[Dallaire-Demers and Killoran\n", + "(2018)](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.012324))\n", + "using two subcircuits, a *generator* and a *discriminator*. The\n", + "generator attempts to generate synthetic quantum data to match a pattern\n", + "of \\\"real\\\" data, while the discriminator tries to discern real data\n", + "from fake data (see image below). The gradient of the discriminator's\n", + "output provides a training signal for the generator to improve its fake\n", + "generated data.\n", + "\n", + "|\n", + "\n", + "{.align-center width=\"75.0%\"}\n", + "\n", + "|\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Q8AMvcT-B4_H" + }, + "source": [ + "Using Cirq + TensorFlow\n", + "=======================\n", + "\n", + "PennyLane allows us to mix and match quantum devices and classical\n", + "machine learning software. For this demo, we will link together\n", + "Google\\'s [Cirq](https://cirq.readthedocs.io/en/stable/) and\n", + "[TensorFlow](https://www.tensorflow.org/) libraries.\n", + "\n", + "We begin by importing PennyLane, NumPy, and TensorFlow.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "id": "jZnEogOMB4_H" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pennylane as qml\n", + "import tensorflow as tf" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VE_Ig3zoB4_H" + }, + "source": [ + "We also declare a 3-qubit simulator device running in Cirq.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "id": "qgDmLFu5B4_I" + }, + "outputs": [], + "source": [ + "dev = qml.device('cirq.qsim', wires=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_6MqJwPsB4_I" + }, + "source": [ + "Generator and Discriminator\n", + "===========================\n", + "\n", + "In classical GANs, the starting point is to draw samples either from\n", + "some \\\"real data\\\" distribution, or from the generator, and feed them to\n", + "the discriminator. In this QGAN example, we will use a quantum circuit\n", + "to generate the real data.\n", + "\n", + "For this simple example, our real data will be a qubit that has been\n", + "rotated (from the starting state $\\left|0\\right\\rangle$) to some\n", + "arbitrary, but fixed, state.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "id": "gjBLrnOnB4_I" + }, + "outputs": [], + "source": [ + "def real(angles, **kwargs):\n", + " qml.Hadamard(wires=0)\n", + " qml.Rot(*angles, wires=0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bPLhFqDEB4_I" + }, + "source": [ + "For the generator and discriminator, we will choose the same basic\n", + "circuit structure, but acting on different wires.\n", + "\n", + "Both the real data circuit and the generator will output on wire 0,\n", + "which will be connected as an input to the discriminator. Wire 1 is\n", + "provided as a workspace for the generator, while the discriminator's\n", + "output will be on wire 2.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "id": "UoOK7QA9B4_I" + }, + "outputs": [], + "source": [ + "def generator(w, **kwargs):\n", + " qml.Hadamard(wires=0)\n", + " qml.RX(w[0], wires=0)\n", + " qml.RX(w[1], wires=1)\n", + " qml.RY(w[2], wires=0)\n", + " qml.RY(w[3], wires=1)\n", + " qml.RZ(w[4], wires=0)\n", + " qml.RZ(w[5], wires=1)\n", + " qml.CNOT(wires=[0, 1])\n", + " qml.RX(w[6], wires=0)\n", + " qml.RY(w[7], wires=0)\n", + " qml.RZ(w[8], wires=0)\n", + "\n", + "\n", + "def discriminator(w):\n", + " qml.Hadamard(wires=0)\n", + " qml.RX(w[0], wires=0)\n", + " qml.RX(w[1], wires=2)\n", + " qml.RY(w[2], wires=0)\n", + " qml.RY(w[3], wires=2)\n", + " qml.RZ(w[4], wires=0)\n", + " qml.RZ(w[5], wires=2)\n", + " qml.CNOT(wires=[0, 2])\n", + " qml.RX(w[6], wires=2)\n", + " qml.RY(w[7], wires=2)\n", + " qml.RZ(w[8], wires=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BA6j3YglB4_I" + }, + "source": [ + "We create two QNodes. One where the real data source is wired up to the\n", + "discriminator, and one where the generator is connected to the\n", + "discriminator. In order to pass TensorFlow Variables into the quantum\n", + "circuits, we specify the `\"tf\"` interface.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "id": "OLk5Bxs9B4_I" + }, + "outputs": [], + "source": [ + "@qml.qnode(dev, interface=\"tf\")\n", + "def real_disc_circuit(phi, theta, omega, disc_weights):\n", + " real([phi, theta, omega])\n", + " discriminator(disc_weights)\n", + " return qml.expval(qml.PauliZ(2))\n", + "\n", + "\n", + "@qml.qnode(dev, interface=\"tf\")\n", + "def gen_disc_circuit(gen_weights, disc_weights):\n", + " generator(gen_weights)\n", + " discriminator(disc_weights)\n", + " return qml.expval(qml.PauliZ(2))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VbnRyFpjB4_I" + }, + "source": [ + "QGAN cost functions\n", + "===================\n", + "\n", + "There are two cost functions of interest, corresponding to the two\n", + "stages of QGAN training. These cost functions are built from two pieces:\n", + "the first piece is the probability that the discriminator correctly\n", + "classifies real data as real. The second piece is the probability that\n", + "the discriminator classifies fake data (i.e., a state prepared by the\n", + "generator) as real.\n", + "\n", + "The discriminator is trained to maximize the probability of correctly\n", + "classifying real data, while minimizing the probability of mistakenly\n", + "classifying fake data.\n", + "\n", + "$$Cost_D = \\mathrm{Pr}(real|\\mathrm{fake}) - \\mathrm{Pr}(real|\\mathrm{real})$$\n", + "\n", + "The generator is trained to maximize the probability that the\n", + "discriminator accepts fake data as real.\n", + "\n", + "$$Cost_G = - \\mathrm{Pr}(real|\\mathrm{fake})$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "id": "qZOU-2G1B4_I" + }, + "outputs": [], + "source": [ + "def prob_real_true(disc_weights):\n", + " true_disc_output = real_disc_circuit(phi, theta, omega, disc_weights)\n", + " # convert to probability\n", + " prob_real_true = (true_disc_output + 1) / 2\n", + " return prob_real_true\n", + "\n", + "\n", + "def prob_fake_true(gen_weights, disc_weights):\n", + " fake_disc_output = gen_disc_circuit(gen_weights, disc_weights)\n", + " # convert to probability\n", + " prob_fake_true = (fake_disc_output + 1) / 2\n", + " return prob_fake_true\n", + "\n", + "\n", + "def disc_cost(disc_weights):\n", + " cost = prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights)\n", + " return cost\n", + "\n", + "\n", + "def gen_cost(gen_weights):\n", + " return -prob_fake_true(gen_weights, disc_weights)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uf3SAVEMB4_J" + }, + "source": [ + "Training the QGAN\n", + "=================\n", + "\n", + "We initialize the fixed angles of the \\\"real data\\\" circuit, as well as\n", + "the initial parameters for both generator and discriminator. These are\n", + "chosen so that the generator initially prepares a state on wire 0 that\n", + "is very close to the $\\left| 1 \\right\\rangle$ state.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "id": "IWGiPa4dB4_J" + }, + "outputs": [], + "source": [ + "phi = np.pi / 6\n", + "theta = np.pi / 2\n", + "omega = np.pi / 7\n", + "np.random.seed(0)\n", + "eps = 1e-2\n", + "init_gen_weights = np.array([np.pi] + [0] * 8) + \\\n", + " np.random.normal(scale=eps, size=(9,))\n", + "init_disc_weights = np.random.normal(size=(9,))\n", + "\n", + "gen_weights = tf.Variable(init_gen_weights)\n", + "disc_weights = tf.Variable(init_disc_weights)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NVz7yaXGB4_J" + }, + "source": [ + "We begin by creating the optimizer:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "id": "8XQoiYjiB4_J" + }, + "outputs": [], + "source": [ + "opt = tf.keras.optimizers.SGD(0.4)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BrUfmFDWB4_J" + }, + "source": [ + "In the first stage of training, we optimize the discriminator while\n", + "keeping the generator parameters fixed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "6_diImNYB4_J", + "outputId": "36b1db3c-3faf-4572-e281-08ea47a280ed" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Step 0: cost = -0.057277023792266846\n", + "Step 5: cost = -0.26348109543323517\n", + "Step 10: cost = -0.4273916482925415\n", + "Step 15: cost = -0.47261591255664825\n", + "Step 20: cost = -0.4840688854455948\n", + "Step 25: cost = -0.48946413397789\n", + "Step 30: cost = -0.4928188920021057\n", + "Step 35: cost = -0.49494941532611847\n", + "Step 40: cost = -0.4962703734636307\n", + "Step 45: cost = -0.49707190692424774\n" + ] + } + ], + "source": [ + "cost = lambda: disc_cost(disc_weights)\n", + "\n", + "for step in range(50):\n", + " opt.minimize(cost, disc_weights)\n", + " if step % 5 == 0:\n", + " cost_val = cost().numpy()\n", + " print(\"Step {}: cost = {}\".format(step, cost_val))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dbm3F7-IB4_J" + }, + "source": [ + "At the discriminator's optimum, the probability for the discriminator to\n", + "correctly classify the real data should be close to one.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "URMw-4uBB4_J", + "outputId": "89ac7516-d3b1-497a-a98b-6b3609bdab29" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(real classified as real): 0.9985873699188232\n" + ] + } + ], + "source": [ + "print(\"Prob(real classified as real): \", prob_real_true(disc_weights).numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7GO0YwksB4_J" + }, + "source": [ + "For comparison, we check how the discriminator classifies the\n", + "generator's (still unoptimized) fake data:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "b4pWtXVHB4_J", + "outputId": "5d1c85ca-52f0-4f19-b670-859033c36c9b" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(fake classified as real): 0.5011128038167953\n" + ] + } + ], + "source": [ + "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JGr9atT9B4_J" + }, + "source": [ + "In the adversarial game we now have to train the generator to better\n", + "fool the discriminator. For this demo, we only perform one stage of the\n", + "game. For more complex models, we would continue training the models in\n", + "an alternating fashion until we reach the optimum point of the\n", + "two-player adversarial game.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "NVrGCFK5B4_J", + "outputId": "01fd9a61-e30b-4cc8-ebc9-9977e48698fb" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Step 0: cost = -0.5833387225866318\n", + "Step 5: cost = -0.8915734887123108\n", + "Step 10: cost = -0.978424459695816\n", + "Step 15: cost = -0.9946485459804535\n", + "Step 20: cost = -0.9984995722770691\n", + "Step 25: cost = -0.9995637834072113\n", + "Step 30: cost = -0.999871701002121\n", + "Step 35: cost = -0.9999619722366333\n", + "Step 40: cost = -0.9999887645244598\n", + "Step 45: cost = -0.9999967217445374\n" + ] + } + ], + "source": [ + "cost = lambda: gen_cost(gen_weights)\n", + "\n", + "for step in range(50):\n", + " opt.minimize(cost, gen_weights)\n", + " if step % 5 == 0:\n", + " cost_val = cost().numpy()\n", + " print(\"Step {}: cost = {}\".format(step, cost_val))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0K59ud11B4_J" + }, + "source": [ + "At the optimum of the generator, the probability for the discriminator\n", + "to be fooled should be close to 1.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "4_3Co4HYB4_J", + "outputId": "abc8f514-58eb-4ef6-aac2-943c7a116e5d" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(fake classified as real): 0.9999988079071045\n" + ] + } + ], + "source": [ + "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uJR4z50uB4_J" + }, + "source": [ + "At the joint optimum the discriminator cost will be close to zero,\n", + "indicating that the discriminator assigns equal probability to both real\n", + "and generated data.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "byWE_IY1B4_J", + "outputId": "9cc7eefa-7880-430b-8a10-5f6ad8b152a6" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Discriminator cost: 0.00141143798828125\n" + ] + } + ], + "source": [ + "print(\"Discriminator cost: \", disc_cost(disc_weights).numpy())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ubhr8ZSsB4_J" + }, + "source": [ + "The generator has successfully learned how to simulate the real data\n", + "enough to fool the discriminator.\n", + "\n", + "Let\\'s conclude by comparing the states of the real data circuit and the\n", + "generator. We expect the generator to have learned to be in a state that\n", + "is very close to the one prepared in the real data circuit. An easy way\n", + "to access the state of the first qubit is through its [Bloch\n", + "sphere](https://en.wikipedia.org/wiki/Bloch_sphere) representation:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "QZ2TkvG4B4_J", + "outputId": "e4f09de4-0ea1-4745-8d4b-543e597138f6" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Real Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.21694186329841614>, <tf.Tensor: shape=(), dtype=float64, numpy=0.45048439502716064>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8660252690315247>]\n", + "Generator Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.28404662013053894>, <tf.Tensor: shape=(), dtype=float64, numpy=0.4189322292804718>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8624440431594849>]\n" + ] + } + ], + "source": [ + "obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)]\n", + "\n", + "@qml.qnode(dev, interface=\"tf\")\n", + "def bloch_vector_real(angles):\n", + " real(angles)\n", + " return [qml.expval(o) for o in obs]\n", + "\n", + "@qml.qnode(dev, interface=\"tf\")\n", + "def bloch_vector_generator(angles):\n", + " generator(angles)\n", + " return [qml.expval(o) for o in obs]\n", + "\n", + "print(f\"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}\")\n", + "print(f\"Generator Bloch vector: {bloch_vector_generator(gen_weights)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zAlOXLekB4_K" + }, + "source": [ + "About the author\n", + "================\n" + ] + }, + { + "cell_type": "code", + "source": [ + "seconds = time.time()\n", + "print(\"Time in seconds since end of run:\", seconds)\n", + "local_time = time.ctime(seconds)\n", + "print(local_time)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "lypK7PwdDujD", + "outputId": "976cd2cf-5439-44c4-fb14-cdcfd85d92b0" + }, + "execution_count": 51, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Time in seconds since end of run: 1693409384.8585372\n", + "Wed Aug 30 15:29:44 2023\n" + ] + } + ] + } + ], + "metadata": { + "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.9.17" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}