{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "collapsed_sections": [
        "jSQDiMIhbWmP",
        "xje-YG2fT-ju",
        "5t3J6XHVUq3u",
        "9AZH8eNEWknq",
        "JLj6FuzZaJ2A",
        "OnfR7jouXlEj",
        "BMCZdLB-cmd_",
        "uqRtfMjpFrLD",
        "Oz7Q18UjsV0t",
        "0EjQH5Eb6fX1"
      ],
      "gpuType": "T4"
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Pytorch Bootcamp\n",
        "Pytorch is an optimised tensor manipulation library that offers an array of packages for deep learning. As compared to static frameworks such as Theano, Caffe and Tensorflow, Pytorch is in the family of dynamic frameworks, which does not require pre-defined computational graphs. This allows for a more flexible, imperative style of development, as it does not require the computational graphs to be first declared, compiled, and then excuted. However, this is potentially at the cost of computational efficiency, which makes it not as advantageous for production and mobile settings, but extremely useful during research and development.\n",
        "\n",
        "#### Overview:\n",
        "1. Installation\n",
        "2. Tensor Basics\n",
        "  - Create\n",
        "  - Operations\n",
        "  - GPU Support\n",
        "3. Autograd\n",
        "  - Linear regression example\n",
        "4. Training Loop with: Model, Loss & Optimizer\n",
        "  - A typical PyTorch training pipeline\n",
        "5. Neural Network\n",
        "  - GPU, Datasets, DataLoader, & Evaluation, Save/Load model\n",
        "6. Exericse: Implement a three-layer PyTorch model\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "mA134xAhMheK"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 1. Installation\n",
        "https://pytorch.org/\n",
        "\n",
        "pytorch is pre-installed in Colab\n",
        "\n",
        "\n"
      ],
      "metadata": {
        "id": "0U3WLWAINeuK"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KGhte3jhMF1Y"
      },
      "outputs": [],
      "source": [
        "import torch"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "torch.cuda.is_available()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VQ8Hj8J7MVA0",
        "outputId": "f3bab5b9-225b-442b-fc2b-c8eae5d82426"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "metadata": {},
          "execution_count": 2
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2. Tensor Basics\n",
        "\n",
        "Everything in PyTorch is based on tensor operations. A tensor is a mathematical object holding some multidimensional data.\n",
        "\n",
        "![image.png](data:image/png;base64,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)\n",
        "\n",
        "- A tensor of order zero is just a number, or a scalar.\n",
        "- A tensor of order one (1st-order tensor) is an array of numbers, or a vector.\n",
        "- A tensor of order two (2nd-order tensor) is an array of vectors, or a matrix.\n",
        "- A tensor of order n (nth-order tensor) is a generalised n-dimensional array of scalars.\n",
        "\n",
        "In NLP (especially when using deep learning), we often work with 3-dimensional tensors, let’s say:\n",
        "\n",
        "1. Batch size = 16 (16 sentences),\n",
        "\n",
        "2. Sequence length = 10 (each sentence is padded/truncated to 10 tokens),\n",
        "\n",
        "3. Embedding size = 100 (each word/token is represented as a 100-dim vector)\n",
        "\n",
        "Then the tensor shape is:\n",
        "(16, 10, 100)\n",
        "\n",
        "\n",
        "This means:\n",
        "\n",
        "16 sentences\n",
        "\n",
        "Each has 10 tokens\n",
        "\n",
        "Each token is a 100-dimensional vector"
      ],
      "metadata": {
        "id": "O1ePRBhNNlYe"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2.1. Create\n",
        "\n"
      ],
      "metadata": {
        "id": "vCqrAmNEKfXe"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 2.1.1 Creating Tensors from Pytorch Functions\n",
        "Using `tensor()` you can create either a scalar or a tensor. PyTorch’s tensors have equivalent functions as its NumPy counterparts, like `ones()`, `zeros()`, `rand()`, `randn()` and many more.\n",
        "\n",
        "In the example below, we create one of each: scalar, vector, matrix, and tensor—or, saying it differently, one scalar and three tensors."
      ],
      "metadata": {
        "id": "jSQDiMIhbWmP"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "\n",
        "scalar = torch.tensor(3.14159)\n",
        "vector = torch.tensor([1, 2, 3])\n",
        "matrix_1 = torch.tensor([[1,2,3], [4,5,6]], dtype=torch.float) # fill with 1\n",
        "matrix_2 = torch.ones((2, 3), dtype=torch.float) # fill with 1\n",
        "# two (2) 3x4 matrices\n",
        "tensor = torch.randn((2, 3, 4), dtype=torch.float)\n",
        "print(scalar)\n",
        "print(vector)\n",
        "print(matrix_1)\n",
        "print(matrix_2)\n",
        "print(tensor)\n",
        "\n",
        "# print(torch.rand(2,3))   # uniform random\n",
        "# print(torch.randn(2,3))  # normal random\n",
        "\n",
        "# more info about dtype: https://pytorch.org/docs/stable/tensor_attributes.html"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "l8UNS3GRPCLw",
        "outputId": "d18d0413-5dd0-4a14-9746-26bb044617b9"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor(3.1416)\n",
            "tensor([1, 2, 3])\n",
            "tensor([[1., 2., 3.],\n",
            "        [4., 5., 6.]])\n",
            "tensor([[1., 1., 1.],\n",
            "        [1., 1., 1.]])\n",
            "tensor([[[ 1.2480,  0.0537,  0.6047, -1.4945],\n",
            "         [-1.6687, -0.2707, -0.5521,  1.3663],\n",
            "         [ 0.1751,  0.3124, -0.3634,  0.6776]],\n",
            "\n",
            "        [[-1.0635, -0.3358, -0.2567,  0.4393],\n",
            "         [ 1.2063, -0.5533, -2.0668,  0.5643],\n",
            "         [ 0.8941,  1.2055,  0.2208, -0.1412]]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Note: The torch.empty() function in PyTorch allocates memory for a tensor but does not initialize its values.\n",
        "# The values you see are just whatever was already in memory at those locations — they are essentially garbage values or random leftovers.\n",
        "print(torch.empty(2, 3))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "558LjmLyMPdX",
        "outputId": "391201f4-978e-4814-8fde-e312f239a4fc"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[ 2.6905e-43,  0.0000e+00,  3.5873e-43],\n",
            "        [ 0.0000e+00, -9.5477e-35,  4.3192e-41]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# torch.tensor() v.s. torch.Tensor()\n",
        "\n",
        "print(torch.Tensor([2,3]))\n",
        "print(torch.tensor([2,3]))\n",
        "\n",
        "# You might have noted that we can create tensors using both torch.tensor() and torch.Tensor(), note the subtle difference between the case of letter “t”.\n",
        "\n",
        "# torch.Tensor is an alias for torch.FloatTensor, which creates tensors of float type.\n",
        "\n",
        "# torch.tensor on the other hand, infers the dtype automatically, and allows explicit specification of dtype during creation.\n",
        "\n",
        "# So let’s stick to torch.tensor instead."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "C937RjVhN4ot",
        "outputId": "d8487c95-a887-4a35-f419-dc48643f2f53"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([2., 3.])\n",
            "tensor([2, 3])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# check size\n",
        "x = torch.zeros(5, 3) # fill with 0\n",
        "print(x)\n",
        "print(\"size\", x.size())  # x.size(0)\n",
        "print(\"shape\", x.shape)  # x.shape[0]\n",
        "#.shape is a Property; .size() is a function\n",
        "#.shape index like a tuple, e.g. .shape[0]; .size(0)\n",
        "print(\"size(0)\", x.size(0))\n",
        "print(\"shape[0]\", x.shape[0])"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "sOTxEKR_SXjt",
        "outputId": "02012ae0-66ea-4498-bfaa-498b53186812"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.]])\n",
            "size torch.Size([5, 3])\n",
            "shape torch.Size([5, 3])\n",
            "size(0) 5\n",
            "shape[0] 5\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# check data type\n",
        "print(x.dtype)\n",
        "\n",
        "# specify types, float32 default\n",
        "x = torch.zeros(5, 3, dtype=torch.float16)\n",
        "print(x)\n",
        "\n",
        "# check type\n",
        "print(x.dtype)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "g0STMbuOSobw",
        "outputId": "2190d9af-9391-409c-96f7-54950343a6db"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "torch.float32\n",
            "tensor([[0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.],\n",
            "        [0., 0., 0.]], dtype=torch.float16)\n",
            "torch.float16\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "def describe(x):\n",
        "    print(\"Type:{}\".format(x.type()))\n",
        "    print(\"Shape:{}\".format(x.shape))\n",
        "    print(\"Size():{}\".format(x.size()))\n",
        "    print(\"Values: \\n{}\".format(x))"
      ],
      "metadata": {
        "id": "_SwVaYe3UXxF"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 2.1.2 Creating Tensors from Lists\n"
      ],
      "metadata": {
        "id": "xje-YG2fT-ju"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "x = torch.tensor([[1, 2, 3],\n",
        "                  [4, 5, 6]])\n",
        "# x = torch.Tensor([[1, 2, 3],\n",
        "#                   [4, 5, 6]])\n",
        "describe(x)\n",
        "# torch.LongTensor is a tensor with 64-bit integer (int64) elements."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "gHKAkgjQUjKP",
        "outputId": "112d005d-0317-4640-9856-1b85859f16ae"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([2, 3])\n",
            "Size():torch.Size([2, 3])\n",
            "Values: \n",
            "tensor([[1, 2, 3],\n",
            "        [4, 5, 6]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 2.1.3 Creating Tensors from Numpy\n",
        "`from_numpy()` automatically inherits input array dtype."
      ],
      "metadata": {
        "id": "5t3J6XHVUq3u"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "import numpy as np\n",
        "\n",
        "a = np.arange(10)\n",
        "print(a)\n",
        "describe(torch.from_numpy(a))\n",
        "print(\"====================\")\n",
        "describe(torch.Tensor(a))\n",
        "print(\"====================\")\n",
        "describe(torch.tensor(a))\n",
        "print(\"====================\")\n",
        "describe(torch.as_tensor(a))\n",
        "\n",
        "# To avoid data copying when converting from NumPy to PyTorch, use torch.from_numpy() or torch.as_tensor()\n",
        "# These create a tensor that shares memory with the original NumPy array.\n",
        "# Changes in one will reflect in the other.\n",
        "\n",
        "# To create a new, independent copy of the NumPy array as a PyTorch tensor, use torch.tensor()\n",
        "# This copies the data into a new tensor.\n",
        "# The resulting tensor is independent of the original NumPy array."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Fb8JBuC9UZoQ",
        "outputId": "25c12dd2-aec5-402b-e662-1b83dfbe1563"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "[0 1 2 3 4 5 6 7 8 9]\n",
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([10])\n",
            "Size():torch.Size([10])\n",
            "Values: \n",
            "tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
            "====================\n",
            "Type:torch.FloatTensor\n",
            "Shape:torch.Size([10])\n",
            "Size():torch.Size([10])\n",
            "Values: \n",
            "tensor([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])\n",
            "====================\n",
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([10])\n",
            "Size():torch.Size([10])\n",
            "Values: \n",
            "tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n",
            "====================\n",
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([10])\n",
            "Size():torch.Size([10])\n",
            "Values: \n",
            "tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Let's see what happens when you modify a after these conversions!\n",
        "import torch\n",
        "import numpy as np\n",
        "\n",
        "# Create a NumPy array\n",
        "a = np.array([1, 2, 3])\n",
        "\n",
        "# Convert to tensor without copying (shares memory)\n",
        "t1 = torch.from_numpy(a)\n",
        "t2 = torch.as_tensor(a)\n",
        "\n",
        "# Convert to tensor with copying (new independent tensor)\n",
        "t3 = torch.tensor(a)\n",
        "\n",
        "# Modify the original NumPy array\n",
        "a[0] = 100\n",
        "\n",
        "# Print the results to observe changes\n",
        "print(\"Original NumPy array (a):\", a)\n",
        "print(\"Tensor t1 (from_numpy):\", t1)\n",
        "print(\"Tensor t2 (as_tensor):\", t2)\n",
        "print(\"Tensor t3 (tensor):\", t3)\n"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "w91HRZEp4EpW",
        "outputId": "19ea611d-28c2-4d0f-e6ee-f955c4678e19"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Original NumPy array (a): [100   2   3]\n",
            "Tensor t1 (from_numpy): tensor([100,   2,   3])\n",
            "Tensor t2 (as_tensor): tensor([100,   2,   3])\n",
            "Tensor t3 (tensor): tensor([1, 2, 3])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# torch to numpy\n",
        "a = torch.ones(5)\n",
        "print(a)\n",
        "\n",
        "# torch to numpy with .numpy()\n",
        "b = a.numpy()\n",
        "print(b)\n",
        "print(type(b))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "oMZU_QGGcG3Z",
        "outputId": "0ba63935-3a3e-4746-d4ba-062ac0a39f01"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([1., 1., 1., 1., 1.])\n",
            "[1. 1. 1. 1. 1.]\n",
            "<class 'numpy.ndarray'>\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 2.1.4 Reshaping a tensor\n",
        "\n",
        "Reshaping tensors is a crucial operation in machine learning and deep learning for several reasons. It helps manipulate data to fit the input requirements of models, change data structures for easier computations, or optimize memory usage. For example,\n",
        "1. matrix multiplication follows the rule: If you want to multiply two matrices, say A of shape (n, m) and B of shape (m, p), the resulting matrix C will have the shape (n, p).\n",
        "2. In data preprocessing, you might want to reshape tensors for augmentation techniques (e.g., flipping, rotating) to enhance model generalization. Reshaping helps adjust data for these transformations.\n",
        "3. Convolutional layers usually require a 4D tensor with shape (batch_size, channels, height, width), while fully connected layers expect 2D tensors with shape (batch_size, features). Reshaping allows you to convert data from one shape to another to fit the model’s requirements.\n",
        "\n",
        "The `view()` method only returns a tensor with the desired shape that shares the underlying data with the original tensor - it DOES NOT create a new, independent, tensor!\n",
        "\n",
        "The `reshape()` method may or may not create a copy! The reasons behind this apparently weird behavior are beyond the scope of this section - but this behavior is the reason why view() is preferred :-)\n",
        "\n",
        "Why does it matter? Using `view()`, we get the same tensor with a different shape, any modification to the reshaped tensor will change the original tensor."
      ],
      "metadata": {
        "id": "9AZH8eNEWknq"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "matrix = torch.ones((2, 3), dtype=torch.float) # fill with 1\n",
        "print(matrix)\n",
        "print(\"============\")\n",
        "# We get a tensor with a different shape but it still is the SAME tensor\n",
        "same_matrix = matrix.view(1, 6)\n",
        "# If we change one of its elements...\n",
        "same_matrix[0, 1] = 2.\n",
        "# It changes both variables: matrix and same_matrix\n",
        "print(matrix)\n",
        "print(same_matrix)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "NjuXHBBvY_oY",
        "outputId": "0993f830-dc29-4b97-d065-9246b4c90a7c"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[1., 1., 1.],\n",
            "        [1., 1., 1.]])\n",
            "============\n",
            "tensor([[1., 2., 1.],\n",
            "        [1., 1., 1.]])\n",
            "tensor([[1., 2., 1., 1., 1., 1.]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "To copy all data into a separate independent tensor in the memory, so future operations will not affect the orignal, we will need to use `clone()` methods."
      ],
      "metadata": {
        "id": "Ar7CJwTSZgeO"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# different_matrix = matrix.clone().detach()\n",
        "# # Now, if we change one of its elements...\n",
        "# different_matrix[0, 1] = 3.\n",
        "\n",
        "# print(matrix)\n",
        "# print(different_matrix)\n",
        "\n",
        "matrix = torch.ones((2, 3), dtype=torch.float) # fill with 1\n",
        "print(matrix)\n",
        "print(\"============\")\n",
        "same_matrix = matrix.view(1, 6).clone()\n",
        "same_matrix[0, 1] = 2.\n",
        "print(matrix)\n",
        "print(same_matrix)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ws0ADS1tZPNe",
        "outputId": "d6aa1312-f35f-4d73-d551-10b0b2034da3"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[1., 1., 1.],\n",
            "        [1., 1., 1.]])\n",
            "============\n",
            "tensor([[1., 1., 1.],\n",
            "        [1., 1., 1.]])\n",
            "tensor([[1., 2., 1., 1., 1., 1.]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 2.1.5 Creating with autograd\n",
        "\n",
        "This will tell pytorch that it will need to calculate the gradients for this tensor later in your optimization steps, i.e. A tensor for a learnable parameter requires a gradient!\n"
      ],
      "metadata": {
        "id": "JLj6FuzZaJ2A"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "x = torch.tensor([5.5, 3], requires_grad=True)\n",
        "print(x)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6rb-CZk-aPj9",
        "outputId": "45206f7a-8c48-41f1-faa4-cc93a6c9a436"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([5.5000, 3.0000], requires_grad=True)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2.2 Operations with Tensors\n"
      ],
      "metadata": {
        "id": "OnfR7jouXlEj"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "x = torch.ones(2,2)\n",
        "y = torch.rand(2,2)\n",
        "\n",
        "z = torch.add(x, y)\n",
        "# to modify th variable y\n",
        "#y.add_(x)\n",
        "\n",
        "print(x)\n",
        "print(y)\n",
        "print(z)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "oQJjUa6bYcVG",
        "outputId": "d12d5d18-91fc-4f1a-fc61-3ea1c4b3765d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[1., 1.],\n",
            "        [1., 1.]])\n",
            "tensor([[0.2330, 0.6463],\n",
            "        [0.9719, 0.2247]])\n",
            "tensor([[1.2330, 1.6463],\n",
            "        [1.9719, 1.2247]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# subtraction\n",
        "z = torch.sub(x, y)\n",
        "\n",
        "# multiplication\n",
        "z = torch.mm(x, y)\n",
        "\n",
        "# division\n",
        "z = torch.div(x, y)"
      ],
      "metadata": {
        "id": "HTzt3bdLY2I-"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# torch.arange() creates LongTensor, for torch.mm(), we need to convert the LongTensor to FloatTensor by using x.float().\n",
        "x1 = torch.arange(6).view(2,3).float()\n",
        "x2 = torch.ones(3,2)\n",
        "print(torch.mm(x1, x2))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "IaGpEqOPjVCw",
        "outputId": "0aff9b79-f74d-48d8-db49-7004dd6a780e"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[ 3.,  3.],\n",
            "        [12., 12.]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# slice\n",
        "x = torch.rand(5, 3)\n",
        "print(x)\n",
        "print(x[:1, :2])\n",
        "print(\"x[:, 0]\", x[:, 0]) # all rows, column 0\n",
        "print(\"x[1, :]\", x[1, :]) # row 1, all columns\n",
        "print(\"x[1, 1]\", x[1, 1]) # element at 1, 1\n",
        "\n",
        "# get the actual value if only 1 element in your tensor\n",
        "print(\"x[1, 1].item()\", x[1, 1].item()) # element at 1, 1"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "NYfXmdUnZE5C",
        "outputId": "819b7dd0-a7d9-43e1-9130-0f1758f2f07e"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([[0.6626, 0.3790, 0.1530],\n",
            "        [0.1616, 0.3276, 0.6448],\n",
            "        [0.5834, 0.7146, 0.7043],\n",
            "        [0.6461, 0.3294, 0.2291],\n",
            "        [0.8493, 0.9888, 0.6061]])\n",
            "tensor([[0.6626, 0.3790]])\n",
            "x[:, 0] tensor([0.6626, 0.1616, 0.5834, 0.6461, 0.8493])\n",
            "x[1, :] tensor([0.1616, 0.3276, 0.6448])\n",
            "x[1, 1] tensor(0.3276)\n",
            "x[1, 1].item() 0.32761436700820923\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# concatenating tensors\n",
        "x = torch.arange(6).view(2,3)\n",
        "describe(x)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "l-sxnuknc6xL",
        "outputId": "51a24ecb-eb0a-466d-99d9-57609cc4733a"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([2, 3])\n",
            "Size():torch.Size([2, 3])\n",
            "Values: \n",
            "tensor([[0, 1, 2],\n",
            "        [3, 4, 5]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# torch.cat() concatenates the given sequence along an existing dimension.\n",
        "describe(torch.cat([x, x], dim=0))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "eeHLpsHjeKu-",
        "outputId": "03599383-c379-422b-cbb7-b1f6ed7b0df5"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([4, 3])\n",
            "Size():torch.Size([4, 3])\n",
            "Values: \n",
            "tensor([[0, 1, 2],\n",
            "        [3, 4, 5],\n",
            "        [0, 1, 2],\n",
            "        [3, 4, 5]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "describe(torch.cat([x, x], dim=1))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "acXAQZo3eMvm",
        "outputId": "09272e57-7125-4516-9387-7e7eb20fb718"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([2, 6])\n",
            "Size():torch.Size([2, 6])\n",
            "Values: \n",
            "tensor([[0, 1, 2, 0, 1, 2],\n",
            "        [3, 4, 5, 3, 4, 5]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# torch.stack() concatenates the given sequence along a new dimension.\n",
        "describe(torch.stack([x, x], dim=0))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "rn-MHCWveVGE",
        "outputId": "8cc92493-3fb5-413c-e2c0-b5c161df4d9f"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([2, 2, 3])\n",
            "Size():torch.Size([2, 2, 3])\n",
            "Values: \n",
            "tensor([[[0, 1, 2],\n",
            "         [3, 4, 5]],\n",
            "\n",
            "        [[0, 1, 2],\n",
            "         [3, 4, 5]]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "describe(torch.stack([x, x], dim=1))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "kkgIK0aleO4Y",
        "outputId": "3d4ff856-9f2f-4347-cbc5-02e7348a2ed6"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.LongTensor\n",
            "Shape:torch.Size([2, 2, 3])\n",
            "Size():torch.Size([2, 2, 3])\n",
            "Values: \n",
            "tensor([[[0, 1, 2],\n",
            "         [0, 1, 2]],\n",
            "\n",
            "        [[3, 4, 5],\n",
            "         [3, 4, 5]]])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2.3 GPU Support\n",
        "By default all tensors are created on the CPU. But we can also move them to the GPU (if it's available ), or create them directly on the GPU."
      ],
      "metadata": {
        "id": "BMCZdLB-cmd_"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
        "print(device)\n",
        "# So, if you don’t have a GPU, your device is called cpu. If you do have a GPU, your device is called cuda or cuda:0.\n",
        "\n",
        "#If you have multiple GPUs, and want to check how many GPUs it has, or which model they are, you can figure it out using cuda.device_count() and cuda.get_device_name():\n",
        "n_cudas = torch.cuda.device_count()\n",
        "for i in range(n_cudas):\n",
        "    print(torch.cuda.get_device_name(i))\n"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "htaTYJfccs2o",
        "outputId": "35e1b4a2-375a-4654-a123-fe88ab832521"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "cuda\n",
            "Tesla T4\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "x = torch.rand(2,2).to(device)  # move tensors to GPU device\n",
        "describe(x)\n",
        "print(\"=========\")\n",
        "#x = x.to(\"cpu\")\n",
        "#x = x.to(\"cuda\")\n",
        "\n",
        "x = torch.rand(3,2, device=device)  # or directy create them on GPU\n",
        "describe(x)\n"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "QLWfBmcLj6W-",
        "outputId": "c69b6b1a-559e-4921-e738-f08290999b87"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.cuda.FloatTensor\n",
            "Shape:torch.Size([2, 2])\n",
            "Size():torch.Size([2, 2])\n",
            "Values: \n",
            "tensor([[0.0237, 0.8514],\n",
            "        [0.4540, 0.0200]], device='cuda:0')\n",
            "=========\n",
            "Type:torch.cuda.FloatTensor\n",
            "Shape:torch.Size([3, 2])\n",
            "Size():torch.Size([3, 2])\n",
            "Values: \n",
            "tensor([[0.6544, 0.0875],\n",
            "        [0.8831, 0.6956],\n",
            "        [0.7322, 0.5696]], device='cuda:0')\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Mixing CUDA tensors with CPU-bound tensors will lead to errors. This is because we need to ensure the tensors are on the same device.\n",
        "y = torch.rand(3,2)\n",
        "x + y"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 180
        },
        "id": "zTrLFgWikRe8",
        "outputId": "b1e51c8e-4ebb-416a-ec8a-8f3e941da97d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "error",
          "ename": "RuntimeError",
          "evalue": "Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!",
          "traceback": [
            "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
            "\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
            "\u001b[0;32m<ipython-input-27-06199c3ae5fd>\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;31m# Mixing CUDA tensors with CPU-bound tensors will lead to errors. This is because we need to ensure the tensors are on the same device.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrand\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mx\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
            "\u001b[0;31mRuntimeError\u001b[0m: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 3. Autograd\n",
        "The autograd package provides automatic differentiation for all operations on Tensors. Generally speaking, in Pytorch, we don’t need to worry about partial derivatives, chain rule, or anything like it. Autograd is PyTorch’s automatic differentiation package.\n",
        "\n",
        "Set requires_grad = True:"
      ],
      "metadata": {
        "id": "uqRtfMjpFrLD"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# To make GPU ready code, we should specify the device at the moment of creation to avoid shadowing the gradient requirement.\n",
        "import torch\n",
        "\n",
        "# requires_grad = True -> tracks all operations on the tensor.\n",
        "x = torch.randn(3, requires_grad=True)\n",
        "y = x + 2\n",
        "\n",
        "# y was created as a result of an operation, so it has a grad_fn attribute.\n",
        "# grad_fn: references a Function that has created the Tensor\n",
        "describe(x)\n",
        "print(\"===========\")\n",
        "describe(y)\n",
        "print(\"===========\")\n",
        "print(y.grad_fn)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "6GnPoiTGkmyJ",
        "outputId": "b5a64103-ead1-4ff9-e213-3b0443c6699d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.FloatTensor\n",
            "Shape:torch.Size([3])\n",
            "Size():torch.Size([3])\n",
            "Values: \n",
            "tensor([-0.7390,  0.0534, -0.2177], requires_grad=True)\n",
            "===========\n",
            "Type:torch.FloatTensor\n",
            "Shape:torch.Size([3])\n",
            "Size():torch.Size([3])\n",
            "Values: \n",
            "tensor([1.2610, 2.0534, 1.7823], grad_fn=<AddBackward0>)\n",
            "===========\n",
            "<AddBackward0 object at 0x78679c304370>\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Do more operations on y\n",
        "z = y * y * 3\n",
        "describe(z)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3-7fSMnLqODA",
        "outputId": "cceea0f2-9024-43de-bea5-2f94e0b783f7"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Type:torch.FloatTensor\n",
            "Shape:torch.Size([3])\n",
            "Size():torch.Size([3])\n",
            "Values: \n",
            "tensor([ 4.7706, 12.6488,  9.5294], grad_fn=<MulBackward0>)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 3.1 backward()\n",
        "\n",
        "To tell PyTorch to compute all gradients, we use the `backward()` method. It will compute gradients for all (requiring gradient) tensors involved in the computation of a given variable.\n",
        "\n",
        "Recall that we need to compute the partial derivatives of the loss function w.r.t. our parameters. Hence, we need to invoke the `backward()` method from the corresponding Python variable: `loss.backward()`."
      ],
      "metadata": {
        "id": "Oz7Q18UjsV0t"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Let’s first generate a synthetic dataset of 100 data points, then perform an 80%:20% split as training and validation dataset, respectively.\n",
        "import numpy as np\n",
        "import torch\n",
        "\n",
        "true_b = 1\n",
        "true_w = 2\n",
        "N = 100\n",
        "# Data Generation\n",
        "np.random.seed(42)\n",
        "x = np.random.rand(N, 1)\n",
        "# Guassian noise to add some randomness to y\n",
        "epsilon = (.1 * np.random.randn(N, 1))\n",
        "y = true_b + true_w * x + epsilon\n",
        "\n",
        "# Shuffles the indices\n",
        "idx = np.arange(N)\n",
        "np.random.shuffle(idx)\n",
        "# Uses first 80 random indices for train\n",
        "train_idx = idx[:int(N*.8)]\n",
        "# Uses the remaining indices for validation\n",
        "val_idx = idx[int(N*.8):]\n",
        "# Generates train and validation sets\n",
        "x_train, y_train = x[train_idx], y[train_idx]\n",
        "x_val, y_val = x[val_idx], y[val_idx]"
      ],
      "metadata": {
        "id": "rJkizFAFtAf7"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# No matter you have a GPU or not, the best practice is to use .to(device) method to make your code GPU ready.\n",
        "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
        "# Our data was in Numpy arrays, but we need to transform them\n",
        "# into PyTorch's Tensors and then we send them to the\n",
        "# chosen device\n",
        "x_train_tensor = torch.as_tensor(x_train).float().to(device)\n",
        "y_train_tensor = torch.as_tensor(y_train).float().to(device)"
      ],
      "metadata": {
        "id": "dU_Fy-EttTkS"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# We can specify the device at the moment of creation\n",
        "# RECOMMENDED!\n",
        "# Step 0 - Initializes parameters \"b\" and \"w\" randomly\n",
        "# These parameters are used in a linear regression model where y = wX + b.\n",
        "# We specify `requires_grad=True` to track gradients for backpropagation.\n",
        "# The `device=device` ensures that the tensors are created on the specified device (CPU or GPU).\n",
        "torch.manual_seed(42)\n",
        "b = torch.randn(1, requires_grad=True, dtype=torch.float, device=device)\n",
        "w = torch.randn(1, requires_grad=True, dtype=torch.float, device=device)\n",
        "print(b, w)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-eFPEvJUsGm3",
        "outputId": "d10be05f-0007-4f6d-95f1-49f431a31935"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([0.1940], device='cuda:0', requires_grad=True) tensor([0.1391], device='cuda:0', requires_grad=True)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Step 1 - Computes our model's predicted output - forward pass\n",
        "yhat = w * x_train_tensor + b\n",
        "# Step 2 - Computes the loss\n",
        "error = yhat - y_train_tensor\n",
        "# It is a regression, so it computes mean squared error (MSE)\n",
        "loss = (error ** 2).mean()\n",
        "# Step 3 - Computes gradients for both \"b\" and \"w\" parameters\n",
        "loss.backward()"
      ],
      "metadata": {
        "id": "kwcQON0v5SGL"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "We have set requires_grad=True to both b and w, so they are obviously included in the list of gradient calculation. We use them both to compute yhat, so it will also make it to the list. Then we use yhat to compute the error, so error is also on the list.\n",
        "\n",
        "x_train_tensor and y_train_tensor however, are not gradient-requiring tensors, so `backward()` does not care about them."
      ],
      "metadata": {
        "id": "VUguVYOI5bgx"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "print(error.requires_grad, yhat.requires_grad, b.requires_grad, w.requires_grad)\n",
        "print(y_train_tensor.requires_grad, x_train_tensor.requires_grad)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "zbGsoK2i5iGu",
        "outputId": "7b75f7bb-3ebb-4335-c59b-775931686088"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "True True True True\n",
            "False False\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 3.2 grad\n",
        "We can inspect the actual values of the gradients by looking at the grad attribute of a tensor."
      ],
      "metadata": {
        "id": "AQvXwpsa6bcS"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "print(b.grad, w.grad)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "SdBAZcv56amV",
        "outputId": "13c88060-9950-46e7-f0be-0708c4ba5df7"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([-3.3881], device='cuda:0') tensor([-1.9439], device='cuda:0')\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 3.3 Accumulated Gradients\n",
        "Let’s run the backward function again:"
      ],
      "metadata": {
        "id": "0EjQH5Eb6fX1"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Step 1 - Computes our model's predicted output - forward pass\n",
        "yhat = b + w * x_train_tensor\n",
        "# Step 2 - Computes the loss\n",
        "# We are using ALL data points, so this is BATCH gradient\n",
        "# descent. How wrong is our model? That's the error!\n",
        "error = (yhat - y_train_tensor)\n",
        "# It is a regression, so it computes mean squared error (MSE)\n",
        "loss = (error ** 2).mean()\n",
        "# Step 3 - Computes gradients for both \"b\" and \"w\" parameters\n",
        "# No more manual computation of gradients!\n",
        "# b_grad = 2 * error.mean()\n",
        "# w_grad = 2 * (x_tensor * error).mean()\n",
        "loss.backward()"
      ],
      "metadata": {
        "id": "TXBe5n8n6gNX"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "print(b.grad, w.grad)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "GzASLoqz6jiN",
        "outputId": "1b7ccf11-f626-4a74-cad2-8c8dd9971519"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([-10.1642], device='cuda:0') tensor([-5.8317], device='cuda:0')\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "In PyTorch, gradients accumulate by default. This means that if you call .`backward()` multiple times without clearing the gradients, they will sum up rather than be replaced.\n",
        "\n",
        "🤔 Why does PyTorch behave like this?\n",
        "This design gives you more flexibility. You can:\n",
        "\n",
        "- Accumulate gradients over multiple smaller batches (called subminibatches),\n",
        "\n",
        "- Then perform an optimisation step once at the end of all subminibatches.\n",
        "\n",
        "🧠 Why do we need “subminibatch”?\n",
        "Imagine you want to use a minibatch of 128 training examples, but your GPU doesn’t have enough memory to fit all 128 examples at once.\n",
        "\n",
        "Solution: Split the minibatch into smaller parts — like four subminibatches of 32 examples.\n",
        "\n",
        "\n",
        "Note: To prevent accumulated gradients, you need to zero out the gradients after each update."
      ],
      "metadata": {
        "id": "6dm3QkLG8Z4k"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# This code will be placed _after_ Step 4\n",
        "# (updating the parameters)\n",
        "b.grad.zero_(), w.grad.zero_()\n",
        "\n",
        "# In PyTorch, every method that ends with an underscore (_), like the requires_grad_() and zero_() method above,\n",
        "# makes changes in-place, in other words, they will modify the underlying variable."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ppyfHek78h75",
        "outputId": "23d03884-1dd4-4edf-99a2-a3833b97b0e4"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(tensor([0.], device='cuda:0'), tensor([0.], device='cuda:0'))"
            ]
          },
          "metadata": {},
          "execution_count": 40
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 3.4 Put it all together"
      ],
      "metadata": {
        "id": "Tjyfdo069Cyw"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Sets learning rate\n",
        "lr = 0.1\n",
        "\n",
        "# Step 0 - Initializes parameters \"b\" and \"w\" randomly\n",
        "torch.manual_seed(42)\n",
        "b = torch.randn(1, requires_grad=True, dtype=torch.float, device=device)\n",
        "w = torch.randn(1, requires_grad=True, dtype=torch.float, device=device)\n",
        "\n",
        "# Defines number of epochs\n",
        "n_epochs = 1000\n",
        "\n",
        "for epoch in range(n_epochs):\n",
        "    # Step 1 - Computes model's predicted output - forward pass\n",
        "    yhat = b + w * x_train_tensor\n",
        "\n",
        "    # Step 2 - Computes the loss\n",
        "    # We are using ALL data points, so this is BATCH gradient\n",
        "    # descent. How wrong is our model? That's the error!\n",
        "    error = (yhat - y_train_tensor)\n",
        "    # It is a regression, so it computes mean squared error (MSE)\n",
        "    loss = (error ** 2).mean()\n",
        "\n",
        "    # Step 3 - Computes gradients for both \"b\" and \"w\"\n",
        "    # parameters. No more manual computation of gradients!\n",
        "    b_grad = 2 * error.mean()\n",
        "    w_grad = 2 * (x_train_tensor * error).mean()\n",
        "    # We just tell PyTorch to work its way BACKWARDS\n",
        "    # from the specified loss!\n",
        "    loss.backward()\n",
        "\n",
        "    # Step 4 - Updates parameters using gradients and\n",
        "    # the learning rate. But not so fast...\n",
        "    # FIRST ATTEMPT - just using the same code as before\n",
        "    # AttributeError: 'NoneType' object has no attribute 'zero_'\n",
        "    # b = b - lr * b.grad\n",
        "    # w = w - lr * w.grad\n",
        "    # print(b)\n",
        "\n",
        "    # SECOND ATTEMPT - using in-place Python assingment\n",
        "    # RuntimeError: a leaf Variable that requires grad\n",
        "    # has been used in an in-place operation.\n",
        "    # b -= lr * b.grad\n",
        "    # w -= lr * w.grad\n",
        "\n",
        "    # THIRD ATTEMPT - NO_GRAD for the win!\n",
        "    # We need to use NO_GRAD to keep the update out of\n",
        "    # the gradient computation. Why is that? It boils\n",
        "    # down to the DYNAMIC GRAPH that PyTorch uses...\n",
        "    with torch.no_grad():\n",
        "        b -= lr * b.grad\n",
        "        w -= lr * w.grad\n",
        "\n",
        "    # PyTorch is \"clingy\" to its computed gradients, we\n",
        "    # need to tell it to let it go...\n",
        "    b.grad.zero_()\n",
        "    w.grad.zero_()\n",
        "\n",
        "print(b, w)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "qZTel7qz9G4c",
        "outputId": "20e6b0eb-884b-43a5-f085-821b6db3da8f"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tensor([1.0235], requires_grad=True) tensor([1.9690], requires_grad=True)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "In the first attempt, if we use the same update structure as in our Numpy code, we’ll get a weird error but we can get a hint of what’s going on by looking at the tensor itself — once again, we “lost” the gradient while reassigning the update results to our parameters. Thus, the grad attribute turns out to be None, and it raises the error…\n",
        "\n",
        "Important:\n",
        "We use with `torch.no_grad()`: to ensure the update is not tracked by the dyanmic computation graph mechanism of Pytorch."
      ],
      "metadata": {
        "id": "wcdksDu7-4KG"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 4. Training Loop with: Model, Loss & Optimizer\n",
        "A typical PyTorch pipeline looks like this:\n",
        "\n",
        "1. Prepare the dataset: Wrap it in a Dataset and DataLoader (see in Section 5)\n",
        "2. Design model (input, output, forward pass with different layers)\n",
        "3. Construct loss and optimizer\n",
        "4. Training loop:\n",
        "  - Forward = compute prediction and loss\n",
        "  - Backward = compute gradients\n",
        "  - Update weights\n"
      ],
      "metadata": {
        "id": "YfsyJJe2_hi-"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "import torch.nn as nn\n",
        "\n",
        "# Linear regression\n",
        "# y = w * x\n",
        "# here : y = 2 * x\n",
        "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
        "# 0) Training samples, watch the shape!\n",
        "X = torch.tensor([[1], [2], [3], [4], [5], [6], [7], [8]], dtype=torch.float32).to(device)\n",
        "Y = torch.tensor([[2], [4], [6], [8], [10], [12], [14], [16]], dtype=torch.float32).to(device)\n",
        "\n",
        "n_samples, n_features = X.shape\n",
        "print(f'n_samples = {n_samples}, n_features = {n_features}')\n",
        "\n",
        "# 0) create a test sample\n",
        "X_test = torch.tensor([5], dtype=torch.float32).to(device)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VSRTbHWb_iyv",
        "outputId": "6765163f-de1c-4753-c6ee-4a869d905049"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "n_samples = 8, n_features = 1\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# 1) Design Model, the model has to implement the forward pass!\n",
        "\n",
        "# Here we could simply use a built-in model from PyTorch\n",
        "# model = nn.Linear(input_size, output_size)\n",
        "\n",
        "class LinearRegression(nn.Module):\n",
        "    def __init__(self, input_dim, output_dim):\n",
        "        super(LinearRegression, self).__init__()\n",
        "        # define different layers\n",
        "        self.lin = nn.Linear(input_dim, output_dim) # weight, https://pytorch.org/docs/stable/generated/torch.nn.Linear.html\n",
        "\n",
        "    def forward(self, x):\n",
        "        return self.lin(x)\n",
        "\n",
        "\n",
        "input_size, output_size = n_features, n_features\n",
        "\n",
        "model = LinearRegression(input_size, output_size).to(device)\n",
        "\n",
        "print(f'Prediction before training: f({X_test.item()}) = {model(X_test).item():.3f}')\n",
        "\n",
        "# 2) Define loss and optimizer\n",
        "learning_rate = 0.01\n",
        "n_epochs = 100\n",
        "\n",
        "# loss function: measures how far off your model’s predictions are from the actual target values.\n",
        "loss = nn.MSELoss()\n",
        "# An optimizer is an algorithm that updates the model's parameters (like weights and biases)\n",
        "# using the gradients computed during backpropagation, with the goal of minimising the loss function.\n",
        "optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)\n",
        "\n",
        "# 3) Training loop\n",
        "for epoch in range(n_epochs):\n",
        "    # predict = forward pass with our model\n",
        "    y_predicted = model(X)\n",
        "\n",
        "    # loss\n",
        "    l = loss(Y, y_predicted)\n",
        "\n",
        "    # calculate gradients = backward pass\n",
        "    l.backward()\n",
        "\n",
        "    # update weights\n",
        "    optimizer.step()\n",
        "\n",
        "    # zero the gradients after updating\n",
        "    optimizer.zero_grad()\n",
        "\n",
        "    if (epoch+1) % 10 == 0:\n",
        "        w, b = model.parameters() # unpack parameters\n",
        "        print('epoch ', epoch+1, f'f({X_test.item()}) = {model(X_test).item():.3f}',': w = ', w[0][0].item(), ' loss = ', l.item())\n",
        "\n",
        "print(f'Prediction after training: f({X_test.item()}) = {model(X_test).item():.3f}')"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "k4ll5mZ-AHWp",
        "outputId": "77198332-e683-4427-9336-d3981ac6f062"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Prediction before training: f(5.0) = -2.013\n",
            "epoch  10 f(5.0) = 9.973 : w =  2.031029224395752  loss =  0.007081401068717241\n",
            "epoch  20 f(5.0) = 9.981 : w =  2.031085252761841  loss =  0.006341004278510809\n",
            "epoch  30 f(5.0) = 9.981 : w =  2.029867172241211  loss =  0.005853458773344755\n",
            "epoch  40 f(5.0) = 9.982 : w =  2.028695821762085  loss =  0.005403381306678057\n",
            "epoch  50 f(5.0) = 9.983 : w =  2.0275704860687256  loss =  0.004987914115190506\n",
            "epoch  60 f(5.0) = 9.984 : w =  2.026489496231079  loss =  0.004604393150657415\n",
            "epoch  70 f(5.0) = 9.984 : w =  2.0254507064819336  loss =  0.004250366240739822\n",
            "epoch  80 f(5.0) = 9.985 : w =  2.0244526863098145  loss =  0.003923562355339527\n",
            "epoch  90 f(5.0) = 9.985 : w =  2.023493766784668  loss =  0.0036218808963894844\n",
            "epoch  100 f(5.0) = 9.986 : w =  2.0225725173950195  loss =  0.0033433909993618727\n",
            "Prediction after training: f(5.0) = 9.986\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 5. First Neural Networks"
      ],
      "metadata": {
        "id": "2uV8EqiGA65A"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 5.1 Training"
      ],
      "metadata": {
        "id": "umFZpSubb7ZV"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "import torch.nn as nn\n",
        "import torchvision\n",
        "import torchvision.transforms as transforms\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "# Device configuration\n",
        "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
        "\n",
        "# Hyper-parameters\n",
        "input_size = 784 # 28x28\n",
        "hidden_size = 500\n",
        "num_classes = 10\n",
        "num_epochs = 20\n",
        "batch_size = 100\n",
        "learning_rate = 0.001\n",
        "\n",
        "# MNIST dataset: The MNIST database (Modified National Institute of Standards and Technology database)\n",
        "# is a large database of handwritten digits that is commonly used for training various image processing systems.\n",
        "train_dataset = torchvision.datasets.MNIST(root='./data',\n",
        "                                           train=True,\n",
        "                                           transform=transforms.ToTensor(),\n",
        "                                           download=True)\n",
        "\n",
        "test_dataset = torchvision.datasets.MNIST(root='./data',\n",
        "                                          train=False,\n",
        "                                          transform=transforms.ToTensor())\n",
        "\n",
        "# Data loader: Loads batches of data for you\n",
        "train_loader = torch.utils.data.DataLoader(dataset=train_dataset,\n",
        "                                           batch_size=batch_size,\n",
        "                                           shuffle=True)\n",
        "\n",
        "test_loader = torch.utils.data.DataLoader(dataset=test_dataset,\n",
        "                                          batch_size=batch_size,\n",
        "                                          shuffle=False)\n",
        "\n",
        "examples = iter(test_loader)\n",
        "example_data, example_targets = next(examples)\n",
        "\n",
        "for i in range(6):\n",
        "    plt.subplot(2,3,i+1)\n",
        "    plt.imshow(example_data[i][0], cmap='gray')\n",
        "plt.show()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 411
        },
        "id": "FVtcWenrBDD_",
        "outputId": "5ea5b3b7-2587-4edb-a68a-32f9e053e3e0"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 640x480 with 6 Axes>"
            ],
            "image/png": "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u76amhrZsWOH7NmzJ/7R2yKfXk9vb6+0t7cP2N/t15Mqfu5fetff6F138mv/um74yM7OlrKyMqmvr49nsVhM6uvrpaKiIo2VOaO0tFSKiooGXF9HR4fs37/fldenlJKamhrZunWr7N69W0pLSwd8v6ysTLKysgZcT0tLi5w8edKV15Nqfu5fetff6F138X3/pnnBq6VNmzapQCCgNmzYoI4cOaIWLFig8vLyVCQSSXdptnR2dqqDBw+qgwcPKhFRK1euVAcPHlQnTpxQSin1k5/8ROXl5alXX31V/e53v1O33367Ki0tVefPn09z5bqHH35YBYNBtXfvXnX69On49vHHH8f3eeihh1RJSYnavXu3OnDggKqoqFAVFRVprDq9vNy/9C69S++6g9/715XDh1JKrV69WpWUlKjs7GxVXl6umpqa0l2SbXv27FEiom3z589XSn36tq8lS5aoUCikAoGAuummm1RLS0t6i74Aq+sQEbV+/fr4PufPn1c/+MEP1KWXXqpGjhyp7rjjDnX69On0Fe0CXu1fepfepXfdwe/9m6GUUqm9twIAAPA51635AAAA/sbwAQAAjGL4AAAARjF8AAAAoxg+AACAUQwfAADAKIYPAABgFMMHAAAwiuEDAAAYxfABAACMYvgAAABGMXwAAACj/g+OvAuHaFVI7QAAAABJRU5ErkJggg==\n"
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Fully connected neural network with one hidden layer\n",
        "class NeuralNet(nn.Module):\n",
        "    def __init__(self, input_size, hidden_size, num_classes):\n",
        "        super(NeuralNet, self).__init__()\n",
        "        self.l1 = nn.Linear(input_size, hidden_size)\n",
        "        self.relu = nn.ReLU()\n",
        "        self.l2 = nn.Linear(hidden_size, num_classes)\n",
        "\n",
        "    def forward(self, x):\n",
        "        out = self.l1(x)\n",
        "        out = self.relu(out)\n",
        "        out = self.l2(out)\n",
        "        # no activation and no softmax at the end\n",
        "        return out\n",
        "\n",
        "model = NeuralNet(input_size, hidden_size, num_classes).to(device)\n",
        "\n",
        "# Loss and optimizer\n",
        "criterion = nn.CrossEntropyLoss()\n",
        "optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n",
        "\n",
        "# Train the model\n",
        "n_total_steps = len(train_loader)\n",
        "for epoch in range(num_epochs):\n",
        "    model.train()  # Set model to training mode\n",
        "    running_loss = 0.0\n",
        "    for i, (images, labels) in enumerate(train_loader):\n",
        "        # origin shape: [100, 1, 28, 28]\n",
        "        # resized: [100, 784]\n",
        "        images = images.reshape(-1, 28*28).to(device)\n",
        "        labels = labels.to(device)\n",
        "\n",
        "        # Computes model's predicted output - forward pass\n",
        "        outputs = model(images)\n",
        "        # Computes the loss\n",
        "        loss = criterion(outputs, labels)\n",
        "\n",
        "        # Computes gradients for all learnable parameters\n",
        "        # Updates parameters using gradients and the learning rate\n",
        "        loss.backward()\n",
        "        optimizer.step()\n",
        "        optimizer.zero_grad()\n",
        "\n",
        "        running_loss += loss.item()\n",
        "\n",
        "    if (epoch+1) % 5 == 0:\n",
        "      print (f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss:.4f}')\n",
        "\n",
        "    # Evaluation every 10 epochs\n",
        "    if (epoch + 1) % 10 == 0:\n",
        "        model.eval()  # Set model to evaluation mode\n",
        "        with torch.no_grad():  # No need to compute gradients during evaluation\n",
        "            correct = 0\n",
        "            total = 0\n",
        "            for images, labels in test_loader:\n",
        "                images = images.reshape(-1, 28*28).to(device)\n",
        "                labels = labels.to(device)\n",
        "\n",
        "                outputs = model(images)\n",
        "                _, predicted = torch.max(outputs, 1)\n",
        "\n",
        "                total += labels.size(0)\n",
        "                correct += (predicted == labels).sum().item()\n",
        "\n",
        "            accuracy = 100 * correct / total\n",
        "            print(f'Accuracy after epoch {epoch+1}: {accuracy:.2f}%')"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "f9jLvUC4Bfz3",
        "outputId": "d805ffd4-152b-4338-a770-5bc354a0c717"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch [5/20], Loss: 24.9308\n",
            "Epoch [10/20], Loss: 6.9261\n",
            "Accuracy after epoch 10: 97.88%\n",
            "Epoch [15/20], Loss: 1.3846\n",
            "Epoch [20/20], Loss: 1.9440\n",
            "Accuracy after epoch 20: 97.74%\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# We can also inspect its parameters using its state_dict\n",
        "print(model.state_dict())"
      ],
      "metadata": {
        "id": "gVEVXrGCebwG",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "collapsed": true,
        "outputId": "6d3287ca-4bce-4368-9589-fd3412b52f81"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "OrderedDict([('l1.weight', tensor([[-0.0009,  0.0269, -0.0352,  ...,  0.0051, -0.0195, -0.0189],\n",
            "        [ 0.0212,  0.0090, -0.0038,  ..., -0.0265,  0.0228, -0.0145],\n",
            "        [-0.0108,  0.0195, -0.0023,  ...,  0.0144,  0.0217,  0.0355],\n",
            "        ...,\n",
            "        [ 0.0150, -0.0316, -0.0187,  ..., -0.0188,  0.0275,  0.0208],\n",
            "        [-0.0347,  0.0098,  0.0094,  ...,  0.0308,  0.0322, -0.0244],\n",
            "        [ 0.0075,  0.0197,  0.0180,  ...,  0.0339,  0.0131,  0.0090]],\n",
            "       device='cuda:0')), ('l1.bias', tensor([ 3.6658e-02, -3.5160e-02, -3.0844e-02,  1.3334e-01, -4.9498e-02,\n",
            "        -1.5916e-01,  3.7986e-02,  7.6331e-02, -8.3421e-02,  1.0538e-02,\n",
            "         3.1453e-02,  9.3481e-03, -3.2491e-02,  4.6289e-02,  4.2102e-04,\n",
            "         5.9511e-03, -1.1150e-01,  1.0813e-01,  1.1291e-01,  1.3970e-01,\n",
            "        -2.9753e-02,  5.8895e-02,  1.0658e-01, -3.0457e-02, -5.6721e-03,\n",
            "         2.0446e-02, -5.2181e-02, -5.0684e-02,  1.2573e-01, -6.5675e-02,\n",
            "         1.2545e-02, -3.4459e-02,  3.9649e-02,  5.7585e-02, -4.3201e-02,\n",
            "        -1.1439e-01,  6.5427e-02,  4.2303e-02, -4.0702e-02, -6.3509e-02,\n",
            "         1.6453e-01, -5.5871e-02, -7.8871e-02,  9.8907e-02, -9.4075e-03,\n",
            "        -1.3939e-02, -1.1374e-01, -1.4725e-03,  4.6899e-02,  1.8217e-01,\n",
            "         6.8544e-02, -7.1725e-03, -2.8732e-02, -8.4515e-02,  5.5527e-02,\n",
            "        -7.4434e-02,  2.5412e-02,  5.9774e-03, -4.2653e-03,  4.8348e-02,\n",
            "         4.0332e-02,  1.5135e-01,  3.9572e-02, -1.3687e-02, -1.1961e-01,\n",
            "         8.5936e-02, -1.1427e-03, -8.6972e-03, -1.9627e-02, -2.7891e-02,\n",
            "         7.1998e-02,  4.9981e-02,  1.5697e-01,  9.4682e-03,  1.4423e-01,\n",
            "        -1.2213e-01,  9.5025e-02, -1.4844e-01,  4.7938e-02,  1.4428e-01,\n",
            "        -3.2282e-02,  7.2096e-02, -3.9603e-02, -3.5005e-03,  4.6053e-02,\n",
            "        -1.4049e-02,  5.6323e-02,  7.3827e-02,  1.2033e-01, -1.1392e-03,\n",
            "        -6.2217e-03,  7.5323e-04, -5.6748e-02,  2.6262e-02,  9.3142e-02,\n",
            "        -1.4104e-02, -6.6887e-02, -7.7323e-02, -1.5355e-02, -3.4064e-02,\n",
            "         1.3229e-01, -1.4858e-02, -8.8157e-03,  6.0129e-02, -2.4469e-02,\n",
            "        -7.2494e-02,  2.8349e-02, -4.3258e-02,  6.5558e-03, -8.0443e-03,\n",
            "         2.4716e-02, -2.9884e-02,  8.5838e-02, -3.7103e-02,  3.3924e-02,\n",
            "        -1.8874e-02, -4.7332e-02,  1.0410e-01,  1.2363e-02, -9.7469e-02,\n",
            "         5.8814e-02,  2.3755e-01,  2.3906e-02,  9.0744e-02, -4.0514e-02,\n",
            "         6.4833e-02,  8.8532e-02, -8.5607e-04, -3.8728e-02, -7.7240e-02,\n",
            "        -7.8408e-02, -4.0085e-02, -3.8726e-02, -4.0213e-02,  1.1616e-01,\n",
            "         1.8326e-01,  3.9353e-03,  4.5262e-03,  4.5309e-02, -3.1651e-02,\n",
            "        -3.2194e-02,  9.5284e-03, -1.2564e-02, -1.5710e-02,  2.2635e-01,\n",
            "        -3.3142e-02,  2.1653e-01, -5.0646e-02,  7.5724e-02,  1.4894e-01,\n",
            "         3.9379e-02,  4.1842e-02, -7.3336e-02, -2.9728e-02, -9.3552e-02,\n",
            "         1.5525e-01, -1.8633e-03,  4.2823e-02,  3.8934e-02,  4.6967e-02,\n",
            "         2.5070e-02, -2.6502e-02,  4.1664e-03,  7.1230e-02,  1.8850e-04,\n",
            "        -3.8675e-02,  1.1320e-01,  1.7613e-02,  1.6182e-02,  5.2629e-03,\n",
            "        -4.5707e-03, -9.9467e-02, -6.0242e-02,  2.9817e-02,  2.8689e-02,\n",
            "         3.5666e-02,  1.6343e-01,  7.3469e-03, -3.1676e-02,  1.2207e-02,\n",
            "         4.7195e-02, -2.6486e-02, -3.4619e-02, -1.0063e-01, -6.6844e-02,\n",
            "        -6.5726e-02,  3.5899e-03,  7.0515e-02, -6.0020e-02,  1.2852e-02,\n",
            "         1.7510e-01,  7.8591e-02,  1.0605e-01, -2.1554e-02,  7.7779e-02,\n",
            "         1.4260e-01, -7.1690e-02,  3.1825e-02,  9.8073e-03, -2.8199e-03,\n",
            "        -1.2057e-01,  3.7532e-02, -7.8494e-02,  3.0521e-02, -9.3488e-02,\n",
            "         1.9862e-02,  1.6202e-02,  6.4013e-02,  7.9974e-02,  1.2045e-01,\n",
            "         6.6901e-02, -2.0492e-02,  3.8784e-02,  2.6942e-02,  2.1316e-02,\n",
            "         3.9357e-02,  1.1404e-03, -3.7038e-02, -7.1634e-02, -1.6584e-02,\n",
            "         1.5291e-01,  3.7048e-02,  1.2863e-01, -9.7596e-02,  9.1655e-02,\n",
            "         1.0866e-02, -9.4101e-02,  2.6158e-02, -7.5823e-02,  1.4058e-01,\n",
            "         3.8898e-02, -6.3456e-02, -3.3871e-02, -3.2330e-02,  3.6632e-02,\n",
            "         3.1445e-02,  1.0176e-01, -6.1528e-03,  5.1671e-02,  2.1436e-02,\n",
            "         1.7328e-01,  8.0240e-02, -1.6278e-02,  1.1882e-01,  4.2965e-02,\n",
            "         4.5348e-02, -3.6783e-02, -1.0201e-01,  1.0406e-01,  5.3244e-02,\n",
            "        -1.0847e-02, -1.8531e-03, -2.3513e-02,  1.4016e-01, -6.5835e-02,\n",
            "        -7.1893e-02,  7.0524e-02, -1.0055e-01, -2.4474e-02,  1.3587e-02,\n",
            "         6.7435e-02,  1.1927e-01, -5.0797e-03,  5.7933e-02,  1.9040e-01,\n",
            "         1.0394e-01,  3.9504e-02,  9.5435e-03, -1.2088e-02,  5.7929e-02,\n",
            "        -2.0002e-02,  6.5866e-02, -5.2999e-02,  1.1404e-01,  7.8952e-03,\n",
            "         2.9935e-02,  3.5257e-02,  1.2239e-01, -1.6095e-02, -8.0356e-02,\n",
            "         1.7426e-03,  4.3688e-02, -2.2426e-02,  1.1449e-02, -6.6919e-02,\n",
            "         6.7570e-02,  7.7706e-02,  4.8230e-02, -1.3104e-01, -3.0323e-02,\n",
            "        -3.6274e-02,  1.4264e-03, -1.1853e-01,  1.1503e-01,  1.5470e-01,\n",
            "        -4.5796e-02,  1.1517e-01,  1.5500e-02,  2.3147e-02, -1.9018e-01,\n",
            "        -4.8005e-02, -4.2539e-02,  8.9959e-02,  9.4253e-02, -2.6220e-02,\n",
            "         5.2713e-02, -8.5456e-03, -2.0855e-03,  3.2058e-02, -2.1514e-02,\n",
            "         8.1701e-03,  4.9431e-03, -1.0962e-03,  8.7844e-02, -3.1887e-02,\n",
            "        -1.8568e-02,  1.1811e-01,  2.1742e-02,  1.2541e-01, -9.9952e-02,\n",
            "        -8.4451e-02,  3.4458e-02,  7.2024e-02, -4.8050e-02,  4.4502e-03,\n",
            "        -1.0173e-01,  1.3570e-01, -1.3264e-02,  5.0385e-02,  1.0932e-01,\n",
            "         7.9643e-02,  3.6021e-02,  2.1306e-01, -2.3694e-02,  5.7812e-02,\n",
            "         2.9810e-02,  1.4373e-01, -5.5121e-02,  7.6350e-02,  5.3862e-02,\n",
            "         5.9517e-02,  1.2649e-02, -6.0002e-02,  4.5729e-02,  1.3233e-01,\n",
            "        -3.6396e-02, -4.1651e-02,  3.1128e-03,  4.5728e-02,  4.9808e-02,\n",
            "        -3.6631e-02, -4.1962e-02,  1.7738e-01, -1.3721e-02,  1.5678e-01,\n",
            "         1.2141e-01, -1.2855e-01,  3.5771e-02, -4.8066e-02,  1.3300e-01,\n",
            "         2.5738e-03,  1.1381e-01,  1.4046e-02, -2.6056e-02,  9.9971e-02,\n",
            "        -1.3782e-02, -8.6903e-03, -1.1509e-02, -2.5372e-02,  3.3169e-02,\n",
            "        -4.1577e-02,  1.1203e-01, -1.0799e-02, -4.1044e-02, -8.9562e-02,\n",
            "         9.1511e-02,  1.0612e-02,  1.4194e-01,  7.3584e-02,  1.6083e-02,\n",
            "         2.3116e-01, -2.6596e-02, -1.8004e-03,  2.4370e-01,  1.6472e-01,\n",
            "        -2.8224e-03,  9.4402e-02, -4.7930e-02,  1.0732e-02, -1.2510e-02,\n",
            "        -2.1965e-02,  2.3047e-01,  1.1933e-01, -9.8903e-02, -1.2700e-02,\n",
            "         7.5620e-02, -8.9740e-02, -2.4277e-02, -1.5254e-02, -2.1438e-02,\n",
            "        -2.3832e-02, -2.7881e-02, -2.2968e-02, -1.0616e-01, -7.6289e-02,\n",
            "        -3.2173e-02,  3.0448e-02,  2.3307e-01, -1.0413e-01,  1.3854e-01,\n",
            "         1.0029e-01, -4.5286e-02,  3.8454e-02, -7.9912e-02, -6.0343e-03,\n",
            "         2.0672e-02,  2.2662e-03, -1.0137e-01,  2.5337e-02,  2.0417e-02,\n",
            "         1.3229e-01,  6.4907e-02,  8.2083e-02,  4.6998e-02, -2.0898e-02,\n",
            "         7.7552e-02, -2.4077e-02, -1.1724e-01,  1.2163e-01,  1.7625e-01,\n",
            "        -1.1891e-02, -1.8967e-02, -3.7827e-02,  1.2937e-01,  1.0021e-01,\n",
            "         4.6113e-02,  1.0992e-02, -4.2679e-03, -2.7365e-02,  1.5896e-01,\n",
            "         4.4447e-02,  8.0285e-02, -3.1694e-02,  6.4682e-02, -9.3482e-02,\n",
            "         1.8739e-02, -5.0191e-02,  1.5128e-02, -1.1328e-01,  4.0039e-02,\n",
            "        -8.0647e-02, -3.1484e-02, -1.8288e-03,  4.7065e-03, -1.1725e-01,\n",
            "         6.2846e-02,  4.0190e-02,  6.9210e-02, -1.0681e-01, -1.0319e-01,\n",
            "        -1.0304e-01, -2.8629e-02,  9.8851e-02,  7.2368e-02, -4.4813e-02,\n",
            "         3.1901e-02,  1.6298e-02,  7.3509e-02, -8.2565e-02,  8.9888e-02,\n",
            "         9.1243e-02,  2.5180e-02,  4.3943e-02, -1.7742e-02,  9.5257e-02,\n",
            "         9.1110e-02,  2.2888e-02,  3.1307e-02,  6.5302e-02, -9.0830e-02,\n",
            "         9.3751e-02, -5.1758e-02, -8.0213e-02,  1.2988e-01,  7.8561e-02,\n",
            "        -5.1461e-02, -3.6824e-02, -6.3562e-02, -2.9599e-03,  6.9749e-02,\n",
            "         5.6788e-02,  5.9493e-02,  1.1856e-01,  3.6455e-02, -2.3575e-02,\n",
            "         1.0758e-01,  2.3973e-02, -3.1509e-03,  1.7804e-02,  7.2218e-02],\n",
            "       device='cuda:0')), ('l2.weight', tensor([[-0.7320, -0.2654, -0.0175,  ...,  0.0907, -0.0989,  0.1428],\n",
            "        [-0.1830,  0.0307, -0.0043,  ..., -0.2036, -0.1543, -0.0282],\n",
            "        [-0.0106,  0.0508, -0.0150,  ..., -0.0750,  0.0356,  0.0491],\n",
            "        ...,\n",
            "        [ 0.3194,  0.0292, -0.0150,  ..., -0.0049, -0.1499, -0.3492],\n",
            "        [ 0.0881,  0.0752,  0.0140,  ..., -0.0285,  0.1563,  0.0570],\n",
            "        [-0.2154,  0.0492, -0.0124,  ...,  0.0417,  0.1200, -0.1265]],\n",
            "       device='cuda:0')), ('l2.bias', tensor([-0.1026, -0.0403, -0.0546, -0.0435,  0.0372,  0.0771,  0.0209, -0.0913,\n",
            "         0.1812,  0.0463], device='cuda:0'))])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 5.2 Saving/Loading model"
      ],
      "metadata": {
        "id": "Rfm9ZWaocKO0"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "PATH = './my_model.pth'\n",
        "torch.save(model.state_dict(), PATH)"
      ],
      "metadata": {
        "id": "NK1m8rQrcTsW"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 5.3 Test"
      ],
      "metadata": {
        "id": "o_cqVl80cBVN"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "loaded_model = NeuralNet(input_size, hidden_size, num_classes).to(device)\n",
        "loaded_model.load_state_dict(torch.load(PATH))\n",
        "# Test the model: we don't need to compute gradients\n",
        "loaded_model.eval()\n",
        "with torch.no_grad():\n",
        "    n_correct = 0\n",
        "    n_samples = len(test_loader.dataset)\n",
        "\n",
        "    for images, labels in test_loader:\n",
        "        images = images.reshape(-1, 28*28).to(device)\n",
        "        labels = labels.to(device)\n",
        "\n",
        "        outputs = loaded_model(images)\n",
        "\n",
        "        # max returns (output_value ,index)\n",
        "        _, predicted = torch.max(outputs, 1)\n",
        "        n_correct += (predicted == labels).sum().item()\n",
        "\n",
        "    acc = n_correct / n_samples\n",
        "    print(f'Accuracy of the network on the {n_samples} test images: {100*acc} %')"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "qN8MBM74cEYJ",
        "outputId": "76e2afb6-d347-4bff-9881-ce08c1923bc7"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Accuracy of the network on the 10000 test images: 97.74000000000001 %\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "To define your own Datasets&DataLoaders:\n",
        "- https://pytorch.org/tutorials/beginner/basics/data_tutorial.html\n",
        "- https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel\n",
        "\n",
        "`Datasets` is a class that wraps your data and tells PyTorch how to access individual samples.\n",
        "\n",
        "You define:\n",
        "\n",
        "- How to load one item (`__getitem__`)\n",
        "\n",
        "- How many items there are (`__len__`)\n",
        "\n",
        "Think of it like a custom container for your data.\n",
        "\n",
        "`DataLoader` is a class that\n",
        "\n",
        "- Loads batches of data for you\n",
        "\n",
        "- Shuffles data if needed (for better training)\n",
        "\n",
        "- Can use multiple CPU cores to load data faster\n",
        "\n",
        "Think of it like a pipeline that feeds data into your model efficiently."
      ],
      "metadata": {
        "id": "Wa7fNVHuhZ8d"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 6. Exercise\n",
        "### Objective\n",
        "Implement a PyTorch model class that consists of:\n",
        "\n",
        "1. Three fully connected (Linear) layers:\n",
        "\n",
        "2. ReLU activation functions between the layers\n",
        "\n",
        "### Training & Evaluation Details\n",
        "- Use CrossEntropyLoss as the loss function\n",
        "\n",
        "- Use Adam as the optimizer\n",
        "\n",
        "- Train the model for 5 epochs, evaluating on the test set every 2 epochs\n",
        "\n",
        "- Report accuracy on the validation set during training.\n",
        "\n",
        "- Report accuracy on the test set after training.\n",
        "\n",
        "### Task Instructions\n",
        "Complete the # TO DO sections in the code provided below."
      ],
      "metadata": {
        "id": "EGQGMYyJf168"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "import torch.nn as nn\n",
        "import torch.optim as optim\n",
        "import torchvision\n",
        "import torchvision.transforms as transforms\n",
        "from torch.utils.data import random_split"
      ],
      "metadata": {
        "id": "pX2otqHag2_t"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Define the model\n",
        "class ThreeLayerModel(nn.Module):\n",
        "    def __init__(self):\n",
        "        super(ThreeLayerModel, self).__init__()\n",
        "        # TO DO: define the three layers and the activation function\n",
        "\n",
        "    def forward(self, x):\n",
        "        # TO DO: pass x to:\n",
        "        # 1. first layer + ReLU\n",
        "        # 2. second layer + ReLU\n",
        "        # 3. output layer\n",
        "        return"
      ],
      "metadata": {
        "id": "klgegIthhraC"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Load dataset (MNIST)\n",
        "transform = transforms.Compose([transforms.ToTensor()])\n",
        "train_dataset = torchvision.datasets.MNIST(root=\"./data\", train=True, transform=transform, download=True)\n",
        "test_dataset = torchvision.datasets.MNIST(root=\"./data\", train=False, transform=transform, download=True)\n",
        "\n",
        "# Split the training dataset into training and validation sets (80% train, 20% validation)\n",
        "train_size = int(0.8 * len(train_dataset))\n",
        "val_size = len(train_dataset) - train_size\n",
        "train_subset, val_subset = random_split(train_dataset, [train_size, val_size])\n",
        "\n",
        "# Create DataLoader for training, validation, and test sets\n",
        "train_loader = torch.utils.data.DataLoader(train_subset, batch_size=64, shuffle=True)\n",
        "val_loader = torch.utils.data.DataLoader(val_subset, batch_size=64, shuffle=False)\n",
        "test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)\n",
        "\n",
        "# TO DO: Initialize model, loss, optimizer\n",
        "\n",
        "\n",
        "# Training loop\n",
        "num_epochs = 5\n",
        "for epoch in range(1, num_epochs + 1):\n",
        "    # TO DO: set model to training mode\n",
        "    for images, labels in train_loader:\n",
        "        # TO DO:\n",
        "        # 1. Computes model's predicted output - forward pass\n",
        "        # 2. Computes the loss\n",
        "        # 3. Computes gradients for all learnable parameters\n",
        "        # 4. Updates parameters using gradients and the learning rate\n",
        "\n",
        "\n",
        "    print(f\"Epoch [{epoch}/{num_epochs}], Loss: {loss.item():.4f}\")\n",
        "\n",
        "    # Evaluate every 2 epochs\n",
        "    if epoch % 2 == 0:\n",
        "        # TO DO: set model to evaluation model\n",
        "        with torch.no_grad():\n",
        "            for images, labels in val_loader:\n",
        "                # TO DO:\n",
        "                # 1. pass validation images to the model\n",
        "                # 2. computer accuracy\n",
        "\n",
        "        # TO DO: print val accuracy"
      ],
      "metadata": {
        "id": "noD0lP-Ag0mn"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# TO DO:\n",
        "# 1. save the model\n",
        "# 2. evalute the model on testing data"
      ],
      "metadata": {
        "id": "jF5RbaSRkUvY"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "\n",
        "Reference:\n",
        "1. Natural Lanuage Processing with PyTorch - Building intelligent lanaguage applications using deep learning, by Delip Rao and Brian McMahan (copyright O’REILLY Feb 2019)\n",
        "\n",
        "2. AssemblyAI"
      ],
      "metadata": {
        "id": "pkL2Zn7bIsK5"
      }
    }
  ]
}