diff --git a/TD2 Deep Learning.ipynb b/TD2 Deep Learning.ipynb index 00e4fdc78c068248ca0742c64725d155b3681f0d..68483a7d09145f512ac89140755af905c35b2541 100644 --- a/TD2 Deep Learning.ipynb +++ b/TD2 Deep Learning.ipynb @@ -33,10 +33,54 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "330a42f5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: torch in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (2.2.0)\n", + "Collecting torchvision\n", + " Downloading torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl.metadata (6.1 kB)\n", + "Requirement already satisfied: filelock in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (3.13.1)\n", + "Requirement already satisfied: typing-extensions>=4.8.0 in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (4.9.0)\n", + "Requirement already satisfied: sympy in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (1.12)\n", + "Requirement already satisfied: networkx in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (3.2.1)\n", + "Requirement already satisfied: jinja2 in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (3.1.3)\n", + "Requirement already satisfied: fsspec in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torch) (2024.2.0)\n", + "Requirement already satisfied: numpy in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torchvision) (1.26.3)\n", + "Collecting torch\n", + " Downloading torch-2.5.1-cp311-none-macosx_11_0_arm64.whl.metadata (28 kB)\n", + "Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from torchvision) (10.2.0)\n", + "Collecting sympy==1.13.1 (from torch)\n", + " Downloading sympy-1.13.1-py3-none-any.whl.metadata (12 kB)\n", + "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from sympy==1.13.1->torch) (1.3.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /Users/heber/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from jinja2->torch) (2.1.5)\n", + "Downloading torchvision-0.20.1-cp311-cp311-macosx_11_0_arm64.whl (1.8 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m827.0 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25hDownloading torch-2.5.1-cp311-none-macosx_11_0_arm64.whl (63.9 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m63.9/63.9 MB\u001b[0m \u001b[31m4.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0mm\n", + "\u001b[?25hDownloading sympy-1.13.1-py3-none-any.whl (6.2 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m6.2/6.2 MB\u001b[0m \u001b[31m4.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0mm\n", + "\u001b[?25hInstalling collected packages: sympy, torch, torchvision\n", + " Attempting uninstall: sympy\n", + " Found existing installation: sympy 1.12\n", + " Uninstalling sympy-1.12:\n", + " Successfully uninstalled sympy-1.12\n", + " Attempting uninstall: torch\n", + " Found existing installation: torch 2.2.0\n", + " Uninstalling torch-2.2.0:\n", + " Successfully uninstalled torch-2.2.0\n", + "Successfully installed sympy-1.13.1 torch-2.5.1 torchvision-0.20.1\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ "%pip install torch torchvision" ] @@ -52,10 +96,72 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "b1950f0a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor([[-0.4614, 0.2167, 1.3662, 0.5457, 2.7665, 0.8728, -0.1837, 0.0607,\n", + " 1.5946, -0.7726],\n", + " [-0.8952, 0.7103, -0.7606, 0.9257, -0.1401, 0.5907, 0.7204, 1.3177,\n", + " -0.4342, 0.4527],\n", + " [ 0.7967, 0.1907, -0.5346, 1.4139, -0.5380, -2.1966, 0.4751, 1.4743,\n", + " 1.2449, -0.8389],\n", + " [ 0.0833, 0.5977, -0.7399, -0.4702, -0.6887, 1.1328, -1.1584, 0.3544,\n", + " 1.0611, -0.0325],\n", + " [ 0.5764, -0.5985, -1.0803, -0.7565, -1.0020, 1.7249, -0.6647, 0.7847,\n", + " 1.7402, 0.8243],\n", + " [-0.9695, 0.5117, 1.9237, 1.7299, 1.0193, 0.3211, -0.5839, 0.5866,\n", + " 1.0019, -0.2681],\n", + " [-0.4172, -2.3619, -1.1206, -0.7292, 0.9231, -0.3644, 0.6110, 1.3185,\n", + " 1.2674, -1.5235],\n", + " [ 0.2213, -0.5554, -0.4785, 0.9106, 0.1333, 1.1237, 0.2859, -1.6737,\n", + " -0.8616, -2.5445],\n", + " [ 0.2351, 1.3325, 0.1848, 0.1473, 1.3133, -0.7523, 0.6736, 1.8610,\n", + " -0.1847, 1.0223],\n", + " [-0.6824, -0.0298, -0.1910, 1.4017, -1.9937, 0.4087, 0.0165, 1.7551,\n", + " -0.6690, -0.7425],\n", + " [-1.3005, -0.5498, -1.3494, -1.2090, 0.3210, 0.7386, 0.5926, -0.6941,\n", + " -0.1688, -0.6065],\n", + " [ 0.4044, 0.6994, -0.9141, -0.3529, 1.0734, -0.9639, 0.0657, -0.2253,\n", + " 0.3391, 0.5039],\n", + " [-2.1911, 1.6130, -0.7344, -1.0796, -0.3465, -0.9285, -0.5405, -0.0072,\n", + " -0.1058, -1.7597],\n", + " [-1.4770, 0.3449, 0.6489, 1.7304, -0.0802, -0.0332, -0.2949, 0.2265,\n", + " -0.7456, 0.8549]])\n", + "AlexNet(\n", + " (features): Sequential(\n", + " (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2))\n", + " (1): ReLU(inplace=True)\n", + " (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", + " (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))\n", + " (4): ReLU(inplace=True)\n", + " (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", + " (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (7): ReLU(inplace=True)\n", + " (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (9): ReLU(inplace=True)\n", + " (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (11): ReLU(inplace=True)\n", + " (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)\n", + " )\n", + " (avgpool): AdaptiveAvgPool2d(output_size=(6, 6))\n", + " (classifier): Sequential(\n", + " (0): Dropout(p=0.5, inplace=False)\n", + " (1): Linear(in_features=9216, out_features=4096, bias=True)\n", + " (2): ReLU(inplace=True)\n", + " (3): Dropout(p=0.5, inplace=False)\n", + " (4): Linear(in_features=4096, out_features=4096, bias=True)\n", + " (5): ReLU(inplace=True)\n", + " (6): Linear(in_features=4096, out_features=1000, bias=True)\n", + " )\n", + ")\n" + ] + } + ], "source": [ "import torch\n", "\n", @@ -95,10 +201,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "6e18f2fd", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CUDA is not available. Training on CPU ...\n" + ] + } + ], "source": [ "import torch\n", "\n", @@ -111,6 +225,29 @@ " print(\"CUDA is available! Training on GPU ...\")" ] }, + { + "cell_type": "code", + "execution_count": 10, + "id": "abb4553c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tensor([1.], device='mps:0')\n" + ] + } + ], + "source": [ + "if torch.backends.mps.is_available():\n", + " mps_device = torch.device(\"mps\")\n", + " x = torch.ones(1, device=mps_device)\n", + " print (x)\n", + "else:\n", + " print (\"MPS device not found.\")" + ] + }, { "cell_type": "markdown", "id": "5cf214eb", @@ -121,10 +258,31 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "462666a2", + "execution_count": 5, + "id": "711b0b8e", "metadata": {}, "outputs": [], + "source": [ + "import numpy as np\n", + "from torchvision import datasets, transforms\n", + "from torch.utils.data.sampler import SubsetRandomSampler" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "462666a2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Files already downloaded and verified\n", + "Files already downloaded and verified\n" + ] + } + ], "source": [ "import numpy as np\n", "from torchvision import datasets, transforms\n", @@ -193,10 +351,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 32, "id": "317bf070", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Net(\n", + " (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n", + " (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", + " (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n", + " (fc1): Linear(in_features=400, out_features=120, bias=True)\n", + " (fc2): Linear(in_features=120, out_features=84, bias=True)\n", + " (fc3): Linear(in_features=84, out_features=10, bias=True)\n", + ")\n" + ] + } + ], "source": [ "import torch.nn as nn\n", "import torch.nn.functional as F\n", @@ -242,10 +415,58 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 34, "id": "4b53f229", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 0 \tTraining Loss: 28.707199 \tValidation Loss: 28.363214\n", + "Validation loss decreased (inf --> 28.363214). Saving model ...\n", + "Epoch: 1 \tTraining Loss: 27.053440 \tValidation Loss: 26.921309\n", + "Validation loss decreased (28.363214 --> 26.921309). Saving model ...\n", + "Epoch: 2 \tTraining Loss: 25.798181 \tValidation Loss: 25.484369\n", + "Validation loss decreased (26.921309 --> 25.484369). Saving model ...\n", + "Epoch: 3 \tTraining Loss: 24.616021 \tValidation Loss: 25.825257\n", + "Epoch: 4 \tTraining Loss: 23.607140 \tValidation Loss: 24.406983\n", + "Validation loss decreased (25.484369 --> 24.406983). Saving model ...\n", + "Epoch: 5 \tTraining Loss: 22.641223 \tValidation Loss: 23.463277\n", + "Validation loss decreased (24.406983 --> 23.463277). Saving model ...\n", + "Epoch: 6 \tTraining Loss: 21.727461 \tValidation Loss: 23.323754\n", + "Validation loss decreased (23.463277 --> 23.323754). Saving model ...\n", + "Epoch: 7 \tTraining Loss: 20.908013 \tValidation Loss: 22.815489\n", + "Validation loss decreased (23.323754 --> 22.815489). Saving model ...\n", + "Epoch: 8 \tTraining Loss: 20.072570 \tValidation Loss: 22.468899\n", + "Validation loss decreased (22.815489 --> 22.468899). Saving model ...\n", + "Epoch: 9 \tTraining Loss: 19.337123 \tValidation Loss: 23.307148\n", + "Epoch: 10 \tTraining Loss: 18.578279 \tValidation Loss: 22.322720\n", + "Validation loss decreased (22.468899 --> 22.322720). Saving model ...\n", + "Epoch: 11 \tTraining Loss: 17.925301 \tValidation Loss: 22.491466\n", + "Epoch: 12 \tTraining Loss: 17.266396 \tValidation Loss: 22.145613\n", + "Validation loss decreased (22.322720 --> 22.145613). Saving model ...\n", + "Epoch: 13 \tTraining Loss: 16.644972 \tValidation Loss: 21.923327\n", + "Validation loss decreased (22.145613 --> 21.923327). Saving model ...\n", + "Epoch: 14 \tTraining Loss: 16.097757 \tValidation Loss: 22.242258\n", + "Epoch: 15 \tTraining Loss: 15.522903 \tValidation Loss: 22.269535\n", + "Epoch: 16 \tTraining Loss: 14.930308 \tValidation Loss: 23.073589\n", + "Epoch: 17 \tTraining Loss: 14.374154 \tValidation Loss: 23.190186\n", + "Epoch: 18 \tTraining Loss: 13.829007 \tValidation Loss: 23.638800\n", + "Epoch: 19 \tTraining Loss: 13.414001 \tValidation Loss: 25.147587\n", + "Epoch: 20 \tTraining Loss: 12.890743 \tValidation Loss: 24.385583\n", + "Epoch: 21 \tTraining Loss: 12.456227 \tValidation Loss: 24.933902\n", + "Epoch: 22 \tTraining Loss: 11.993389 \tValidation Loss: 25.289021\n", + "Epoch: 23 \tTraining Loss: 11.565563 \tValidation Loss: 26.004760\n", + "Epoch: 24 \tTraining Loss: 11.188692 \tValidation Loss: 26.451757\n", + "Epoch: 25 \tTraining Loss: 10.716678 \tValidation Loss: 27.236794\n", + "Epoch: 26 \tTraining Loss: 10.315807 \tValidation Loss: 27.493770\n", + "Epoch: 27 \tTraining Loss: 9.975283 \tValidation Loss: 27.571290\n", + "Epoch: 28 \tTraining Loss: 9.440035 \tValidation Loss: 29.006522\n", + "Epoch: 29 \tTraining Loss: 9.220511 \tValidation Loss: 29.190469\n" + ] + } + ], "source": [ "import torch.optim as optim\n", "\n", @@ -324,18 +545,179 @@ "Does overfit occur? If so, do an early stopping." ] }, + { + "cell_type": "markdown", + "id": "4e567158", + "metadata": {}, + "source": [ + "Yes, overfitting occurs. This is evident starting around Epoch 15, where the Validation Loss stops decreasing and begins to oscillate or increase, while the Training Loss continues to decrease. \n", + "This indicates the model is fitting too closely to the training data and failling to generalize well to the validation data.\n", + "By doing an early stopping, the training should stop around Epoch 15, where the Validation Loss reaches its minimum value of 21.882406. Continuing beyond this point does not improve validation performance and increases the risk of overfitting." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, + "id": "11952c52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch: 0 \tTraining Loss: 8.891932 \tValidation Loss: 30.875338\n", + "Validation loss decreased (inf --> 30.875338). Saving model ...\n" + ] + }, + { + "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[35], line 35\u001b[0m\n\u001b[1;32m 33\u001b[0m loss \u001b[38;5;241m=\u001b[39m criterion(output, target)\n\u001b[1;32m 34\u001b[0m \u001b[38;5;66;03m# Backward pass: compute gradient of the loss with respect to model parameters\u001b[39;00m\n\u001b[0;32m---> 35\u001b[0m \u001b[43mloss\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 36\u001b[0m \u001b[38;5;66;03m# Perform a single optimization step (parameter update)\u001b[39;00m\n\u001b[1;32m 37\u001b[0m optimizer\u001b[38;5;241m.\u001b[39mstep()\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/_tensor.py:581\u001b[0m, in \u001b[0;36mTensor.backward\u001b[0;34m(self, gradient, retain_graph, create_graph, inputs)\u001b[0m\n\u001b[1;32m 571\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m has_torch_function_unary(\u001b[38;5;28mself\u001b[39m):\n\u001b[1;32m 572\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m handle_torch_function(\n\u001b[1;32m 573\u001b[0m Tensor\u001b[38;5;241m.\u001b[39mbackward,\n\u001b[1;32m 574\u001b[0m (\u001b[38;5;28mself\u001b[39m,),\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 579\u001b[0m inputs\u001b[38;5;241m=\u001b[39minputs,\n\u001b[1;32m 580\u001b[0m )\n\u001b[0;32m--> 581\u001b[0m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mautograd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 582\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgradient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minputs\u001b[49m\n\u001b[1;32m 583\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/autograd/__init__.py:347\u001b[0m, in \u001b[0;36mbackward\u001b[0;34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[0m\n\u001b[1;32m 342\u001b[0m retain_graph \u001b[38;5;241m=\u001b[39m create_graph\n\u001b[1;32m 344\u001b[0m \u001b[38;5;66;03m# The reason we repeat the same comment below is that\u001b[39;00m\n\u001b[1;32m 345\u001b[0m \u001b[38;5;66;03m# some Python versions print out the first line of a multi-line function\u001b[39;00m\n\u001b[1;32m 346\u001b[0m \u001b[38;5;66;03m# calls in the traceback and some print out the last line\u001b[39;00m\n\u001b[0;32m--> 347\u001b[0m \u001b[43m_engine_run_backward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 348\u001b[0m \u001b[43m \u001b[49m\u001b[43mtensors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 349\u001b[0m \u001b[43m \u001b[49m\u001b[43mgrad_tensors_\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 350\u001b[0m \u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 351\u001b[0m \u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 352\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 353\u001b[0m \u001b[43m \u001b[49m\u001b[43mallow_unreachable\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 354\u001b[0m \u001b[43m \u001b[49m\u001b[43maccumulate_grad\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 355\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/autograd/graph.py:825\u001b[0m, in \u001b[0;36m_engine_run_backward\u001b[0;34m(t_outputs, *args, **kwargs)\u001b[0m\n\u001b[1;32m 823\u001b[0m unregister_hooks \u001b[38;5;241m=\u001b[39m _register_logging_hooks_on_whole_graph(t_outputs)\n\u001b[1;32m 824\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 825\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mVariable\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_execution_engine\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun_backward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Calls into the C++ engine to run the backward pass\u001b[39;49;00m\n\u001b[1;32m 826\u001b[0m \u001b[43m \u001b[49m\u001b[43mt_outputs\u001b[49m\u001b[43m,\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\n\u001b[1;32m 827\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# Calls into the C++ engine to run the backward pass\u001b[39;00m\n\u001b[1;32m 828\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[1;32m 829\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m attach_logging_hooks:\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "# EARLY STOP\n", + "import torch.optim as optim\n", + "\n", + "min_epochs = 10\n", + "patience = 3 # Nb of epochs to wait after no improvement\n", + "epochs_no_improve = 0\n", + "\n", + "\n", + "criterion = nn.CrossEntropyLoss() # specify loss function\n", + "optimizer = optim.SGD(model.parameters(), lr=0.01) # specify optimizer\n", + "\n", + "n_epochs = 30 # number of epochs to train the model\n", + "valid_loss_list = [] # list to store validation loss to visualize\n", + "train_loss_list = [] # list to store trainloss to visualize\n", + "valid_loss_min = np.Inf # track change in validation loss\n", + "\n", + "for epoch in range(n_epochs):\n", + " # Keep track of training and validation loss\n", + " train_loss = 0.0\n", + " valid_loss = 0.0\n", + "\n", + " # Train the model\n", + " model.train()\n", + " for data, target in train_loader:\n", + " # Move tensors to GPU if CUDA is available\n", + " if train_on_gpu:\n", + " data, target = data.cuda(), target.cuda()\n", + " # Clear the gradients of all optimized variables\n", + " optimizer.zero_grad()\n", + " # Forward pass: compute predicted outputs by passing inputs to the model\n", + " output = model(data)\n", + " # Calculate the batch loss\n", + " loss = criterion(output, target)\n", + " # Backward pass: compute gradient of the loss with respect to model parameters\n", + " loss.backward()\n", + " # Perform a single optimization step (parameter update)\n", + " optimizer.step()\n", + " # Update training loss\n", + " train_loss += loss.item() * data.size(0)\n", + "\n", + " # Validate the model\n", + " model.eval()\n", + " for data, target in valid_loader:\n", + " # Move tensors to GPU if CUDA is available\n", + " if train_on_gpu:\n", + " data, target = data.cuda(), target.cuda()\n", + " # Forward pass: compute predicted outputs by passing inputs to the model\n", + " output = model(data)\n", + " # Calculate the batch loss\n", + " loss = criterion(output, target)\n", + " # Update average validation loss\n", + " valid_loss += loss.item() * data.size(0)\n", + "\n", + " # Calculate average losses\n", + " train_loss = train_loss / len(train_loader)\n", + " valid_loss = valid_loss / len(valid_loader)\n", + " train_loss_list.append(train_loss)\n", + " valid_loss_list.append(valid_loss)\n", + "\n", + " # Print training/validation statistics\n", + " print(\n", + " \"Epoch: {} \\tTraining Loss: {:.6f} \\tValidation Loss: {:.6f}\".format(\n", + " epoch, train_loss, valid_loss\n", + " )\n", + " )\n", + "\n", + " # Save model if validation loss has decreased\n", + " if valid_loss <= valid_loss_min:\n", + " print(\n", + " \"Validation loss decreased ({:.6f} --> {:.6f}). Saving model ...\".format(\n", + " valid_loss_min, valid_loss\n", + " )\n", + " )\n", + " torch.save(model.state_dict(), \"model_cifar_1_early_stop.pt\")\n", + " valid_loss_min = valid_loss\n", + " epochs_no_improve = 0\n", + " elif epoch >= min_epochs:\n", + " epochs_no_improve += 1\n", + " if epochs_no_improve >= patience:\n", + " print(f\"Validation loss increased for {patience} times consecutives. Applying Early Stop.\")\n", + " break\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, "id": "d39df818", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "plt.plot(range(len(train_loss_list)), train_loss_list)\n", + "plt.xlabel(\"Epoch\")\n", + "plt.ylabel(\"Train Loss\")\n", + "plt.title(\"Performance of Model 1\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2111dfe9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "\n", - "plt.plot(range(n_epochs), train_loss_list)\n", + "plt.plot(range(len(valid_loss_list)), valid_loss_list)\n", "plt.xlabel(\"Epoch\")\n", - "plt.ylabel(\"Loss\")\n", + "plt.ylabel(\"Validation Loss\")\n", "plt.title(\"Performance of Model 1\")\n", "plt.show()" ] @@ -353,9 +735,39 @@ "execution_count": null, "id": "e93efdfc", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/qb/94v41qkx157gvjjjv1rchcr00000gn/T/ipykernel_25820/3291884398.py:1: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " model.load_state_dict(torch.load(\"./model_cifar.pt\"))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test Loss: 21.811477\n", + "\n", + "Test Accuracy of airplane: 71% (716/1000)\n", + "Test Accuracy of automobile: 75% (750/1000)\n", + "Test Accuracy of bird: 55% (558/1000)\n", + "Test Accuracy of cat: 44% (442/1000)\n", + "Test Accuracy of deer: 60% (604/1000)\n", + "Test Accuracy of dog: 52% (521/1000)\n", + "Test Accuracy of frog: 64% (644/1000)\n", + "Test Accuracy of horse: 58% (588/1000)\n", + "Test Accuracy of ship: 74% (746/1000)\n", + "Test Accuracy of truck: 68% (681/1000)\n", + "\n", + "Test Accuracy (Overall): 62% (6250/10000)\n" + ] + } + ], "source": [ - "model.load_state_dict(torch.load(\"./model_cifar.pt\"))\n", + "# model.load_state_dict(torch.load(\"./model_cifar.pt\"))\n", + "model.load_state_dict(torch.load(\"./model_cifar_1_early_stop.pt\"))\n", "\n", "# track test loss\n", "test_loss = 0.0\n", @@ -434,6 +846,337 @@ "Compare the results obtained with this new network to those obtained previously." ] }, + { + "cell_type": "code", + "execution_count": 36, + "id": "8b67c2c6", + "metadata": {}, + "outputs": [], + "source": [ + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "# define the CNN architecture\n", + "\n", + "class NewNet(nn.Module):\n", + " def __init__(self, dropout_value=0.5):\n", + " super(NewNet, self).__init__()\n", + " # Convolutional layers\n", + " self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)\n", + " self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1)\n", + " self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n", + " \n", + " # MaxPool\n", + " self.pool = nn.MaxPool2d(kernel_size=2)\n", + "\n", + " # Dropout\n", + " self.dropout = nn.Dropout(p=dropout_value)\n", + " \n", + " # Fully connected layers\n", + " # self.fc1 = nn.Linear(in_features=64 * (input_size // 8) * (input_size // 8), out_features=512)\n", + " self.fc1 = nn.Linear(in_features=64 * 4 * 4, out_features=512)\n", + " self.fc2 = nn.Linear(in_features=512, out_features=64)\n", + " self.fc3 = nn.Linear(64, 10)\n", + "\n", + " def forward(self, x):\n", + " # Convolutional layers with ReLU and MaxPool\n", + " x = self.pool(F.relu(self.conv1(x)))\n", + " x = self.pool(F.relu(self.conv2(x)))\n", + " x = self.pool(F.relu(self.conv3(x)))\n", + "\n", + " x = x.view(x.size(0), -1)\n", + " x = self.dropout(F.relu(self.fc1(x)))\n", + " x = self.dropout(F.relu(self.fc2(x)))\n", + " x = self.fc3(x)\n", + " return x\n", + "\n", + "\n", + "# # create a complete CNN\n", + "# new_model = NewNet()\n", + "# print(new_model)\n", + "# # move tensors to GPU if CUDA is available\n", + "# if train_on_gpu:\n", + "# new_model.cuda()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cc6cc8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NewNet(\n", + " (conv1): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (conv2): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (conv3): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", + " (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", + " (dropout): Dropout(p=0.5, inplace=False)\n", + " (fc1): Linear(in_features=1024, out_features=512, bias=True)\n", + " (fc2): Linear(in_features=512, out_features=64, bias=True)\n", + " (fc3): Linear(in_features=64, out_features=10, bias=True)\n", + ")\n", + "Epoch: 0 \tTraining Loss: 44.934554 \tValidation Loss: 40.292926\n", + "Validation loss decreased (inf --> 40.292926). Saving model ...\n", + "Epoch: 1 \tTraining Loss: 38.547384 \tValidation Loss: 34.307230\n", + "Validation loss decreased (40.292926 --> 34.307230). Saving model ...\n", + "Epoch: 2 \tTraining Loss: 34.167031 \tValidation Loss: 30.783441\n", + "Validation loss decreased (34.307230 --> 30.783441). Saving model ...\n", + "Epoch: 3 \tTraining Loss: 31.514744 \tValidation Loss: 29.177271\n", + "Validation loss decreased (30.783441 --> 29.177271). Saving model ...\n", + "Epoch: 4 \tTraining Loss: 29.490232 \tValidation Loss: 26.770098\n", + "Validation loss decreased (29.177271 --> 26.770098). Saving model ...\n", + "Epoch: 5 \tTraining Loss: 27.982251 \tValidation Loss: 25.774428\n", + "Validation loss decreased (26.770098 --> 25.774428). Saving model ...\n", + "Epoch: 6 \tTraining Loss: 26.515079 \tValidation Loss: 24.038370\n", + "Validation loss decreased (25.774428 --> 24.038370). Saving model ...\n", + "Epoch: 7 \tTraining Loss: 25.259680 \tValidation Loss: 23.620053\n", + "Validation loss decreased (24.038370 --> 23.620053). Saving model ...\n", + "Epoch: 8 \tTraining Loss: 23.969766 \tValidation Loss: 22.249926\n", + "Validation loss decreased (23.620053 --> 22.249926). Saving model ...\n", + "Epoch: 9 \tTraining Loss: 23.044149 \tValidation Loss: 21.061266\n", + "Validation loss decreased (22.249926 --> 21.061266). Saving model ...\n", + "Epoch: 10 \tTraining Loss: 21.929328 \tValidation Loss: 20.193573\n", + "Validation loss decreased (21.061266 --> 20.193573). Saving model ...\n", + "Epoch: 11 \tTraining Loss: 21.162510 \tValidation Loss: 19.769918\n", + "Validation loss decreased (20.193573 --> 19.769918). Saving model ...\n", + "Epoch: 12 \tTraining Loss: 20.163602 \tValidation Loss: 19.290062\n", + "Validation loss decreased (19.769918 --> 19.290062). Saving model ...\n", + "Epoch: 13 \tTraining Loss: 19.370121 \tValidation Loss: 18.626375\n", + "Validation loss decreased (19.290062 --> 18.626375). Saving model ...\n", + "Epoch: 14 \tTraining Loss: 18.558041 \tValidation Loss: 18.075628\n", + "Validation loss decreased (18.626375 --> 18.075628). Saving model ...\n" + ] + } + ], + "source": [ + "\n", + "# create a complete CNN\n", + "new_model = NewNet()\n", + "print(new_model)\n", + "# move tensors to GPU if CUDA is available\n", + "if train_on_gpu:\n", + " new_model.cuda()\n", + "\n", + "\n", + "import torch.optim as optim\n", + "\n", + "min_epochs = 10\n", + "patience = 3 # Nb of epochs to wait after no improvement\n", + "epochs_no_improve = 0\n", + "\n", + "\n", + "criterion = nn.CrossEntropyLoss() # specify loss function\n", + "optimizer = optim.SGD(new_model.parameters(), lr=0.01) # specify optimizer\n", + "\n", + "n_epochs = 30 # number of epochs to train the model\n", + "valid_loss_list = [] # list to store validation loss to visualize\n", + "train_loss_list = [] # list to store trainloss to visualize\n", + "valid_loss_min = np.Inf # track change in validation loss\n", + "\n", + "for epoch in range(n_epochs):\n", + " # Keep track of training and validation loss\n", + " train_loss = 0.0\n", + " valid_loss = 0.0\n", + "\n", + " # Train the model\n", + " new_model.train()\n", + " for data, target in train_loader:\n", + " # Move tensors to GPU if CUDA is available\n", + " if train_on_gpu:\n", + " data, target = data.cuda(), target.cuda()\n", + " # Clear the gradients of all optimized variables\n", + " optimizer.zero_grad()\n", + " # Forward pass: compute predicted outputs by passing inputs to the model\n", + " output = new_model(data)\n", + " # Calculate the batch loss\n", + " loss = criterion(output, target)\n", + " # Backward pass: compute gradient of the loss with respect to model parameters\n", + " loss.backward()\n", + " # Perform a single optimization step (parameter update)\n", + " optimizer.step()\n", + " # Update training loss\n", + " train_loss += loss.item() * data.size(0)\n", + "\n", + " # Validate the model\n", + " new_model.eval()\n", + " for data, target in valid_loader:\n", + " # Move tensors to GPU if CUDA is available\n", + " if train_on_gpu:\n", + " data, target = data.cuda(), target.cuda()\n", + " # Forward pass: compute predicted outputs by passing inputs to the model\n", + " output = new_model(data)\n", + " # Calculate the batch loss\n", + " loss = criterion(output, target)\n", + " # Update average validation loss\n", + " valid_loss += loss.item() * data.size(0)\n", + "\n", + " # Calculate average losses\n", + " train_loss = train_loss / len(train_loader)\n", + " valid_loss = valid_loss / len(valid_loader)\n", + " train_loss_list.append(train_loss)\n", + " valid_loss_list.append(valid_loss)\n", + "\n", + " # Print training/validation statistics\n", + " print(\n", + " \"Epoch: {} \\tTraining Loss: {:.6f} \\tValidation Loss: {:.6f}\".format(\n", + " epoch, train_loss, valid_loss\n", + " )\n", + " )\n", + "\n", + " # Save model if validation loss has decreased\n", + " if valid_loss <= valid_loss_min:\n", + " print(\n", + " \"Validation loss decreased ({:.6f} --> {:.6f}). Saving model ...\".format(\n", + " valid_loss_min, valid_loss\n", + " )\n", + " )\n", + " torch.save(new_model.state_dict(), \"model_cifar_2.pt\")\n", + " valid_loss_min = valid_loss\n", + " epochs_no_improve = 0\n", + " elif epoch >= min_epochs:\n", + " epochs_no_improve += 1\n", + " if epochs_no_improve >= patience:\n", + " print(f\"Validation loss increased for {patience} times consecutives. Applying Early Stop.\")\n", + " break\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "97355006", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/qb/94v41qkx157gvjjjv1rchcr00000gn/T/ipykernel_32008/3634208260.py:1: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n", + " model.load_state_dict(torch.load(\"./model_cifar_2.pt\"))\n" + ] + }, + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: './model_cifar_2.pt'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[31], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m model\u001b[38;5;241m.\u001b[39mload_state_dict(\u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mload\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m./model_cifar_2.pt\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m)\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m# track test loss\u001b[39;00m\n\u001b[1;32m 4\u001b[0m test_loss \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.0\u001b[39m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/serialization.py:1319\u001b[0m, in \u001b[0;36mload\u001b[0;34m(f, map_location, pickle_module, weights_only, mmap, **pickle_load_args)\u001b[0m\n\u001b[1;32m 1316\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mencoding\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m pickle_load_args\u001b[38;5;241m.\u001b[39mkeys():\n\u001b[1;32m 1317\u001b[0m pickle_load_args[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mencoding\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m-> 1319\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43m_open_file_like\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrb\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m opened_file:\n\u001b[1;32m 1320\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _is_zipfile(opened_file):\n\u001b[1;32m 1321\u001b[0m \u001b[38;5;66;03m# The zipfile reader is going to advance the current file position.\u001b[39;00m\n\u001b[1;32m 1322\u001b[0m \u001b[38;5;66;03m# If we want to actually tail call to torch.jit.load, we need to\u001b[39;00m\n\u001b[1;32m 1323\u001b[0m \u001b[38;5;66;03m# reset back to the original position.\u001b[39;00m\n\u001b[1;32m 1324\u001b[0m orig_position \u001b[38;5;241m=\u001b[39m opened_file\u001b[38;5;241m.\u001b[39mtell()\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/serialization.py:659\u001b[0m, in \u001b[0;36m_open_file_like\u001b[0;34m(name_or_buffer, mode)\u001b[0m\n\u001b[1;32m 657\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_open_file_like\u001b[39m(name_or_buffer, mode):\n\u001b[1;32m 658\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _is_path(name_or_buffer):\n\u001b[0;32m--> 659\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_open_file\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 660\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 661\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m mode:\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/serialization.py:640\u001b[0m, in \u001b[0;36m_open_file.__init__\u001b[0;34m(self, name, mode)\u001b[0m\n\u001b[1;32m 639\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, name, mode):\n\u001b[0;32m--> 640\u001b[0m \u001b[38;5;28msuper\u001b[39m()\u001b[38;5;241m.\u001b[39m\u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmode\u001b[49m\u001b[43m)\u001b[49m)\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: './model_cifar_2.pt'" + ] + } + ], + "source": [ + "model.load_state_dict(torch.load(\"./model_cifar_2.pt\"))\n", + "\n", + "# track test loss\n", + "test_loss = 0.0\n", + "class_correct = list(0.0 for i in range(10))\n", + "class_total = list(0.0 for i in range(10))\n", + "\n", + "model.eval()\n", + "# iterate over test data\n", + "for data, target in test_loader:\n", + " # move tensors to GPU if CUDA is available\n", + " if train_on_gpu:\n", + " data, target = data.cuda(), target.cuda()\n", + " # forward pass: compute predicted outputs by passing inputs to the model\n", + " output = model(data)\n", + " # calculate the batch loss\n", + " loss = criterion(output, target)\n", + " # update test loss\n", + " test_loss += loss.item() * data.size(0)\n", + " # convert output probabilities to predicted class\n", + " _, pred = torch.max(output, 1)\n", + " # compare predictions to true label\n", + " correct_tensor = pred.eq(target.data.view_as(pred))\n", + " correct = (\n", + " np.squeeze(correct_tensor.numpy())\n", + " if not train_on_gpu\n", + " else np.squeeze(correct_tensor.cpu().numpy())\n", + " )\n", + " # calculate test accuracy for each object class\n", + " for i in range(batch_size):\n", + " label = target.data[i]\n", + " class_correct[label] += correct[i].item()\n", + " class_total[label] += 1\n", + "\n", + "# average test loss\n", + "test_loss = test_loss / len(test_loader)\n", + "print(\"Test Loss: {:.6f}\\n\".format(test_loss))\n", + "\n", + "for i in range(10):\n", + " if class_total[i] > 0:\n", + " print(\n", + " \"Test Accuracy of %5s: %2d%% (%2d/%2d)\"\n", + " % (\n", + " classes[i],\n", + " 100 * class_correct[i] / class_total[i],\n", + " np.sum(class_correct[i]),\n", + " np.sum(class_total[i]),\n", + " )\n", + " )\n", + " else:\n", + " print(\"Test Accuracy of %5s: N/A (no training examples)\" % (classes[i]))\n", + "\n", + "print(\n", + " \"\\nTest Accuracy (Overall): %2d%% (%2d/%2d)\"\n", + " % (\n", + " 100.0 * np.sum(class_correct) / np.sum(class_total),\n", + " np.sum(class_correct),\n", + " np.sum(class_total),\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6245b27f", + "metadata": {}, + "source": [ + "# Test Accuracy: Model 1 v/s Model 2\n", + "\n", + "## Test Accuracy Model 1: \n", + "* Test Loss: 21.811477\n", + "\n", + "* Test Accuracy of airplane: 71% (716/1000)\n", + "* Test Accuracy of automobile: 75% (750/1000)\n", + "* Test Accuracy of bird: 55% (558/1000)\n", + "* Test Accuracy of cat: 44% (442/1000)\n", + "* Test Accuracy of deer: 60% (604/1000)\n", + "* Test Accuracy of dog: 52% (521/1000)\n", + "* Test Accuracy of frog: 64% (644/1000)\n", + "* Test Accuracy of horse: 58% (588/1000)\n", + "* Test Accuracy of ship: 74% (746/1000)\n", + "* Test Accuracy of truck: 68% (681/1000)\n", + "\n", + "* Test Accuracy (Overall): 62% (6250/10000)\n", + "\n", + "\n", + "## Test Accuracy Model 2:\n", + "* Test Loss: 16.239906\n", + "\n", + "* Test Accuracy of airplane: 78% (784/1000)\n", + "* Test Accuracy of automobile: 88% (889/1000)\n", + "* Test Accuracy of bird: 61% (618/1000)\n", + "* Test Accuracy of cat: 61% (615/1000)\n", + "* Test Accuracy of deer: 66% (662/1000)\n", + "* Test Accuracy of dog: 50% (509/1000)\n", + "* Test Accuracy of frog: 82% (823/1000)\n", + "* Test Accuracy of horse: 73% (732/1000)\n", + "* Test Accuracy of ship: 86% (862/1000)\n", + "* Test Accuracy of truck: 75% (751/1000)\n", + "\n", + "* Test Accuracy (Overall): 72% (7245/10000)" + ] + }, { "cell_type": "markdown", "id": "bc381cf4", @@ -451,10 +1194,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "ef623c26", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "model: fp32 \t Size (KB): 2330.946\n" + ] + }, + { + "data": { + "text/plain": [ + "2330946" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import os\n", "\n", @@ -483,15 +1244,43 @@ "execution_count": null, "id": "c4c65d4b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "Didn't find engine for operation quantized::linear_prepack NoQEngine", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[30], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mtorch\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mquantization\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m quantized_model \u001b[38;5;241m=\u001b[39m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mquantization\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mquantize_dynamic\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mqint8\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5\u001b[0m print_size_of_model(quantized_model, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mint8\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/quantization/quantize.py:564\u001b[0m, in \u001b[0;36mquantize_dynamic\u001b[0;34m(model, qconfig_spec, dtype, mapping, inplace)\u001b[0m\n\u001b[1;32m 562\u001b[0m model\u001b[38;5;241m.\u001b[39meval()\n\u001b[1;32m 563\u001b[0m propagate_qconfig_(model, qconfig_spec)\n\u001b[0;32m--> 564\u001b[0m \u001b[43mconvert\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmapping\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minplace\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m model\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/quantization/quantize.py:659\u001b[0m, in \u001b[0;36mconvert\u001b[0;34m(module, mapping, inplace, remove_qconfig, is_reference, convert_custom_config_dict, use_precomputed_fake_quant)\u001b[0m\n\u001b[1;32m 657\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m inplace:\n\u001b[1;32m 658\u001b[0m module \u001b[38;5;241m=\u001b[39m copy\u001b[38;5;241m.\u001b[39mdeepcopy(module)\n\u001b[0;32m--> 659\u001b[0m \u001b[43m_convert\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 660\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodule\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 661\u001b[0m \u001b[43m \u001b[49m\u001b[43mmapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 662\u001b[0m \u001b[43m \u001b[49m\u001b[43minplace\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 663\u001b[0m \u001b[43m \u001b[49m\u001b[43mis_reference\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mis_reference\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 664\u001b[0m \u001b[43m \u001b[49m\u001b[43mconvert_custom_config_dict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mconvert_custom_config_dict\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 665\u001b[0m \u001b[43m \u001b[49m\u001b[43muse_precomputed_fake_quant\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43muse_precomputed_fake_quant\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 666\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 667\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m remove_qconfig:\n\u001b[1;32m 668\u001b[0m _remove_qconfig(module)\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/quantization/quantize.py:724\u001b[0m, in \u001b[0;36m_convert\u001b[0;34m(module, mapping, inplace, is_reference, convert_custom_config_dict, use_precomputed_fake_quant)\u001b[0m\n\u001b[1;32m 712\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (\n\u001b[1;32m 713\u001b[0m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(mod, _FusedModule)\n\u001b[1;32m 714\u001b[0m \u001b[38;5;129;01mand\u001b[39;00m type_before_parametrizations(mod) \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m custom_module_class_mapping\n\u001b[1;32m 715\u001b[0m ):\n\u001b[1;32m 716\u001b[0m _convert(\n\u001b[1;32m 717\u001b[0m mod,\n\u001b[1;32m 718\u001b[0m mapping,\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 722\u001b[0m use_precomputed_fake_quant\u001b[38;5;241m=\u001b[39muse_precomputed_fake_quant,\n\u001b[1;32m 723\u001b[0m )\n\u001b[0;32m--> 724\u001b[0m reassign[name] \u001b[38;5;241m=\u001b[39m \u001b[43mswap_module\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 725\u001b[0m \u001b[43m \u001b[49m\u001b[43mmod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmapping\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcustom_module_class_mapping\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muse_precomputed_fake_quant\u001b[49m\n\u001b[1;32m 726\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 728\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m key, value \u001b[38;5;129;01min\u001b[39;00m reassign\u001b[38;5;241m.\u001b[39mitems():\n\u001b[1;32m 729\u001b[0m module\u001b[38;5;241m.\u001b[39m_modules[key] \u001b[38;5;241m=\u001b[39m value\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/quantization/quantize.py:766\u001b[0m, in \u001b[0;36mswap_module\u001b[0;34m(mod, mapping, custom_module_class_mapping, use_precomputed_fake_quant)\u001b[0m\n\u001b[1;32m 764\u001b[0m sig \u001b[38;5;241m=\u001b[39m inspect\u001b[38;5;241m.\u001b[39msignature(qmod\u001b[38;5;241m.\u001b[39mfrom_float)\n\u001b[1;32m 765\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124muse_precomputed_fake_quant\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m sig\u001b[38;5;241m.\u001b[39mparameters:\n\u001b[0;32m--> 766\u001b[0m new_mod \u001b[38;5;241m=\u001b[39m \u001b[43mqmod\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_float\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 767\u001b[0m \u001b[43m \u001b[49m\u001b[43mmod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muse_precomputed_fake_quant\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43muse_precomputed_fake_quant\u001b[49m\n\u001b[1;32m 768\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 769\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 770\u001b[0m new_mod \u001b[38;5;241m=\u001b[39m qmod\u001b[38;5;241m.\u001b[39mfrom_float(mod)\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/nn/quantized/dynamic/modules/linear.py:145\u001b[0m, in \u001b[0;36mLinear.from_float\u001b[0;34m(cls, mod, use_precomputed_fake_quant)\u001b[0m\n\u001b[1;32m 141\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 142\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\n\u001b[1;32m 143\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUnsupported dtype specified for dynamic quantized Linear!\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 144\u001b[0m )\n\u001b[0;32m--> 145\u001b[0m qlinear \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mmod\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43min_features\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmod\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mout_features\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 146\u001b[0m qlinear\u001b[38;5;241m.\u001b[39mset_weight_bias(qweight, mod\u001b[38;5;241m.\u001b[39mbias)\n\u001b[1;32m 147\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m qlinear\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/nn/quantized/dynamic/modules/linear.py:42\u001b[0m, in \u001b[0;36mLinear.__init__\u001b[0;34m(self, in_features, out_features, bias_, dtype)\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, in_features, out_features, bias_\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mqint8):\n\u001b[0;32m---> 42\u001b[0m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__init__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43min_features\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_features\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbias_\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 43\u001b[0m \u001b[38;5;66;03m# We don't muck around with buffers or attributes or anything here\u001b[39;00m\n\u001b[1;32m 44\u001b[0m \u001b[38;5;66;03m# to keep the module simple. *everything* is simply a Python attribute.\u001b[39;00m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;66;03m# Serialization logic is explicitly handled in the below serialization and\u001b[39;00m\n\u001b[1;32m 46\u001b[0m \u001b[38;5;66;03m# deserialization modules\u001b[39;00m\n\u001b[1;32m 47\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mversion \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m4\u001b[39m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/nn/quantized/modules/linear.py:172\u001b[0m, in \u001b[0;36mLinear.__init__\u001b[0;34m(self, in_features, out_features, bias_, dtype)\u001b[0m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 170\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUnsupported dtype specified for quantized Linear!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m--> 172\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_packed_params \u001b[38;5;241m=\u001b[39m \u001b[43mLinearPackedParams\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdtype\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 173\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_packed_params\u001b[38;5;241m.\u001b[39mset_weight_bias(qweight, bias)\n\u001b[1;32m 174\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscale \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1.0\u001b[39m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/nn/quantized/modules/linear.py:31\u001b[0m, in \u001b[0;36mLinearPackedParams.__init__\u001b[0;34m(self, dtype)\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdtype \u001b[38;5;241m==\u001b[39m torch\u001b[38;5;241m.\u001b[39mfloat16:\n\u001b[1;32m 30\u001b[0m wq \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mzeros([\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1\u001b[39m], dtype\u001b[38;5;241m=\u001b[39mtorch\u001b[38;5;241m.\u001b[39mfloat)\n\u001b[0;32m---> 31\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mset_weight_bias\u001b[49m\u001b[43m(\u001b[49m\u001b[43mwq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/ao/nn/quantized/modules/linear.py:38\u001b[0m, in \u001b[0;36mLinearPackedParams.set_weight_bias\u001b[0;34m(self, weight, bias)\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[38;5;129m@torch\u001b[39m\u001b[38;5;241m.\u001b[39mjit\u001b[38;5;241m.\u001b[39mexport\n\u001b[1;32m 34\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mset_weight_bias\u001b[39m(\n\u001b[1;32m 35\u001b[0m \u001b[38;5;28mself\u001b[39m, weight: torch\u001b[38;5;241m.\u001b[39mTensor, bias: Optional[torch\u001b[38;5;241m.\u001b[39mTensor]\n\u001b[1;32m 36\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 37\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdtype \u001b[38;5;241m==\u001b[39m torch\u001b[38;5;241m.\u001b[39mqint8:\n\u001b[0;32m---> 38\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_packed_params \u001b[38;5;241m=\u001b[39m \u001b[43mtorch\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mops\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mquantized\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinear_prepack\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\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdtype \u001b[38;5;241m==\u001b[39m torch\u001b[38;5;241m.\u001b[39mfloat16:\n\u001b[1;32m 40\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_packed_params \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mops\u001b[38;5;241m.\u001b[39mquantized\u001b[38;5;241m.\u001b[39mlinear_prepack_fp16(weight, bias)\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.7/lib/python3.11/site-packages/torch/_ops.py:1116\u001b[0m, in \u001b[0;36mOpOverloadPacket.__call__\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 1114\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_has_torchbind_op_overload \u001b[38;5;129;01mand\u001b[39;00m _must_dispatch_in_python(args, kwargs):\n\u001b[1;32m 1115\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _call_overload_packet_from_python(\u001b[38;5;28mself\u001b[39m, args, kwargs)\n\u001b[0;32m-> 1116\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_op\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[43m(\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mRuntimeError\u001b[0m: Didn't find engine for operation quantized::linear_prepack NoQEngine" + ] + } + ], "source": [ "import torch.quantization\n", "\n", - "\n", + "torch.backends.quantized.engine = 'qnnpack'\n", "quantized_model = torch.quantization.quantize_dynamic(model, dtype=torch.qint8)\n", "print_size_of_model(quantized_model, \"int8\")" ] }, + { + "cell_type": "markdown", + "id": "063d405c", + "metadata": {}, + "source": [] + }, { "cell_type": "markdown", "id": "7b108e17", @@ -926,7 +1715,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.5 ('base')", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -940,12 +1729,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" - }, - "vscode": { - "interpreter": { - "hash": "9e3efbebb05da2d4a1968abe9a0645745f54b63feb7a85a514e4da0495be97eb" - } + "version": "3.11.7" } }, "nbformat": 4,