{ "cells": [ { "cell_type": "markdown", "id": "9d123d30", "metadata": {}, "source": [ "# Lesson 29: PyTorch CIFAR-10 classifier activity\n", "\n", "In this activity, you will build a deep neural network classifier for the CIFAR-10 dataset using PyTorch. The data loading and preparation code is provided. Your task is to:\n", "\n", "1. **Define the model** - Build a DNN using `nn.Sequential`\n", "2. **Train the model** - Write a training loop with validation tracking\n", "3. **Evaluate the model** - Assess performance on the test set\n", "\n", "## Notebook set-up\n", "\n", "### Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "cc5fca69", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using device: cuda\n" ] } ], "source": [ "# Standard library imports\n", "from pathlib import Path\n", "\n", "# Third party imports\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "from torchvision import datasets, transforms\n", "from torch.utils.data import DataLoader\n", "\n", "# Set random seeds for reproducibility\n", "torch.manual_seed(315)\n", "np.random.seed(315)\n", "\n", "# Check for GPU availability\n", "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", "print(f'Using device: {device}')" ] }, { "cell_type": "markdown", "id": "7d1a829d", "metadata": {}, "source": [ "### Configuration" ] }, { "cell_type": "code", "execution_count": 5, "id": "0fb4b5c7", "metadata": {}, "outputs": [], "source": [ "# Make sure data directory exists\n", "data_dir = Path('../data')\n", "data_dir.mkdir(parents=True, exist_ok=True)" ] }, { "cell_type": "markdown", "id": "6bac7a0f", "metadata": {}, "source": [ "### Hyperparameters" ] }, { "cell_type": "code", "execution_count": 6, "id": "6e8a0b2f", "metadata": {}, "outputs": [], "source": [ "batch_size = 10000\n", "learning_rate = 1e-2\n", "epochs = 100\n", "print_every = 10" ] }, { "cell_type": "markdown", "id": "f1ef760e", "metadata": {}, "source": [ "## 1. Load and preprocess CIFAR-10 data\n", "\n", "CIFAR-10 contains 32x32 color images across 10 classes. We convert the images to grayscale for this exercise.\n", "\n", "### 1.1. Define transformations and class names" ] }, { "cell_type": "code", "execution_count": 7, "id": "bb419f4a", "metadata": {}, "outputs": [], "source": [ "# Define class names\n", "class_names = [\n", " 'airplane', 'automobile', 'bird', 'cat', 'deer',\n", " 'dog', 'frog', 'horse', 'ship', 'truck'\n", "]\n", "\n", "# Data preprocessing: convert to grayscale, tensor, and normalize\n", "transform = transforms.Compose([\n", " transforms.Grayscale(num_output_channels=1),\n", " transforms.ToTensor(),\n", " transforms.Normalize((0.5,), (0.5,))\n", "])" ] }, { "cell_type": "markdown", "id": "9e923908", "metadata": {}, "source": [ "### 1.2. Load datasets" ] }, { "cell_type": "code", "execution_count": null, "id": "5eae2637", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Files already downloaded and verified\n", "Files already downloaded and verified\n", "\n", "Training samples: 50000\n", "Test samples: 10000\n", "Image shape: torch.Size([1, 32, 32])\n", "Number of classes: 10\n" ] } ], "source": [ "# Use the fast BrainChip mirror for downloading CIFAR-10\n", "datasets.CIFAR10.url = 'https://data.brainchip.com/dataset-mirror/cifar10/cifar-10-python.tar.gz'\n", "\n", "# Load training and test datasets\n", "train_dataset = datasets.CIFAR10(\n", " root=data_dir,\n", " train=True,\n", " download=True,\n", " transform=transform\n", ")\n", "\n", "test_dataset = datasets.CIFAR10(\n", " root=data_dir,\n", " train=False,\n", " download=True,\n", " transform=transform\n", ")\n", "\n", "print(f'\\nTraining samples: {len(train_dataset)}')\n", "print(f'Test samples: {len(test_dataset)}')\n", "print(f'Image shape: {train_dataset[0][0].shape}')\n", "print(f'Number of classes: {len(class_names)}')" ] }, { "cell_type": "markdown", "id": "ca4049d1", "metadata": {}, "source": [ "### 1.3. Pre-load data and create data loaders" ] }, { "cell_type": "code", "execution_count": 10, "id": "7f7ab15e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "X_train shape: torch.Size([40000, 1, 32, 32]), device: cuda:0\n", "y_train shape: torch.Size([40000]), device: cuda:0\n", "X_val shape: torch.Size([10000, 1, 32, 32]), device: cuda:0\n", "y_val shape: torch.Size([10000]), device: cuda:0\n", "X_test shape: torch.Size([10000, 1, 32, 32]), device: cuda:0\n", "y_test shape: torch.Size([10000]), device: cuda:0\n" ] } ], "source": [ "# Pre-load entire dataset to device for faster training\n", "X_train_full = torch.stack([img for img, _ in train_dataset]).to(device)\n", "y_train_full = torch.tensor([label for _, label in train_dataset]).to(device)\n", "\n", "X_test = torch.stack([img for img, _ in test_dataset]).to(device)\n", "y_test = torch.tensor([label for _, label in test_dataset]).to(device)\n", "\n", "# Split training data into train and validation sets (80/20 split)\n", "n_train = int(0.8 * len(X_train_full))\n", "indices = torch.randperm(len(X_train_full))\n", "\n", "X_train = X_train_full[indices[:n_train]]\n", "y_train = y_train_full[indices[:n_train]]\n", "X_val = X_train_full[indices[n_train:]]\n", "y_val = y_train_full[indices[n_train:]]\n", "\n", "print(f'X_train shape: {X_train.shape}, device: {X_train.device}')\n", "print(f'y_train shape: {y_train.shape}, device: {y_train.device}')\n", "print(f'X_val shape: {X_val.shape}, device: {X_val.device}')\n", "print(f'y_val shape: {y_val.shape}, device: {y_val.device}')\n", "print(f'X_test shape: {X_test.shape}, device: {X_test.device}')\n", "print(f'y_test shape: {y_test.shape}, device: {y_test.device}')" ] }, { "cell_type": "code", "execution_count": 11, "id": "e563bd2c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training batches: 4\n", "Validation batches: 1\n", "Test batches: 1\n" ] } ], "source": [ "# Create TensorDatasets\n", "train_tensor_dataset = torch.utils.data.TensorDataset(X_train, y_train)\n", "val_tensor_dataset = torch.utils.data.TensorDataset(X_val, y_val)\n", "test_tensor_dataset = torch.utils.data.TensorDataset(X_test, y_test)\n", "\n", "# Create DataLoaders\n", "train_loader = DataLoader(\n", " train_tensor_dataset,\n", " batch_size=batch_size,\n", " shuffle=True\n", ")\n", "\n", "val_loader = DataLoader(\n", " val_tensor_dataset,\n", " batch_size=batch_size,\n", " shuffle=False\n", ")\n", "\n", "test_loader = DataLoader(\n", " test_tensor_dataset,\n", " batch_size=batch_size,\n", " shuffle=False\n", ")\n", "\n", "print(f'Training batches: {len(train_loader)}')\n", "print(f'Validation batches: {len(val_loader)}')\n", "print(f'Test batches: {len(test_loader)}')" ] }, { "cell_type": "markdown", "id": "6eb1ae0a", "metadata": {}, "source": [ "### 1.4. Visualize sample images" ] }, { "cell_type": "code", "execution_count": 12, "id": "c6b9e3eb", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAswAAAEkCAYAAAA7LmdoAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAezhJREFUeJztvXnUVdWVrj/pGzGCSiOCHwiKgIKKvRg6FUUlxijRuuZCJSZaNom3TKoqelOg0bIklZhRyTWWSZWapG7FJjb3DgN2wQZjREUBBRUE0WhsQFFjsAH274/82PdZL+zF+b4g3znmfcZwuM639tl77bXmWmtz5rvnbFMURRHGGGOMMcaYzdK2tRtgjDHGGGNMPeMHZmOMMcYYYzL4gdkYY4wxxpgMfmA2xhhjjDEmgx+YjTHGGGOMyeAHZmOMMcYYYzL4gdkYY4wxxpgMfmA2xhhjjDEmgx+YjTHGGGOMyfAX8cA8duzY2Hvvvbd43AsvvBBt2rSJ66677uNvlGlIZsyYEW3atGntZpgG4dFHH43DDjsstttuu2jTpk08+eSTrd0k06BsXHtWrVqVPW7AgAExbdq0P+taY8eOjbFjx/5Z5zCtS632YmqnfWs3wBhjPol89NFHccopp0Tnzp3jyiuvjK5du0ZTU1NrN8sYY0wL8AMzaGpqirVr10aHDh1auynGmAbn+eefj5UrV8aPf/zjOOOMM1q7OeYvhGeffTbatv2LcB4bs03xrAJt2rSJzp07R7t27Vq7KcaYBuf111+PiIju3btnj3vvvfe2QWvMXwqdOnXa4o8+tjnz51IURaxdu7a1m7FN+UQ8ML/77rtx/vnnx4ABA6JTp07Rq1evOOqoo2L+/PnJcYsXL45x48ZF165dY9ddd42ZM2cm9ZvTME+bNi26desWy5cvj4kTJ8Z2220Xffv2jUsuuSSKotgWt2daiblz58aBBx4YnTt3jkGDBsW//du/bXLMunXr4tvf/nYMGjQoOnXqFAMGDIgLL7wwPvjgg+S4DRs2xIwZM6Jv377RtWvXGDduXCxevHir6A1N/TFt2rQYM2ZMRESccsop0aZNmxg7dmy5njz//PMxadKk2H777eO//bf/FhF/eoi54IILon///tGpU6cYMmRI/Mu//Msm68zatWvjq1/9auy8886x/fbbx+TJk+Pll1+ONm3axIwZM7b1rZptzKpVq2LKlCnxqU99Knbaaaf42te+Fu+//35Zr2vKddddF23atIn7778/zj777OjVq1f069evrL/mmmti0KBB0aVLlzjooIPiwQcf3Ja3Yz5m1qxZE9OmTYvu3bvHDjvsEH/9138df/zjH8v6WvewAQMGxPHHHx933nlnHHDAAdGlS5dyT7z77rtj9OjR0b179+jWrVsMGTIkLrzwwuT7H3zwQUyfPj0GDx4cnTp1iv79+8ff/d3fbXKdeuYTIck466yz4uabb45zzz03hg0bFqtXr465c+fGkiVLYv/994+IiLfeeiuOOeaYOOmkk2LKlClx8803x9///d/HPvvsE8cee2z2/OvXr49jjjkmDjnkkJg5c2bMnj07pk+fHuvWrYtLLrlkW9yi2cYsWrQojj766OjZs2fMmDEj1q1bF9OnT4/evXsnx51xxhlx/fXXx8knnxwXXHBBPPLII3H55ZfHkiVL4tZbby2P++Y3vxkzZ86ME044ISZOnBgLFiyIiRMnJhud+eRw5plnxq677hr/9E//FF/96lfjwAMPjN69e8d//ud/xrp162LixIkxevTo+Jd/+Zfo2rVrFEURkydPjjlz5sSXvvSl2HfffePOO++Mb3zjG/Hyyy/HlVdeWZ572rRpceONN8YXvvCFOOSQQ+L++++P4447rhXv1mxLpkyZEgMGDIjLL788fvvb38a//uu/xltvvRU//elPs987++yzo2fPnvGP//iP5S/M//7v/x5nnnlmHHbYYXH++efH8uXLY/LkybHjjjtG//79t8XtmI+ZKVOmxMCBA+Pyyy+P+fPnx09+8pPo1atXXHHFFRFR+x4W8Se5z2mnnRZnnnlmfPnLX44hQ4bE008/Hccff3yMGDEiLrnkkujUqVMsW7YsHnroofJ7GzZsiMmTJ8fcuXPjK1/5SgwdOjQWLVoUV155ZTz33HNx2223bcsuaTnFJ4AddtihOOeccyrrx4wZU0RE8dOf/rT82wcffFD06dOn+NznPlf+bcWKFUVEFNdee235t6lTpxYRUZx33nnl3zZs2FAcd9xxRceOHYs33nhj696MqQtOPPHEonPnzsXKlSvLvy1evLho165dsXHaPPnkk0VEFGeccUby3a9//etFRBS//vWvi6IoildffbVo3759ceKJJybHzZgxo4iIYurUqR/vzZhWYc6cOUVEFDfddFP5t43ryT/8wz8kx952221FRBSXXnpp8veTTz65aNOmTbFs2bKiKIri8ccfLyKiOP/885Pjpk2bVkREMX369I/nZkyrM3369CIiismTJyd/P/vss4uIKBYsWFAURVE0NTUla8q1115bREQxevToYt26deXfP/zww6JXr17FvvvuW3zwwQfl36+55poiIooxY8Z8rPdjPl422ssXv/jF5O+f/exni5122qkoitr3sKL4k11FRDF79uzk2CuvvLKIiOyz0M9+9rOibdu2xYMPPpj8/eqrry4ionjooYdadI/bmk+EJKN79+7xyCOPxCuvvFJ5TLdu3eL0008vP3fs2DEOOuigWL58eU3XOPfcc8tymzZt4txzz40PP/ww7rnnnpY33NQl69evjzvvvDNOPPHE2G233cq/Dx06NCZOnFh+/tWvfhUREX/7t3+bfP+CCy6IiIg77rgjIiLuvffeWLduXZx99tnJceedd97H0n5T//zN3/xN8vlXv/pVtGvXLr761a8mf7/ggguiKIqYNWtWRETMnj07IsK29BfMOeeck3zeOPYb16MqvvzlLyfv5zz22GPx+uuvx1lnnRUdO3Ys/z5t2rTYYYcdtmKLTWty1llnJZ+POOKIWL16dbzzzjs172EbGThwYLIHRvy/dzRuv/322LBhw2bbcNNNN8XQoUNjr732ilWrVpX/jR8/PiIi5syZ07Kb28Z8Ih6YZ86cGU899VT0798/DjrooJgxY8YmD8L9+vXbJH5ujx494q233tri+du2bRu777578rc999wzIv6kezafLN54441Yu3Zt7LHHHpvUDRkypCyvXLky2rZtG4MHD06O6dOnT3Tv3j1WrlxZHhcRmxy34447Ro8ePbZ2802d0759+0RDGvEnG+nbt29sv/32yd+HDh1a1m/8f9u2bWPgwIHJcWpb5pOLrkuDBg2Ktm3bbnEvUpvZaFN6vg4dOmyy35nGhT/6RES557z11ls172EbURuKiPj85z8fhx9+eJxxxhnRu3fvOPXUU+PGG29MHp6XLl0aTz/9dPTs2TP5b+Nz1MYXpOudT4SGecqUKXHEEUfErbfeGnfddVd85zvfiSuuuCJuueWWUp9cFfmi8It75s/EiUxMc+jUqZPDfpmtRq3rT5cuXT7mlph6pJZnnz/Hhrp06RIPPPBAzJkzJ+64446YPXt23HDDDTF+/Pi46667ol27drFhw4bYZ5994nvf+95mz9soevlPzKq9yy67xNlnnx233XZbrFixInbaaae47LLLtsq5N2zYsMkv1s8991xE/OnNUfPJomfPntGlS5dYunTpJnXPPvtsWW5qaooNGzZsctxrr70Wa9asKZNUbPz/smXLkuNWr15dk4fDfPJpamqKV155Jd59993k788880xZv/H/GzZsiBUrViTHqW2ZTy663ixbtiw2bNjQ7L1oo03p+T766KNN7Mt8Mql1D9sSbdu2jQkTJsT3vve9WLx4cVx22WXx61//upRaDBo0KN58882YMGFCHHnkkZv8R89tPdPwD8zr16+Pt99+O/lbr169om/fvls1XMkPf/jDslwURfzwhz+MDh06xIQJE7baNUx90K5du5g4cWLcdttt8eKLL5Z/X7JkSdx5553l50mTJkVExPe///3k+xv/Fb0xcsGECROiffv28aMf/Sg5jjZl/rKZNGlSrF+/fhObuPLKK6NNmzalp2yjfvCqq65KjvvBD36wbRpqWp3/9b/+V/J549hvKdqTcsABB0TPnj3j6quvjg8//LD8+3XXXRdr1qz5s9tp6p9a97Acb7755iZ/23fffSMiymewKVOmxMsvvxw//vGPNzl27dq1DRMXvOElGe+++27069cvTj755Bg5cmR069Yt7rnnnnj00Ufju9/97la5RufOnWP27NkxderUOPjgg2PWrFlxxx13xIUXXhg9e/bcKtcw9cXFF18cs2fPjiOOOCLOPvvsWLduXfzgBz+I4cOHx8KFCyMiYuTIkTF16tS45pprYs2aNTFmzJiYN29eXH/99XHiiSfGuHHjIiKid+/e8bWvfS2++93vxuTJk+OYY46JBQsWxKxZs2LnnXe2pMPECSecEOPGjYuLLrooXnjhhRg5cmTcddddcfvtt8f5558fgwYNioiIUaNGxec+97n4/ve/H6tXry7Dym30eNmWPvmsWLGiXEcefvjh+PnPfx5/9Vd/FSNHjmzWeTp06BCXXnppnHnmmTF+/Pj4/Oc/HytWrIhrr73WGua/EGrdw3Jccskl8cADD8Rxxx0XTU1N8frrr8dVV10V/fr1i9GjR0dExBe+8IW48cYb46yzzoo5c+bE4YcfHuvXr49nnnkmbrzxxjK2c93TukE6/nw++OCD4hvf+EYxcuTIYvvtty+22267YuTIkcVVV11VHjNmzJhi+PDhm3x36tSpRVNTU/m5KqzcdtttVzz//PPF0UcfXXTt2rXo3bt3MX369GL9+vUf562ZVub+++8vRo0aVXTs2LHYfffdi6uvvroM1bORjz76qLj44ouLgQMHFh06dCj69+9ffPOb3yzef//95Fzr1q0rvvWtbxV9+vQpunTpUowfP75YsmRJsdNOOxVnnXXWtr41sw2oCiu33Xbbbfb4d999t/gf/+N/FH379i06dOhQ7LHHHsV3vvOdYsOGDclx7733XnHOOecUO+64Y9GtW7fixBNPLJ599tkiIop//ud//ljvybQeG9eexYsXFyeffHKx/fbbFz169CjOPffcYu3ateVxVWHlHn300c2e96qrrioGDhxYdOrUqTjggAOKBx54oBgzZozDyjU4G+1Fw71ttIcVK1YURVH7HtbU1FQcd9xxm1zn3nvvLT7zmc8Uffv2LTp27Fj07du3OO2004rnnnsuOe7DDz8srrjiimL48OFFp06dih49ehSjRo0qLr744uLtt9/eujf/MdGmKPzWW45p06bFzTffHH/4wx9auynmE8aaNWuiR48ecemll8ZFF13U2s0xDcyTTz4Z++23X/z85z8vMwcaY4zZejS8htmYRmDt2rWb/G2jbmzs2LHbtjGmoamypbZt28anP/3pVmiRMcZ88ml4DbMxjcANN9wQ1113XUyaNCm6desWc+fOjf/6r/+Ko48+Og4//PDWbp5pIGbOnBmPP/54jBs3Ltq3bx+zZs2KWbNmxVe+8pWGCc9kjDGNhh+YjdkGjBgxItq3bx8zZ86Md955p3wR8NJLL23tppkG47DDDou77747vv3tb8cf/vCH2G233WLGjBmW9RhjzMeINczGGGOMMcZksIbZGGOMMcaYDH5gNsYYY4wxJoMfmI0xxhhjjMlQ80t///Ef/1GWP/roo6Subdv/99ytmabat2+/2eOaA8/Jcrt27Wo+x/r165t9XZV3b9iwobKO7WIdv6Pt0HPwM/tKz1HVpoi0v7/85S9Xfq816d27d1nOSeh5LxFpn3Dsc3ZV6zjlUDvr3LnzZo9TG8tdi8fm2s+UtZrqnefU87PNu+22W1let25d5Tk6deqU1HXs2LEsz5kzp7KNrcnMmTPLcocOHZI69kGu/1mnY8jPWle1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72/rWzPbiLPOOituvvnmOPfcc2PYsGGxevXqmDt3bixZsiT233//iIh466234phjjomTTjoppkyZEjfffHP8/d//feyzzz5x7LHHZs+/fv36OOaYY+KQQw6JmTNnxuzZs2P69Omxbt26uOSSS7bFLZqPCe9xZmthW9qKFA3E9OnTi4govvjFLyZ//+xnP1vstNNORVEUxZNPPllERHHGGWckx3z9618vIqL49a9/Xf6tqampiIhi9uzZybFXXnllERHFG2+8UdmWn/3sZ0Xbtm2LBx98MPn71VdfXURE8dBDD7XoHk19cOKJJxadO3cuVq5cWf5t8eLFRbt27YqN06ZWW3v11VeL9u3bFyeeeGJy3IwZM4qIKKZOnfrx3oxpFXbYYYfinHPOqawfM2ZMERHFT3/60/JvH3zwQdGnT5/ic5/7XPm3FStWFBFRXHvtteXfpk6dWkREcd5555V/27BhQ3HccccVHTt2zK5dpn7xHme2FralrU9DSjLOOuus5PMRRxwRq1evjnfeeSd+9atfRUTE3/7t3ybHXHDBBRERcccddyR/HzhwYEycODH5W/fu3SMi4vbbb48NGzZstg033XRTDB06NPbaa69YtWpV+d/48eMjImLOnDktuznT6qxfvz7uvPPOOPHEE2O33XYr/z506NDEVmq1tXvvvTfWrVsXZ599dnLceeed97G039QH3bt3j0ceeSReeeWVymO6desWp59+evm5Y8eOcdBBB8Xy5ctrusa5555bltu0aRPnnntufPjhh3HPPfe0vOGm1fEeZ7YWtqWtR0M+MPMhJiKiR48eEfEn9+bKlSujbdu2MXjw4OSYPn36RPfu3WPlypXJ3wcOHLjJ+T//+c/H4YcfHmeccUb07t07Tj311LjxxhsTY1i6dGk8/fTT0bNnz+S/PffcMyIiXn/99a1yr2bb88Ybb8TatWtjjz322KRuyJAhZblWW9v4fz1uxx13LG3XfPKYOXNmPPXUU9G/f/846KCDYsaMGZs8CPfr12+TuN49evSIt956a4vnb9u2bey+++7J3zauPy+88MKf13jTqniPM1sL29LWoyE1zFVvixd42aXW5BJdunTZ7N8eeOCBmDNnTtxxxx0xe/bsuOGGG2L8+PFx1113Rbt27WLDhg2xzz77xPe+973Nnrd///41Xd80Pk5kYjbHlClT4ogj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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Get a batch of training images\n", "images, labels = next(iter(train_loader))\n", "\n", "# Plot first 10 images\n", "ncols = 5\n", "nrows = 2\n", "\n", "fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(ncols*1.5, nrows*1.5))\n", "axes = axes.flatten()\n", "\n", "for i, ax in enumerate(axes):\n", "\n", " img = images[i].cpu() * 0.5 + 0.5\n", " img = img.numpy().squeeze()\n", " ax.set_title(class_names[labels[i]])\n", " ax.imshow(img, cmap='gray')\n", " ax.axis('off')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "7991bb09", "metadata": {}, "source": [ "## 2. Build DNN classifier\n", "\n", "### Task 1: Define model architecture\n", "\n", "Build a fully connected neural network using `nn.Sequential` to classify CIFAR-10 images.\n", "\n", "**Requirements:**\n", "- Flatten the input images (32x32x1 = 1024 features)\n", "- Use at least 2 hidden layers with ReLU activation\n", "- Add dropout for regularization\n", "- Output layer should have 10 units (one per class)\n", "\n", "**Hints:**\n", "- Use `nn.Flatten()` as the first layer to convert images to vectors\n", "- Use `nn.Linear(in_features, out_features)` for fully connected layers\n", "- Use `nn.ReLU()` for activation functions\n", "- Use `nn.Dropout(p)` for regularization (e.g., p=0.2)\n", "- Don't forget to move the model to the device with `.to(device)`\n", "- `nn.CrossEntropyLoss` applies softmax internally, so no activation needed on output" ] }, { "cell_type": "code", "execution_count": null, "id": "1d3c5002", "metadata": {}, "outputs": [], "source": [ "# TODO: Define your model architecture\n", "input_size = 32 * 32 * 1 # Grayscale image flattened\n", "num_classes = 10\n", "\n", "model = nn.Sequential(\n", "\n", " # Add your layers here\n", "\n", ").to(device)\n", "\n", "print(model)" ] }, { "cell_type": "markdown", "id": "17d7b17b", "metadata": {}, "source": [ "### Task 2: Define loss function and optimizer\n", "\n", "**Hints:**\n", "- Use `nn.CrossEntropyLoss()` for multi-class classification\n", "- Use `optim.Adam(model.parameters(), lr=learning_rate)` as the optimizer" ] }, { "cell_type": "code", "execution_count": null, "id": "5168d727", "metadata": {}, "outputs": [], "source": [ "# TODO: Define loss function and optimizer\n", "criterion = None # Replace with loss function\n", "optimizer = None # Replace with optimizer" ] }, { "cell_type": "markdown", "id": "3385ef14", "metadata": {}, "source": [ "## 3. Training\n", "\n", "### Task 3: Write training function\n", "\n", "Write a training loop that trains the model and tracks both training and validation metrics.\n", "\n", "**Requirements:**\n", "- Iterate over epochs\n", "- For each epoch, iterate over batches in the training loader\n", "- Track training loss and accuracy\n", "- After training, evaluate on validation set (without gradient computation)\n", "- Track validation loss and accuracy\n", "- Return a history dictionary with all metrics\n", "\n", "**Hints:**\n", "- Use `model.train()` before training and `model.eval()` before validation\n", "- Use `optimizer.zero_grad()` to clear gradients before each batch\n", "- Use `loss.backward()` for backpropagation\n", "- Use `optimizer.step()` to update weights\n", "- Use `torch.no_grad()` context manager for validation\n", "- Use `torch.max(outputs, 1)` to get predictions from logits\n", "- Accuracy = 100 * correct / total" ] }, { "cell_type": "code", "execution_count": null, "id": "f2546213", "metadata": {}, "outputs": [], "source": [ "def train_model(\n", " model: nn.Module,\n", " train_loader: DataLoader,\n", " val_loader: DataLoader,\n", " criterion: nn.Module,\n", " optimizer: optim.Optimizer,\n", " epochs: int = 10,\n", " print_every: int = 1\n", ") -> dict[str, list[float]]:\n", " '''Training loop for PyTorch classification model.\n", " \n", " TODO: Implement the training loop with:\n", " 1. Training phase - iterate over train_loader batches\n", " 2. Validation phase - evaluate on val_loader after each epoch\n", " 3. Track and return history of train_loss, val_loss, train_accuracy, val_accuracy\n", " '''\n", "\n", " history = {'train_loss': [], 'val_loss': [], 'train_accuracy': [], 'val_accuracy': []}\n", "\n", " for epoch in range(epochs):\n", "\n", " # TODO: Training phase\n", " # - Set model to training mode\n", " # - Loop over batches in train_loader\n", " # - For each batch: zero gradients, forward pass, compute loss, backward pass, update weights\n", " # - Track running loss and accuracy\n", "\n", " # TODO: Validation phase\n", " # - Set model to evaluation mode\n", " # - Use torch.no_grad() context\n", " # - Loop over batches in val_loader\n", " # - Compute loss and accuracy (no gradient computation needed)\n", "\n", " # TODO: Record metrics in history dict\n", "\n", " # TODO: Print progress\n", "\n", " print('\\nTraining complete.')\n", "\n", " return history" ] }, { "cell_type": "markdown", "id": "70394432", "metadata": {}, "source": [ "### Task 4: Train the model" ] }, { "cell_type": "code", "execution_count": null, "id": "f228e200", "metadata": {}, "outputs": [], "source": [ "history = train_model(\n", " model=model,\n", " train_loader=train_loader,\n", " val_loader=val_loader,\n", " criterion=criterion,\n", " optimizer=optimizer,\n", " epochs=epochs,\n", " print_every=print_every\n", ")" ] }, { "cell_type": "markdown", "id": "85a3dd9e", "metadata": {}, "source": [ "### Task 5: Plot learning curves\n", "\n", "**Hints:**\n", "- Create a 1x2 subplot for loss and accuracy\n", "- Plot both training and validation metrics on each subplot\n", "- Add legends to distinguish the curves" ] }, { "cell_type": "code", "execution_count": null, "id": "e9d2b248", "metadata": {}, "outputs": [], "source": [ "fig, axes = plt.subplots(1, 2, figsize=(10, 4))\n", "\n", "axes[0].set_title('Loss')\n", "axes[0].plot(history['train_loss'], label='Train')\n", "axes[0].plot(history['val_loss'], label='Validation')\n", "axes[0].set_xlabel('Epoch')\n", "axes[0].set_ylabel('Loss (cross-entropy)')\n", "axes[0].legend(loc='best')\n", "\n", "axes[1].set_title('Accuracy')\n", "axes[1].plot(history['train_accuracy'], label='Train')\n", "axes[1].plot(history['val_accuracy'], label='Validation')\n", "axes[1].set_xlabel('Epoch')\n", "axes[1].set_ylabel('Accuracy (%)')\n", "axes[1].legend(loc='best')\n", "\n", "plt.tight_layout()\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "59e55813", "metadata": {}, "source": [ "## 4. Evaluate model on test set\n", "\n", "### Task 6: Write evaluation function\n", "\n", "Write a function to evaluate the model on the test set and return accuracy and predictions.\n", "\n", "**Hints:**\n", "- Set model to evaluation mode with `model.eval()`\n", "- Use `torch.no_grad()` context\n", "- Iterate over test_loader and accumulate predictions\n", "- Return accuracy, predictions array, and true labels array" ] }, { "cell_type": "code", "execution_count": null, "id": "ea0c0a63", "metadata": {}, "outputs": [], "source": [ "def evaluate_model(\n", " model: nn.Module,\n", " test_loader: DataLoader\n", ") -> tuple[float, np.ndarray, np.ndarray]:\n", " '''Evaluate model on test set.\n", " \n", " Note: Assumes data is already on the correct device.\n", " '''\n", "\n", " model.eval()\n", " correct = 0\n", " total = 0\n", " all_predictions = []\n", " all_labels = []\n", "\n", " with torch.no_grad():\n", "\n", " for images, labels in test_loader:\n", "\n", " outputs = model(images)\n", " _, predicted = torch.max(outputs.data, 1)\n", "\n", " total += labels.size(0)\n", " correct += (predicted == labels).sum().item()\n", "\n", " all_predictions.extend(predicted.cpu().numpy())\n", " all_labels.extend(labels.cpu().numpy())\n", "\n", " accuracy = 100 * correct / total\n", " return accuracy, np.array(all_predictions), np.array(all_labels)\n", "\n", "test_accuracy, predictions, true_labels = evaluate_model(model, test_loader)\n", "print(f'Test accuracy: {test_accuracy:.2f}%')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 5 }