{ "cells": [ { "cell_type": "code", "execution_count": 70, "id": "73585b7a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'2.7.1+cu126'" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import torch\n", "from torch import nn # nn contains all of PyTorch's building blocks for neural networks\n", "import matplotlib.pyplot as plt\n", "from torchvision import datasets, transforms\n", "from torch.utils.data import random_split, DataLoader\n", "import os\n", "import numpy as np\n", "\n", "\n", "# Check PyTorch version\n", "torch.__version__" ] }, { "cell_type": "code", "execution_count": 71, "id": "674a74c2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'cuda'" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Setup device-agnostic code\n", "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", "device" ] }, { "cell_type": "code", "execution_count": 239, "id": "7d08e875", "metadata": {}, "outputs": [], "source": [ "\n", "data_dir = \"/home/arpan/torchenv_learning/data/num_dataset_jpg\"\n", "\n", "transform = transforms.Compose([\n", " transforms.RandomInvert(1),\n", " transforms.Resize((28, 28)), # Resize images to 28x28\n", " transforms.Grayscale(num_output_channels=1), # Convert to grayscale\n", " transforms.RandomRotation(15),\n", " transforms.RandomAffine(0, translate=(0.1, 0.1)),\n", " transforms.ColorJitter(brightness=0.2, contrast=0.2),\n", " transforms.RandomPerspective(distortion_scale=0.2, p=0.5),\n", " transforms.ToTensor(),\n", " transforms.RandomInvert(1),\n", "])\n", "\n", "full_dataset = datasets.ImageFolder(root=data_dir, transform=transform)\n", "\n", "# Split into train/test\n", "train_ratio = 0.8\n", "train_size = int(train_ratio * len(full_dataset))\n", "test_size = len(full_dataset) - train_size\n", "\n", "train_dataset, test_dataset = random_split(full_dataset, [train_size, test_size])\n", "\n", "\n", "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n", "test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)" ] }, { "cell_type": "code", "execution_count": 74, "id": "7a072dc8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "NumberClassifier(\n", " (conv_block_1): Sequential(\n", " (0): Conv2d(1, 10, kernel_size=(2, 2), stride=(1, 1))\n", " (1): ReLU()\n", " (2): Conv2d(10, 10, kernel_size=(2, 2), stride=(1, 1))\n", " (3): ReLU()\n", " (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", " )\n", " (conv_block_2): Sequential(\n", " (0): Conv2d(10, 10, kernel_size=(2, 2), stride=(1, 1))\n", " (1): ReLU()\n", " (2): Conv2d(10, 10, kernel_size=(2, 2), stride=(1, 1))\n", " (3): ReLU()\n", " (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", " )\n", " (classifier): Sequential(\n", " (0): Flatten(start_dim=1, end_dim=-1)\n", " (1): Linear(in_features=250, out_features=10, bias=True)\n", " )\n", ")" ] }, "execution_count": 74, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class NumberClassifier(nn.Module):\n", "\n", " def __init__(self, input_shape: int, hidden_units: int, output_shape: int) -> None:\n", " super().__init__()\n", " self.conv_block_1 = nn.Sequential(\n", " nn.Conv2d(in_channels=input_shape, \n", " out_channels=hidden_units, \n", " kernel_size=2, # how big is the square that's going over the image?\n", " stride=1, # default\n", " ), # options = \"valid\" (no padding) or \"same\" (output has same shape as input) or int for specific number \n", " nn.ReLU(),\n", " nn.Conv2d(in_channels=hidden_units, \n", " out_channels=hidden_units,\n", " kernel_size=2,\n", " stride=1,\n", " ),\n", " nn.ReLU(),\n", " nn.MaxPool2d(kernel_size=2,\n", " stride=2) # default stride value is same as kernel_size\n", " )\n", " self.conv_block_2 = nn.Sequential(\n", " nn.Conv2d(hidden_units, hidden_units, kernel_size=2),\n", " nn.ReLU(),\n", " nn.Conv2d(hidden_units, hidden_units, kernel_size=2),\n", " nn.ReLU(),\n", " nn.MaxPool2d(2)\n", " )\n", " self.classifier = nn.Sequential(\n", " nn.Flatten(),\n", " # Where did this in_features shape come from? \n", " # It's because each layer of our network compresses and changes the shape of our input data.\n", " nn.Linear(in_features=hidden_units*5*5,\n", " out_features=output_shape)\n", " )\n", " \n", " def forward(self, x: torch.Tensor):\n", " x = self.conv_block_1(x)\n", " # print(x.shape)\n", " x = self.conv_block_2(x)\n", " # print(x.shape)\n", " x = self.classifier(x)\n", " # print(x.shape)\n", " return x\n", " # return self.classifier(self.conv_block_2(self.conv_block_1(x))) # <- leverage the benefits of operator fusion\n", "\n", "torch.manual_seed(42)\n", "model_0 = NumberClassifier(input_shape=1, # number of color channels (3 for RGB) \n", " hidden_units=10, \n", " output_shape=len(full_dataset.classes)).to(device)\n", "model_0" ] }, { "cell_type": "code", "execution_count": 75, "id": "15b013cb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Single image shape: torch.Size([1, 1, 28, 28])\n", "\n", "Output logits:\n", "tensor([[-0.0163, 0.0994, -0.0605, -0.0853, 0.0305, 0.0064, -0.0244, 0.0897,\n", " -0.1144, -0.1026]], device='cuda:0')\n", "\n", "Output prediction probabilities:\n", "tensor([[0.0999, 0.1121, 0.0956, 0.0932, 0.1047, 0.1022, 0.0991, 0.1111, 0.0906,\n", " 0.0916]], device='cuda:0')\n", "\n", "Output prediction label:\n", "tensor([1], device='cuda:0')\n", "\n", "Actual label:\n", "1\n" ] } ], "source": [ "# 1. Get a batch of images and labels from the DataLoader\n", "img_batch, label_batch = next(iter(train_loader))\n", "\n", "# 2. Get a single image from the batch and unsqueeze the image so its shape fits the model\n", "img_single, label_single = img_batch[0].unsqueeze(dim=0), label_batch[0]\n", "print(f\"Single image shape: {img_single.shape}\\n\")\n", "\n", "# 3. Perform a forward pass on a single image\n", "model_0.eval()\n", "with torch.inference_mode():\n", " pred = model_0(img_single.to(device))\n", " \n", "# 4. Print out what's happening and convert model logits -> pred probs -> pred label\n", "print(f\"Output logits:\\n{pred}\\n\")\n", "print(f\"Output prediction probabilities:\\n{torch.softmax(pred, dim=1)}\\n\")\n", "print(f\"Output prediction label:\\n{torch.argmax(torch.softmax(pred, dim=1), dim=1)}\\n\")\n", "print(f\"Actual label:\\n{label_single}\")" ] }, { "cell_type": "code", "execution_count": 76, "id": "80ad6e9a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "==========================================================================================\n", "Layer (type:depth-idx) Output Shape Param #\n", "==========================================================================================\n", "NumberClassifier [1, 10] --\n", "├─Sequential: 1-1 [1, 10, 13, 13] --\n", "│ └─Conv2d: 2-1 [1, 10, 27, 27] 50\n", "│ └─ReLU: 2-2 [1, 10, 27, 27] --\n", "│ └─Conv2d: 2-3 [1, 10, 26, 26] 410\n", "│ └─ReLU: 2-4 [1, 10, 26, 26] --\n", "│ └─MaxPool2d: 2-5 [1, 10, 13, 13] --\n", "├─Sequential: 1-2 [1, 10, 5, 5] --\n", "│ └─Conv2d: 2-6 [1, 10, 12, 12] 410\n", "│ └─ReLU: 2-7 [1, 10, 12, 12] --\n", "│ └─Conv2d: 2-8 [1, 10, 11, 11] 410\n", "│ └─ReLU: 2-9 [1, 10, 11, 11] --\n", "│ └─MaxPool2d: 2-10 [1, 10, 5, 5] --\n", "├─Sequential: 1-3 [1, 10] --\n", "│ └─Flatten: 2-11 [1, 250] --\n", "│ └─Linear: 2-12 [1, 10] 2,510\n", "==========================================================================================\n", "Total params: 3,790\n", "Trainable params: 3,790\n", "Non-trainable params: 0\n", "Total mult-adds (Units.MEGABYTES): 0.42\n", "==========================================================================================\n", "Input size (MB): 0.00\n", "Forward/backward pass size (MB): 0.13\n", "Params size (MB): 0.02\n", "Estimated Total Size (MB): 0.15\n", "==========================================================================================" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Install torchinfo if it's not available, import it if it is\n", "import torchinfo\n", "from torchinfo import summary\n", "\n", "summary(model_0, input_size=[1, 1, 28, 28]) # do a test pass through of an example input size" ] }, { "cell_type": "code", "execution_count": 77, "id": "7630f560", "metadata": {}, "outputs": [], "source": [ "def train_step(model: torch.nn.Module, \n", " dataloader: torch.utils.data.DataLoader, \n", " loss_fn: torch.nn.Module, \n", " optimizer: torch.optim.Optimizer):\n", " # Put model in train mode\n", " model.train()\n", " \n", " # Setup train loss and train accuracy values\n", " train_loss, train_acc = 0, 0\n", " \n", " # Loop through data loader data batches\n", " for batch, (X, y) in enumerate(dataloader):\n", " # Send data to target device\n", " X, y = X.to(device), y.to(device)\n", "\n", " # 1. Forward pass\n", " y_pred = model(X)\n", "\n", " # 2. Calculate and accumulate loss\n", " loss = loss_fn(y_pred, y)\n", " train_loss += loss.item() \n", "\n", " # 3. Optimizer zero grad\n", " optimizer.zero_grad()\n", "\n", " # 4. Loss backward\n", " loss.backward()\n", "\n", " # 5. Optimizer step\n", " optimizer.step()\n", "\n", " # Calculate and accumulate accuracy metrics across all batches\n", " y_pred_class = torch.argmax(torch.softmax(y_pred, dim=1), dim=1)\n", " train_acc += (y_pred_class == y).sum().item()/len(y_pred)\n", "\n", " # Adjust metrics to get average loss and accuracy per batch \n", " train_loss = train_loss / len(dataloader)\n", " train_acc = train_acc / len(dataloader)\n", " return train_loss, train_acc" ] }, { "cell_type": "code", "execution_count": 78, "id": "d912272a", "metadata": {}, "outputs": [], "source": [ "def test_step(model: torch.nn.Module, \n", " dataloader: torch.utils.data.DataLoader, \n", " loss_fn: torch.nn.Module):\n", " # Put model in eval mode\n", " model.eval() \n", " \n", " # Setup test loss and test accuracy values\n", " test_loss, test_acc = 0, 0\n", " \n", " # Turn on inference context manager\n", " with torch.inference_mode():\n", " # Loop through DataLoader batches\n", " for batch, (X, y) in enumerate(dataloader):\n", " # Send data to target device\n", " X, y = X.to(device), y.to(device)\n", " \n", " # 1. Forward pass\n", " test_pred_logits = model(X)\n", "\n", " # 2. Calculate and accumulate loss\n", " loss = loss_fn(test_pred_logits, y)\n", " test_loss += loss.item()\n", " \n", " # Calculate and accumulate accuracy\n", " test_pred_labels = test_pred_logits.argmax(dim=1)\n", " test_acc += ((test_pred_labels == y).sum().item()/len(test_pred_labels))\n", " \n", " # Adjust metrics to get average loss and accuracy per batch \n", " test_loss = test_loss / len(dataloader)\n", " test_acc = test_acc / len(dataloader)\n", " return test_loss, test_acc" ] }, { "cell_type": "code", "execution_count": 79, "id": "7886be66", "metadata": {}, "outputs": [], "source": [ "from tqdm.auto import tqdm\n", "\n", "# 1. Take in various parameters required for training and test steps\n", "def train(model: torch.nn.Module, \n", " train_dataloader: torch.utils.data.DataLoader, \n", " test_dataloader: torch.utils.data.DataLoader, \n", " optimizer: torch.optim.Optimizer,\n", " loss_fn: torch.nn.Module = nn.CrossEntropyLoss(),\n", " epochs: int = 5):\n", " \n", " # 2. Create empty results dictionary\n", " results = {\"train_loss\": [],\n", " \"train_acc\": [],\n", " \"test_loss\": [],\n", " \"test_acc\": []\n", " }\n", " \n", " # 3. Loop through training and testing steps for a number of epochs\n", " for epoch in tqdm(range(epochs)):\n", " train_loss, train_acc = train_step(model=model,\n", " dataloader=train_dataloader,\n", " loss_fn=loss_fn,\n", " optimizer=optimizer)\n", " test_loss, test_acc = test_step(model=model,\n", " dataloader=test_dataloader,\n", " loss_fn=loss_fn)\n", " \n", " # 4. Print out what's happening\n", " print(\n", " f\"Epoch: {epoch+1} | \"\n", " f\"train_loss: {train_loss:.4f} | \"\n", " f\"train_acc: {train_acc:.4f} | \"\n", " f\"test_loss: {test_loss:.4f} | \"\n", " f\"test_acc: {test_acc:.4f}\"\n", " )\n", "\n", " # 5. Update results dictionary\n", " # Ensure all data is moved to CPU and converted to float for storage\n", " results[\"train_loss\"].append(train_loss.item() if isinstance(train_loss, torch.Tensor) else train_loss)\n", " results[\"train_acc\"].append(train_acc.item() if isinstance(train_acc, torch.Tensor) else train_acc)\n", " results[\"test_loss\"].append(test_loss.item() if isinstance(test_loss, torch.Tensor) else test_loss)\n", " results[\"test_acc\"].append(test_acc.item() if isinstance(test_acc, torch.Tensor) else test_acc)\n", "\n", " # 6. Return the filled results at the end of the epochs\n", " return results" ] }, { "cell_type": "code", "execution_count": 287, "id": "f0d501b6", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " 20%|██ | 1/5 [03:12<12:49, 192.33s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 1 | train_loss: 0.2044 | train_acc: 0.9403 | test_loss: 0.1693 | test_acc: 0.9519\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 40%|████ | 2/5 [06:28<09:43, 194.54s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 2 | train_loss: 0.1614 | train_acc: 0.9522 | test_loss: 0.1490 | test_acc: 0.9564\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 60%|██████ | 3/5 [09:56<06:41, 200.94s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 3 | train_loss: 0.1578 | train_acc: 0.9531 | test_loss: 0.1402 | test_acc: 0.9579\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 80%|████████ | 4/5 [13:10<03:18, 198.12s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 4 | train_loss: 0.1527 | train_acc: 0.9551 | test_loss: 0.1406 | test_acc: 0.9588\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 5/5 [16:19<00:00, 195.95s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 5 | train_loss: 0.1539 | train_acc: 0.9546 | test_loss: 0.1670 | test_acc: 0.9497\n", "Total training time: 979.759 seconds\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# Set random seeds\n", "torch.manual_seed(40) \n", "torch.cuda.manual_seed(40)\n", "\n", "# Set number of epochs\n", "NUM_EPOCHS = 5\n", "\n", "# Setup loss function and optimizer\n", "loss_fn = nn.CrossEntropyLoss()\n", "optimizer = torch.optim.Adam(params=model_0.parameters(), lr=0.01)\n", "\n", "# Start the timer\n", "from timeit import default_timer as timer \n", "start_time = timer()\n", "\n", "# Train model_0 \n", "model_0_results = train(model=model_0, \n", " train_dataloader=train_loader,\n", " test_dataloader=test_loader,\n", " optimizer=optimizer,\n", " loss_fn=loss_fn, \n", " epochs=NUM_EPOCHS)\n", "\n", "# End the timer and print out how long it took\n", "end_time = timer()\n", "print(f\"Total training time: {end_time-start_time:.3f} seconds\")" ] }, { "cell_type": "code", "execution_count": 309, "id": "34e9b5e0", "metadata": {}, "outputs": [], "source": [ "from PIL import Image, ImageEnhance, ImageOps\n", "\n", "def predict_number(custom_image: Image.Image):\n", "\n", " custom_image = ImageEnhance.Contrast(custom_image.convert(\"L\")).enhance(5.0).point(lambda p: 255 if p > 128 else 0).resize((28, 28))\n", "\n", " transform = transforms.Compose([\n", " transforms.ToTensor(),\n", " ])\n", "\n", " model_0.eval()\n", " with torch.inference_mode():\n", " pred = torch.softmax(model_0(transform(custom_image).unsqueeze(0).to(device)), dim=1)\n", "\n", " class_names = full_dataset.classes\n", " return {class_names[i]: float(pred[0][i]) for i in range(len(class_names))}" ] }, { "cell_type": "code", "execution_count": 299, "id": "96dfd120", "metadata": {}, "outputs": [], "source": [ "img_ = Image.open(\"/home/arpan/torchenv_learning/showcase_dataset/5_inverted.png\")\n", "img_ = img_.convert(\"RGB\")\n", "img_ = ImageOps.invert(img_)\n", "img_.save(\"/home/arpan/torchenv_learning/showcase_dataset/5_4.png\")" ] }, { "cell_type": "code", "execution_count": 300, "id": "20809009", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/home/arpan/torchenv_learning/showcase_dataset/4_2.png ==> Prediction: 4, with probability 0.9377965331077576 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/0.jpg ==> Prediction: 0, with probability 0.5970964431762695 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/9_1.png ==> Prediction: 9, with probability 0.9998043179512024 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/0_1.png ==> Prediction: 0, with probability 0.9994669556617737 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/3.png ==> Prediction: 3, with probability 0.9927063584327698 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/6.png ==> Prediction: 6, with probability 0.9987753033638 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/7_1.png ==> Prediction: 7, with probability 0.814873993396759 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/9.png ==> Prediction: 8, with probability 0.5316581726074219 - ❌\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/3_2.png ==> Prediction: 3, with probability 0.9990369081497192 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/5_3.png ==> Prediction: 5, with probability 0.9977946281433105 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/5_4.png ==> Prediction: 9, with probability 0.5392404198646545 - ❌\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/3_1.png ==> Prediction: 3, with probability 0.9937238097190857 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/5.png ==> Prediction: 5, with probability 0.5505344271659851 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/1_1.png ==> Prediction: 7, with probability 0.7994639873504639 - ❌\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/7.png ==> Prediction: 7, with probability 0.9400009512901306 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/5_2.png ==> Prediction: 5, with probability 0.9454333782196045 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/4.png ==> Prediction: 4, with probability 0.9998598098754883 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/7_2.png ==> Prediction: 7, with probability 0.9654712677001953 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/1.png ==> Prediction: 1, with probability 0.9999350309371948 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/4_1.png ==> Prediction: 8, with probability 0.9917175769805908 - ❌\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/2.png ==> Prediction: 2, with probability 0.993058443069458 - ✅\n", "\n", "/home/arpan/torchenv_learning/showcase_dataset/5_1.png ==> Prediction: 5, with probability 0.6415048241615295 - ✅\n", "\n" ] } ], "source": [ "from glob import glob\n", "\n", "for image_path in glob(\"/home/arpan/torchenv_learning/showcase_dataset/*.*\"):\n", " predict_number(image_path)" ] }, { "cell_type": "code", "execution_count": 301, "id": "206562e0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 301, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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ANevXwcAREZGYuXKlcjLy8Mff/yB+fPn/+130IcPHw65XI6wsDDk5eXp9pmTkwMAkEql4PF4yM/Px927d1FXVweRSIRFixYhOjoaW7duRVVVFU6dOoV169bpLkj76KOPcPHiRSxevBgVFRXIyspCZmZmp37eESNGoLq6GtnZ2aiqqkJqamq7FwKamJhALpfjzJkzOHLkCBYsWICgoCBYW1sDABISEqBUKpGamooLFy6grKwMGRkZWLNmTafyEEK6BhV2Qv5iamqK4uJiSCQSBAYGwsXFBeHh4WhoaNDN4D/55BN88MEHkMvlkMlkEIlEeOedd/52v2lpaXj33Xcxf/58ODs7Y968eaivrwcA2NraIiEhAbGxsbCyskJERAQAIDExEXFxcVAqlXBxccG0adOwf/9+2NnZAXh83nv37t3Iy8uDu7s7NmzYgOTk5E79vG+//Taio6MRERGBsWPH4tixY4iLi2vTz9HREYGBgXjrrbcwZcoUuLm56X2dbe7cudi8eTMyMjLg6uoKHx8fZGZm6rISQl4uHnveVT+EEEII+c+hGTshhBBiQKiwE0IIIQaECjshhBBiQKiwE0IIIQaECjshhBBiQKiwE0IIIQaECjshhBBiQKiwE0IIIQaECjshhBBiQKiwE0IIIQaECjshhBBiQP4P5qciwVbps0cAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import torch\n", "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", "\n", "all_preds = []\n", "all_labels = []\n", "\n", "model_0.eval()\n", "with torch.no_grad():\n", " for images, labels in test_loader:\n", " images = images.to(device)\n", " labels = labels.to(device)\n", "\n", " outputs = model_0(images)\n", " _, preds = torch.max(outputs, 1)\n", "\n", " all_preds.extend(preds.cpu().numpy())\n", " all_labels.extend(labels.cpu().numpy())\n", "\n", "cm = confusion_matrix(all_labels, all_preds)\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm)\n", "cm_normalized = cm.astype(\"float\") / cm.sum(axis=1, keepdims=True)\n", "disp = ConfusionMatrixDisplay(confusion_matrix=cm_normalized, display_labels=list(full_dataset.classes))\n", "disp.plot(cmap=\"Blues\", values_format=\".2f\")" ] }, { "cell_type": "markdown", "id": "3357a514", "metadata": {}, "source": [ "### save Model" ] }, { "cell_type": "code", "execution_count": 315, "id": "d61a4e77", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saving model to: /home/arpan/torchenv_learning/models/pytorch_num_classifier_final_model_with_EMNIST.pth\n" ] } ], "source": [ "from pathlib import Path\n", "\n", "# 1. Create models directory \n", "MODEL_PATH = Path(\"/home/arpan/torchenv_learning/models\")\n", "MODEL_PATH.mkdir(parents=True, exist_ok=True)\n", "\n", "# 2. Create model save path \n", "MODEL_NAME = \"pytorch_num_classifier_final_model_with_EMNIST.pth\"\n", "MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME\n", "\n", "# 3. Save the model state dict \n", "print(f\"Saving model to: {MODEL_SAVE_PATH}\")\n", "torch.save(obj=model_0.state_dict(), # only saving the state_dict() only saves the models learned parameters\n", " f=MODEL_SAVE_PATH)" ] }, { "cell_type": "code", "execution_count": 236, "id": "a38216d9", "metadata": {}, "outputs": [], "source": [ "import torch\n", "from torch.utils.data import Dataset, DataLoader, random_split\n", "from torchvision import transforms\n", "import numpy as np\n", "\n", "class ImageWithWriterDataset(Dataset):\n", " def __init__(self, image_path, writer_info_path, transform=None):\n", " self.images = np.load(image_path) # shape: (N, 28, 28)\n", " self.writer_info = np.load(writer_info_path, allow_pickle=True) # shape: (N,), each item: list/array\n", " self.transform = transform\n", "\n", " def __len__(self):\n", " return len(self.images)\n", "\n", " def __getitem__(self, idx):\n", " img = self.images[idx] # (28, 28)\n", " label = int(self.writer_info[idx][0]) # First item is the label\n", "\n", " # Convert grayscale to 3D tensor (1, 28, 28) and normalize to [0, 1]\n", " if self.transform:\n", " img = self.transform(img)\n", " else:\n", " img = torch.tensor(img, dtype=torch.float32).unsqueeze(0) / 255.0 # shape: (1, 28, 28)\n", "\n", " return img, label\n", " \n", "transform = transforms.Compose([\n", " transforms.ToPILImage(),\n", " transforms.RandomInvert(1),\n", " transforms.Resize((28, 28)), # Resize images to 28x28\n", " transforms.Grayscale(num_output_channels=1), # Convert to grayscale\n", " transforms.RandomRotation(15),\n", " transforms.RandomAffine(0, translate=(0.1, 0.1)),\n", " transforms.ColorJitter(brightness=0.2, contrast=0.2),\n", " transforms.RandomPerspective(distortion_scale=0.2, p=0.5),\n", " transforms.ToTensor(),\n", " transforms.RandomInvert(1),\n", "])\n", "\n", "dataset = ImageWithWriterDataset(\n", " \"/home/arpan/torchenv_learning/data/Images(28x28).npy\",\n", " \"/home/arpan/torchenv_learning/data/WriterInfo.npy\",\n", " transform=None # or use `transform=transform` if you want PIL-based pipeline\n", ")\n", "\n", "# Split into train/test\n", "train_size = int(0.8 * len(dataset))\n", "test_size = len(dataset) - train_size\n", "train_dataset, test_dataset = random_split(dataset, [train_size, test_size])\n", "\n", "# Dataloaders\n", "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n", "test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)\n" ] }, { "cell_type": "markdown", "id": "c86b2e55", "metadata": {}, "source": [ "### With Pretrained model - Hidden" ] }, { "cell_type": "code", "execution_count": 162, "id": "54168e52", "metadata": {}, "outputs": [], "source": [ "import torchvision\n", "weights = torchvision.models.EfficientNet_B1_Weights.DEFAULT # .DEFAULT = best available weights \n", "model_pretrained = torchvision.models.efficientnet_b1(weights=weights).to(device)\n", "\n", "# # Freeze all base layers in the \"features\" section of the model (the feature extractor) by setting requires_grad=False\n", "# for param in model_pretrained.features.parameters():\n", "# param.requires_grad = False\n", "\n", "# Set the manual seeds\n", "torch.manual_seed(40)\n", "torch.cuda.manual_seed(40)\n", "\n", "# Get the length of class_names (one output unit for each class)\n", "output_shape = 10\n", "\n", "# Recreate the classifier layer and seed it to the target device\n", "model_pretrained.classifier = torch.nn.Sequential(\n", " torch.nn.Dropout(p=0.2, inplace=True), \n", " torch.nn.Linear(in_features=1280, \n", " out_features=output_shape, # same number of output units as our number of classes\n", " bias=True)).to(device)\n", "\n", "old_conv = model_pretrained.features[0][0]\n", "model_pretrained.features[0][0] = nn.Conv2d(\n", " in_channels=3,\n", " out_channels=old_conv.out_channels,\n", " kernel_size=old_conv.kernel_size,\n", " stride=old_conv.stride,\n", " padding=old_conv.padding,\n", " bias=False)\n", "\n", "\n", "data_dir = \"/home/arpan/torchenv_learning/EU_num_processed_dataset\"\n", "\n", "transform = weights.transforms()\n", "\n", "full_dataset = datasets.ImageFolder(root=data_dir, transform=transform)\n", "\n", "# Split into train/test\n", "train_ratio = 0.8\n", "train_size = int(train_ratio * len(full_dataset))\n", "test_size = len(full_dataset) - train_size\n", "\n", "train_dataset, test_dataset = random_split(full_dataset, [train_size, test_size])\n", "\n", "\n", "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n", "test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)" ] }, { "cell_type": "code", "execution_count": 163, "id": "c7cba614", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "============================================================================================================================================\n", "Layer (type (var_name)) Input Shape Output Shape Param # Trainable\n", "============================================================================================================================================\n", "EfficientNet (EfficientNet) [32, 3, 240, 240] [32, 10] -- True\n", "├─Sequential (features) [32, 3, 240, 240] [32, 1280, 8, 8] -- True\n", "│ └─Conv2dNormActivation (0) [32, 3, 240, 240] [32, 32, 120, 120] -- True\n", "│ │ └─Conv2d (0) [32, 3, 240, 240] [32, 32, 120, 120] 864 True\n", "│ │ └─BatchNorm2d (1) [32, 32, 120, 120] [32, 32, 120, 120] 64 True\n", "│ │ └─SiLU (2) [32, 32, 120, 120] [32, 32, 120, 120] -- --\n", "│ └─Sequential (1) [32, 32, 120, 120] [32, 16, 120, 120] -- True\n", "│ │ └─MBConv (0) [32, 32, 120, 120] [32, 16, 120, 120] 1,448 True\n", "│ │ └─MBConv (1) [32, 16, 120, 120] [32, 16, 120, 120] 612 True\n", "│ └─Sequential (2) [32, 16, 120, 120] [32, 24, 60, 60] -- True\n", "│ │ └─MBConv (0) [32, 16, 120, 120] [32, 24, 60, 60] 6,004 True\n", "│ │ └─MBConv (1) [32, 24, 60, 60] [32, 24, 60, 60] 10,710 True\n", "│ │ └─MBConv (2) [32, 24, 60, 60] [32, 24, 60, 60] 10,710 True\n", "│ └─Sequential (3) [32, 24, 60, 60] [32, 40, 30, 30] -- True\n", "│ │ └─MBConv (0) [32, 24, 60, 60] [32, 40, 30, 30] 15,350 True\n", "│ │ └─MBConv (1) [32, 40, 30, 30] [32, 40, 30, 30] 31,290 True\n", "│ │ └─MBConv (2) [32, 40, 30, 30] [32, 40, 30, 30] 31,290 True\n", "│ └─Sequential (4) [32, 40, 30, 30] [32, 80, 15, 15] -- True\n", "│ │ └─MBConv (0) [32, 40, 30, 30] [32, 80, 15, 15] 37,130 True\n", "│ │ └─MBConv (1) [32, 80, 15, 15] [32, 80, 15, 15] 102,900 True\n", "│ │ └─MBConv (2) [32, 80, 15, 15] [32, 80, 15, 15] 102,900 True\n", "│ │ └─MBConv (3) [32, 80, 15, 15] [32, 80, 15, 15] 102,900 True\n", "│ └─Sequential (5) [32, 80, 15, 15] [32, 112, 15, 15] -- True\n", "│ │ └─MBConv (0) [32, 80, 15, 15] [32, 112, 15, 15] 126,004 True\n", "│ │ └─MBConv (1) [32, 112, 15, 15] [32, 112, 15, 15] 208,572 True\n", "│ │ └─MBConv (2) [32, 112, 15, 15] [32, 112, 15, 15] 208,572 True\n", "│ │ └─MBConv (3) [32, 112, 15, 15] [32, 112, 15, 15] 208,572 True\n", "│ └─Sequential (6) [32, 112, 15, 15] [32, 192, 8, 8] -- True\n", "│ │ └─MBConv (0) [32, 112, 15, 15] [32, 192, 8, 8] 262,492 True\n", "│ │ └─MBConv (1) [32, 192, 8, 8] [32, 192, 8, 8] 587,952 True\n", "│ │ └─MBConv (2) [32, 192, 8, 8] [32, 192, 8, 8] 587,952 True\n", "│ │ └─MBConv (3) [32, 192, 8, 8] [32, 192, 8, 8] 587,952 True\n", "│ │ └─MBConv (4) [32, 192, 8, 8] [32, 192, 8, 8] 587,952 True\n", "│ └─Sequential (7) [32, 192, 8, 8] [32, 320, 8, 8] -- True\n", "│ │ └─MBConv (0) [32, 192, 8, 8] [32, 320, 8, 8] 717,232 True\n", "│ │ └─MBConv (1) [32, 320, 8, 8] [32, 320, 8, 8] 1,563,600 True\n", "│ └─Conv2dNormActivation (8) [32, 320, 8, 8] [32, 1280, 8, 8] -- True\n", "│ │ └─Conv2d (0) [32, 320, 8, 8] [32, 1280, 8, 8] 409,600 True\n", "│ │ └─BatchNorm2d (1) [32, 1280, 8, 8] [32, 1280, 8, 8] 2,560 True\n", "│ │ └─SiLU (2) [32, 1280, 8, 8] [32, 1280, 8, 8] -- --\n", "├─AdaptiveAvgPool2d (avgpool) [32, 1280, 8, 8] [32, 1280, 1, 1] -- --\n", "├─Sequential (classifier) [32, 1280] [32, 10] -- True\n", "│ └─Dropout (0) [32, 1280] [32, 1280] -- --\n", "│ └─Linear (1) [32, 1280] [32, 10] 12,810 True\n", "============================================================================================================================================\n", "Total params: 6,525,994\n", "Trainable params: 6,525,994\n", "Non-trainable params: 0\n", "Total mult-adds (Units.GIGABYTES): 21.94\n", "============================================================================================================================================\n", "Input size (MB): 22.12\n", "Forward/backward pass size (MB): 5568.64\n", "Params size (MB): 26.10\n", "Estimated Total Size (MB): 5616.86\n", "============================================================================================================================================" ] }, "execution_count": 163, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Print a summary using torchinfo (uncomment for actual output)\n", "summary(model=model_pretrained, \n", " input_size=(32, 3, 240, 240), # make sure this is \"input_size\", not \"input_shape\"\n", " # col_names=[\"input_size\"], # uncomment for smaller output\n", " col_names=[\"input_size\", \"output_size\", \"num_params\", \"trainable\"],\n", " col_width=20,\n", " row_settings=[\"var_names\"]\n", ") " ] }, { "cell_type": "code", "execution_count": 164, "id": "2cf385ae", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ " 20%|██ | 1/5 [01:00<04:01, 60.39s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 1 | train_loss: 2.3039 | train_acc: 0.1027 | test_loss: 2.3032 | test_acc: 0.0988\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 40%|████ | 2/5 [02:03<03:06, 62.01s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 2 | train_loss: 2.3035 | train_acc: 0.1047 | test_loss: 2.3030 | test_acc: 0.1074\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 60%|██████ | 3/5 [03:06<02:04, 62.33s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 3 | train_loss: 2.3034 | train_acc: 0.1017 | test_loss: 2.3027 | test_acc: 0.1030\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " 80%|████████ | 4/5 [04:08<01:02, 62.21s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 4 | train_loss: 2.3032 | train_acc: 0.1042 | test_loss: 2.3027 | test_acc: 0.1041\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 5/5 [05:09<00:00, 61.99s/it]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Epoch: 5 | train_loss: 2.3042 | train_acc: 0.0992 | test_loss: 2.3031 | test_acc: 0.1034\n", "Total training time: 309.954 seconds\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# Set random seeds\n", "torch.manual_seed(40) \n", "torch.cuda.manual_seed(40)\n", "\n", "# Set number of epochs\n", "NUM_EPOCHS = 5\n", "\n", "# Setup loss function and optimizer\n", "loss_fn = nn.CrossEntropyLoss()\n", "optimizer = torch.optim.Adam(params=model_0.parameters(), lr=0.01)\n", "\n", "# Start the timer\n", "from timeit import default_timer as timer \n", "start_time = timer()\n", "\n", "# Train model_0 \n", "model_0_results = train(model=model_pretrained, \n", " train_dataloader=train_loader,\n", " test_dataloader=test_loader,\n", " optimizer=optimizer,\n", " loss_fn=loss_fn, \n", " epochs=NUM_EPOCHS)\n", "\n", "# End the timer and print out how long it took\n", "end_time = timer()\n", "print(f\"Total training time: {end_time-start_time:.3f} seconds\")" ] }, { "cell_type": "code", "execution_count": null, "id": "cb765530", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "4e134030", "metadata": {}, "source": [ "### MNIST Data" ] }, { "cell_type": "code", "execution_count": 248, "id": "7014dcee", "metadata": {}, "outputs": [], "source": [ "import torch\n", "from torchvision import datasets, transforms\n", "from torch.utils.data import DataLoader\n", "import matplotlib.pyplot as plt\n", "\n", "# Transform: converts images to tensor (shape [1, 28, 28])\n", "transform = transforms.Compose([\n", " transforms.Resize((28, 28)), # Resize images to 28x28\n", " transforms.Grayscale(num_output_channels=1), # Convert to grayscale\n", " transforms.RandomRotation(15),\n", " transforms.RandomAffine(0, translate=(0.1, 0.1)),\n", " transforms.ColorJitter(brightness=0.2, contrast=0.2),\n", " transforms.RandomPerspective(distortion_scale=0.2, p=0.5),\n", " transforms.ToTensor(),\n", " transforms.RandomInvert(1),\n", "])\n", "\n", "# Load MNIST train and test sets\n", "train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)\n", "test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)\n", "\n", "# Create 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" ] }, { "cell_type": "markdown", "id": "32a0df3b", "metadata": {}, "source": [ "### EMANIST Data" ] }, { "cell_type": "code", "execution_count": 279, "id": "f34d0886", "metadata": {}, "outputs": [], "source": [ "import torch\n", "from torchvision import datasets, transforms\n", "from torch.utils.data import DataLoader\n", "\n", "# Define transforms (EMNIST is grayscale, 28x28)\n", "transform = transforms.Compose([\n", " transforms.Resize((28, 28)), # Resize images to 28x28\n", " transforms.Grayscale(num_output_channels=1), # Convert to grayscale\n", " transforms.RandomRotation(15),\n", " transforms.RandomAffine(0, translate=(0.1, 0.1)),\n", " transforms.ColorJitter(brightness=0.2, contrast=0.2),\n", " transforms.RandomPerspective(distortion_scale=0.2, p=0.5),\n", " transforms.ToTensor(),\n", " transforms.Lambda(lambda x: torch.rot90(x, k=-1, dims=[1, 2])),\n", " transforms.Lambda(lambda x: torch.flip(x, [2])), # flip horizontally\n", " transforms.RandomInvert(1),\n", "])\n", "\n", "# Load EMNIST 'digits' split (you can also use 'byclass', 'digits', etc.)\n", "train_dataset = datasets.EMNIST(root='./data', split='digits', train=True, download=True, transform=transform)\n", "test_dataset = datasets.EMNIST(root='./data', split='digits', train=False, download=True, transform=transform)\n", "\n", "# DataLoaders\n", "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\n", "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)\n" ] }, { "cell_type": "markdown", "id": "2b0a3d55", "metadata": {}, "source": [ "### Visualize Train data" ] }, { "cell_type": "code", "execution_count": 286, "id": "1d0859ad", "metadata": {}, "outputs": [ { "data": { "image/png": "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w4YNM9cidOzatcvMrSFDv/jFL1T2wAMPmPXWz5hPP/1UZc8884xZ/95776nMGpo1dOhQs95aO2TIEHPt9u3bVdarVy+VtW7d2qyvD/dNtyGhX375pcouueQSc2013rL4JioqSmWzZ88211599dUqa9OmTY3vCbXH+jl+1113mWtXr16tMmvYpNvQ2yNHjqjMel9rvS8UEfnP//xPlbVs2dJci7rvxIkTKvvmm29U5jb80TpnVekJrCF+1nC0wsJCs/7OO+9U2fz58z1fPxjwiTcAAAAAAD6i8QYAAAAAwEc03gAAAAAA+IjGGwAAAAAAH9F4AwAAAADgo/Da3sC54ja9fPPmzSrLzc1V2UsvvWTWf/zxxyo7duyYytymArZv315lMTExKvu3f/s3s75fv34qS09PV5nbBMPqTjDHmZWXl6ts1apV5trFixdX61rh4fpb2ppq7jZR0pqsbGV79uwx61955RWV9e7dW2UNGjQw693OKWqONVnamowrIjJp0iSVZWZmqqwqT7iwJqz279/fc7219tJLLzXXWve3xx9/XGVpaWlmfUREhOd9ue0B/y8/P9/MN23apLK6OL1cRGTlypUqu+aaa1Rm3YtRP1hP/Rg7dqy51novYL2HtDK3a1n3M+tpEiJMMA811hMWUlJSavw6bvdna0q/2/tNy+jRo896T8GCrgsAAAAAAB/ReAMAAAAA4CMabwAAAAAAfETjDQAAAACAj0Jy+of1S/87d+4011qDUqy17777rufrW4Oj2rRpY679zW9+o7IuXbqorHHjxmZ98+bNVRYdHX2mLeIceu+991Q2efJkc601tMwaXOU2yGngwIEqswZMWQMERUSOHj2qMmvg2f/8z/+Y9evWrVPZwoULVXbPPfeY9ag5boOsFi1apLK8vDxz7ZIlS1Rm3V/d7k933HGHyqzz4DZsz9rXrl27VPbwww+b9W5DDFE7ioqKzDw7O/sc7wTwj/Xz1Ro6VRWtWrUy8+nTp6ts6NChKouNja3W9YHvO3HihJm//vrrnurj4+PN3OppQg2feAMAAAAA4CMabwAAAAAAfETjDQAAAACAj2i8AQAAAADwUdAMVysvL1dZSUmJufbkyZMqe/vtt821f/rTn1RmDfqprKw065s0aaKy5ORklU2YMMGsHzZsmMqsoQOBQMCsd8trkzUMTEQkLCzsHO+k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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Get one batch of images and labels\n", "images, labels = next(iter(train_loader))\n", "\n", "# Show the first 6 images in the batch\n", "plt.figure(figsize=(10, 2))\n", "for i in range(6):\n", " plt.subplot(1, 6, i+1)\n", " plt.imshow(images[i].squeeze(), cmap='gray')\n", " plt.title(f\"Label: {labels[i].item()}\")\n", " plt.axis('off')\n", "plt.tight_layout()\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "79b17f78", "metadata": {}, "source": [ "### Model deployment" ] }, { "cell_type": "code", "execution_count": 316, "id": "3100dab7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[['/home/arpan/torchenv_learning/showcase_dataset/7_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/4.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/6.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/6_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/3.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/1_2.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/2.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/9.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/3_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/2_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/6_3.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/5.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/7_3.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/8_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/5_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/8.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/4_3.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/5_3.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/2_2.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/0.jpg'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/9_2.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/5_2.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/9_1.png'],\n", " ['/home/arpan/torchenv_learning/showcase_dataset/0_1.png']]" ] }, "execution_count": 316, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "import random\n", "# Create a list of example inputs to our Gradio demo\n", "example_list = [[str(filepath)] for filepath in random.sample(glob(\"/home/arpan/torchenv_learning/showcase_dataset/*\"), k=25)]\n", "example_list" ] }, { "cell_type": "code", "execution_count": 317, "id": "c52e50b3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "* Running on local URL: http://127.0.0.1:7866\n", "* Running on public URL: https://d09e948d3cdff83a48.gradio.live\n", "\n", "This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n" ] }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [] }, "execution_count": 317, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import gradio as gr\n", "\n", "# Create title, description and article strings\n", "title = \"Number Classifier Minimal\"\n", "description = \"An Image feature extractor computer vision model to classify images of handwritten digits.\"\n", "article = \"Created at [09. PyTorch Model Deployment](https://www.learnpytorch.io/09_pytorch_model_deployment/).\"\n", "\n", "# Create the Gradio demo\n", "demo = gr.Interface(fn=predict_number, # mapping function from input to output\n", " inputs=gr.Image(type=\"pil\"), # what are the inputs?\n", " outputs=[gr.Label(num_top_classes=10, label=\"Predictions\")],\n", " examples=example_list, \n", " title=title,\n", " description=description,\n", " article=article)\n", "\n", "# Launch the demo!\n", "demo.launch(debug=False, # print errors locally?\n", " share=True) # generate a publically shareable URL?" ] }, { "cell_type": "code", "execution_count": null, "id": "d7cc45dd", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "torchenv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.11" } }, "nbformat": 4, "nbformat_minor": 5 }