{
"cells": [
{
"cell_type": "markdown",
"id": "2c33caea-527f-4bc7-a337-5642943f4249",
"metadata": {},
"source": [
"# Image classification for the MNIST dataset"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "279aabec-4a30-4708-ae14-5232495a6f1d",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# To use full widht of screen in the notebook\n",
"from IPython.display import display, HTML\n",
"display(HTML(\"\"))"
]
},
{
"cell_type": "markdown",
"id": "0f3dd6dd-ff9a-4e84-a3cd-9e747fc17cbc",
"metadata": {},
"source": [
"## Import Libraries and test GPUs\n",
"\n",
"We import the required libraries and check if the GPU cards are available (cuda)."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "ed0cffe6-5633-4d94-abde-b6adcc18c5fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using device: cuda\n"
]
}
],
"source": [
"# Import libraries\n",
"import torch\n",
"import torch.nn as nn\n",
"from torch.utils.data import DataLoader\n",
"from torch.optim import Adam\n",
"from torchvision import datasets, transforms\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# Check if GPU is available\n",
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
"print(f\"Using device: {device}\")"
]
},
{
"cell_type": "markdown",
"id": "49427fca-cec1-4b67-826e-ca6e345a93a4",
"metadata": {},
"source": [
"## Dataset\n",
"\n",
"We download the MNIST datasets for the first time and transform them tensors in order to be used by PyTorch. Afterward, when running the notebook again, the datasets are already stored locally."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "6661e5ae-68e6-45a4-a3d3-18a92808468f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Dataset MNIST\n",
" Number of datapoints: 60000\n",
" Root location: ./data\n",
" Split: Train\n",
" StandardTransform\n",
"Transform: Compose(\n",
" ToTensor()\n",
" )\n",
"Dataset MNIST\n",
" Number of datapoints: 10000\n",
" Root location: ./data\n",
" Split: Test\n",
" StandardTransform\n",
"Transform: Compose(\n",
" ToTensor()\n",
" )\n",
"Image batch shape: torch.Size([64, 1, 28, 28])\n",
"Label batch shape: torch.Size([64])\n"
]
}
],
"source": [
"# Define transformations\n",
"my_transform = transforms.Compose([transforms.ToTensor()])\n",
"\n",
"# Load datasets\n",
"train_dataset = datasets.MNIST(root='./data', train=True, transform=my_transform, download=True)\n",
"test_dataset = datasets.MNIST(root='./data', train=False, transform=my_transform, download=True)\n",
"print(train_dataset)\n",
"print(test_dataset)\n",
"\n",
"# Data loaders\n",
"train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)\n",
"\n",
"# Inspect data shape\n",
"images, labels = next(iter(train_loader))\n",
"print(f\"Image batch shape: {images.shape}\")\n",
"print(f\"Label batch shape: {labels.shape}\")"
]
},
{
"cell_type": "markdown",
"id": "88d1036e-4026-416d-9013-aa5c79a4ee51",
"metadata": {},
"source": [
"## Data visualization\n",
"\n",
"Let's print an element of the training dataset as a tensor."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "b47f2c6e-fdff-407b-909f-9663a57cb156",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.1, 0.1, 0.5, 0.5, 0.7, 0.1, 0.7, 1.0, 1.0, 0.5, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.1, 0.4, 0.6, 0.7, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 0.7, 1.0, 0.9, 0.8, 0.3, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.4, 0.3, 0.3, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0, 0.8, 0.7, 1.0, 0.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.6, 0.4, 1.0, 1.0, 0.8, 0.0, 0.0, 0.2, 0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.6, 1.0, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7, 1.0, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.9, 0.9, 0.6, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.3, 0.9, 1.0, 1.0, 0.5, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.7, 1.0, 1.0, 0.6, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.4, 1.0, 1.0, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.5, 0.7, 1.0, 1.0, 0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.6, 0.9, 1.0, 1.0, 1.0, 1.0, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.4, 0.9, 1.0, 1.0, 1.0, 1.0, 0.8, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.3, 0.8, 1.0, 1.0, 1.0, 1.0, 0.8, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.9, 1.0, 1.0, 1.0, 1.0, 0.8, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.2, 0.7, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 0.8, 0.5, 0.5, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n",
" [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]])"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# We set the decimal precision to 1 (or 2) only for printing purposes, i.e. to be able to visualize the matrix (tensor) similarly as the actual image\n",
"torch.set_printoptions(precision=1, linewidth=300)\n",
"\n",
"# Let's check the first element as a tensor\n",
"train_dataset[0][0][0]"
]
},