From 66e153ff4a3e7f8bd146e57ec690dc0c86c9c47e Mon Sep 17 00:00:00 2001 From: KeshavAnandCode Date: Fri, 20 Mar 2026 13:12:06 -0500 Subject: [PATCH] final submission readyu...moved notebookks --- notebooks/06-deeper-cnn.ipynb | 690 ------------------ notebooks/Final-Submission.ipynb | 511 +++++++++++++ .../{ => experiments}/01-tutorial-cnn.ipynb | 42 +- .../02-tutorial-cnn-modified-vals.ipynb | 82 +-- .../{ => experiments}/03-adam-optimizer.ipynb | 82 +-- .../04-batch-norm-dropout.ipynb | 84 +-- .../05-data-augmentation.ipynb | 110 +-- results/training_curves.png | Bin 0 -> 61518 bytes src/.gitkeep | 0 9 files changed, 711 insertions(+), 890 deletions(-) delete mode 100644 notebooks/06-deeper-cnn.ipynb create mode 100644 notebooks/Final-Submission.ipynb rename notebooks/{ => experiments}/01-tutorial-cnn.ipynb (97%) rename notebooks/{ => experiments}/02-tutorial-cnn-modified-vals.ipynb (95%) rename notebooks/{ => experiments}/03-adam-optimizer.ipynb (96%) rename notebooks/{ => experiments}/04-batch-norm-dropout.ipynb (96%) rename notebooks/{ => experiments}/05-data-augmentation.ipynb (95%) create mode 100644 results/training_curves.png delete mode 100644 src/.gitkeep diff --git a/notebooks/06-deeper-cnn.ipynb b/notebooks/06-deeper-cnn.ipynb deleted file mode 100644 index ed82efb..0000000 --- a/notebooks/06-deeper-cnn.ipynb +++ /dev/null @@ -1,690 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "7a37220a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello, World!\n" - ] - } - ], - "source": [ - "print(\"Hello, World!\")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d318d1f0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Van: 1111 images\n", - "Taxi: 748 images\n", - "Bicycle: 1618 images\n", - "Bus: 2133 images\n", - "Car: 6781 images\n", - "Motorcycle: 2986 images\n", - "Truck: 2033 images\n", - "NonVehicles: 8968 images\n" - ] - } - ], - "source": [ - "import os\n", - "\n", - "data_dir = '../data/raw/vehicle_classification'\n", - "\n", - "for class_name in os.listdir(data_dir):\n", - " class_path = os.path.join(data_dir, class_name)\n", - " if os.path.isdir(class_path):\n", - " count = len(os.listdir(class_path))\n", - " print(f\"{class_name}: {count} images\")" - ] - }, - { - "cell_type": "markdown", - "id": "64122ad4", - "metadata": {}, - "source": [ - "Check out sample image from dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5604ace3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Size: (64, 64)\n", - "Mode: RGB\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from PIL import Image\n", - "import matplotlib.pyplot as plt\n", - "\n", - "# first image in first folder\n", - "first_class = os.listdir(data_dir)[0]\n", - "first_image_path = os.path.join(data_dir, first_class, os.listdir(os.path.join(data_dir, first_class))[0])\n", - "\n", - "img = Image.open(first_image_path)\n", - "print(f\"Size: {img.size}\")\n", - "print(f\"Mode: {img.mode}\")\n", - "plt.imshow(img)\n", - "plt.title(first_class)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "c19ec00a", - "metadata": {}, - "source": [ - "Ensure that all images are RGB, all of same resolution" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "3cedd586", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Unique sizes: {(64, 64)}\n", - "Unique modes: {'RGB'}\n" - ] - } - ], - "source": [ - "sizes = set()\n", - "modes = set()\n", - "\n", - "for class_name in os.listdir(data_dir):\n", - " class_path = os.path.join(data_dir, class_name)\n", - " if not os.path.isdir(class_path):\n", - " continue\n", - " for img_name in os.listdir(class_path):\n", - " img = Image.open(os.path.join(class_path, img_name))\n", - " sizes.add(img.size)\n", - " modes.add(img.mode)\n", - "\n", - "print(f\"Unique sizes: {sizes}\")\n", - "print(f\"Unique modes: {modes}\")" - ] - }, - { - "cell_type": "markdown", - "id": "88ac961b", - "metadata": {}, - "source": [ - "Ensure that torch works with GPU (5080) [Credit: Claude]" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "8f556b22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n", - "NVIDIA GeForce RTX 5080\n", - "GPU works! torch.Size([1000, 1000])\n" - ] - } - ], - "source": [ - "import torch\n", - "print(torch.cuda.is_available()) \n", - "print(torch.cuda.get_device_name(0)) \n", - "\n", - "x = torch.randn(1000, 1000).cuda()\n", - "y = x @ x\n", - "print(\"GPU works!\", y.shape)" - ] - }, - { - "cell_type": "markdown", - "id": "870fadbe", - "metadata": {}, - "source": [ - "Transform image dataset into tensors. Normalized with 0.5 mean and 0.5 STD (can be differente to use pretrained weights)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "37793c77", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['Bicycle', 'Bus', 'Car', 'Motorcycle', 'NonVehicles', 'Taxi', 'Truck', 'Van']\n", - "26378\n" - ] - } - ], - "source": [ - "from torchvision import datasets, transforms\n", - "from torch.utils.data import random_split, DataLoader\n", - "\n", - "transform = transforms.Compose([\n", - " transforms.ToTensor(),\n", - " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])\n", - "])\n", - "\n", - "dataset = datasets.ImageFolder(root=data_dir, transform=transform)\n", - "\n", - "print(dataset.classes) # should print 8 classes\n", - "print(len(dataset)) # total image count" - ] - }, - { - "cell_type": "markdown", - "id": "ac33bc21", - "metadata": {}, - "source": [ - "Split 80-20 and save into 2 variables....augments the test set by rotating and lighting it differently to add training nuance." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f68c1a25", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train: 21102, Test: 5276\n" - ] - } - ], - "source": [ - "import math \n", - "\n", - "train_transform = transforms.Compose([\n", - " transforms.RandomHorizontalFlip(), # randomly mirror image\n", - " transforms.RandomRotation(20), # rotate up to 10 degrees\n", - " transforms.ColorJitter(brightness=0.3, contrast=0.3), # vary lighting\n", - " transforms.ToTensor(),\n", - " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])\n", - "])\n", - "\n", - "test_transform = transforms.Compose([\n", - " transforms.ToTensor(),\n", - " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])\n", - "])\n", - "\n", - "train_full = datasets.ImageFolder(root=data_dir, transform=train_transform)\n", - "test_full = datasets.ImageFolder(root=data_dir, transform=test_transform)\n", - "\n", - "train_size = math.floor(len(train_full) * 0.8)\n", - "test_size = len(train_full) - train_size\n", - "\n", - "# Use same indices for both so split is consistent\n", - "indices = torch.randperm(len(train_full)).tolist()\n", - "train_indices = indices[:train_size]\n", - "test_indices = indices[train_size:]\n", - "\n", - "train_dataset = torch.utils.data.Subset(train_full, train_indices)\n", - "test_dataset = torch.utils.data.Subset(test_full, test_indices)\n", - "\n", - "print(f\"Train: {len(train_dataset)}, Test: {len(test_dataset)}\")" - ] - }, - { - "cell_type": "markdown", - "id": "2eede814", - "metadata": {}, - "source": [ - "Load the data into batches for faster loading...use 4 threads on CPU\n", - "\n", - "Shuffling train dataset so as to not overfit on one class" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "e1539eaa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['Bicycle', 'Bus', 'Car', 'Motorcycle', 'NonVehicles', 'Taxi', 'Truck', 'Van']\n", - "8\n" - ] - } - ], - "source": [ - "train_loader = DataLoader(train_dataset, batch_size=256, shuffle=True, num_workers=16)\n", - "test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False, num_workers=16)\n", - "\n", - "classes = train_full.classes \n", - "print(classes)\n", - "print(len(classes))" - ] - }, - { - "cell_type": "markdown", - "id": "acebe98e", - "metadata": {}, - "source": [ - "MOdified tutorial arch to handle BathhNorm and Dropout (to try to prevent overfitting)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "d1b7d9ca", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Net(\n", - " (features): Sequential(\n", - " (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (2): ReLU()\n", - " (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", - " (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " (5): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (6): ReLU()\n", - " (7): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n", - " (8): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n", - " (9): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n", - " (10): ReLU()\n", - " (11): 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=8192, out_features=512, bias=True)\n", - " (2): ReLU()\n", - " (3): Dropout(p=0.5, inplace=False)\n", - " (4): Linear(in_features=512, out_features=8, bias=True)\n", - " )\n", - ")\n" - ] - } - ], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "\n", - "class Net(nn.Module):\n", - " def __init__(self):\n", - " super(Net, self).__init__()\n", - "\n", - " self.features = nn.Sequential(\n", - " # go from 64x64 to 32x32\n", - "\n", - " # kernel size = 3x3 filter patch\n", - " #padding = 1, so 64x64 stays 64x64 after conv\n", - " nn.Conv2d(3, 32, kernel_size=3, padding=1), # 3 channel RRGB, 32 filters (recommended)\n", - "\n", - " nn.BatchNorm2d(32), # mirrors conv2d output\n", - " nn.ReLU(),\n", - " nn.MaxPool2d(2,2), # 2x2 window so halved --> 32x32\n", - "\n", - " #Go from 32x32 --> 16x16\n", - "\n", - " nn.Conv2d(32, 64, kernel_size=3, padding=1),\n", - " nn.BatchNorm2d(64),\n", - " nn.ReLU(),\n", - " nn.MaxPool2d(2,2), \n", - " \n", - " # Go from 16x 16 0 --> 8x8\n", - "\n", - " nn.Conv2d(64, 128, kernel_size=3, padding=1),\n", - " nn.BatchNorm2d(128),\n", - " nn.ReLU(),\n", - " nn.MaxPool2d(2,2), \n", - "\n", - " )\n", - "\n", - " self.classifier = nn.Sequential(\n", - " nn.Flatten(),\n", - " nn.Linear (128 * 8 *8, 512), # flattened size of 8x8 * 128, 512 is arbitrary output (recommended by sources)\n", - " nn.ReLU(),\n", - " nn.Dropout(0.5),#Randomly zero 50% of neurons --> prevent moeoorization\n", - " nn.Linear(512, len(classes)) # one score per vehicle class\n", - " )\n", - "\n", - " def forward(self, x):\n", - " x = self.features(x)\n", - " x = self.classifier(x)\n", - " return x\n", - "\n", - "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", - "model = Net().to(device)\n", - "print(model)" - ] - }, - { - "cell_type": "markdown", - "id": "22e71032", - "metadata": {}, - "source": [ - "Loss fn and optimizer" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "54d11a04", - "metadata": {}, - "outputs": [], - "source": [ - "import torch.optim as optim\n", - "\n", - "criterion = nn.CrossEntropyLoss() # Applied softmax to convert scores --> probabilities --> penalizes model \n", - "\n", - "# Changed to adam optimizer (internal momentumn calculation)\n", - "optimizer = optim.Adam(model.parameters(), lr=0.001)" - ] - }, - { - "cell_type": "markdown", - "id": "572d80e3", - "metadata": {}, - "source": [ - "Tutorial training cell adapted to use 30 epochs " - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "374d0590", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch 1: Loss=1.508, Accuracy=57.21%\n", - "Epoch 2: Loss=0.984, Accuracy=65.81%\n", - "Epoch 3: Loss=0.867, Accuracy=69.43%\n", - "Epoch 4: Loss=0.787, Accuracy=72.45%\n", - "Epoch 5: Loss=0.737, Accuracy=74.33%\n", - "Epoch 6: Loss=0.719, Accuracy=74.72%\n", - "Epoch 7: Loss=0.678, Accuracy=76.07%\n", - "Epoch 8: Loss=0.666, Accuracy=76.84%\n", - "Epoch 9: Loss=0.641, Accuracy=77.40%\n", - "Epoch 10: Loss=0.622, Accuracy=78.10%\n", - "Epoch 11: Loss=0.617, Accuracy=78.24%\n", - "Epoch 12: Loss=0.611, Accuracy=78.47%\n", - "Epoch 13: Loss=0.596, Accuracy=78.80%\n", - "Epoch 14: Loss=0.589, Accuracy=78.99%\n", - "Epoch 15: Loss=0.579, Accuracy=79.79%\n", - "Epoch 16: Loss=0.572, Accuracy=79.73%\n", - "Epoch 17: Loss=0.555, Accuracy=80.47%\n", - "Epoch 18: Loss=0.556, Accuracy=80.27%\n", - "Epoch 19: Loss=0.542, Accuracy=81.20%\n", - "Epoch 20: Loss=0.522, Accuracy=81.49%\n", - "Epoch 21: Loss=0.529, Accuracy=81.22%\n", - "Epoch 22: Loss=0.513, Accuracy=81.88%\n", - "Epoch 23: Loss=0.513, Accuracy=81.66%\n", - "Epoch 24: Loss=0.511, Accuracy=81.77%\n", - "Epoch 25: Loss=0.509, Accuracy=82.22%\n", - "Epoch 26: Loss=0.493, Accuracy=82.67%\n", - "Epoch 27: Loss=0.496, Accuracy=82.43%\n", - "Epoch 28: Loss=0.483, Accuracy=83.06%\n", - "Epoch 29: Loss=0.470, Accuracy=83.32%\n", - "Epoch 30: Loss=0.473, Accuracy=83.12%\n", - "Epoch 31: Loss=0.469, Accuracy=83.45%\n", - "Epoch 32: Loss=0.464, Accuracy=83.21%\n", - "Epoch 33: Loss=0.449, Accuracy=84.09%\n", - "Epoch 34: Loss=0.457, Accuracy=83.44%\n", - "Epoch 35: Loss=0.442, Accuracy=84.20%\n", - "Epoch 36: Loss=0.441, Accuracy=84.18%\n", - "Epoch 37: Loss=0.442, Accuracy=84.15%\n", - "Epoch 38: Loss=0.434, Accuracy=84.47%\n", - "Epoch 39: Loss=0.434, Accuracy=84.32%\n", - "Epoch 40: Loss=0.430, Accuracy=84.64%\n", - "Epoch 41: Loss=0.413, Accuracy=85.00%\n", - "Epoch 42: Loss=0.412, Accuracy=84.85%\n", - "Epoch 43: Loss=0.408, Accuracy=85.42%\n", - "Epoch 44: Loss=0.406, Accuracy=85.49%\n", - "Epoch 45: Loss=0.420, Accuracy=84.76%\n", - "Epoch 46: Loss=0.399, Accuracy=85.53%\n", - "Epoch 47: Loss=0.392, Accuracy=85.81%\n", - "Epoch 48: Loss=0.395, Accuracy=85.66%\n", - "Epoch 49: Loss=0.400, Accuracy=85.58%\n", - "Epoch 50: Loss=0.376, Accuracy=86.29%\n", - "Epoch 51: Loss=0.381, Accuracy=86.35%\n", - "Epoch 52: Loss=0.383, Accuracy=86.17%\n", - "Epoch 53: Loss=0.375, Accuracy=86.05%\n", - "Epoch 54: Loss=0.372, Accuracy=86.46%\n", - "Epoch 55: Loss=0.370, Accuracy=86.57%\n", - "Epoch 56: Loss=0.372, Accuracy=86.41%\n", - "Epoch 57: Loss=0.363, Accuracy=86.85%\n", - "Epoch 58: Loss=0.375, Accuracy=86.39%\n", - "Epoch 59: Loss=0.360, Accuracy=86.89%\n", - "Epoch 60: Loss=0.353, Accuracy=86.97%\n", - "Epoch 61: Loss=0.348, Accuracy=87.36%\n", - "Epoch 62: Loss=0.346, Accuracy=87.09%\n", - "Epoch 63: Loss=0.347, Accuracy=87.38%\n", - "Epoch 64: Loss=0.343, Accuracy=87.38%\n", - "Epoch 65: Loss=0.343, Accuracy=87.31%\n", - "Epoch 66: Loss=0.335, Accuracy=87.90%\n", - "Epoch 67: Loss=0.330, Accuracy=87.83%\n", - "Epoch 68: Loss=0.325, Accuracy=88.06%\n", - "Epoch 69: Loss=0.322, Accuracy=88.07%\n", - "Epoch 70: Loss=0.323, Accuracy=88.27%\n", - "Epoch 71: Loss=0.326, Accuracy=87.77%\n", - "Epoch 72: Loss=0.313, Accuracy=88.58%\n", - "Epoch 73: Loss=0.322, Accuracy=88.25%\n", - "Epoch 74: Loss=0.317, Accuracy=88.43%\n", - "Epoch 75: Loss=0.311, Accuracy=88.54%\n", - "Epoch 76: Loss=0.303, Accuracy=88.57%\n", - "Epoch 77: Loss=0.305, Accuracy=88.83%\n", - "Epoch 78: Loss=0.303, Accuracy=88.87%\n", - "Epoch 79: Loss=0.303, Accuracy=88.74%\n", - "Epoch 80: Loss=0.296, Accuracy=89.00%\n", - "Epoch 81: Loss=0.291, Accuracy=89.44%\n", - "Epoch 82: Loss=0.291, Accuracy=89.43%\n", - "Epoch 83: Loss=0.287, Accuracy=89.10%\n", - "Epoch 84: Loss=0.290, Accuracy=89.05%\n", - "Epoch 85: Loss=0.291, Accuracy=89.09%\n", - "Epoch 86: Loss=0.284, Accuracy=89.34%\n", - "Epoch 87: Loss=0.282, Accuracy=89.40%\n", - "Epoch 88: Loss=0.283, Accuracy=89.40%\n", - "Epoch 89: Loss=0.283, Accuracy=89.61%\n", - "Epoch 90: Loss=0.272, Accuracy=89.71%\n", - "Epoch 91: Loss=0.272, Accuracy=89.84%\n", - "Epoch 92: Loss=0.276, Accuracy=89.68%\n", - "Epoch 93: Loss=0.276, Accuracy=89.78%\n", - "Epoch 94: Loss=0.262, Accuracy=90.11%\n", - "Epoch 95: Loss=0.271, Accuracy=89.81%\n", - "Epoch 96: Loss=0.267, Accuracy=89.95%\n", - "Epoch 97: Loss=0.262, Accuracy=90.14%\n", - "Epoch 98: Loss=0.259, Accuracy=90.25%\n", - "Epoch 99: Loss=0.266, Accuracy=89.97%\n", - "Epoch 100: Loss=0.255, Accuracy=90.47%\n", - "Finished Training\n" - ] - } - ], - "source": [ - "for epoch in range(100): # 10 --> 30 epochs\n", - " running_loss = 0.0\n", - " correct = 0\n", - " total = 0\n", - "\n", - " for i, data in enumerate(train_loader, 0):\n", - " inputs, labels = data\n", - " inputs, labels = inputs.to(device), labels.to(device) # use GPU\n", - "\n", - " optimizer.zero_grad()\n", - "\n", - " outputs = model(inputs)\n", - " loss = criterion(outputs, labels)\n", - " loss.backward()\n", - " optimizer.step()\n", - "\n", - " # loss tracking\n", - " running_loss += loss.item()\n", - "\n", - " # accuracy tracking\n", - " _, predicted = torch.max(outputs, 1)\n", - " total += labels.size(0)\n", - " correct += (predicted == labels).sum().item()\n", - "\n", - " accuracy = 100 * correct / total\n", - " print(f'Epoch {epoch + 1}: Loss={running_loss / len(train_loader):.3f}, Accuracy={accuracy:.2f}%') # Credit: Claude for printing accuracy in good formatting\n", - "\n", - "print('Finished Training')" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "2bf2b9a2", - "metadata": {}, - "outputs": [], - "source": [ - "PATH = '../models/deeper-training.pth'\n", - "torch.save(model.state_dict(), PATH)" - ] - }, - { - "cell_type": "markdown", - "id": "a24dd4f0", - "metadata": {}, - "source": [ - "Test on test set using Pytorch tutorial method for both total and per class (maybe spot overfitting as well)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "bc158602", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Test Accuracy: 84.27%\n" - ] - } - ], - "source": [ - "correct = 0\n", - "total = 0\n", - "\n", - "with torch.no_grad():\n", - " for data in test_loader:\n", - " images, labels = data\n", - " images, labels = images.to(device), labels.to(device)\n", - " outputs = model(images)\n", - " _, predicted = torch.max(outputs, 1)\n", - " total += labels.size(0)\n", - " correct += (predicted == labels).sum().item()\n", - "\n", - "print(f'Test Accuracy: {100 * correct / total:.2f}%')" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "8cc7ed40", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Accuracy for class: Bicycle is 76.8%\n", - "Accuracy for class: Bus is 83.2%\n", - "Accuracy for class: Car is 84.8%\n", - "Accuracy for class: Motorcycle is 84.7%\n", - "Accuracy for class: NonVehicles is 98.7%\n", - "Accuracy for class: Taxi is 45.9%\n", - "Accuracy for class: Truck is 53.8%\n", - "Accuracy for class: Van is 56.9%\n" - ] - } - ], - "source": [ - "correct_pred = {classname: 0 for classname in dataset.classes}\n", - "total_pred = {classname: 0 for classname in dataset.classes}\n", - "\n", - "with torch.no_grad():\n", - " for data in test_loader:\n", - " images, labels = data\n", - " images, labels = images.to(device), labels.to(device)\n", - " outputs = model(images)\n", - " _, predictions = torch.max(outputs, 1)\n", - " for label, prediction in zip(labels, predictions):\n", - " if label == prediction:\n", - " correct_pred[dataset.classes[label]] += 1\n", - " total_pred[dataset.classes[label]] += 1\n", - "\n", - "for classname, correct_count in correct_pred.items():\n", - " accuracy = 100 * float(correct_count) / total_pred[classname]\n", - " print(f'Accuracy for class: {classname:10s} is {accuracy:.1f}%')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "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.11.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/Final-Submission.ipynb b/notebooks/Final-Submission.ipynb new file mode 100644 index 0000000..3235694 --- /dev/null +++ b/notebooks/Final-Submission.ipynb @@ -0,0 +1,511 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "21b10b99", + "metadata": {}, + "source": [ + "# Task 1: Load Dataset\n", + "Load images from disk and count per class to verify dataset integrity" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d318d1f0", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "data_dir = '../data/raw/vehicle_classification'\n", + "\n", + "total_count = 0\n", + "\n", + "for class_name in os.listdir(data_dir):\n", + " class_path = os.path.join(data_dir, class_name)\n", + " if os.path.isdir(class_path):\n", + " count = len(os.listdir(class_path))\n", + " total_count += count\n", + " print(f\"{class_name}: {count} images\")\n", + "\n", + "print(f\"Total Count: {total_count} images\")" + ] + }, + { + "cell_type": "markdown", + "id": "64122ad4", + "metadata": {}, + "source": [ + "Check out sample image from dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5604ace3", + "metadata": {}, + "outputs": [], + "source": [ + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# first image in first folder\n", + "first_class = os.listdir(data_dir)[0]\n", + "first_image_path = os.path.join(data_dir, first_class, os.listdir(os.path.join(data_dir, first_class))[0])\n", + "\n", + "img = Image.open(first_image_path)\n", + "print(f\"Size: {img.size}\")\n", + "print(f\"Mode: {img.mode}\")\n", + "plt.imshow(img)\n", + "plt.title(first_class)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "c19ec00a", + "metadata": {}, + "source": [ + "Ensure that all images are RGB, all of same resolution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cedd586", + "metadata": {}, + "outputs": [], + "source": [ + "sizes = set()\n", + "modes = set()\n", + "\n", + "for class_name in os.listdir(data_dir):\n", + " class_path = os.path.join(data_dir, class_name)\n", + " if not os.path.isdir(class_path):\n", + " continue\n", + " for img_name in os.listdir(class_path):\n", + " img = Image.open(os.path.join(class_path, img_name))\n", + " sizes.add(img.size)\n", + " modes.add(img.mode)\n", + "\n", + "print(f\"Unique sizes: {sizes}\")\n", + "print(f\"Unique modes: {modes}\")" + ] + }, + { + "cell_type": "markdown", + "id": "88ac961b", + "metadata": {}, + "source": [ + "Accelrate torch with GPU or MPS if available (credit: Claude)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f556b22", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "if torch.cuda.is_available():\n", + " DEVICE = torch.device('cuda')\n", + " print(f'GPU: {torch.cuda.get_device_name(0)}')\n", + "elif torch.backends.mps.is_available():\n", + " DEVICE = torch.device('mps')\n", + " print('Apple Silicon (MPS)')\n", + "else:\n", + " DEVICE = torch.device('cpu')\n", + " print('CPU')\n", + "\n", + "print(f'Running on: {DEVICE}')" + ] + }, + { + "cell_type": "markdown", + "id": "3ad97919", + "metadata": {}, + "source": [ + "# Task 2: Split Dataset 80:20 (Train / Test)\n", + "\n", + "Augmentation applied to training set only — test set kept clean for fair evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f68c1a25", + "metadata": {}, + "outputs": [], + "source": [ + "import math \n", + "from torchvision import datasets, transforms\n", + "from torch.utils.data import random_split, DataLoader\n", + "\n", + "\n", + "train_transform = transforms.Compose([\n", + " transforms.Resize((64, 64)), # Resize to 64x64 (even though all images are)\n", + " transforms.RandomHorizontalFlip(), # randomly mirror image\n", + " transforms.RandomRotation(20), # rotate up to 20 degrees\n", + " transforms.ColorJitter(brightness=0.3, contrast=0.3), # vary lighting\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) # Normalize tensors as in Pytorch tutorial\n", + "])\n", + "\n", + "test_transform = transforms.Compose([\n", + " transforms.Resize((64,64)), # Resize to 64x64 (even though all images are)\n", + " transforms.ToTensor(),\n", + " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) # Normalize tensors as in Pytorch tutorial\n", + "])\n", + "\n", + "train_full = datasets.ImageFolder(root=data_dir, transform=train_transform) #Load full dataset with train transform\n", + "test_full = datasets.ImageFolder(root=data_dir, transform=test_transform) # Load full dataset with test transform\n", + "\n", + "train_size = math.floor(len(train_full) * 0.8) #80% split for training\n", + "test_size = len(train_full) - train_size # Remaining 20% used for testing\n", + "\n", + "torch.manual_seed(42) # Fixes the RNG to the same starting point...42 is convention according to GeeksForGeeks\n", + "indices = torch.randperm(len(train_full)).tolist() #randomly shuffle the indices\n", + "\n", + "train_indices = indices[:train_size] # First 80% of indices\n", + "test_indices = indices[train_size:] # remaining 20% of indices\n", + "\n", + "train_dataset = torch.utils.data.Subset(train_full, train_indices) #Create final datasets\n", + "test_dataset = torch.utils.data.Subset(test_full, test_indices)\n", + "\n", + "print(f\"Train: {len(train_dataset)}, Test: {len(test_dataset)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2eede814", + "metadata": {}, + "source": [ + "Credit: Claude: load dataset into batches (64 is standard), and dedicate n threads to the process (min 1, preferrably 4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1539eaa", + "metadata": {}, + "outputs": [], + "source": [ + "NUM_WORKERS = min(4, os.cpu_count() or 1)\n", + "PIN_MEMORY = (DEVICE.type == 'cuda') # Pin memory if GPU available for CUDA\n", + "\n", + "print(NUM_WORKERS)\n", + "\n", + "train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True,\n", + " num_workers=NUM_WORKERS, pin_memory=PIN_MEMORY)\n", + "test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False,\n", + " num_workers=NUM_WORKERS, pin_memory=PIN_MEMORY)\n", + "classes = train_full.classes \n", + "print(classes)\n", + "print(len(classes))" + ] + }, + { + "cell_type": "markdown", + "id": "e7255041", + "metadata": {}, + "source": [ + "# Task 3: CNN Architecture\n", + "Model takes a batch of (3, 64, 64) images and outputs 8 class scores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1b7d9ca", + "metadata": {}, + "outputs": [], + "source": [ + "import torch.nn as nn\n", + "\n", + "class Net(nn.Module):\n", + " def __init__(self):\n", + " super(Net, self).__init__()\n", + "\n", + " self.features = nn.Sequential(\n", + " # go from 64x64 to 32x32\n", + "\n", + " # kernel size = 3x3 filter patch\n", + " #padding = 1, so 64x64 stays 64x64 after conv\n", + " nn.Conv2d(3, 32, kernel_size=3, padding=1), # 3 channel RRGB, 32 filters (recommended), and adding 1 pixel of zeros so keep output at same size\n", + "\n", + " nn.BatchNorm2d(32), # mirrors conv2d output\n", + " nn.ReLU(), #Activation fn\n", + " nn.MaxPool2d(2,2), # 2x2 window, stride = 2: so halved --> 32x32\n", + "\n", + " #Go from 32x32 --> 16x16 in the same manner\n", + "\n", + " nn.Conv2d(32, 64, kernel_size=3, padding=1), # Double filters --> more complex features detected\n", + " nn.BatchNorm2d(64),\n", + " nn.ReLU(),\n", + " nn.MaxPool2d(2,2), \n", + " \n", + " # Go from 16x 16 0 --> 8x8 in the same manner\n", + "\n", + " nn.Conv2d(64, 128, kernel_size=3, padding=1), # Double filters again --> even more complex rfeatures detected\n", + " nn.BatchNorm2d(128),\n", + " nn.ReLU(),\n", + " nn.MaxPool2d(2,2), \n", + "\n", + " )\n", + "\n", + " self.classifier = nn.Sequential(\n", + " nn.Flatten(),\n", + " nn.Linear (128 * 8 *8, 512), # flattened size of 8x8 * 128, 512 is arbitrary number of hidden neurons (recommended by GeeksForGeeks)....tunned to learn details without overfitting\n", + " nn.ReLU(),\n", + " nn.Dropout(0.5),#Randomly zero 50% of neurons --> prevent memorization and overfitting\n", + " nn.Linear(512, len(classes)) # one score per vehicle class\n", + " )\n", + "\n", + " def forward(self, x): \n", + " x = self.features(x) # extract spatial features via conv blocks\n", + " x = self.classifier(x) # flatten to 8 vehicle clases\n", + " return x\n", + "\n", + "model = Net().to(DEVICE)\n", + "device = DEVICE\n", + "\n", + "print(model)" + ] + }, + { + "cell_type": "markdown", + "id": "22e71032", + "metadata": {}, + "source": [ + "Loss fn and optimizer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54d11a04", + "metadata": {}, + "outputs": [], + "source": [ + "import torch.optim as optim\n", + "\n", + "criterion = nn.CrossEntropyLoss() # Applied softmax to convert scores --> probabilities --> penalizes model \n", + "\n", + "# Changed to adam optimizer (internal momentumn calculation)\n", + "optimizer = optim.Adam(model.parameters(), lr=0.001)" + ] + }, + { + "cell_type": "markdown", + "id": "572d80e3", + "metadata": {}, + "source": [ + "# Task 4: Train Model\n", + "\n", + "Track loss and accuracy per epoch — stored in lists for plotting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "374d0590", + "metadata": {}, + "outputs": [], + "source": [ + "train_losses, train_accs = [], [] #To store accs for visualization\n", + "\n", + "for epoch in range(30): # 30 epochs\n", + " running_loss = 0.0 # keep track of running loss\n", + " correct = 0\n", + " total = 0\n", + "\n", + " for i, data in enumerate(train_loader, 0):\n", + " inputs, labels = data\n", + " inputs, labels = inputs.to(device), labels.to(device)\n", + " optimizer.zero_grad() # clear prior gradients\n", + " outputs = model(inputs) # forward pass \n", + " loss = criterion(outputs, labels) # compare to GT\n", + " loss.backward() # Backprop...compute gradient of loss\n", + " optimizer.step() #Use adam optimizer to update weights using gradients\n", + "\n", + " running_loss += loss.item() #Extract scalar loss value \n", + " _, predicted = torch.max(outputs, 1) # Take index of highest score as predicted class\n", + " total += labels.size(0)\n", + " correct += (predicted == labels).sum().item() #update correct tally\n", + "\n", + " epoch_loss = running_loss / len(train_loader) #Compute avg loss\n", + " epoch_acc = 100 * correct / total #Avg acc accross epoch\n", + "\n", + "\n", + " #Adding epochs to list\n", + " train_losses.append(epoch_loss)\n", + " train_accs.append(epoch_acc) \n", + "\n", + " print(f'Epoch {epoch+1}: Loss={epoch_loss:.3f}, Accuracy={epoch_acc:.2f}%')\n", + "\n", + "print('Finished Training')" + ] + }, + { + "cell_type": "markdown", + "id": "26ab705a", + "metadata": {}, + "source": [ + "# Task 6 (Bonus): Plot Loss & Accuracy Curves\n", + "\n", + "Visualises how loss decreased and accuracy improved across 30 epochs [credit: Claude]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c71ee0ff", + "metadata": {}, + "outputs": [], + "source": [ + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))\n", + "\n", + "\n", + "# Plot 1: Training loss vs epoch \n", + "ax1.plot(train_losses, color='steelblue', linewidth=2)\n", + "ax1.set_title('Training Loss')\n", + "ax1.set_xlabel('Epoch')\n", + "ax1.set_ylabel('Loss')\n", + "ax1.grid(True, alpha=0.3)\n", + "\n", + "#Plot 2: training acc. vs epoch\n", + "ax2.plot(train_accs, color='darkorange', linewidth=2)\n", + "ax2.set_title('Training Accuracy')\n", + "ax2.set_xlabel('Epoch')\n", + "ax2.set_ylabel('Accuracy (%)')\n", + "ax2.grid(True, alpha=0.3)\n", + "\n", + "#Concat two plots, save, and show\n", + "plt.suptitle('Training Curves', fontsize=14, fontweight='bold')\n", + "plt.tight_layout()\n", + "os.makedirs('../results', exist_ok=True)\n", + "plt.savefig('../results/training_curves.png', dpi=150, bbox_inches='tight')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b3bfda75", + "metadata": {}, + "source": [ + "Save Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2bf2b9a2", + "metadata": {}, + "outputs": [], + "source": [ + "os.makedirs('../models', exist_ok=True)\n", + "PATH = '../models/final-classifier.pth'\n", + "torch.save(model.state_dict(), PATH)" + ] + }, + { + "cell_type": "markdown", + "id": "057d5d72", + "metadata": {}, + "source": [ + "# Task 5: Final Accuracy\n", + "Evaluate on both train and test sets with Dropout disabled (model.eval())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e54f566", + "metadata": {}, + "outputs": [], + "source": [ + "model.eval() # Switch to eval mode (disabled droupout --> higher acc) (credit: Claude)\n", + "train_correct, train_total = 0, 0\n", + "with torch.no_grad(): #Gradient computation not needed for inference\n", + " for images, labels in train_loader:\n", + " images, labels = images.to(device), labels.to(device)\n", + " outputs = model(images) #Fwd pass only\n", + " _, predicted = torch.max(outputs, 1) #highest score = predicted class\n", + " train_total += labels.size(0) #Count total \n", + " train_correct += (predicted == labels).sum().item() # Count correct\n", + "\n", + "# Test accuracy - repeat with test set\n", + "test_correct, test_total = 0, 0\n", + "with torch.no_grad():\n", + " for images, labels in test_loader:\n", + " images, labels = images.to(device), labels.to(device)\n", + " outputs = model(images)\n", + " _, predicted = torch.max(outputs, 1)\n", + " test_total += labels.size(0)\n", + " test_correct += (predicted == labels).sum().item()\n", + "\n", + "print(f'Final Train Accuracy : {100 * train_correct / train_total:.2f}%')\n", + "print(f'Final Test Accuracy : {100 * test_correct / test_total:.2f}%')" + ] + }, + { + "cell_type": "markdown", + "id": "60666242", + "metadata": {}, + "source": [ + "Credit Claude: Testing accuracy per class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cc7ed40", + "metadata": {}, + "outputs": [], + "source": [ + "correct_pred = {classname: 0 for classname in classes} # Correct predicitions per class\n", + "total_pred = {classname: 0 for classname in classes} # total images seen per class\n", + "\n", + "model.eval()\n", + "with torch.no_grad():\n", + " for data in test_loader:\n", + " images, labels = data\n", + " images, labels = images.to(device), labels.to(device)\n", + " outputs = model(images)\n", + " _, predictions = torch.max(outputs, 1) #predicted class index per class\n", + " for label, prediction in zip(labels, predictions):\n", + " if label == prediction:\n", + " correct_pred[classes[label]] += 1\n", + " total_pred[classes[label]] += 1\n", + "\n", + "for classname, correct_count in correct_pred.items():\n", + " accuracy = 100 * float(correct_count) / total_pred[classname]\n", + " print(f'Accuracy for class: {classname:10s} is {accuracy:.1f}%')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.11.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/01-tutorial-cnn.ipynb b/notebooks/experiments/01-tutorial-cnn.ipynb similarity index 97% rename from notebooks/01-tutorial-cnn.ipynb rename to notebooks/experiments/01-tutorial-cnn.ipynb index 3afc9c2..b1a71bb 100644 --- a/notebooks/01-tutorial-cnn.ipynb +++ b/notebooks/experiments/01-tutorial-cnn.ipynb @@ -42,7 +42,7 @@ "source": [ "import os\n", "\n", - "data_dir = '../data/raw/vehicle_classification'\n", + "data_dir = '../../data/raw/vehicle_classification'\n", "\n", "for class_name in os.listdir(data_dir):\n", " class_path = os.path.join(data_dir, class_name)\n", @@ -378,16 +378,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1: Loss=1.480, Accuracy=50.95%\n", - "Epoch 2: Loss=1.073, Accuracy=62.46%\n", - "Epoch 3: Loss=0.974, Accuracy=66.04%\n", - "Epoch 4: Loss=0.899, Accuracy=68.51%\n", - "Epoch 5: Loss=0.834, Accuracy=70.87%\n", - "Epoch 6: Loss=0.779, Accuracy=72.60%\n", - "Epoch 7: Loss=0.730, Accuracy=74.29%\n", - "Epoch 8: Loss=0.689, Accuracy=75.64%\n", - "Epoch 9: Loss=0.649, Accuracy=77.14%\n", - "Epoch 10: Loss=0.620, Accuracy=78.21%\n", + "Epoch 1: Loss=1.518, Accuracy=51.03%\n", + "Epoch 2: Loss=1.110, Accuracy=62.02%\n", + "Epoch 3: Loss=0.976, Accuracy=66.24%\n", + "Epoch 4: Loss=0.906, Accuracy=68.52%\n", + "Epoch 5: Loss=0.838, Accuracy=70.77%\n", + "Epoch 6: Loss=0.782, Accuracy=72.33%\n", + "Epoch 7: Loss=0.735, Accuracy=74.25%\n", + "Epoch 8: Loss=0.696, Accuracy=75.42%\n", + "Epoch 9: Loss=0.665, Accuracy=76.53%\n", + "Epoch 10: Loss=0.634, Accuracy=77.40%\n", "Finished Training\n" ] } @@ -430,7 +430,7 @@ "metadata": {}, "outputs": [], "source": [ - "PATH = '../models/tutorial-cnn.pth'\n", + "PATH = '../../models/tutorial-cnn.pth'\n", "torch.save(model.state_dict(), PATH)" ] }, @@ -452,7 +452,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Test Accuracy: 76.35%\n" + "Test Accuracy: 75.66%\n" ] } ], @@ -482,14 +482,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Accuracy for class: Bicycle is 45.2%\n", - "Accuracy for class: Bus is 71.3%\n", - "Accuracy for class: Car is 77.3%\n", - "Accuracy for class: Motorcycle is 81.2%\n", - "Accuracy for class: NonVehicles is 98.2%\n", - "Accuracy for class: Taxi is 37.0%\n", - "Accuracy for class: Truck is 35.6%\n", - "Accuracy for class: Van is 35.5%\n" + "Accuracy for class: Bicycle is 73.6%\n", + "Accuracy for class: Bus is 65.1%\n", + "Accuracy for class: Car is 86.2%\n", + "Accuracy for class: Motorcycle is 55.7%\n", + "Accuracy for class: NonVehicles is 99.3%\n", + "Accuracy for class: Taxi is 32.9%\n", + "Accuracy for class: Truck is 24.8%\n", + "Accuracy for class: Van is 22.9%\n" ] } ], diff --git a/notebooks/02-tutorial-cnn-modified-vals.ipynb b/notebooks/experiments/02-tutorial-cnn-modified-vals.ipynb similarity index 95% rename from notebooks/02-tutorial-cnn-modified-vals.ipynb rename to notebooks/experiments/02-tutorial-cnn-modified-vals.ipynb index e1319c0..53f26df 100644 --- a/notebooks/02-tutorial-cnn-modified-vals.ipynb +++ b/notebooks/experiments/02-tutorial-cnn-modified-vals.ipynb @@ -42,7 +42,7 @@ "source": [ "import os\n", "\n", - "data_dir = '../data/raw/vehicle_classification'\n", + "data_dir = '../../data/raw/vehicle_classification'\n", "\n", "for class_name in os.listdir(data_dir):\n", " class_path = os.path.join(data_dir, class_name)\n", @@ -378,36 +378,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1: Loss=1.732, Accuracy=43.80%\n", - "Epoch 2: Loss=1.403, Accuracy=54.06%\n", - "Epoch 3: Loss=1.137, Accuracy=60.66%\n", - "Epoch 4: Loss=1.003, Accuracy=64.95%\n", - "Epoch 5: Loss=0.917, Accuracy=67.92%\n", - "Epoch 6: Loss=0.845, Accuracy=70.22%\n", - "Epoch 7: Loss=0.796, Accuracy=72.06%\n", - "Epoch 8: Loss=0.741, Accuracy=73.88%\n", - "Epoch 9: Loss=0.711, Accuracy=75.05%\n", - "Epoch 10: Loss=0.670, Accuracy=76.52%\n", - "Epoch 11: Loss=0.636, Accuracy=77.59%\n", - "Epoch 12: Loss=0.611, Accuracy=78.49%\n", - "Epoch 13: Loss=0.580, Accuracy=79.42%\n", - "Epoch 14: Loss=0.546, Accuracy=80.87%\n", - "Epoch 15: Loss=0.519, Accuracy=81.77%\n", - "Epoch 16: Loss=0.502, Accuracy=82.36%\n", - "Epoch 17: Loss=0.474, Accuracy=83.28%\n", - "Epoch 18: Loss=0.445, Accuracy=84.34%\n", - "Epoch 19: Loss=0.424, Accuracy=85.12%\n", - "Epoch 20: Loss=0.392, Accuracy=86.07%\n", - "Epoch 21: Loss=0.366, Accuracy=86.99%\n", - "Epoch 22: Loss=0.330, Accuracy=88.30%\n", - "Epoch 23: Loss=0.304, Accuracy=89.51%\n", - "Epoch 24: Loss=0.267, Accuracy=90.67%\n", - "Epoch 25: Loss=0.227, Accuracy=92.35%\n", - "Epoch 26: Loss=0.222, Accuracy=92.38%\n", - "Epoch 27: Loss=0.176, Accuracy=94.24%\n", - "Epoch 28: Loss=0.156, Accuracy=94.71%\n", - "Epoch 29: Loss=0.143, Accuracy=94.92%\n", - "Epoch 30: Loss=0.124, Accuracy=95.87%\n", + "Epoch 1: Loss=1.693, Accuracy=43.96%\n", + "Epoch 2: Loss=1.285, Accuracy=56.70%\n", + "Epoch 3: Loss=1.070, Accuracy=62.78%\n", + "Epoch 4: Loss=0.964, Accuracy=66.34%\n", + "Epoch 5: Loss=0.891, Accuracy=68.85%\n", + "Epoch 6: Loss=0.826, Accuracy=70.92%\n", + "Epoch 7: Loss=0.764, Accuracy=72.87%\n", + "Epoch 8: Loss=0.734, Accuracy=74.25%\n", + "Epoch 9: Loss=0.690, Accuracy=75.77%\n", + "Epoch 10: Loss=0.636, Accuracy=77.49%\n", + "Epoch 11: Loss=0.616, Accuracy=78.49%\n", + "Epoch 12: Loss=0.581, Accuracy=79.67%\n", + "Epoch 13: Loss=0.558, Accuracy=80.53%\n", + "Epoch 14: Loss=0.526, Accuracy=81.44%\n", + "Epoch 15: Loss=0.498, Accuracy=82.36%\n", + "Epoch 16: Loss=0.458, Accuracy=83.76%\n", + "Epoch 17: Loss=0.436, Accuracy=84.62%\n", + "Epoch 18: Loss=0.398, Accuracy=86.02%\n", + "Epoch 19: Loss=0.378, Accuracy=86.44%\n", + "Epoch 20: Loss=0.327, Accuracy=88.69%\n", + "Epoch 21: Loss=0.310, Accuracy=89.20%\n", + "Epoch 22: Loss=0.265, Accuracy=90.82%\n", + "Epoch 23: Loss=0.237, Accuracy=91.60%\n", + "Epoch 24: Loss=0.212, Accuracy=92.75%\n", + "Epoch 25: Loss=0.193, Accuracy=93.34%\n", + "Epoch 26: Loss=0.161, Accuracy=94.54%\n", + "Epoch 27: Loss=0.119, Accuracy=96.25%\n", + "Epoch 28: Loss=0.095, Accuracy=96.94%\n", + "Epoch 29: Loss=0.099, Accuracy=96.86%\n", + "Epoch 30: Loss=0.078, Accuracy=97.66%\n", "Finished Training\n" ] } @@ -450,7 +450,7 @@ "metadata": {}, "outputs": [], "source": [ - "PATH = '../models/tutorial-cnn-modified.pth'\n", + "PATH = '../../models/tutorial-cnn-modified.pth'\n", "torch.save(model.state_dict(), PATH)" ] }, @@ -472,7 +472,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Test Accuracy: 78.54%\n" + "Test Accuracy: 76.90%\n" ] } ], @@ -502,14 +502,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Accuracy for class: Bicycle is 52.4%\n", - "Accuracy for class: Bus is 79.0%\n", - "Accuracy for class: Car is 74.9%\n", - "Accuracy for class: Motorcycle is 85.9%\n", - "Accuracy for class: NonVehicles is 98.7%\n", - "Accuracy for class: Taxi is 58.2%\n", - "Accuracy for class: Truck is 44.9%\n", - "Accuracy for class: Van is 29.8%\n" + "Accuracy for class: Bicycle is 61.4%\n", + "Accuracy for class: Bus is 75.6%\n", + "Accuracy for class: Car is 76.0%\n", + "Accuracy for class: Motorcycle is 76.0%\n", + "Accuracy for class: NonVehicles is 98.5%\n", + "Accuracy for class: Taxi is 45.6%\n", + "Accuracy for class: Truck is 38.0%\n", + "Accuracy for class: Van is 31.6%\n" ] } ], diff --git a/notebooks/03-adam-optimizer.ipynb b/notebooks/experiments/03-adam-optimizer.ipynb similarity index 96% rename from notebooks/03-adam-optimizer.ipynb rename to notebooks/experiments/03-adam-optimizer.ipynb index 20c84ca..a119cae 100644 --- a/notebooks/03-adam-optimizer.ipynb +++ b/notebooks/experiments/03-adam-optimizer.ipynb @@ -42,7 +42,7 @@ "source": [ "import os\n", "\n", - "data_dir = '../data/raw/vehicle_classification'\n", + "data_dir = '../../data/raw/vehicle_classification'\n", "\n", "for class_name in os.listdir(data_dir):\n", " class_path = os.path.join(data_dir, class_name)\n", @@ -378,36 +378,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1: Loss=1.337, Accuracy=56.65%\n", - "Epoch 2: Loss=0.965, Accuracy=66.88%\n", - "Epoch 3: Loss=0.870, Accuracy=69.80%\n", - "Epoch 4: Loss=0.789, Accuracy=72.31%\n", - "Epoch 5: Loss=0.746, Accuracy=73.78%\n", - "Epoch 6: Loss=0.692, Accuracy=75.63%\n", - "Epoch 7: Loss=0.659, Accuracy=76.80%\n", - "Epoch 8: Loss=0.624, Accuracy=77.99%\n", - "Epoch 9: Loss=0.608, Accuracy=78.58%\n", - "Epoch 10: Loss=0.566, Accuracy=80.22%\n", - "Epoch 11: Loss=0.540, Accuracy=80.91%\n", - "Epoch 12: Loss=0.517, Accuracy=81.71%\n", - "Epoch 13: Loss=0.495, Accuracy=82.49%\n", - "Epoch 14: Loss=0.460, Accuracy=84.00%\n", - "Epoch 15: Loss=0.436, Accuracy=84.83%\n", - "Epoch 16: Loss=0.405, Accuracy=85.72%\n", - "Epoch 17: Loss=0.380, Accuracy=86.55%\n", - "Epoch 18: Loss=0.348, Accuracy=87.83%\n", - "Epoch 19: Loss=0.322, Accuracy=88.65%\n", - "Epoch 20: Loss=0.294, Accuracy=89.97%\n", - "Epoch 21: Loss=0.265, Accuracy=90.71%\n", - "Epoch 22: Loss=0.233, Accuracy=91.95%\n", - "Epoch 23: Loss=0.210, Accuracy=92.85%\n", - "Epoch 24: Loss=0.184, Accuracy=93.87%\n", - "Epoch 25: Loss=0.163, Accuracy=94.46%\n", - "Epoch 26: Loss=0.137, Accuracy=95.61%\n", - "Epoch 27: Loss=0.118, Accuracy=96.28%\n", - "Epoch 28: Loss=0.096, Accuracy=97.20%\n", - "Epoch 29: Loss=0.080, Accuracy=97.70%\n", - "Epoch 30: Loss=0.066, Accuracy=98.04%\n", + "Epoch 1: Loss=1.301, Accuracy=56.17%\n", + "Epoch 2: Loss=1.014, Accuracy=64.94%\n", + "Epoch 3: Loss=0.933, Accuracy=67.15%\n", + "Epoch 4: Loss=0.858, Accuracy=69.63%\n", + "Epoch 5: Loss=0.783, Accuracy=72.12%\n", + "Epoch 6: Loss=0.749, Accuracy=73.16%\n", + "Epoch 7: Loss=0.695, Accuracy=75.33%\n", + "Epoch 8: Loss=0.662, Accuracy=76.60%\n", + "Epoch 9: Loss=0.634, Accuracy=77.59%\n", + "Epoch 10: Loss=0.618, Accuracy=78.29%\n", + "Epoch 11: Loss=0.586, Accuracy=79.37%\n", + "Epoch 12: Loss=0.554, Accuracy=80.26%\n", + "Epoch 13: Loss=0.538, Accuracy=81.11%\n", + "Epoch 14: Loss=0.507, Accuracy=82.21%\n", + "Epoch 15: Loss=0.489, Accuracy=82.95%\n", + "Epoch 16: Loss=0.462, Accuracy=83.55%\n", + "Epoch 17: Loss=0.435, Accuracy=84.84%\n", + "Epoch 18: Loss=0.412, Accuracy=85.40%\n", + "Epoch 19: Loss=0.390, Accuracy=86.27%\n", + "Epoch 20: Loss=0.364, Accuracy=87.19%\n", + "Epoch 21: Loss=0.348, Accuracy=87.81%\n", + "Epoch 22: Loss=0.305, Accuracy=89.40%\n", + "Epoch 23: Loss=0.279, Accuracy=90.41%\n", + "Epoch 24: Loss=0.268, Accuracy=90.56%\n", + "Epoch 25: Loss=0.227, Accuracy=92.43%\n", + "Epoch 26: Loss=0.209, Accuracy=92.78%\n", + "Epoch 27: Loss=0.181, Accuracy=94.17%\n", + "Epoch 28: Loss=0.173, Accuracy=94.14%\n", + "Epoch 29: Loss=0.140, Accuracy=95.50%\n", + "Epoch 30: Loss=0.126, Accuracy=95.96%\n", "Finished Training\n" ] } @@ -450,7 +450,7 @@ "metadata": {}, "outputs": [], "source": [ - "PATH = '../models/adam-optimized.pth'\n", + "PATH = '../../models/adam-optimized.pth'\n", "torch.save(model.state_dict(), PATH)" ] }, @@ -472,7 +472,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Test Accuracy: 75.68%\n" + "Test Accuracy: 76.50%\n" ] } ], @@ -502,14 +502,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Accuracy for class: Bicycle is 68.8%\n", - "Accuracy for class: Bus is 76.1%\n", - "Accuracy for class: Car is 66.5%\n", - "Accuracy for class: Motorcycle is 78.4%\n", - "Accuracy for class: NonVehicles is 98.5%\n", - "Accuracy for class: Taxi is 42.6%\n", - "Accuracy for class: Truck is 41.2%\n", - "Accuracy for class: Van is 39.9%\n" + "Accuracy for class: Bicycle is 66.5%\n", + "Accuracy for class: Bus is 68.0%\n", + "Accuracy for class: Car is 73.8%\n", + "Accuracy for class: Motorcycle is 77.1%\n", + "Accuracy for class: NonVehicles is 99.0%\n", + "Accuracy for class: Taxi is 41.3%\n", + "Accuracy for class: Truck is 43.8%\n", + "Accuracy for class: Van is 23.6%\n" ] } ], diff --git a/notebooks/04-batch-norm-dropout.ipynb b/notebooks/experiments/04-batch-norm-dropout.ipynb similarity index 96% rename from notebooks/04-batch-norm-dropout.ipynb rename to notebooks/experiments/04-batch-norm-dropout.ipynb index 5c2ceb2..1cd8e72 100644 --- a/notebooks/04-batch-norm-dropout.ipynb +++ b/notebooks/experiments/04-batch-norm-dropout.ipynb @@ -42,7 +42,7 @@ "source": [ "import os\n", "\n", - "data_dir = '../data/raw/vehicle_classification'\n", + "data_dir = '../../data/raw/vehicle_classification'\n", "\n", "for class_name in os.listdir(data_dir):\n", " class_path = os.path.join(data_dir, class_name)\n", @@ -418,36 +418,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1: Loss=1.455, Accuracy=59.60%\n", - "Epoch 2: Loss=0.872, Accuracy=69.47%\n", - "Epoch 3: Loss=0.748, Accuracy=73.57%\n", - "Epoch 4: Loss=0.669, Accuracy=76.44%\n", - "Epoch 5: Loss=0.613, Accuracy=78.60%\n", - "Epoch 6: Loss=0.575, Accuracy=79.73%\n", - "Epoch 7: Loss=0.539, Accuracy=80.91%\n", - "Epoch 8: Loss=0.507, Accuracy=82.19%\n", - "Epoch 9: Loss=0.483, Accuracy=82.79%\n", - "Epoch 10: Loss=0.461, Accuracy=83.69%\n", - "Epoch 11: Loss=0.439, Accuracy=84.35%\n", - "Epoch 12: Loss=0.409, Accuracy=85.46%\n", - "Epoch 13: Loss=0.396, Accuracy=85.98%\n", - "Epoch 14: Loss=0.369, Accuracy=86.39%\n", - "Epoch 15: Loss=0.356, Accuracy=87.00%\n", - "Epoch 16: Loss=0.351, Accuracy=87.15%\n", - "Epoch 17: Loss=0.330, Accuracy=88.06%\n", - "Epoch 18: Loss=0.307, Accuracy=88.95%\n", - "Epoch 19: Loss=0.286, Accuracy=89.57%\n", - "Epoch 20: Loss=0.253, Accuracy=90.44%\n", - "Epoch 21: Loss=0.244, Accuracy=91.00%\n", - "Epoch 22: Loss=0.233, Accuracy=91.22%\n", - "Epoch 23: Loss=0.225, Accuracy=91.56%\n", - "Epoch 24: Loss=0.201, Accuracy=92.63%\n", - "Epoch 25: Loss=0.198, Accuracy=92.55%\n", - "Epoch 26: Loss=0.178, Accuracy=93.46%\n", - "Epoch 27: Loss=0.169, Accuracy=93.60%\n", - "Epoch 28: Loss=0.149, Accuracy=94.44%\n", - "Epoch 29: Loss=0.153, Accuracy=94.34%\n", - "Epoch 30: Loss=0.135, Accuracy=95.05%\n", + "Epoch 1: Loss=1.427, Accuracy=61.06%\n", + "Epoch 2: Loss=0.814, Accuracy=71.39%\n", + "Epoch 3: Loss=0.709, Accuracy=74.82%\n", + "Epoch 4: Loss=0.645, Accuracy=77.18%\n", + "Epoch 5: Loss=0.597, Accuracy=79.08%\n", + "Epoch 6: Loss=0.564, Accuracy=80.10%\n", + "Epoch 7: Loss=0.529, Accuracy=81.31%\n", + "Epoch 8: Loss=0.500, Accuracy=82.12%\n", + "Epoch 9: Loss=0.468, Accuracy=83.23%\n", + "Epoch 10: Loss=0.451, Accuracy=84.01%\n", + "Epoch 11: Loss=0.430, Accuracy=84.79%\n", + "Epoch 12: Loss=0.423, Accuracy=84.93%\n", + "Epoch 13: Loss=0.385, Accuracy=86.47%\n", + "Epoch 14: Loss=0.368, Accuracy=86.48%\n", + "Epoch 15: Loss=0.346, Accuracy=87.31%\n", + "Epoch 16: Loss=0.341, Accuracy=87.57%\n", + "Epoch 17: Loss=0.310, Accuracy=89.02%\n", + "Epoch 18: Loss=0.303, Accuracy=89.11%\n", + "Epoch 19: Loss=0.274, Accuracy=89.85%\n", + "Epoch 20: Loss=0.258, Accuracy=90.29%\n", + "Epoch 21: Loss=0.246, Accuracy=90.69%\n", + "Epoch 22: Loss=0.239, Accuracy=91.06%\n", + "Epoch 23: Loss=0.227, Accuracy=91.57%\n", + "Epoch 24: Loss=0.207, Accuracy=92.31%\n", + "Epoch 25: Loss=0.191, Accuracy=92.94%\n", + "Epoch 26: Loss=0.178, Accuracy=93.24%\n", + "Epoch 27: Loss=0.163, Accuracy=93.96%\n", + "Epoch 28: Loss=0.147, Accuracy=94.67%\n", + "Epoch 29: Loss=0.150, Accuracy=94.36%\n", + "Epoch 30: Loss=0.130, Accuracy=95.14%\n", "Finished Training\n" ] } @@ -490,7 +490,7 @@ "metadata": {}, "outputs": [], "source": [ - "PATH = '../models/batch-norm-dropout.pth'\n", + "PATH = '../../models/batch-norm-dropout.pth'\n", "torch.save(model.state_dict(), PATH)" ] }, @@ -512,7 +512,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Test Accuracy: 83.30%\n" + "Test Accuracy: 82.26%\n" ] } ], @@ -542,14 +542,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Accuracy for class: Bicycle is 72.2%\n", - "Accuracy for class: Bus is 83.6%\n", - "Accuracy for class: Car is 82.9%\n", - "Accuracy for class: Motorcycle is 84.2%\n", - "Accuracy for class: NonVehicles is 99.6%\n", - "Accuracy for class: Taxi is 52.5%\n", - "Accuracy for class: Truck is 48.5%\n", - "Accuracy for class: Van is 45.0%\n" + "Accuracy for class: Bicycle is 72.7%\n", + "Accuracy for class: Bus is 81.5%\n", + "Accuracy for class: Car is 78.2%\n", + "Accuracy for class: Motorcycle is 82.6%\n", + "Accuracy for class: NonVehicles is 100.0%\n", + "Accuracy for class: Taxi is 53.2%\n", + "Accuracy for class: Truck is 52.9%\n", + "Accuracy for class: Van is 41.8%\n" ] } ], @@ -569,7 +569,7 @@ " total_pred[dataset.classes[label]] += 1\n", "\n", "for classname, correct_count in correct_pred.items():\n", - " accuracy = 100 * float(correct_count) / total_pred[classname]\n", + " accuracy = 100 * float(correct_count) / total_pred[classname] \n", " print(f'Accuracy for class: {classname:10s} is {accuracy:.1f}%')" ] } diff --git a/notebooks/05-data-augmentation.ipynb b/notebooks/experiments/05-data-augmentation.ipynb similarity index 95% rename from notebooks/05-data-augmentation.ipynb rename to notebooks/experiments/05-data-augmentation.ipynb index 383ebee..c1ff5e6 100644 --- a/notebooks/05-data-augmentation.ipynb +++ b/notebooks/experiments/05-data-augmentation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 12, "id": "7a37220a", "metadata": {}, "outputs": [ @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 13, "id": "d318d1f0", "metadata": {}, "outputs": [ @@ -42,7 +42,7 @@ "source": [ "import os\n", "\n", - "data_dir = '../data/raw/vehicle_classification'\n", + "data_dir = '../../data/raw/vehicle_classification'\n", "\n", "for class_name in os.listdir(data_dir):\n", " class_path = os.path.join(data_dir, class_name)\n", @@ -61,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "id": "5604ace3", "metadata": {}, "outputs": [ @@ -110,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 15, "id": "3cedd586", "metadata": {}, "outputs": [ @@ -150,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 16, "id": "8f556b22", "metadata": {}, "outputs": [ @@ -184,7 +184,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 17, "id": "37793c77", "metadata": {}, "outputs": [ @@ -222,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 18, "id": "f68c1a25", "metadata": {}, "outputs": [ @@ -279,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 19, "id": "e1539eaa", "metadata": {}, "outputs": [ @@ -311,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 20, "id": "d1b7d9ca", "metadata": {}, "outputs": [ @@ -409,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 21, "id": "54d11a04", "metadata": {}, "outputs": [], @@ -432,7 +432,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 22, "id": "374d0590", "metadata": {}, "outputs": [ @@ -440,36 +440,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "Epoch 1: Loss=1.405, Accuracy=59.91%\n", - "Epoch 2: Loss=0.880, Accuracy=69.16%\n", - "Epoch 3: Loss=0.770, Accuracy=72.99%\n", - "Epoch 4: Loss=0.714, Accuracy=75.05%\n", - "Epoch 5: Loss=0.660, Accuracy=76.92%\n", - "Epoch 6: Loss=0.629, Accuracy=78.41%\n", - "Epoch 7: Loss=0.596, Accuracy=79.03%\n", - "Epoch 8: Loss=0.588, Accuracy=79.40%\n", - "Epoch 9: Loss=0.556, Accuracy=80.52%\n", - "Epoch 10: Loss=0.543, Accuracy=81.06%\n", - "Epoch 11: Loss=0.520, Accuracy=81.85%\n", - "Epoch 12: Loss=0.513, Accuracy=81.94%\n", - "Epoch 13: Loss=0.501, Accuracy=82.56%\n", - "Epoch 14: Loss=0.485, Accuracy=82.90%\n", - "Epoch 15: Loss=0.481, Accuracy=82.92%\n", - "Epoch 16: Loss=0.469, Accuracy=83.50%\n", - "Epoch 17: Loss=0.459, Accuracy=84.02%\n", - "Epoch 18: Loss=0.453, Accuracy=83.94%\n", - "Epoch 19: Loss=0.445, Accuracy=84.24%\n", - "Epoch 20: Loss=0.434, Accuracy=84.77%\n", - "Epoch 21: Loss=0.427, Accuracy=84.97%\n", - "Epoch 22: Loss=0.412, Accuracy=85.20%\n", - "Epoch 23: Loss=0.411, Accuracy=85.15%\n", - "Epoch 24: Loss=0.402, Accuracy=85.52%\n", - "Epoch 25: Loss=0.403, Accuracy=85.32%\n", - "Epoch 26: Loss=0.393, Accuracy=85.93%\n", - "Epoch 27: Loss=0.386, Accuracy=86.07%\n", - "Epoch 28: Loss=0.380, Accuracy=86.28%\n", - "Epoch 29: Loss=0.372, Accuracy=86.65%\n", - "Epoch 30: Loss=0.362, Accuracy=87.02%\n", + "Epoch 1: Loss=1.515, Accuracy=58.09%\n", + "Epoch 2: Loss=0.901, Accuracy=68.38%\n", + "Epoch 3: Loss=0.786, Accuracy=72.40%\n", + "Epoch 4: Loss=0.723, Accuracy=74.59%\n", + "Epoch 5: Loss=0.665, Accuracy=76.57%\n", + "Epoch 6: Loss=0.634, Accuracy=77.73%\n", + "Epoch 7: Loss=0.602, Accuracy=78.87%\n", + "Epoch 8: Loss=0.593, Accuracy=79.30%\n", + "Epoch 9: Loss=0.580, Accuracy=79.91%\n", + "Epoch 10: Loss=0.548, Accuracy=80.70%\n", + "Epoch 11: Loss=0.535, Accuracy=80.95%\n", + "Epoch 12: Loss=0.517, Accuracy=81.76%\n", + "Epoch 13: Loss=0.521, Accuracy=81.70%\n", + "Epoch 14: Loss=0.499, Accuracy=82.46%\n", + "Epoch 15: Loss=0.488, Accuracy=82.87%\n", + "Epoch 16: Loss=0.483, Accuracy=83.06%\n", + "Epoch 17: Loss=0.480, Accuracy=82.94%\n", + "Epoch 18: Loss=0.465, Accuracy=83.34%\n", + "Epoch 19: Loss=0.464, Accuracy=83.39%\n", + "Epoch 20: Loss=0.443, Accuracy=84.22%\n", + "Epoch 21: Loss=0.445, Accuracy=84.40%\n", + "Epoch 22: Loss=0.436, Accuracy=84.60%\n", + "Epoch 23: Loss=0.417, Accuracy=85.12%\n", + "Epoch 24: Loss=0.412, Accuracy=85.36%\n", + "Epoch 25: Loss=0.410, Accuracy=85.11%\n", + "Epoch 26: Loss=0.405, Accuracy=85.42%\n", + "Epoch 27: Loss=0.402, Accuracy=85.44%\n", + "Epoch 28: Loss=0.388, Accuracy=85.96%\n", + "Epoch 29: Loss=0.383, Accuracy=86.22%\n", + "Epoch 30: Loss=0.377, Accuracy=86.23%\n", "Finished Training\n" ] } @@ -507,12 +507,12 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 23, "id": "2bf2b9a2", "metadata": {}, "outputs": [], "source": [ - "PATH = '../models/data-augmented.pth'\n", + "PATH = '../../models/data-augmented.pth'\n", "torch.save(model.state_dict(), PATH)" ] }, @@ -526,7 +526,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 24, "id": "bc158602", "metadata": {}, "outputs": [ @@ -534,7 +534,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Test Accuracy: 82.71%\n" + "Test Accuracy: 84.17%\n" ] } ], @@ -556,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 25, "id": "8cc7ed40", "metadata": {}, "outputs": [ @@ -564,14 +564,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Accuracy for class: Bicycle is 68.8%\n", - "Accuracy for class: Bus is 83.2%\n", - "Accuracy for class: Car is 78.8%\n", - "Accuracy for class: Motorcycle is 90.5%\n", - "Accuracy for class: NonVehicles is 99.7%\n", - "Accuracy for class: Taxi is 49.4%\n", - "Accuracy for class: Truck is 59.2%\n", - "Accuracy for class: Van is 40.1%\n" + "Accuracy for class: Bicycle is 77.4%\n", + "Accuracy for class: Bus is 85.3%\n", + "Accuracy for class: Car is 86.3%\n", + "Accuracy for class: Motorcycle is 81.8%\n", + "Accuracy for class: NonVehicles is 99.8%\n", + "Accuracy for class: Taxi is 60.8%\n", + "Accuracy for class: Truck is 52.8%\n", + "Accuracy for class: Van is 41.3%\n" ] } ], diff --git a/results/training_curves.png b/results/training_curves.png new file mode 100644 index 0000000000000000000000000000000000000000..c004cdb65450bb5dd1da6e5879afbe2542882392 GIT binary patch literal 61518 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%0D%ciXCv|wKnLfj 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 JJ!x>|e*toL19$)c literal 0 HcmV?d00001 diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000