--- a +++ b/Code/All Qiskit, PennyLane QML Nov 23/36a2 GAN CPU Light.qub 11.91s kkawchak.ipynb @@ -0,0 +1,714 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "URenBt8iB4_G", + "outputId": "5acd6ab6-415d-4da0-cdc4-a246afdeac02" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Time in seconds since beginning of run: 1700610438.5878162\n", + "Tue Nov 21 23:47:18 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": 37, + "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": 38, + "metadata": { + "id": "qgDmLFu5B4_I" + }, + "outputs": [], + "source": [ + "dev = qml.device('lightning.qubit', 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": 39, + "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": 40, + "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": 41, + "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": 42, + "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": 43, + "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": 44, + "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": 45, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "6_diImNYB4_J", + "outputId": "ac2d2d96-7dae-4e67-8b0f-063a3728e7c5" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Step 0: cost = -0.05727697679577837\n", + "Step 5: cost = -0.26348125084490526\n", + "Step 10: cost = -0.42739188533175093\n", + "Step 15: cost = -0.4726159748418596\n", + "Step 20: cost = -0.48406899740530496\n", + "Step 25: cost = -0.48946413443470094\n", + "Step 30: cost = -0.49281877884752445\n", + "Step 35: cost = -0.4949493282586438\n", + "Step 40: cost = -0.4962703876869723\n", + "Step 45: cost = -0.4970720262026467\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": 46, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "URMw-4uBB4_J", + "outputId": "164f4701-7968-4ff2-eba8-e7bfa703be74" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(real classified as real): 0.9985872751209892\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": 47, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "b4pWtXVHB4_J", + "outputId": "66cbd0a1-02e6-4d2e-eeef-51408a302c0d" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(fake classified as real): 0.5011127803383656\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": 48, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "NVrGCFK5B4_J", + "outputId": "839da8bd-1f97-437a-8df9-2703c4cd9da0" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Step 0: cost = -0.5833387118384107\n", + "Step 5: cost = -0.8915733598437311\n", + "Step 10: cost = -0.9784243532819918\n", + "Step 15: cost = -0.9946483809432043\n", + "Step 20: cost = -0.9984996426172494\n", + "Step 25: cost = -0.9995638464006635\n", + "Step 30: cost = -0.9998717844534688\n", + "Step 35: cost = -0.9999621462112336\n", + "Step 40: cost = -0.9999888012418465\n", + "Step 45: cost = -0.9999966825023898\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": 49, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "4_3Co4HYB4_J", + "outputId": "83103012-fb0d-4c49-bd01-2d97376c472c" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Prob(fake classified as real): 0.9999987450417567\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": 50, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "byWE_IY1B4_J", + "outputId": "63544cdb-bfcc-4471-f84a-e062c00e9f4e" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Discriminator cost: 0.0014114699207674608\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": 51, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 0 + }, + "id": "QZ2TkvG4B4_J", + "outputId": "f692e110-6622-44c1-a223-aaa91c80355c" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Real Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.21694186955877903>, <tf.Tensor: shape=(), dtype=float64, numpy=0.4504844339512095>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8660254037844386>]\n", + "Generator Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.28404665756341024>, <tf.Tensor: shape=(), dtype=float64, numpy=0.41893226844532244>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.862444148446728>]\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": "38447ec6-0faf-49ab-acf5-dda0c1275740" + }, + "execution_count": 52, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Time in seconds since end of run: 1700610450.496186\n", + "Tue Nov 21 23:47:30 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 +}