{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Hybrid Quantum Neural Networks \n", "\n", "The example below highlights a hybrid quantum neural network workflow with CUDA Quantum and Pytorch where both layers are GPU accelerated to maximise performance. \n", "\n", "\n", "\"hybrid\"\n", "\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We perform binary classification on the MNIST dataset where data flows through the neural network architecture to the quantum circuit whose output is used to classify hand written digits." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%pip install matplotlib torch torchvision" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Import the relevant packages\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "import torch\n", "from torch.autograd import Function\n", "from torchvision import datasets, transforms\n", "import torch.optim as optim\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", "\n", "import cudaq\n", "from cudaq import spin" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# GPU utilities\n", "\n", "cudaq.set_target(\"nvidia\") # Set CUDAQ to run on GPU's\n", "\n", "torch.cuda.is_available(\n", ") # If this is True then the NVIDIA drivers are correctly installed\n", "\n", "torch.cuda.device_count() # Counts the number of GPU's available\n", "\n", "torch.cuda.current_device()\n", "\n", "torch.cuda.get_device_name(0)\n", "\n", "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz\n", "Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 9912422/9912422 [00:00<00:00, 72148113.67it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw\n", "\n", "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz\n", "Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 28881/28881 [00:00<00:00, 79537553.40it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw\n", "\n", "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz\n", "Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n", "100%|██████████| 1648877/1648877 [00:00<00:00, 45882038.30it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw\n", "\n", "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz\n", "Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 4542/4542 [00:00<00:00, 15252625.11it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw\n", "\n" ] } ], "source": [ "# Training set.\n", "sample_count = 140\n", "\n", "X_train = datasets.MNIST(\n", " root=\"./data\",\n", " train=True,\n", " download=True,\n", " transform=transforms.Compose([transforms.ToTensor()]),\n", ")\n", "\n", "# Leaving only labels 0 and 1.\n", "idx = np.append(\n", " np.where(X_train.targets == 0)[0][:sample_count],\n", " np.where(X_train.targets == 1)[0][:sample_count],\n", ")\n", "\n", "X_train.data = X_train.data[idx]\n", "X_train.targets = X_train.targets[idx]\n", "\n", "train_loader = torch.utils.data.DataLoader(X_train, batch_size=1, shuffle=True)\n", "\n", "# Test set.\n", "sample_count = 70\n", "\n", "X_test = datasets.MNIST(\n", " root=\"./data\",\n", " train=False,\n", " download=True,\n", " transform=transforms.Compose([transforms.ToTensor()]),\n", ")\n", "idx = np.append(\n", " np.where(X_test.targets == 0)[0][:sample_count],\n", " np.where(X_test.targets == 1)[0][:sample_count],\n", ")\n", "\n", "X_test.data = X_test.data[idx]\n", "X_test.targets = X_test.targets[idx]\n", "\n", "test_loader = torch.utils.data.DataLoader(X_test, batch_size=1, shuffle=True)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "class QuantumFunction(Function):\n", " \"\"\"This class defines the quantum circuit structure, the forward and the backward method.\"\"\"\n", "\n", " def __init__(self, qubit_count: int, hamiltonian: cudaq.SpinOperator):\n", " \"\"\"Define the quantum circuit in CUDA Quantum\"\"\"\n", "\n", " kernel, thetas = cudaq.make_kernel(list)\n", "\n", " self.kernel = kernel\n", " self.theta = thetas\n", " self.hamiltonian = hamiltonian\n", "\n", " qubits = kernel.qalloc(qubit_count)\n", "\n", " self.kernel.h(qubits)\n", "\n", " # Variational gate parameters which are optimised during training.\n", " kernel.ry(thetas[0], qubits[0])\n", " kernel.rx(thetas[1], qubits[0])\n", "\n", " def run(self, thetas: torch.tensor) -> torch.tensor:\n", " \"\"\"Excetute the quantum circuit to output an expectation value\"\"\"\n", "\n", " expectation = torch.tensor(cudaq.observe(self.kernel, spin.z(0),\n", " thetas.tolist()).expectation(),\n", " device=device)\n", "\n", " return expectation" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class QuantumFunction(Function):\n", " \"\"\"Allows the quantum circuit to pass data through it and compute the gradients\"\"\"\n", "\n", " @staticmethod\n", " def forward(ctx, thetas: torch.tensor, quantum_circuit,\n", " shift) -> torch.tensor:\n", "\n", " # Save shift and quantum_circuit in context to use in backward.\n", " ctx.shift = shift\n", " ctx.quantum_circuit = quantum_circuit\n", "\n", " # Calculate expectation value.\n", " expectation = ctx.quantum_circuit.run(thetas)\n", "\n", " ctx.save_for_backward(thetas, expectation)\n", "\n", " return expectation\n", "\n", " @staticmethod\n", " def backward(ctx, grad_output):\n", " \"\"\"Backward pass computation via finite difference parameter shift\"\"\"\n", "\n", " thetas, expectation = ctx.saved_tensors\n", "\n", " gradients = torch.zeros(len(thetas), device=device)\n", "\n", " for i in range(len(thetas)):\n", " shift_right = torch.clone(thetas)\n", "\n", " shift_right[i] += ctx.shift\n", "\n", " shift_left = torch.clone(thetas)\n", "\n", " shift_left[i] -= ctx.shift\n", "\n", " expectation_right = ctx.quantum_circuit.run(shift_right)\n", " expectation_left = ctx.quantum_circuit.run(shift_left)\n", "\n", " gradients[i] = (expectation_right -\n", " expectation_left) / 2 * ctx.shift\n", "\n", " return gradients * grad_output.float(), None, None" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "class QuantumLayer(nn.Module):\n", " \"\"\"Encapsulates a quantum circuit and a quantum function into a quantum layer\"\"\"\n", "\n", " def __init__(self, qubit_count: int, hamiltonian, shift: torch.tensor):\n", " super(QuantumLayer, self).__init__()\n", "\n", " # 1 qubit quantum circuit.\n", " self.quantum_circuit = QuantumFunction(qubit_count, hamiltonian)\n", " self.shift = shift\n", "\n", " def forward(self, input):\n", " ans = QuantumFunction.apply(input, self.quantum_circuit, self.shift)\n", "\n", " return ans" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "qubit_count = 1\n", "hamiltonian = spin.z(0)\n", "shift = torch.tensor(np.pi / 2)\n", "\n", "\n", "class Net(nn.Module):\n", "\n", " def __init__(self):\n", " super(Net, self).__init__()\n", "\n", " # Neural network structure.\n", " self.conv1 = nn.Conv2d(1, 6, kernel_size=5)\n", " self.conv2 = nn.Conv2d(6, 16, kernel_size=5)\n", " self.dropout = nn.Dropout2d()\n", " self.fc1 = nn.Linear(256, 64)\n", " self.fc2 = nn.Linear(\n", " 64, 2\n", " ) # Output a 2D tensor since we have 2 variational parameters in our quantum circuit.\n", " self.hybrid = QuantumLayer(\n", " qubit_count, hamiltonian, shift\n", " ) # Input is the magnitude of the parameter shifts to calculate gradients.\n", "\n", " def forward(self, x):\n", " x = F.relu(self.conv1(x))\n", " x = F.max_pool2d(x, 2)\n", " x = F.relu(self.conv2(x))\n", " x = F.max_pool2d(x, 2)\n", " x = self.dropout(x)\n", " x = x.view(1, -1)\n", " x = F.relu(self.fc1(x))\n", " x = self.fc2(x).reshape(\n", " -1\n", " ) # Reshapes required to satisfy input dimensions to CUDA Quantum.\n", " x = self.hybrid(x).reshape(-1)\n", "\n", " return torch.cat((x, 1 - x), -1).unsqueeze(0)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training [5%]\tLoss: -1.1656\n", "Training [10%]\tLoss: -1.3695\n", "Training [15%]\tLoss: -1.4121\n", "Training [20%]\tLoss: -1.4068\n", "Training [25%]\tLoss: -1.4315\n", "Training [30%]\tLoss: -1.4356\n", "Training [35%]\tLoss: -1.4486\n", "Training [40%]\tLoss: -1.4598\n", "Training [45%]\tLoss: -1.4571\n", "Training [50%]\tLoss: -1.4700\n", "Training [55%]\tLoss: -1.4790\n", "Training [60%]\tLoss: -1.4798\n", "Training [65%]\tLoss: -1.4854\n", "Training [70%]\tLoss: -1.4866\n", "Training [75%]\tLoss: -1.4916\n", "Training [80%]\tLoss: -1.4938\n", "Training [85%]\tLoss: -1.4896\n", "Training [90%]\tLoss: -1.4874\n", "Training [95%]\tLoss: -1.4941\n", "Training [100%]\tLoss: -1.4970\n" ] } ], "source": [ "# We move our model to the CUDA device to minimise data transfer between GPU and CPU.\n", "\n", "model = Net().to(device)\n", "\n", "optimizer = optim.Adam(model.parameters(), lr=0.001)\n", "\n", "loss_func = nn.NLLLoss().to(device)\n", "\n", "epochs = 20\n", "\n", "epoch_loss = []\n", "\n", "model.train()\n", "for epoch in range(epochs):\n", " batch_loss = 0.0\n", " for batch_idx, (data, target) in enumerate(train_loader): # Batch training.\n", " optimizer.zero_grad()\n", "\n", " data, target = data.to(device), target.to(device)\n", "\n", " # Forward pass.\n", " output = model(data).to(device)\n", "\n", " # Calculating loss.\n", " loss = loss_func(output, target).to(device)\n", "\n", " # Backward pass.\n", " loss.backward()\n", "\n", " # Optimize the weights.\n", " optimizer.step()\n", "\n", " batch_loss += loss.item()\n", "\n", " epoch_loss.append(batch_loss / batch_idx)\n", "\n", " print(\"Training [{:.0f}%]\\tLoss: {:.4f}\".format(\n", " 100.0 * (epoch + 1) / epochs, epoch_loss[-1]))" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Neg Log Likelihood Loss')" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(epoch_loss)\n", "plt.title(\"Hybrid NN Training Convergence\")\n", "plt.xlabel(\"Training Iterations\")\n", "\n", "plt.ylabel(\"Neg Log Likelihood Loss\")" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "# Testing on the test set.\n", "\n", "model.eval()\n", "with torch.no_grad():\n", " correct = 0\n", " for batch_idx, (data, target) in enumerate(test_loader):\n", " data, target = data.to(device), target.to(device)\n", "\n", " output = model(data).to(device)\n", "\n", " pred = output.argmax(dim=1, keepdim=True)\n", " correct += pred.eq(target.view_as(pred)).sum().item()\n", "\n", " loss = loss_func(output, target)\n", " epoch_loss.append(loss.item())\n", "\n", " print(\"Performance on test data:\\n\\tAccuracy: {:.1f}%\".format(\n", " correct / len(test_loader) * 100))" ] } ], "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.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" } } }, "nbformat": 4, "nbformat_minor": 2 }