{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Quantum Binary classifier using Keras\n", "\n", "## Nikolaos Schetakis , nsxetakis@yahoo.gr\n", "\n", "\n", "### This notebook runs with Python 3.7-3.8 \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Can sometimes be important for multi-GPU systems\n", "# or if you wish to hide a GPU, set to -1\n", "import os\n", "os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pennylane as qml\n", "import tensorflow as tf\n", "from pennylane import numpy as np\n", "import pandas as pd\n", "from sklearn.datasets import make_moons\n", "from sklearn.model_selection import train_test_split\n", "from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n", "from tensorflow.keras.models import load_model\n", "from tensorflow.keras.metrics import AUC\n", "from os import path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Set parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "n_qubits = 15 # Number of qubits should be the same as number of features, max number = 25\n", "LABELplot=\"\"\n", "blocks = 6 #number of blocks (AngleEmbedding and StronglyEntanglingLayers is one block )\n", "layers = 1 #layers per block (multiple “layers” of StronglyEntanglingLayers per block )\n", "learning_rate = 0.02 #Learning rate for optimizer\n", "opt = tf.keras.optimizers.Adam(learning_rate=learning_rate,beta_1=0.9,beta_2=0.999) #tf.keras.optimizers.SGD(learning_rate=learning_rate) #Select optimizer\n", "\n", "epochsH = 10 # Hybrid training epochs\n", "batch_size = 16 # Batch size\n", "test_size = 0.02 # Choose train-test split ratio\n", "np.random.seed(42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Functions to generate datasets" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Generate Data\n", "N = 1200 #No of points to generate\n", "noise = 0.01 #add noise, for the makemoons dataset only\n", "\n", "# Make a dataset of points inside and outside of a circle\n", "def circle(samples, center=[0.0, 0.0], radius=np.sqrt(2 / np.pi)):\n", " \"\"\"\n", " Generates a dataset of points with 1/0 labels inside a given radius.\n", "\n", " Args:\n", " samples (int): number of samples to generate\n", " center (tuple): center of the circle\n", " radius (float: radius of the circle\n", "\n", " Returns:\n", " Xvals (array[tuple]): coordinates of points\n", " yvals (array[int]): classification labels\n", " \"\"\"\n", " Xvals, yvals = [], []\n", "\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(2)) - 1\n", " y = 0\n", " if np.linalg.norm(x - center) < radius:\n", " y = 1\n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", "\n", "\n", "def squares(samples):\n", " data=[]\n", " Xvals, yvals = [], []\n", " dim=2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[0] < 0 and x[1] < 0: y = 0\n", " if x[0] < 0 and x[1] > 0: y = 1\n", " if x[0] > 0 and x[1] < 0: y = 1\n", " if x[0] > 0 and x[1] > 0: y = 0 \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals) \n", " return data, None\n", "\n", "def wavy_lines0(samples, freq = 1):\n", " Xvals, yvals = [], []\n", " def fun1(s):\n", " return s + np.sin(freq * np.pi * s)\n", " \n", " def fun2(s):\n", " return -s + np.sin(freq * np.pi * s)\n", " data=[]\n", " dim=2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[1] < fun1(x[0]) and x[1] < fun2(x[0]): y = 0\n", " if x[1] < fun1(x[0]) and x[1] > fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] < fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] > fun2(x[0]): y = 0 \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", " return data, freq \n", " \n", "def wavy_lines1(samples, freq = 1):\n", " Xvals, yvals = [], []\n", " def fun1(s):\n", " return s + np.sin(freq * np.pi * s)\n", " \n", " def fun2(s):\n", " return -s + np.sin(freq * np.pi * s)\n", " data=[]\n", " dim=2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[1] < fun1(x[0]) and x[1] < fun2(x[0]): y = 0\n", " if x[1] < fun1(x[0]) and x[1] > fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] < fun2(x[0]): y = 0\n", " if x[1] > fun1(x[0]) and x[1] > fun2(x[0]): y = 1 \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", " return data, freq \n", "\n", "def wavy_lines2(samples, freq = 1):\n", " Xvals, yvals = [], []\n", " def fun1(s):\n", " return s + np.sin(freq * np.pi * s)\n", " \n", " def fun2(s):\n", " return -s + np.sin(freq * np.pi * s)\n", " data=[]\n", " dim=2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[1] < fun1(x[0]) and x[1] < fun2(x[0]): y = 0\n", " if x[1] < fun1(x[0]) and x[1] > fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] < fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] > fun2(x[0]): y = 1 \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", " return data, freq \n", "\n", "\n", "def wavy_lines3(samples, freq = 1):\n", " Xvals, yvals = [], []\n", " def fun1(s):\n", " return s + np.sin(freq * np.pi * s)\n", " \n", " def fun2(s):\n", " return -s + np.sin(freq * np.pi * s)\n", " data=[]\n", " dim=2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[1] < fun1(x[0]) and x[1] < fun2(x[0]): y = 1\n", " if x[1] < fun1(x[0]) and x[1] > fun2(x[0]): y = 0\n", " if x[1] > fun1(x[0]) and x[1] < fun2(x[0]): y = 1\n", " if x[1] > fun1(x[0]) and x[1] > fun2(x[0]): y = 0 \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", " return data, freq \n", " \n", " \n", "def threecircles(samples):\n", " Xvals, yvals = [], []\n", " centers = np.array([[-1, 1], [1, 0], [-.5, -.5]])\n", " radii = np.array([1, np.sqrt(6/np.pi - 1), 1/2]) \n", " data=[]\n", " dim = 2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " y = 0\n", " for j, (c, r) in enumerate(zip(centers, radii)): \n", " if np.linalg.norm(x - c) < r:\n", " y = j + 1 \n", " \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals) \n", " \n", " \n", "def _non_convex(samples, freq = 1, x_val = 2, sin_val = 1.5):\n", " Xvals, yvals = [], []\n", " def fun(s):\n", " return -x_val * s + sin_val * np.sin(freq * np.pi * s)\n", " \n", " data = []\n", " dim = 2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if x[1] < fun(x[0]): y = 0\n", " if x[1] > fun(x[0]): y = 1\n", " data.append([x, y])\n", "\n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", "\n", "def _crown(samples):\n", " Xvals, yvals = [], []\n", " c = [[0,0],[0,0]]\n", " r = [np.sqrt(.8), np.sqrt(.8 - 2/np.pi)]\n", " data = []\n", " dim = 2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " if np.linalg.norm(x - c[0]) < r[0] and np.linalg.norm(x - c[1]) > r[1]:\n", " y = 1\n", " else: \n", " y=0\n", " data.append([x, y])\n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", "\n", "\n", "def _tricrown(samples):\n", " Xvals, yvals = [], []\n", " centers = [[0,0],[0,0]]\n", " radii = [np.sqrt(.8 - 2/np.pi), np.sqrt(.8)]\n", " data = []\n", " dim = 2\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " y=0\n", " for j,(r,c) in enumerate(zip(radii, centers)):\n", " if np.linalg.norm(x - c) > r:\n", " y = j + 1\n", " data.append([x, y])\n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", "\n", "def _sphere(samples):\n", " Xvals, yvals = [], []\n", " centers = np.array([[0, 0, 0]]) \n", " radii = np.array([(3/np.pi)**(1/3)]) \n", " data=[]\n", " dim = 3\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1\n", " y = 0\n", " for c, r in zip(centers, radii): \n", " if np.linalg.norm(x - c) < r:\n", " y = 1 \n", " \n", " \n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", "\n", "def _hypersphere(samples):\n", " Xvals, yvals = [], []\n", " centers = np.array([[0, 0, 0, 0]]) \n", " radii = np.array([(2/np.pi)**(1/2)]) \n", " data=[]\n", " dim = 4\n", " for i in range(samples):\n", " x = 2 * (np.random.rand(dim)) - 1 \n", " y = 0\n", " for c, r in zip(centers, radii): \n", " if np.linalg.norm(x - c) < r:\n", " y = 1 \n", "\n", " Xvals.append(x)\n", " yvals.append(y)\n", " return np.array(Xvals), np.array(yvals)\n", " \n", "def plot_data(x, y, fig=None, ax=None):\n", " \"\"\"\n", " Plot data with red/blue values for a binary classification.\n", "\n", " Args:\n", " x (array[tuple]): array of data points as tuples\n", " y (array[int]): array of data points as tuples\n", " \"\"\"\n", " if fig == None:\n", " fig, ax = plt.subplots(1, 1, figsize=(5, 5))\n", " reds = y == 0\n", " blues = y == 1\n", " ax.scatter(x[reds, 0], x[reds, 1], c=\"red\", s=20, edgecolor=\"k\")\n", " ax.scatter(x[blues, 0], x[blues, 1], c=\"blue\", s=20, edgecolor=\"k\")\n", " ax.set_xlabel(\"$x_1$\")\n", " ax.set_ylabel(\"$x_2$\") \n", " \n", "def density_matrix(state):\n", " \"\"\"Calculates the density matrix representation of a state.\n", "\n", " Args:\n", " state (array[complex]): array representing a quantum state vector\n", "\n", " Returns:\n", " dm: (array[complex]): array representing the density matrix\n", " \"\"\"\n", " return state * np.conj(state).T\n", "\n", " # Define output labels as quantum state vectors\n", "label_0 = [[1], [0]]\n", "label_1 = [[0], [1]]\n", "state_labels = [label_0, label_1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Select dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#-------------------------\n", "# CHOOSE A DATASET: Uncomment to choose a different dataset\n", "#-------------------------\n", "\n", "#X, y = make_moons(n_samples=N, noise=noise)\n", "#X, y = circle(N)\n", "#X, y = squares(N)\n", "X, y = wavy_lines2(N)\n", "#X, y = threecircles(N)\n", "#X, y = _non_convex(N)\n", "#X, y = _crown(N)\n", "#X, y = _tricrown(N)\n", "#X, y = _sphere(N)\n", "#X, y = _hypersphere(N)\n", "\n", "#------------------------- \n", "#-------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Visualize dataset and data pre-processing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y = pd.DataFrame.from_dict(y)\n", "y = y.iloc[:, :]\n", "y = y[0].apply(lambda x: 1 if x <= 0 else 0)\n", "y = y.to_numpy()\n", "#y_hot = tf.keras.utils.to_categorical(y, num_classes=2) # one-hot encoded labels\n", "#Normalize from 0 to pi\n", "from sklearn.preprocessing import StandardScaler , minmax_scale\n", "X = minmax_scale(X, feature_range=(0, np.pi))\n", "import matplotlib.pyplot as plt\n", "plt.figure()\n", "plt.scatter(X[:, 0][y == 0], X[:, 1][y == 0], c=\"r\", marker=\"o\", edgecolors=\"k\")\n", "plt.scatter(X[:, 0][y == 1], X[:, 1][y == 1], c=\"b\", marker=\"o\", edgecolors=\"k\")\n", "plt.title(\"Dataset\")\n", "plt.axis('off')\n", "plt.show()\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=1)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## 5. Build Quantum node\n", "\n", "### Blocks: AngleEmbedding and StronglyEntanglingLayers is one block \n", "### Layers: multiple “layers” of StronglyEntanglingLayers per block " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# Define quantum node\n", "dev = qml.device(\"default.qubit.tf\", wires=n_qubits) # Run the model in classical CPU\n", "\n", "# Added below to ensure the QNode is JIT compiled\n", "@tf.function\n", "@qml.qnode(dev, interface=\"tf\", diff_method=\"backprop\")\n", "def qnode(inputs, weights):\n", " for i in range(blocks):\n", " qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))\n", " qml.templates.StronglyEntanglingLayers(weights[i], wires=range(n_qubits)) #STRONGLY ENTANGLING LAYERS\n", " return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]\n", "\n", "weights_shape = (blocks, layers, n_qubits, 3) # Uncomment for Strongly entangling layers" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## 6. Building and Training the Hybrid model:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# The next line can be switched from GPU to CPU and vice-versa to compare runtimes\n", "# I have also modified all data here to explicitly use Float32 types\n", "#with tf.device('/device:CPU:0'):\n", "with tf.device('/device:GPU:0'):\n", " tf.keras.backend.set_floatx(\"float32\")\n", " weight_shapes = {\"weights\": weights_shape}\n", "\n", " # Create the Hybrid model\n", " #------------ classical Master layer ------------\n", " clayerM = tf.keras.layers.Dense(X_train.shape[1], activation=\"relu\", dtype=tf.float32) \n", " #------------ Quantum layer. It consists of the quantum node as defined before ------------\n", " qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, n_qubits, dtype=tf.float32)\n", " #------------ Classical Decision layer ------------\n", " clayerD = tf.keras.layers.Dense(1, activation=\"sigmoid\", dtype=tf.float32)\n", " #--------------------------------------------------\n", "\n", " inputs = tf.constant(np.random.random((batch_size, n_qubits)))\n", "\n", " # Include classical and quantum layers \n", " modelh = tf.keras.models.Sequential([clayerM,qlayer,clayerD])\n", "\n", "\n", " modelh.compile(opt, loss='binary_crossentropy',\n", " metrics=[AUC(name = 'auc')])#'sparse_categorical_accuracy','categorical_accuracy','binary_accuracy', 'accuracy'])\n", "\n", " modelh.build(input_shape=X_train.shape)\n", "\n", " historyh = modelh.fit(X_train, y_train,\n", " #validation_split = 0.1, \n", " validation_data=(X_test, y_test),\n", " epochs=epochsH,\n", " batch_size=batch_size,\n", " shuffle=True)\n", "\n", " modelh.summary()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. Make predictions and plot training history + prediction grids" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Make predictions \n", "y_pred = modelh.predict(X_test)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "from matplotlib import pyplot\n", "pyplot.plot(historyh.history['loss'], label='loss train')\n", "pyplot.plot(historyh.history['val_loss'], label='loss val')\n", "pyplot.legend()\n", "pyplot.show()\n", "\n", "#pyplot.plot(historyh.history['binary_accuracy'], label='accuracy train')\n", "#pyplot.plot(historyh.history['val_binary_accuracy'], label='accuracy test')\n", "#pyplot.plot(history.history['mse'], label='mse')\n", "#pyplot.plot(history.history['mae'], label='mae')\n", "#pyplot.legend()\n", "#pyplot.show()\n", "\n", "pyplot.plot(historyh.history['auc'], label='auc train')\n", "pyplot.plot(historyh.history['val_auc'], label='auc val')\n", "pyplot.legend()\n", "pyplot.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_pred = (modelh.predict(X_test) > 0.5).astype(\"int32\")\n", "from numpy import arange, meshgrid, hstack\n", "plt.figure()\n", "cm = plt.cm.RdBu_r\n", "fig= plt.figure(figsize=(15,15))\n", "# make data for decision regions\n", "# define bounds of the domain\n", "min1, max1 = X_test[:, 0].min()-0.2, X_test[:, 0].max()+0.2\n", "min2, max2 = X_test[:, 1].min()-0.2, X_test[:, 1].max()+0.2\n", "# define the x and y scale\n", "x1grid = arange(min1, max1, 0.1)\n", "x2grid = arange(min2, max2, 0.1)\n", "# create all of the lines and rows of the grid\n", "xx, yy = meshgrid(x1grid, x2grid)\n", "# flatten each grid to a vector\n", "r1, r2 = xx.flatten(), yy.flatten()\n", "r1, r2 = r1.reshape((len(r1), 1)), r2.reshape((len(r2), 1))\n", "# horizontal stack vectors to create x1,x2 input for the model\n", "grid = hstack((r1,r2))\n", "# make predictions for the grid\n", "yhat = (modelh.predict(grid) > 0.5).astype(\"int32\")\n", "# reshape the predictions back into a grid\n", "zz = yhat.reshape(xx.shape)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from numpy import arange, meshgrid, hstack\n", "plt.figure()\n", "fig= plt.figure(figsize=(5,5))\n", "cm = plt.cm.RdBu_r\n", "# plot decision regions\n", "cnt = plt.contourf(xx, yy,zz, levels=np.arange(0, 1, 0.1), cmap=cm, alpha=0.8, extend=\"both\")\n", "plt.contour(xx, yy,zz, levels=[0.5], colors=(\"black\",), linestyles=(\"--\",), linewidths=(4.0,))\n", "\n", "\n", "# plot data\n", "plt.scatter(\n", " X_train[:, 0][y_train == 1],\n", " X_train[:, 1][y_train == 1],\n", " c=\"r\",s=50,\n", " marker=\"o\",\n", " edgecolors=\"k\",\n", " label=\"class 1 Train\",\n", ")\n", "plt.scatter(\n", " X_train[:, 0][y_train == 0],\n", " X_train[:, 1][y_train == 0],\n", " c=\"b\",\n", " marker=\"o\",s=50,\n", " edgecolors=\"k\",\n", " label=\"class 0 train\",\n", ")\n", "\n", "plt.title(label=LABELplot,\n", " fontsize=12,\n", " color=\"Black\")\n", "plt.xlabel('')\n", "plt.ylabel('')\n", "plt.legend(loc='upper left',fontsize=10)\n", "plt.tick_params(left=False,\n", " bottom=False,\n", " labelleft=False,\n", " labelbottom=False)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import accuracy_score\n", "print(\"Test Accuracy = \",accuracy_score(y_test, y_pred))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.7" } }, "nbformat": 4, "nbformat_minor": 4 }