
{
"cell_type": "markdown",
"id": "25d2b649-b7c4-4a85-81b6-dddc75939814",
"metadata": {},
"source": [
"We can barely distinguish the element with the naked eye. Let's check a couple more of elements (this is the first one), but now as images."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c78b2a40-e4fa-4866-969c-091daa4fd7db",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAACtCAYAAABfjTYXAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAAArxUlEQVR4nO3dd3xUVf7/8U9IQqSIlGRB8EsxSJPelCKgNEGkKSKCVAFFigrYQFFqVtFdikg1gPAwoFRdFFGqigrr4i4oCqxgEAKIIqEm4P394U/WO59zzWRyJzN38no+Hvxx3py58yEc7kwOk/OJsizLEgAAAAAAAAAAoOQLdQEAAAAAAAAAAIQrNtEBAAAAAAAAAHDAJjoAAAAAAAAAAA7YRAcAAAAAAAAAwAGb6AAAAAAAAAAAOGATHQAAAAAAAAAAB2yiAwAAAAAAAADggE10AAAAAAAAAAAcsIkOAAAAAAAAAIADz22iL1y4UKKiomTnzp2uXC8qKkqGDh3qyrX+eM3nnnsuoMcePHhQoqKijL9SUlJcrRO/ifQ1JSKSmZkpzz//vJQvX17i4uKkSpUqMmPGDPcKxBV5YT390QcffHDlHvXjjz+6ck3Y5YU1NXbsWOnQoYOUKVNGoqKipG/fvq7VBru8sJ6+/fZbueuuu6RYsWJSsGBBuemmm2Tt2rXuFQibSF9T//znP+Xhhx+WGjVqyNVXXy0lS5aUVq1aycaNG12tEb+J9PUkwmtebov0NZWamipdunSR66+/XgoVKiTXXHON1KlTR2bOnCmXLl1ytU5E/noS4R6V2/LCmvojr+8feG4TPa8YNmyYbN++3fardevWoS4LHjVkyBCZMmWKPPzww7J+/Xrp0qWLjBgxQiZPnhzq0uBhZ86ckYEDB0rp0qVDXQo87m9/+5ucPHlSOnbsKPnz5w91OfCwgwcPSqNGjeSbb76R2bNny5tvvikJCQnSuXNnWbFiRajLgwe98cYb8vnnn0v//v1lzZo1Mn/+fImLi5OWLVvK4sWLQ10ePIjXPLjp7NmzUqRIEXnmmWdk7dq1kpKSIk2bNpVhw4bJgw8+GOry4EHcoxAskbB/EBPqAmBWtmxZufnmm0NdBiLAnj17ZMGCBTJp0iQZPXq0iIi0aNFCTp48KRMnTpQHH3xQihcvHuIq4UVPPvmkFCtWTO644w6ZOHFiqMuBh6Wnp0u+fL/9v/7rr78e4mrgZUlJSXLu3DlZv369lClTRkREbr/9dqlRo4Y8+uij0qVLlytrDfDH448/LlOnTrVl7du3l7p168r48eOld+/eIaoMXsVrHtxUpUoVWbRokS1r166dHD9+XBYtWiSvvPKKxMXFhag6eBH3KARLJOwfROR3ERcuXJCRI0dK7dq15ZprrpHixYtLo0aNZM2aNY6PmTNnjlSqVEni4uKkWrVqxqNT0tLSZPDgwXLddddJ/vz5pUKFCvL888/zY1J5gJfX1OrVq8WyLOnXr58t79evn5w/f17ee+89154L/vHyevrdtm3bZO7cuTJ//nyJjo52/frIHq+vKTY1w4uX19PHH38stWrVurKBLiISHR0t7dq1k9TUVPn8889dey74z8tr6i9/+YvKoqOjpV69epKamura88B/Xl5PIrzmhSOvrymThIQEyZcvH+/TQ8Dr64l7VPjx+poSiZz9g4j8JPrFixflp59+klGjRkmZMmUkIyNDPvjgA+nataskJyerT4ysXbtWNm3aJOPHj5dChQrJrFmzpEePHhITEyN33323iPy2uBo2bCj58uWTZ599VhITE2X79u0yceJEOXjwoCQnJ/9pTeXLlxeR337M2B9JSUny9NNPS0xMjNStW1cef/xx6dixY7a/FnCHl9fU7t27JSEhQUqVKmXLa9aseeX3kbu8vJ5ERM6fPy8DBgyQRx55ROrWrctZw2HA62sK4cXL6ykjI8P401W/fwrv3//+Nz/pFwJeXlMmly5dkm3btsmNN96Y7cci5yJtPSH0ImFNWZYlly9flvT0dHn//fdl4cKFMnLkSImJicgtn7AWCesJ4cXrayqi9g8sj0lOTrZExNqxY4ffj7l06ZKVmZlpDRgwwKpTp47t90TEKlCggJWWlmabX6VKFatixYpXssGDB1uFCxe2Dh06ZHv81KlTLRGx9uzZY7vmuHHjbPMSExOtxMTELGs9cuSINXDgQGv58uXWtm3brKVLl1o333yzJSLWvHnz/P4zw3+RvqZat25tVa5c2fh7+fPntwYNGpTlNeC/SF9PlmVZI0eOtK6//nrr3LlzlmVZ1rhx4ywRsU6cOOHX45E9eWFN/VGhQoWsPn36ZPtx8E+kr6fOnTtbRYsWtdLT0235LbfcYomINXny5CyvgeyJ9DVlMmbMGEtErNWrVwf0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4gAf119/fahLQIDS0tJUlpCQYBvHxcWpOaY+Lybr1q1T2datW23j1atXqzkHDx5UWW6ff47ItmfPHpX5cy/j+7rcZfr+vnr16lk+7vHHH1dZenq6KzWJmM9hr1u3rspM77F9bd68WWWvvvqqyny/R0TeY1pPvu/7EVnKlSunsgceeMA2Nq2LuXPnquzw4cPuFRbh2AkBAAAAAAAAAMABm+gAAAAAAAAAADhgEx0AAAAAAAAAAAdsogMAAAAAAAAA4CCsG4sWL17cNp4zZ46aY2qw5mYTK1Nzx5deekll69evV9n58+ddqwPu2L59u228Y8cONadBgwZ+XatUqVIqMzW69XXy5EmVpaSkqGzEiBF+1QHktkaNGqls4cKFuV9IHlW0aFGVme5HJj/88INtPGrUKDdKAhxt27ZNZabmxDTkCz/NmjVTWefOnW1jU7O848ePq+y1115T2c8//6wymqAhHJiart15550hqATB8NBDD4W6BBEx3yvffvtt29j0/eCFCxeCVhO8q0iRIirr1KmTbbxq1arcKge5YMOGDSrzbTa6ZMkSNWfcuHFBqykv4JPoAAAAAAAAAAA4YBMdAAAAAAAAAAAHbKIDAAAAAAAAAOCATXQAAAAAAAAAAByEpLHoTTfdpLLRo0errGHDhrZxmTJlXK3j3LlztvH06dPVnMmTJ6vs7NmzrtaB3HP48GHbuGvXrmrO4MGDVTZ27NiAnm/atGkqe/XVV1W2f//+gK4PBFtUVFSoSwDgYbt371bZvn37VGZqCp+YmGgbnzhxwr3CkKX09HSVvf766386BiLBV199pbKvv/5aZVWrVs2NcuCgb9++Khs2bJht3KdPn6DWcODAAZX57jGImJtsmxrYml4zAV/33HOPyi5evKgy030LkSM5OVllEyZMsI3XrFmTW+XkGXwSHQAAAAAAAAAAB2yiAwAAAAAAAADggE10AAAAAAAAAAAcsIkOAAAAAAAAAICDKMuyLL8muthcLikpSWWmxqL+MDV+eeedd1R26dIllb300ku28alTpwKqIS/zc/kY0bAQJoGuKdZTzpkaNL322msqmzdvnspMDXnDQSTeo0qVKqWyZcuWqaxp06Yq++6772zjihUruldYHsE9KudM95r58+erbMuWLbaxb8M4EfP7QC+JxHsUQot7FNzktXtUXFycbWx6vZk4caLKihUrprLVq1erbMOGDbaxqWlfWlpaFlXmbdyjci4lJUVlpkbHHTt2tI0PHToUtJpCxWv3KIS/rNYUn0QHAAAAAAAAAMABm+gAAAAAAAAAADhgEx0AAAAAAAAAAAdsogMAAAAAAAAA4CAkjUUROWjkALfRbAZu4h4Ft3GPyrkiRYqobPny5Spr1aqVbbxy5Uo1p1+/fio7e/ZsDqrLXdyj4DbuUXAT9yi4jXsU3MQ9Cm6jsSgAAAAAAAAAAAFiEx0AAAAAAAAAAAdsogMAAAAAAAAA4IAz0ZEjnEEFt3FOHtzEPQpu4x4VHKZz0idNmmQbP/TQQ2pOzZo1VfbVV1+5V1iQcY+C27hHwU3co+A27lFwE/couI0z0QEAAAAAAAAACBCb6AAAAAAAAAAAOGATHQAAAAAAAAAAB2yiAwAAAAAAAADggMaiyBEaOcBtNJuBm7hHwW3co+Am7lFwG/couIl7FNzGPQpu4h4Ft9FYFAAAAAAAAACAALGJDgAAAAAAAACAAzbRAQAAAAAAAABwwCY6AAAAAAAAAAAO/G4sCgAAAAAAAABAXsMn0QEAAAAAAAAAcMAmOgAAAAAAAAAADthEBwAAAAAAAADAAZvoAAAAAAAAAAA4YBMdAAAAAAAAAAAHbKIDAAAAAAAAAOCATXQAAAAAAAAAABywiQ4AAAAAAAAAgAM20QEAAAAAAAAAcPD/ALthrtb717kNAAAAAElFTkSuQmCC",
"text/plain": [
"