[404218]: / Code / QML Parameters for Breakthrough Parallel Algorithms / Dataset 0.2 Noise 1-8 Layers / 10Q3L 4RYe1RLRYt 0.2Noise LR.52B5 0.0537, 96.0% 4.4GB 78.23s kkawchak.ipynb

Download this file

688 lines (688 with data), 75.0 kB

{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": 87,
      "metadata": {
        "id": "gDf49Zs-YG8S",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "d4d370ab-f526-41e9-8a7e-39b6caf48cab"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Time in seconds since beginning of run: 1704338037.6588562\n",
            "Thu Jan  4 03:13:57 2024\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",
        "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": "m4zD_EUsYG8Y"
      },
      "source": [
        "Turning quantum nodes into Torch Layers\n",
        "=======================================\n",
        "\n",
        "::: {.meta}\n",
        ":property=\\\"og:description\\\": Learn how to create hybrid ML models in\n",
        "PennyLane using Torch :property=\\\"og:image\\\":\n",
        "<https://pennylane.ai/qml/_images/PyTorch_icon.png>\n",
        ":::\n",
        "\n",
        "::: {.related}\n",
        "tutorial\\_qnn\\_module\\_tf Turning quantum nodes into Keras Layers\n",
        ":::\n",
        "\n",
        "*Author: Tom Bromley --- Posted: 02 November 2020. Last updated: 28\n",
        "January 2021.*\n",
        "\n",
        "Creating neural networks in [PyTorch](https://pytorch.org/) is easy\n",
        "using the [nn module](https://pytorch.org/docs/stable/nn.html). Models\n",
        "are constructed from elementary *layers* and can be trained using the\n",
        "PyTorch API. For example, the following code defines a two-layer network\n",
        "that could be used for binary classification:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 88,
      "metadata": {
        "id": "ELkMIhhMYG8c"
      },
      "outputs": [],
      "source": [
        "from pennylane import broadcast\n",
        "import torch\n",
        "from torch.utils.data import DataLoader, TensorDataset\n",
        "\n",
        "layer_1 = torch.nn.Linear(2, 2)\n",
        "layer_2 = torch.nn.Linear(2, 2)\n",
        "softmax = torch.nn.Softmax(dim=1)\n",
        "\n",
        "layers = [layer_1, layer_2, softmax]\n",
        "model = torch.nn.Sequential(*layers)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ym9INdgjYG8d"
      },
      "source": [
        "**What if we want to add a quantum layer to our model?** This is\n",
        "possible in PennyLane:\n",
        "`QNodes <../glossary/hybrid_computation>`{.interpreted-text role=\"doc\"}\n",
        "can be converted into `torch.nn` layers and combined with the wide range\n",
        "of built-in classical [layers](https://pytorch.org/docs/stable/nn.html)\n",
        "to create truly hybrid models. This tutorial will guide you through a\n",
        "simple example to show you how it\\'s done!\n",
        "\n",
        "::: {.note}\n",
        "::: {.title}\n",
        "Note\n",
        ":::\n",
        "\n",
        "A similar demo explaining how to\n",
        "`turn quantum nodes into Keras layers <tutorial_qnn_module_tf>`{.interpreted-text\n",
        "role=\"doc\"} is also available.\n",
        ":::\n",
        "\n",
        "Fixing the dataset and problem\n",
        "==============================\n",
        "\n",
        "Let us begin by choosing a simple dataset and problem to allow us to\n",
        "focus on how the hybrid model is constructed. Our objective is to\n",
        "classify points generated from scikit-learn\\'s binary-class\n",
        "[make\\_moons()](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_moons.html)\n",
        "dataset:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 89,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 406
        },
        "id": "DJnE2JiyYG8d",
        "outputId": "7adc8c20-d5c0-4504-9d3b-f5e87928f0a5"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ],
            "image/png": "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\n"
          },
          "metadata": {}
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from sklearn.datasets import make_moons\n",
        "\n",
        "# Set random seeds\n",
        "torch.manual_seed(42)\n",
        "np.random.seed(42)\n",
        "\n",
        "X, y = make_moons(n_samples=200, noise=0.2)\n",
        "y_ = torch.unsqueeze(torch.tensor(y), 1)  # used for one-hot encoded labels\n",
        "y_hot = torch.scatter(torch.zeros((200, 2)), 1, y_, 1)\n",
        "\n",
        "c = [\"#1f77b4\" if y_ == 0 else \"#ff7f0e\" for y_ in y]  # colours for each class\n",
        "plt.axis(\"off\")\n",
        "plt.scatter(X[:, 0], X[:, 1], c=c)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QwENIn2CYG8e"
      },
      "source": [
        "Defining a QNode\n",
        "================\n",
        "\n",
        "Our next step is to define the QNode that we want to interface with\n",
        "`torch.nn`. Any combination of device, operations and measurements that\n",
        "is valid in PennyLane can be used to compose the QNode. However, the\n",
        "QNode arguments must satisfy additional `conditions\n",
        "<code/api/pennylane.qnn.TorchLayer>`{.interpreted-text role=\"doc\"}\n",
        "including having an argument called `inputs`. All other arguments must\n",
        "be arrays or tensors and are treated as trainable weights in the model.\n",
        "We fix a two-qubit QNode using the\n",
        "`default.qubit <code/api/pennylane.devices.default_qubit.DefaultQubit>`{.interpreted-text\n",
        "role=\"doc\"} simulator and operations from the\n",
        "`templates <introduction/templates>`{.interpreted-text role=\"doc\"}\n",
        "module.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 90,
      "metadata": {
        "id": "TO-sU02MYG8f"
      },
      "outputs": [],
      "source": [
        "import pennylane as qml\n",
        "\n",
        "n_qubits = 10\n",
        "dev = qml.device(\"default.qubit\", wires=n_qubits)\n",
        "\n",
        "@qml.qnode(dev)\n",
        "def qnode(inputs, weights):\n",
        "    qml.AngleEmbedding(inputs, wires=range(n_qubits), rotation='Y')\n",
        "    qml.AngleEmbedding(inputs, wires=range(n_qubits), rotation='Y')\n",
        "    qml.AngleEmbedding(inputs, wires=range(n_qubits), rotation='Y')\n",
        "    qml.AngleEmbedding(inputs, wires=range(n_qubits), rotation='Y')\n",
        "    qml.RandomLayers(weights, wires=range(n_qubits), ratio_imprim=0, rotations=[qml.RY], seed=42)\n",
        "    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]\n",
        "weights = np.array([1,2,3,4,5,6,7,8,9,10]);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vwo-ucc4YG8g"
      },
      "source": [
        "Interfacing with Torch\n",
        "======================\n",
        "\n",
        "With the QNode defined, we are ready to interface with `torch.nn`. This\n",
        "is achieved using the `~pennylane.qnn.TorchLayer`{.interpreted-text\n",
        "role=\"class\"} class of the `~pennylane.qnn`{.interpreted-text\n",
        "role=\"mod\"} module, which converts the QNode to the elementary building\n",
        "block of `torch.nn`: a *layer*. We shall see in the following how the\n",
        "resultant layer can be combined with other well-known neural network\n",
        "layers to form a hybrid model.\n",
        "\n",
        "We must first define the `weight_shapes` dictionary. Recall that all of\n",
        "the arguments of the QNode (except the one named `inputs`) are treated\n",
        "as trainable weights. For the QNode to be successfully converted to a\n",
        "layer in `torch.nn`, we need to provide the details of the shape of each\n",
        "trainable weight for them to be initialized. The `weight_shapes`\n",
        "dictionary maps from the argument names of the QNode to corresponding\n",
        "shapes:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 91,
      "metadata": {
        "id": "O-RFeXqmYG8h"
      },
      "outputs": [],
      "source": [
        "n_layers = 1\n",
        "weight_shapes = {\"weights\": (n_layers, n_qubits)}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1uMLnLnGYG8h"
      },
      "source": [
        "In our example, the `weights` argument of the QNode is trainable and has\n",
        "shape given by `(n_layers, n_qubits)`, which is passed to\n",
        "`~pennylane.templates.layers.BasicEntanglerLayers`{.interpreted-text\n",
        "role=\"func\"}.\n",
        "\n",
        "Now that `weight_shapes` is defined, it is easy to then convert the\n",
        "QNode:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 92,
      "metadata": {
        "id": "m5fcIRAWYG8i"
      },
      "outputs": [],
      "source": [
        "qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "p9-wiN-0YG8i"
      },
      "source": [
        "With this done, the QNode can now be treated just like any other\n",
        "`torch.nn` layer and we can proceed using the familiar Torch workflow.\n",
        "\n",
        "Creating a hybrid model\n",
        "=======================\n",
        "\n",
        "Let\\'s create a basic three-layered hybrid model consisting of:\n",
        "\n",
        "1.  a 2-neuron fully connected classical layer\n",
        "2.  our 2-qubit QNode converted into a layer\n",
        "3.  another 2-neuron fully connected classical layer\n",
        "4.  a softmax activation to convert to a probability vector\n",
        "\n",
        "A diagram of the model can be seen in the figure below.\n",
        "\n",
        "![](/demonstrations/qnn_module/qnn_torch.png){.align-center\n",
        "width=\"100.0%\"}\n",
        "\n",
        "We can construct the model using the\n",
        "[Sequential](https://pytorch.org/docs/stable/generated/torch.nn.Sequential.html)\n",
        "API:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 93,
      "metadata": {
        "id": "Pgc-TkPpYG8j"
      },
      "outputs": [],
      "source": [
        "clayer_1 = torch.nn.Linear(2, 10)\n",
        "clayer_2 = torch.nn.Linear(10, 2)\n",
        "softmax = torch.nn.Softmax(dim=1)\n",
        "layers = [clayer_1, qlayer, clayer_2, softmax]\n",
        "model = torch.nn.Sequential(*layers)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "suZkU2tEYG8j"
      },
      "source": [
        "Training the model\n",
        "==================\n",
        "\n",
        "We can now train our hybrid model on the classification dataset using\n",
        "the usual Torch approach. We\\'ll use the standard\n",
        "[SGD](https://pytorch.org/docs/stable/optim.html#torch.optim.SGD)\n",
        "optimizer and the mean absolute error loss function:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 94,
      "metadata": {
        "id": "57MwgNnEYG8j"
      },
      "outputs": [],
      "source": [
        "opt = torch.optim.SGD(model.parameters(), lr=0.52)\n",
        "loss_func = torch.nn.L1Loss()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6Y_yohUlYG8j"
      },
      "source": [
        "Note that there are more advanced combinations of optimizer and loss\n",
        "function, but here we are focusing on the basics.\n",
        "\n",
        "The model is now ready to be trained!\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 95,
      "metadata": {
        "id": "NtA9KgZ-YG8k",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "96b44d5a-b38e-47c8-d037-d59284da6d31"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Train - Epoch 1: Loss: 0.2163\n",
            "Train Accuracy: 90.00%\n",
            "Test - Epoch 1: Loss: 0.1213\n",
            "Test Accuracy: 90.00%\n",
            "Train - Epoch 2: Loss: 0.1818\n",
            "Train Accuracy: 77.50%\n",
            "Test - Epoch 2: Loss: 0.2371\n",
            "Test Accuracy: 77.50%\n",
            "Train - Epoch 3: Loss: 0.1593\n",
            "Train Accuracy: 91.50%\n",
            "Test - Epoch 3: Loss: 0.1407\n",
            "Test Accuracy: 91.50%\n",
            "Train - Epoch 4: Loss: 0.1418\n",
            "Train Accuracy: 91.50%\n",
            "Test - Epoch 4: Loss: 0.0931\n",
            "Test Accuracy: 91.50%\n",
            "Train - Epoch 5: Loss: 0.1391\n",
            "Train Accuracy: 91.00%\n",
            "Test - Epoch 5: Loss: 0.0984\n",
            "Test Accuracy: 91.00%\n",
            "Train - Epoch 6: Loss: 0.1476\n",
            "Train Accuracy: 89.50%\n",
            "Test - Epoch 6: Loss: 0.1213\n",
            "Test Accuracy: 89.50%\n"
          ]
        }
      ],
      "source": [
        "X = torch.tensor(X, requires_grad=True).float()\n",
        "y_hot = y_hot.float()\n",
        "\n",
        "batch_size = 5\n",
        "batches = 200 // batch_size\n",
        "\n",
        "train_dataset = TensorDataset(X, y_hot)\n",
        "train_loader = DataLoader(train_dataset, batch_size=5, shuffle=True, drop_last=True)\n",
        "\n",
        "epochs = 6\n",
        "\n",
        "for epoch in range(epochs):\n",
        "    train_running_loss = 0\n",
        "\n",
        "    for xs, ys in train_loader:\n",
        "        opt.zero_grad()\n",
        "\n",
        "        loss_evaluated = loss_func(model(xs), ys)\n",
        "        loss_evaluated.backward()\n",
        "\n",
        "        opt.step()\n",
        "\n",
        "        train_running_loss += loss_evaluated.item()\n",
        "\n",
        "    train_avg_loss = train_running_loss / batches\n",
        "    print(f\"Train - Epoch {epoch + 1}: Loss: {train_avg_loss:.4f}\")\n",
        "\n",
        "    # Calculate train accuracy\n",
        "    y_pred_train = model(X)\n",
        "    predictions_train = torch.argmax(y_pred_train, axis=1).detach().numpy()\n",
        "\n",
        "    correct_train = [1 if p == p_true else 0 for p, p_true in zip(predictions_train, y)]\n",
        "    train_accuracy = sum(correct_train) / len(correct_train)\n",
        "    print(f\"Train Accuracy: {train_accuracy * 100:.2f}%\")\n",
        "\n",
        "    # Calculate test loss and accuracy\n",
        "    test_loss = loss_func(model(X), y_hot).item()\n",
        "    print(f\"Test - Epoch {epoch + 1}: Loss: {test_loss:.4f}\")\n",
        "\n",
        "    y_pred_test = model(X)\n",
        "    predictions_test = torch.argmax(y_pred_test, axis=1).detach().numpy()\n",
        "\n",
        "    correct_test = [1 if p == p_true else 0 for p, p_true in zip(predictions_test, y)]\n",
        "    test_accuracy = sum(correct_test) / len(correct_test)\n",
        "    print(f\"Test Accuracy: {test_accuracy * 100:.2f}%\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "orKWhPb5YG8k"
      },
      "source": [
        "How did we do? The model looks to have successfully trained and the\n",
        "accuracy is reasonably high. In practice, we would aim to push the\n",
        "accuracy higher by thinking carefully about the model design and the\n",
        "choice of hyperparameters such as the learning rate.\n",
        "\n",
        "Creating non-sequential models\n",
        "==============================\n",
        "\n",
        "The model we created above was composed of a sequence of classical and\n",
        "quantum layers. This type of model is very common and is suitable in a\n",
        "lot of situations. However, in some cases we may want a greater degree\n",
        "of control over how the model is constructed, for example when we have\n",
        "multiple inputs and outputs or when we want to distribute the output of\n",
        "one layer into multiple subsequent layers.\n",
        "\n",
        "Suppose we want to make a hybrid model consisting of:\n",
        "\n",
        "1.  a 4-neuron fully connected classical layer\n",
        "2.  a 2-qubit quantum layer connected to the first two neurons of the\n",
        "    previous classical layer\n",
        "3.  a 2-qubit quantum layer connected to the second two neurons of the\n",
        "    previous classical layer\n",
        "4.  a 2-neuron fully connected classical layer which takes a\n",
        "    4-dimensional input from the combination of the previous quantum\n",
        "    layers\n",
        "5.  a softmax activation to convert to a probability vector\n",
        "\n",
        "A diagram of the model can be seen in the figure below.\n",
        "\n",
        "![](/demonstrations/qnn_module/qnn2_torch.png){.align-center\n",
        "width=\"100.0%\"}\n",
        "\n",
        "This model can also be constructed by creating a new class that inherits\n",
        "from the `torch.nn`\n",
        "[Module](https://pytorch.org/docs/stable/nn.html#torch.nn.Module) and\n",
        "overriding the `forward()` method:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 96,
      "metadata": {
        "id": "3nRj296bYG8k"
      },
      "outputs": [],
      "source": [
        "class HybridModel(torch.nn.Module):\n",
        "    def __init__(self):\n",
        "        super().__init__()\n",
        "        self.clayer_1 = torch.nn.Linear(2, 30)\n",
        "        self.qlayer_1 = qml.qnn.TorchLayer(qnode, weight_shapes)\n",
        "        self.qlayer_2 = qml.qnn.TorchLayer(qnode, weight_shapes)\n",
        "        self.qlayer_3 = qml.qnn.TorchLayer(qnode, weight_shapes)\n",
        "        self.clayer_2 = torch.nn.Linear(30, 2)\n",
        "        self.softmax = torch.nn.Softmax(dim=1)\n",
        "\n",
        "    def forward(self, x):\n",
        "        x = self.clayer_1(x)\n",
        "        x_1, x_2, x_3 = torch.split(x, 10, dim=1)\n",
        "        x_1 = self.qlayer_1(x_1)\n",
        "        x_2 = self.qlayer_2(x_2)\n",
        "        x_3 = self.qlayer_3(x_3)\n",
        "        x = torch.cat([x_1, x_2, x_3], axis=1)\n",
        "        x = self.clayer_2(x)\n",
        "        return self.softmax(x)\n",
        "\n",
        "model = HybridModel()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XU2-uOcYYG8l"
      },
      "source": [
        "As a final step, let\\'s train the model to check if it\\'s working:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 97,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "id": "0TOzLpJBYG8l",
        "outputId": "de14a29a-1246-4058-bdb2-13a1f35b3e58"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Train - Epoch 1: Loss: 0.1303\n",
            "Train Accuracy: 93.50%\n",
            "Test - Epoch 1: Loss: 0.0775\n",
            "Test Accuracy: 93.50%\n",
            "Train - Epoch 2: Loss: 0.0562\n",
            "Train Accuracy: 97.00%\n",
            "Test - Epoch 2: Loss: 0.0445\n",
            "Test Accuracy: 97.00%\n",
            "Train - Epoch 3: Loss: 0.0592\n",
            "Train Accuracy: 96.50%\n",
            "Test - Epoch 3: Loss: 0.0430\n",
            "Test Accuracy: 96.50%\n",
            "Train - Epoch 4: Loss: 0.0509\n",
            "Train Accuracy: 96.50%\n",
            "Test - Epoch 4: Loss: 0.0430\n",
            "Test Accuracy: 96.50%\n",
            "Train - Epoch 5: Loss: 0.0493\n",
            "Train Accuracy: 97.00%\n",
            "Test - Epoch 5: Loss: 0.0356\n",
            "Test Accuracy: 97.00%\n",
            "Train - Epoch 6: Loss: 0.0502\n",
            "Train Accuracy: 96.00%\n",
            "Test - Epoch 6: Loss: 0.0537\n",
            "Test Accuracy: 96.00%\n"
          ]
        }
      ],
      "source": [
        "opt = torch.optim.SGD(model.parameters(), lr=0.52)\n",
        "epochs = 6\n",
        "\n",
        "for epoch in range(epochs):\n",
        "    train_running_loss = 0\n",
        "\n",
        "    for xs, ys in train_loader:\n",
        "        opt.zero_grad()\n",
        "\n",
        "        loss_evaluated = loss_func(model(xs), ys)\n",
        "        loss_evaluated.backward()\n",
        "\n",
        "        opt.step()\n",
        "\n",
        "        train_running_loss += loss_evaluated.item()\n",
        "\n",
        "    train_avg_loss = train_running_loss / batches\n",
        "    print(f\"Train - Epoch {epoch + 1}: Loss: {train_avg_loss:.4f}\")\n",
        "\n",
        "    # Calculate train accuracy\n",
        "    y_pred_train = model(X)\n",
        "    predictions_train = torch.argmax(y_pred_train, axis=1).detach().numpy()\n",
        "\n",
        "    correct_train = [1 if p == p_true else 0 for p, p_true in zip(predictions_train, y)]\n",
        "    train_accuracy = sum(correct_train) / len(correct_train)\n",
        "    print(f\"Train Accuracy: {train_accuracy * 100:.2f}%\")\n",
        "\n",
        "    # Calculate test loss and accuracy\n",
        "    test_loss = loss_func(model(X), y_hot).item()\n",
        "    print(f\"Test - Epoch {epoch + 1}: Loss: {test_loss:.4f}\")\n",
        "\n",
        "    y_pred_test = model(X)\n",
        "    predictions_test = torch.argmax(y_pred_test, axis=1).detach().numpy()\n",
        "\n",
        "    correct_test = [1 if p == p_true else 0 for p, p_true in zip(predictions_test, y)]\n",
        "    test_accuracy = sum(correct_test) / len(correct_test)\n",
        "    print(f\"Test Accuracy: {test_accuracy * 100:.2f}%\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5hYXv8CkYG8l"
      },
      "source": [
        "Great! We\\'ve mastered the basics of constructing hybrid\n",
        "classical-quantum models using PennyLane and Torch. Can you think of any\n",
        "interesting hybrid models to construct? How do they perform on realistic\n",
        "datasets?\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9QsGJNbCYG8m"
      },
      "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": "MNv-uf3nGrec",
        "outputId": "27738d87-670b-4c78-814d-7ca6c7af1e94"
      },
      "execution_count": 98,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Time in seconds since end of run: 1704338115.8901455\n",
            "Thu Jan  4 03:15:15 2024\n"
          ]
        }
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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.18"
    },
    "colab": {
      "provenance": [],
      "machine_shape": "hm"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}