{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y46It-QqC-hj"
      },
      "source": [
        "# Cake classification (part 2)\n",
        "\n",
        "In this second part we will use a neural network to classify cakes."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pXfY9Ol_Bcnw"
      },
      "source": [
        "## CNN\n",
        "\n",
        "Since don't have enough data to train a new neural network from scratch, we will use transfer learning.\n",
        "\n",
        "First, we load a pretrained version of the ResNet architecture. We also collect the preprocessing pipeline and the list of classes."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "J2Pjofs2yUc0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ResNet(\n",
            "  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n",
            "  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "  (relu): ReLU(inplace=True)\n",
            "  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n",
            "  (layer1): Sequential(\n",
            "    (0): BasicBlock(\n",
            "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "    )\n",
            "    (1): BasicBlock(\n",
            "      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "    )\n",
            "  )\n",
            "  (layer2): Sequential(\n",
            "    (0): BasicBlock(\n",
            "      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (downsample): Sequential(\n",
            "        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
            "        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      )\n",
            "    )\n",
            "    (1): BasicBlock(\n",
            "      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "    )\n",
            "  )\n",
            "  (layer3): Sequential(\n",
            "    (0): BasicBlock(\n",
            "      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (downsample): Sequential(\n",
            "        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
            "        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      )\n",
            "    )\n",
            "    (1): BasicBlock(\n",
            "      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "    )\n",
            "  )\n",
            "  (layer4): Sequential(\n",
            "    (0): BasicBlock(\n",
            "      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (downsample): Sequential(\n",
            "        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
            "        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      )\n",
            "    )\n",
            "    (1): BasicBlock(\n",
            "      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "      (relu): ReLU(inplace=True)\n",
            "      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
            "      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
            "    )\n",
            "  )\n",
            "  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
            "  (fc): Linear(in_features=512, out_features=1000, bias=True)\n",
            ")\n",
            "11,689,512 parameters\n"
          ]
        }
      ],
      "source": [
        "# sono veramente troppo poche le mie immagini per allenare una rete da 0\n",
        "\n",
        "import torch\n",
        "import torchvision\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "net = torchvision.models.resnet18(pretrained=True)\n",
        "print(net)\n",
        "ps = sum(param.nelement() for param in net.parameters())\n",
        "print(f\"{ps:,d} parameters\")\n",
        "\n",
        "weights = torchvision.models.ResNet18_Weights.DEFAULT\n",
        "preprocess = weights.transforms()           # serve per il preprocessing delle immagini (normalizzazione e cose varie)\n",
        "\n",
        "imagenet_classes = torchvision.models.resnet._IMAGENET_CATEGORIES"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "È abbastanza comune avere una convoluzione aggressiva all'inizio, come in questo caso\n",
        "Il finale mappa le 512 features nelle 1000 classi di output della resnet\n",
        "Sul sito della torchvision ci sono un tot di reti pre-trainate con le performance\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wRA8K-SnyTfA"
      },
      "source": [
        "Let's download an image to try the CNN.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "id": "YmKeybn1y1qd"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "--2025-05-19 16:03:43--  https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/Okonjima_Lioness.jpg/330px-Okonjima_Lioness.jpg\n",
            "Resolving upload.wikimedia.org (upload.wikimedia.org)... 185.15.58.240, 2a02:ec80:600:ed1a::2:b\n",
            "Connecting to upload.wikimedia.org (upload.wikimedia.org)|185.15.58.240|:443... connected.\n",
            "HTTP request sent, awaiting response... 200 OK\n",
            "Length: 37492 (37K) [image/jpeg]\n",
            "Saving to: ‘img.jpg’\n",
            "\n",
            "img.jpg             100%[===================>]  36.61K  62.0KB/s    in 0.6s    \n",
            "\n",
            "2025-05-19 16:03:45 (62.0 KB/s) - ‘img.jpg’ saved [37492/37492]\n",
            "\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 444532 characters]",
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [-2.1140018..2.3747566].\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "torch.Size([1, 3, 224, 224]) ImageClassification(\n",
            "    crop_size=[224]\n",
            "    resize_size=[256]\n",
            "    mean=[0.485, 0.456, 0.406]\n",
            "    std=[0.229, 0.224, 0.225]\n",
            "    interpolation=InterpolationMode.BILINEAR\n",
            ")\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 195540 characters]",
            "text/plain": [
              "<Figure size 640x480 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "#!wget https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Aquarius_Plan_Cornu.JPG/330px-Aquarius_Plan_Cornu.JPG -Oimg.jpg\n",
        "\n",
        "!wget https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/Okonjima_Lioness.jpg/330px-Okonjima_Lioness.jpg -O img.jpg\n",
        "\n",
        "# !wget https://upload.wikimedia.org/wikipedia/commons/thumb/c/c2/Amanita_muscaria_%28fly_agaric%29.JPG/250px-Amanita_muscaria_%28fly_agaric%29.JPG -Oimg.jpg\n",
        "\n",
        "# !wget https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Sachertorte_DSC03027_retouched.jpg/330px-Sachertorte_DSC03027_retouched.jpg -q -Oimg.jpg\n",
        "\n",
        "im = plt.imread('img.jpg')\n",
        "plt.imshow(im)\n",
        "plt.show()\n",
        "im = torch.tensor(im).permute(2, 0, 1).unsqueeze(0) / 255.0\n",
        "im = preprocess(im)\n",
        "print(im.shape, preprocess)\n",
        "plt.imshow(im[0].permute(1, 2, 0))\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WalUyKDqCau9"
      },
      "source": [
        "Let's classify the image and show the top 5 answers from the CNN. Remember to set the network in evaluation mode by calling `net.eval()`.  "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "metadata": {
        "id": "DzAZqMePzrjj"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "1: lion       99.22%\n",
            "2: cougar     0.32%\n",
            "3: dingo      0.27%\n",
            "4: timber wolf 0.03%\n",
            "5: collie     0.03%\n"
          ]
        }
      ],
      "source": [
        "net.eval()              # è importante perché ci sono layer che si comportano in modo diverso in fase di training e di test\n",
        "probs = torch.softmax(net(im), dim=1)       # questa non ha il softmax embedded quindi se volglio le prob devo fare softmax\n",
        "\n",
        "top_classes = torch.topk(probs, 5)      #torch.topk(probs, 5)\n",
        "for n, k in enumerate(top_classes.indices[0]):\n",
        "    print(f\"{n+1}: {imagenet_classes[k]:10} {100 * probs[0, k].item():.2f}%\")\n",
        "    # print(f\"{n+1}: {imagenet_classes[k]} ({top_classes.values[0][n]})\")\n",
        "    # print(f\"{n+1}: {imagenet_classes[k]} ({top_classes.values[0][n].item()})\")\n",
        "    # print(f\"{n+1}: {imagenet_classes[k]} ({top_classes.values[0][n].item():.2%})\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "tensor([291, 286, 273, 269, 231])"
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "top_classes.indices[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5SA9wzXsvTX5"
      },
      "source": [
        "## Dataset\n",
        "\n",
        "Let's download and setup the training and the test set. We will configure the data loaders to apply the preprocessing function."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "u1NLER0irT2d"
      },
      "outputs": [],
      "source": [
        "# !pip install gdown\n",
        "URL = 'https://drive.google.com/file/d/1_sixkDSQEN6TYyLVmXXmTNFF_9SIiFjx/view?usp=sharing'\n",
        "!gdown --fuzzy $URL -O- | tar -xz"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "metadata": {
        "id": "DgcsFNcdvUqj"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "['carrot_cake', 'cup_cakes', 'cup_cakes', 'ice_cream', 'carrot_cake', 'waffles', 'tiramisu', 'tiramisu', 'chocolate_mousse', 'waffles', 'chocolate_mousse', 'apple_pie', 'ice_cream', 'cup_cakes', 'ice_cream', 'churros', 'apple_pie', 'chocolate_cake', 'churros', 'tiramisu', 'chocolate_cake', 'donuts', 'donuts', 'panna_cotta', 'tiramisu', 'panna_cotta', 'panna_cotta', 'churros', 'apple_pie', 'cannoli', 'chocolate_cake', 'churros']\n",
            "torch.Size([32, 3, 224, 224])\n"
          ]
        },
        {
          "data": {
            "image/png": "[encoded data, 1296808 characters]",
            "text/plain": [
              "<Figure size 1280x960 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "transform = torchvision.transforms.Compose([\n",
        "    torchvision.transforms.RandomHorizontalFlip(),\n",
        "    preprocess\n",
        "])\n",
        "\n",
        "train_set = torchvision.datasets.ImageFolder(\"./cake-classification/images/train\", transform=transform)\n",
        "train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True, drop_last=True, num_workers=2)\n",
        "\n",
        "test_set = torchvision.datasets.ImageFolder(\"./cake-classification/images/test\", transform=preprocess)\n",
        "test_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=False, drop_last=False, num_workers=2)\n",
        "\n",
        "\n",
        "images, labels = next(iter(train_loader))\n",
        "plt.figure(dpi=200)\n",
        "grid = torchvision.utils.make_grid(images, nrow=8, normalize=True).permute(1, 2, 0)\n",
        "plt.axis(\"off\")\n",
        "plt.imshow(grid)\n",
        "print([train_set.classes[i] for i in labels])\n",
        "print(images.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "i0btbTN2DCEf"
      },
      "source": [
        "## Transfer learning\n",
        "\n",
        "To perform transfer learning we must:\n",
        "1. Replace the last layer `net.fc` with a new linear layer, with the right number of output values (one for each class);\n",
        "2. setup an optimizer that will update only the parameters of the new layer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "metadata": {
        "id": "X_MNPP89vcfs"
      },
      "outputs": [],
      "source": [
        "LR = 0.01\n",
        "DEVICE = (\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
        "loss_fun = torch.nn.CrossEntropyLoss()\n",
        "loss_fun.to(DEVICE)\n",
        "\n",
        "# 512 sono le net.fc.in_features\n",
        "net.fc = torch.nn.Linear(512, len(train_set.classes))\n",
        "net = net.to(DEVICE)\n",
        "\n",
        "## ATTENZIONE: non sto passando tutto net.parameters(), ma SOLO il nuovo layer che ho inserito!\n",
        "# Quindi non traino tutto, ma SOLO l'ultimo layer\n",
        "optimizer = torch.optim.Adam(net.fc.parameters(), lr=LR)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AhrTGueFDjys"
      },
      "source": [
        "## Training\n",
        "\n",
        "The training loop is standard."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "-byNHaB0vrqs"
      },
      "outputs": [
        {
          "ename": "KeyboardInterrupt",
          "evalue": "",
          "output_type": "error",
          "traceback": [
            "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
            "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
            "Cell \u001b[0;32mIn[25], line 10\u001b[0m\n\u001b[1;32m      8\u001b[0m labels \u001b[38;5;241m=\u001b[39m labels\u001b[38;5;241m.\u001b[39mto(DEVICE)\n\u001b[1;32m      9\u001b[0m optimizer\u001b[38;5;241m.\u001b[39mzero_grad()\n\u001b[0;32m---> 10\u001b[0m logits \u001b[38;5;241m=\u001b[39m \u001b[43mnet\u001b[49m\u001b[43m(\u001b[49m\u001b[43mimages\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     11\u001b[0m loss \u001b[38;5;241m=\u001b[39m loss_fun(logits, labels)\n\u001b[1;32m     12\u001b[0m loss\u001b[38;5;241m.\u001b[39mbackward()\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1748\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1749\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torchvision/models/resnet.py:285\u001b[0m, in \u001b[0;36mResNet.forward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m    284\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, x: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 285\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_forward_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torchvision/models/resnet.py:275\u001b[0m, in \u001b[0;36mResNet._forward_impl\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m    273\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlayer1(x)\n\u001b[1;32m    274\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlayer2(x)\n\u001b[0;32m--> 275\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlayer3\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    276\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlayer4(x)\n\u001b[1;32m    278\u001b[0m x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mavgpool(x)\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1748\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1749\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/container.py:250\u001b[0m, in \u001b[0;36mSequential.forward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m    248\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m):\n\u001b[1;32m    249\u001b[0m     \u001b[38;5;28;01mfor\u001b[39;00m module \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m:\n\u001b[0;32m--> 250\u001b[0m         \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mmodule\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m    251\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28minput\u001b[39m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1748\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1749\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torchvision/models/resnet.py:92\u001b[0m, in \u001b[0;36mBasicBlock.forward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m     89\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, x: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[1;32m     90\u001b[0m     identity \u001b[38;5;241m=\u001b[39m x\n\u001b[0;32m---> 92\u001b[0m     out \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconv1\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     93\u001b[0m     out \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbn1(out)\n\u001b[1;32m     94\u001b[0m     out \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrelu(out)\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1739\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1737\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1738\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1739\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/module.py:1750\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1745\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1746\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1747\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1748\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1749\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1750\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1752\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1753\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/conv.py:554\u001b[0m, in \u001b[0;36mConv2d.forward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m    553\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 554\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_conv_forward\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbias\u001b[49m\u001b[43m)\u001b[49m\n",
            "File \u001b[0;32m~/.pyenv/versions/lab/lib/python3.12/site-packages/torch/nn/modules/conv.py:549\u001b[0m, in \u001b[0;36mConv2d._conv_forward\u001b[0;34m(self, input, weight, bias)\u001b[0m\n\u001b[1;32m    537\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mzeros\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m    538\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m F\u001b[38;5;241m.\u001b[39mconv2d(\n\u001b[1;32m    539\u001b[0m         F\u001b[38;5;241m.\u001b[39mpad(\n\u001b[1;32m    540\u001b[0m             \u001b[38;5;28minput\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_reversed_padding_repeated_twice, mode\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m    547\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgroups,\n\u001b[1;32m    548\u001b[0m     )\n\u001b[0;32m--> 549\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mF\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconv2d\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m    550\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbias\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstride\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpadding\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdilation\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgroups\u001b[49m\n\u001b[1;32m    551\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
            "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
          ]
        }
      ],
      "source": [
        "EPOCHS = 10\n",
        "\n",
        "# BISOGNA RICORDARSI DI RIPORTARE LA NET NELLA MODALITA' GIUSTA!\n",
        "# oss qui stiamo buttando computational power, dovrei settare to grad solo gli ultimi cosi\n",
        "# quindi o scansiono e setto solo quelli o boh\n",
        "net.train()\n",
        "steps = 0\n",
        "for epoch in range(EPOCHS):\n",
        "    for images, labels in train_loader:\n",
        "        images = images.to(DEVICE)\n",
        "        labels = labels.to(DEVICE)\n",
        "        optimizer.zero_grad()\n",
        "        logits = net(images)\n",
        "        loss = loss_fun(logits, labels)\n",
        "        loss.backward()\n",
        "        optimizer.step()\n",
        "        steps += 1\n",
        "        if steps % 100 == 0:\n",
        "            predictions = logits.argmax(1)\n",
        "            accuracy = 100 * (predictions == labels).float().mean()\n",
        "            print(f\"{steps} [{epoch}]  Loss: {loss.item():.4f}  Acc: {accuracy:.1f}%\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lpwn6wqRDqwf"
      },
      "source": [
        "## Evaluation\n",
        "\n",
        "Evaluation is also standard."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GhKHeVdlxhag"
      },
      "outputs": [],
      "source": [
        "nclasses = len(train_set.classes)\n",
        "cm = torch.zeros(nclasses, nclasses, dtype=torch.long)\n",
        "net.eval()\n",
        "\n",
        "for images, labels in test_loader:\n",
        "    images = images.to(DEVICE)\n",
        "    labels = labels.to(DEVICE)\n",
        "    with torch.no_grad():\n",
        "        logits = net(images)\n",
        "    predictions = logits.argmax(1)\n",
        "    counts = torch.bincount(labels * nclasses + predictions, minlength=nclasses ** 2)\n",
        "    cm += counts.view(nclasses, nclasses).cpu()\n",
        "\n",
        "accuracy = cm.diag().sum() / cm.sum()\n",
        "print(f\"Accuracy: {100 * accuracy:.1f}%\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KC9waGOMxvWF"
      },
      "outputs": [],
      "source": [
        "cmnorm = cm / cm.sum(1)\n",
        "\n",
        "plt.figure(dpi=100)\n",
        "plt.imshow(cm, cmap=\"Blues\")\n",
        "plt.xticks(range(nclasses), train_set.classes, rotation=90)\n",
        "plt.yticks(range(nclasses), train_set.classes)\n",
        "plt.colorbar()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PDaH0RQZD58x"
      },
      "source": [
        "## Errors\n",
        "\n",
        "Investigate the worst classification errors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "i5oTlyc_yfMe"
      },
      "outputs": [],
      "source": [
        "# ...\n",
        "errors = []\n",
        "\n",
        "for images, labels in test_loader:\n",
        "    images = images.to(DEVICE)\n",
        "    labels = labels.to(DEVICE)\n",
        "    with torch.no_grad():\n",
        "        logits = net(images)\n",
        "    scores = logits.max(1).values\n",
        "    predictions = logits.argmax(1)\n",
        "    for l, p, s, im in zip(labels, predictions, scores, images):\n",
        "        if l != p:\n",
        "            errors.append((s, im.cpu(), l, p))\n",
        "\n",
        "errors.sort(reverse=True)\n",
        "for s, im, l, p in errors[:5]:\n",
        "    plt.figure()\n",
        "    plt.imshow(im.permute(1, 2, 0) * 0.2 + 0.4)         # not 100% correct but roughly reverse the normalization so that i can see the picture\n",
        "    plt.title(train_set.classes[l] + \" -> \" + train_set.classes[p])\n",
        "    plt.axis(\"off\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Posso anche provare reti differenti prese da torchvision\n",
        "Nel punto 2 butto alcune convoluzioni (rimpiazzandole con identity) e vedo che succede\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y2JI9fZZEyS3"
      },
      "source": [
        "## Assignment\n",
        "\n",
        "As homework, review and refine the scripts programmed in the lab activity.\n",
        "You can try the followings:\n",
        "\n",
        "1. Compare different low-level features and their combination by concatenation;\n",
        "2. Try transfer learening from earlier layers of the ResNet. You can remove some of the layers by replacing them with `torch.nn.Identity`.\n",
        "3. Try to fine-tune the CNN after transfer learning (i.e. continue\n",
        "training with as very small learning rate like $10^{-5}$).\n",
        "\n",
        "### Report\n",
        "\n",
        "Prepare a report of one or two pages with the answers to the questions\n",
        "(include a short comment for each question).  The report must be in\n",
        "the PDF format.  Include your name in the report and conclude the\n",
        "document with the following statement: ``I affirm that this report is\n",
        "the result of my own work and that I did not share any part of it with\n",
        "anyone else except the teacher.''\n",
        "\n",
        "Make a ZIP archive with the report and the PDF of the notebook you used (with all the outputs) and and upload it on the course website."
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "gpuType": "L4",
      "machine_shape": "hm",
      "provenance": []
    },
    "kernelspec": {
      "display_name": "lab",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
