Files
vehicle-classification/notebooks/01-tutorial-cnn.ipynb
2026-03-18 22:59:28 -05:00

539 lines
48 KiB
Plaintext

{
"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": [
"<Figure size 640x480 with 1 Axes>"
]
},
"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"
]
},
{
"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_size = math.floor(len(dataset) * 0.8)\n",
"test_size = len(dataset) - train_size\n",
"\n",
"train_dataset, test_dataset = random_split(dataset, [train_size, test_size])\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": [
"8\n"
]
}
],
"source": [
"train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)\n",
"test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=4)\n",
"\n",
"classes = ('bicycle', 'bus', 'car', 'motorcycle',\n",
" 'nonvehicles', 'taxi', 'truck', 'van')\n",
"\n",
"print(len(classes))"
]
},
{
"cell_type": "markdown",
"id": "acebe98e",
"metadata": {},
"source": [
"Tutorial CNN architecture implmenetation"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "d1b7d9ca",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Net(\n",
" (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
" (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
" (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
" (fc1): Linear(in_features=2704, out_features=120, bias=True)\n",
" (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
" (fc3): Linear(in_features=84, out_features=8, bias=True)\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().__init__()\n",
" self.conv1 = nn.Conv2d(3, 6, 5) # 3 input channels for RGB, 6 filters [arbitrailly chosen ffrom PyTorch], 5x5 Kernel Size\n",
" self.pool = nn.MaxPool2d(2, 2) # Halves spatial dimensions\n",
" self.conv2 = nn.Conv2d(6, 16, 5) # 6 matches output of first conv layer, 16 arbitrary filter (more than earlier), 5x5 kernel\n",
" self.fc1 = nn.Linear(16 * 13 * 13, 120) # 16 channels from conv layer 2; 13 *13 bc shape after 2 layers and 2 pools, 120 is arbitrary output size from tutorial\n",
" self.fc2 = nn.Linear(120, 84) # 120 output from fc1 --> arbitrary lowering to 84\n",
" self.fc3 = nn.Linear(84, len(classes)) # Bring down to number of classes \n",
"\n",
" def forward(self, x):\n",
" x = self.pool(F.relu(self.conv1(x))) #Conv layer 1 --> relu --> pool \n",
" x = self.pool(F.relu(self.conv2(x))) #Conv layer 1 --> relu --> pool \n",
" x = torch.flatten(x, 1) # Flattedn to 1d\n",
" x = F.relu(self.fc1(x)) #fully connected layer shriking 1\n",
" x = F.relu(self.fc2(x)) # Shrink again\n",
" x = self.fc3(x) # Shrink to number of classes\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",
"# SGD is used on tutorial, but Adam optimizer is more popular as well\n",
"optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # Defualt learning rate from tutorial: compromise btwn overshoot and training infinitely long, 0.9 --> 90% of update comes from prev. direction\n"
]
},
{
"cell_type": "markdown",
"id": "572d80e3",
"metadata": {},
"source": [
"Tutorial training cell adapted to use 10 epochs "
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "374d0590",
"metadata": {},
"outputs": [
{
"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",
"Finished Training\n"
]
}
],
"source": [
"for epoch in range(10): # 10 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/tutorial-cnn.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: 76.35%\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 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"
]
}
],
"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
}