Deep Learning Models: A collection of various deep learning architectures, models, & their implementations (PyTorch or TensorFlow) in Jupyter Notebooks.
- Author: Chizoba Obasi
- Github Repo: https://github.com/chizkidd/deep-learning-models
In [1]:
!pip install watermark
Requirement already satisfied: watermark in /usr/local/lib/python3.12/dist-packages (2.6.0) Requirement already satisfied: ipython>=6.0 in /usr/local/lib/python3.12/dist-packages (from watermark) (7.34.0) Requirement already satisfied: importlib-metadata>=1.4 in /usr/local/lib/python3.12/dist-packages (from watermark) (8.7.1) Requirement already satisfied: setuptools in /usr/local/lib/python3.12/dist-packages (from watermark) (75.2.0) Requirement already satisfied: zipp>=3.20 in /usr/local/lib/python3.12/dist-packages (from importlib-metadata>=1.4->watermark) (3.23.0) Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (0.19.2) Requirement already satisfied: decorator in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (4.4.2) Requirement already satisfied: pickleshare in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (0.7.5) Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (5.7.1) Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (3.0.52) Requirement already satisfied: pygments in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (2.19.2) Requirement already satisfied: backcall in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (0.2.0) Requirement already satisfied: matplotlib-inline in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (0.2.1) Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.12/dist-packages (from ipython>=6.0->watermark) (4.9.0) Requirement already satisfied: parso<0.9.0,>=0.8.4 in /usr/local/lib/python3.12/dist-packages (from jedi>=0.16->ipython>=6.0->watermark) (0.8.5) Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.12/dist-packages (from pexpect>4.3->ipython>=6.0->watermark) (0.7.0) Requirement already satisfied: wcwidth in /usr/local/lib/python3.12/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=6.0->watermark) (0.2.14)
In [2]:
!pip install torchinfo
Requirement already satisfied: torchinfo in /usr/local/lib/python3.12/dist-packages (1.8.0)
In [3]:
%load_ext watermark
# %reload_ext watermark
%watermark -a 'Chizoba Obasi' -v -p torch
Author: Chizoba Obasi Python implementation: CPython Python version : 3.12.12 IPython version : 7.34.0 torch: 2.9.0+cu126
ShuffleNetV2 Classifier on CIFAR-10¶
Network Architecture:
The network in this notebook is an implementation of the ShuffleNetV2 [1] architecture on the CIFAR-10 dataset to train an image classifier.
ShuffleNetV2: Unit Block¶
REFERENCES:
- Ma, N., Zhang, X., Zheng, H. T., & Sun, J. (2018). ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design. In Proceedings of the European Conference on Computer Vision (ECCV) (pp. 116-131).
- Johnson, J. (2022, February 2). Lecture 11: CNN Architectures II [PowerPoint Slides]. University of Michigan EECS 498/598: Deep Learning for Computer Vision. pp 54-59. https://web.eecs.umich.edu/~justincj/slides/eecs498/WI2022/598_WI2022_lecture11.pdf
In [4]:
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from sklearn.model_selection import train_test_split
from torchinfo import summary
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd
import numpy as np
import math
import time
import os
import random
if torch.cuda.is_available():
torch.backends.cudnn.deterministic = True
In [5]:
def set_all_seeds(seed):
os.environ["PL_GLOBAL_SEED"] = str(seed)
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
def set_deterministic():
if torch.cuda.is_available():
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
torch.set_deterministic(True)
In [6]:
# Check if GPU is available and set the device
if torch.cuda.is_available():
num_gpus = torch.cuda.device_count()
print("Num GPUs Available: ", num_gpus)
for i in range(num_gpus):
gpu_name = torch.cuda.get_device_name(i)
print(f"GPU {i}: {gpu_name}")
else:
print("No GPU available.")
Num GPUs Available: 1 GPU 0: Tesla T4
In [7]:
##########################
### SETTINGS
##########################
# Hyperparameters
RANDOM_SEED = 123
BATCH_SIZE = 128 #256
LEARNING_RATE = 0.001
NUM_EPOCHS = 30
INPUT_SIZE = 32 # CIFAR-10 is 32x32
NUM_CLASSES = 10
# Set device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
cuda
In [8]:
set_all_seeds(RANDOM_SEED)
In [9]:
# Mean and Std Dev for CIFAR-10 (3 channels)
CIFAR_MEAN = (0.4914, 0.4822, 0.4465)
CIFAR_STD = (0.2471, 0.2435, 0.2616)
# Data Augmentation (Crucial for VGG-16 on CIFAR-10)
train_transforms = transforms.Compose([
transforms.RandomCrop(32, padding=4), # Crop after padding
transforms.RandomHorizontalFlip(), # Flip
transforms.ToTensor(),
transforms.Normalize(CIFAR_MEAN, CIFAR_STD) # Mean Subtraction
])
# Test/Validation Transforms (No Augmentation)
test_transforms = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(CIFAR_MEAN, CIFAR_STD)
])
# Load Datasets
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transforms)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transforms)
# Split training set into training and validation sets
train_size = int(0.9 * len(train_dataset))
val_size = len(train_dataset) - train_size
train_subset, val_subset = torch.utils.data.random_split(train_dataset, [train_size, val_size])
# Create DataLoaders
train_loader = DataLoader(train_subset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)
val_loader = DataLoader(val_subset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)
In [10]:
# Check that shuffling works properly
# i.e., label indices should be in random order.
# Also, the label order should be different in the second
# epoch.
for images, labels in train_loader:
pass
print(labels[:10])
for images, labels in train_loader:
pass
print(labels[:10])
tensor([1, 5, 5, 4, 2, 8, 5, 2, 4, 2]) tensor([9, 2, 4, 3, 7, 5, 8, 6, 2, 8])
In [11]:
# Check that validation set and test sets are diverse
# i.e., that they contain all classes
for images, labels in val_loader:
pass
print(labels[:10])
for images, labels in test_loader:
pass
print(labels[:10])
tensor([1, 6, 5, 9, 9, 0, 6, 6]) tensor([7, 5, 8, 0, 8, 2, 7, 0, 3, 5])
In [12]:
def channel_shuffle(x, groups):
batchsize, num_channels, height, width = x.data.size()
channels_per_group = num_channels // groups
x = x.view(batchsize, groups, channels_per_group, height, width)
x = torch.transpose(x, 1, 2).contiguous()
x = x.view(batchsize, -1, height, width)
return x
class ShuffleUnit(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(ShuffleUnit, self).__init__()
self.stride = stride
mid_channels = out_channels // 2
if stride > 1:
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, in_channels, 3, stride, 1, groups=in_channels, bias=False),
nn.BatchNorm2d(in_channels),
nn.Conv2d(in_channels, mid_channels, 1, 1, 0, bias=False),
nn.BatchNorm2d(mid_channels),
nn.ReLU(inplace=True)
)
else:
self.branch1 = nn.Sequential()
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels if stride > 1 else mid_channels, mid_channels, 1, 1, 0, bias=False),
nn.BatchNorm2d(mid_channels),
nn.ReLU(inplace=True),
nn.Conv2d(mid_channels, mid_channels, 3, stride, 1, groups=mid_channels, bias=False),
nn.BatchNorm2d(mid_channels),
nn.Conv2d(mid_channels, mid_channels, 1, 1, 0, bias=False),
nn.BatchNorm2d(mid_channels),
nn.ReLU(inplace=True)
)
def forward(self, x):
if self.stride > 1:
x = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
else:
x1, x2 = x.chunk(2, dim=1)
x = torch.cat((x1, self.branch2(x2)), dim=1)
return channel_shuffle(x, 2)
class ShuffleNetV2(nn.Module):
def __init__(self, net_size=1.0):
super(ShuffleNetV2, self).__init__()
# Config for ShuffleNetV2 1.0x
out_channels = [24, 116, 232, 464, 1024]
# Initial layer: modified for CIFAR (3x3 conv instead of 7x7)
self.conv1 = nn.Sequential(
nn.Conv2d(3, out_channels[0], 3, 1, 1, bias=False),
nn.BatchNorm2d(out_channels[0]),
nn.ReLU(inplace=True)
)
self.stage2 = self._make_stage(out_channels[0], out_channels[1], 4, 2)
self.stage3 = self._make_stage(out_channels[1], out_channels[2], 8, 2)
self.stage4 = self._make_stage(out_channels[2], out_channels[3], 4, 2)
self.conv5 = nn.Sequential(
nn.Conv2d(out_channels[3], out_channels[4], 1, 1, 0, bias=False),
nn.BatchNorm2d(out_channels[4]),
nn.ReLU(inplace=True)
)
self.fc = nn.Linear(out_channels[4], 10)
def _make_stage(self, in_channels, out_channels, repeats, stride):
layers = [ShuffleUnit(in_channels, out_channels, stride)]
for _ in range(repeats - 1):
layers.append(ShuffleUnit(out_channels, out_channels, 1))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = self.conv5(x)
x = F.adaptive_avg_pool2d(x, (1, 1))
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
model = ShuffleNetV2().to(device)
In [13]:
model = ShuffleNetV2().to(device)
In [14]:
# Model parameter details
summary(model,
input_size=(BATCH_SIZE, 3, INPUT_SIZE, INPUT_SIZE),
# verbose=1,
col_names=["input_size", "output_size", "num_params", "kernel_size", "mult_adds"]
)
Out[14]:
===================================================================================================================================================================== Layer (type:depth-idx) Input Shape Output Shape Param # Kernel Shape Mult-Adds ===================================================================================================================================================================== ShuffleNetV2 [128, 3, 32, 32] [128, 10] -- -- -- ├─Sequential: 1-1 [128, 3, 32, 32] [128, 24, 32, 32] -- -- -- │ └─Conv2d: 2-1 [128, 3, 32, 32] [128, 24, 32, 32] 648 [3, 3] 84,934,656 │ └─BatchNorm2d: 2-2 [128, 24, 32, 32] [128, 24, 32, 32] 48 -- 6,144 │ └─ReLU: 2-3 [128, 24, 32, 32] [128, 24, 32, 32] -- -- -- ├─Sequential: 1-2 [128, 24, 32, 32] [128, 116, 16, 16] -- -- -- │ └─ShuffleUnit: 2-4 [128, 24, 32, 32] [128, 116, 16, 16] -- -- -- │ │ └─Sequential: 3-1 [128, 24, 32, 32] [128, 58, 16, 16] 1,772 -- 52,711,936 │ │ └─Sequential: 3-2 [128, 24, 32, 32] [128, 58, 16, 16] 5,626 -- 309,833,216 │ └─ShuffleUnit: 2-5 [128, 116, 16, 16] [128, 116, 16, 16] -- -- -- │ │ └─Sequential: 3-3 [128, 58, 16, 16] [128, 58, 16, 16] 7,598 -- 237,612,544 │ └─ShuffleUnit: 2-6 [128, 116, 16, 16] [128, 116, 16, 16] -- -- -- │ │ └─Sequential: 3-4 [128, 58, 16, 16] [128, 58, 16, 16] 7,598 -- 237,612,544 │ └─ShuffleUnit: 2-7 [128, 116, 16, 16] [128, 116, 16, 16] -- -- -- │ │ └─Sequential: 3-5 [128, 58, 16, 16] [128, 58, 16, 16] 7,598 -- 237,612,544 ├─Sequential: 1-3 [128, 116, 16, 16] [128, 232, 8, 8] -- -- -- │ └─ShuffleUnit: 2-8 [128, 116, 16, 16] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-6 [128, 116, 16, 16] [128, 116, 8, 8] 14,964 -- 118,843,392 │ │ └─Sequential: 3-7 [128, 116, 16, 16] [128, 116, 8, 8] 28,652 -- 559,799,296 │ └─ShuffleUnit: 2-9 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-8 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-10 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-9 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-11 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-10 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-12 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-11 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-13 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-12 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-14 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-13 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 │ └─ShuffleUnit: 2-15 [128, 232, 8, 8] [128, 232, 8, 8] -- -- -- │ │ └─Sequential: 3-14 [128, 116, 8, 8] [128, 116, 8, 8] 28,652 -- 229,104,640 ├─Sequential: 1-4 [128, 232, 8, 8] [128, 464, 4, 4] -- -- -- │ └─ShuffleUnit: 2-16 [128, 232, 8, 8] [128, 464, 4, 4] -- -- -- │ │ └─Sequential: 3-15 [128, 232, 8, 8] [128, 232, 4, 4] 56,840 -- 114,626,560 │ │ └─Sequential: 3-16 [128, 232, 8, 8] [128, 232, 4, 4] 111,128 -- 555,612,160 │ └─ShuffleUnit: 2-17 [128, 464, 4, 4] [128, 464, 4, 4] -- -- -- │ │ └─Sequential: 3-17 [128, 232, 4, 4] [128, 232, 4, 4] 111,128 -- 224,917,504 │ └─ShuffleUnit: 2-18 [128, 464, 4, 4] [128, 464, 4, 4] -- -- -- │ │ └─Sequential: 3-18 [128, 232, 4, 4] [128, 232, 4, 4] 111,128 -- 224,917,504 │ └─ShuffleUnit: 2-19 [128, 464, 4, 4] [128, 464, 4, 4] -- -- -- │ │ └─Sequential: 3-19 [128, 232, 4, 4] [128, 232, 4, 4] 111,128 -- 224,917,504 ├─Sequential: 1-5 [128, 464, 4, 4] [128, 1024, 4, 4] -- -- -- │ └─Conv2d: 2-20 [128, 464, 4, 4] [128, 1024, 4, 4] 475,136 [1, 1] 973,078,528 │ └─BatchNorm2d: 2-21 [128, 1024, 4, 4] [128, 1024, 4, 4] 2,048 -- 262,144 │ └─ReLU: 2-22 [128, 1024, 4, 4] [128, 1024, 4, 4] -- -- -- ├─Linear: 1-6 [128, 1024] [128, 10] 10,250 -- 1,312,000 ===================================================================================================================================================================== Total params: 1,263,854 Trainable params: 1,263,854 Non-trainable params: 0 Total mult-adds (Units.GIGABYTES): 5.76 ===================================================================================================================================================================== Input size (MB): 1.57 Forward/backward pass size (MB): 1153.18 Params size (MB): 5.06 Estimated Total Size (MB): 1159.81 =====================================================================================================================================================================
In [15]:
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=5e-4)
# optimizer = optim.SGD(model.parameters(), lr=LEARNING_RATE, momentum=0.9, weight_decay=5e-4) # Use weight decay (L2) to combat overfitting
# scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5)
def calculate_accuracy(loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
return 100 * correct / total
# Training Loop
start_time = time.time()
history = {'train_loss': [], 'val_loss': [], 'train_acc': [], 'val_acc': [], 'lrs': []}
print(f"Starting training on {device}...")
for epoch in range(NUM_EPOCHS):
model.train()
running_loss = 0.0
for i, (inputs, labels) in enumerate(train_loader):
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
train_loss = running_loss / len(train_subset)
train_acc = calculate_accuracy(train_loader)
# Calculate validation loss and accuracy
model.eval()
val_loss = 0.0
with torch.no_grad():
for inputs, labels in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
val_loss += loss.item() * inputs.size(0)
val_loss /= len(val_subset)
val_acc = calculate_accuracy(val_loader)
# scheduler.step(val_loss)
# current_lr = optimizer.param_groups[0]['lr']
# Log metrics
history['train_loss'].append(train_loss)
history['val_loss'].append(val_loss)
history['train_acc'].append(train_acc)
history['val_acc'].append(val_acc)
# history['lrs'].append(current_lr)
print(f'Epoch {epoch + 1}/{NUM_EPOCHS} | '
f'Train Loss: {train_loss:.5f}, '
f'Train Acc.: {train_acc:.2f}% | '
f'Val Loss: {val_loss:.5f}, '
f'Val Acc.: {val_acc:.2f}% | '
# f'LR: {current_lr:.8f}')
)
print('\nTotal Training Time: %.2f min' % ((time.time() - start_time)/60))
# --- Final Evaluation ---
test_accuracy = calculate_accuracy(test_loader)
print(f'\nTest Accuracy: {test_accuracy:.2f}%')
Starting training on cuda... Epoch 1/30 | Train Loss: 1.71806, Train Acc.: 45.01% | Val Loss: 1.53539, Val Acc.: 43.18% | Epoch 2/30 | Train Loss: 1.36425, Train Acc.: 53.28% | Val Loss: 1.32741, Val Acc.: 51.80% | Epoch 3/30 | Train Loss: 1.17134, Train Acc.: 61.79% | Val Loss: 1.12907, Val Acc.: 59.54% | Epoch 4/30 | Train Loss: 1.01715, Train Acc.: 67.34% | Val Loss: 0.97544, Val Acc.: 64.70% | Epoch 5/30 | Train Loss: 0.88393, Train Acc.: 70.49% | Val Loss: 0.90727, Val Acc.: 68.18% | Epoch 6/30 | Train Loss: 0.79267, Train Acc.: 74.53% | Val Loss: 0.80005, Val Acc.: 71.64% | Epoch 7/30 | Train Loss: 0.72289, Train Acc.: 76.02% | Val Loss: 0.75722, Val Acc.: 73.96% | Epoch 8/30 | Train Loss: 0.66590, Train Acc.: 77.69% | Val Loss: 0.72508, Val Acc.: 74.36% | Epoch 9/30 | Train Loss: 0.62746, Train Acc.: 79.22% | Val Loss: 0.68530, Val Acc.: 75.80% | Epoch 10/30 | Train Loss: 0.59550, Train Acc.: 81.06% | Val Loss: 0.61650, Val Acc.: 78.56% | Epoch 11/30 | Train Loss: 0.56085, Train Acc.: 81.91% | Val Loss: 0.61428, Val Acc.: 79.04% | Epoch 12/30 | Train Loss: 0.53128, Train Acc.: 81.10% | Val Loss: 0.63547, Val Acc.: 77.82% | Epoch 13/30 | Train Loss: 0.50932, Train Acc.: 83.14% | Val Loss: 0.60744, Val Acc.: 78.82% | Epoch 14/30 | Train Loss: 0.49018, Train Acc.: 84.76% | Val Loss: 0.54207, Val Acc.: 81.48% | Epoch 15/30 | Train Loss: 0.47403, Train Acc.: 84.88% | Val Loss: 0.54331, Val Acc.: 81.22% | Epoch 16/30 | Train Loss: 0.45681, Train Acc.: 84.48% | Val Loss: 0.55351, Val Acc.: 80.74% | Epoch 17/30 | Train Loss: 0.44454, Train Acc.: 84.96% | Val Loss: 0.53899, Val Acc.: 81.22% | Epoch 18/30 | Train Loss: 0.43036, Train Acc.: 85.88% | Val Loss: 0.53128, Val Acc.: 82.26% | Epoch 19/30 | Train Loss: 0.42551, Train Acc.: 87.16% | Val Loss: 0.47808, Val Acc.: 82.84% | Epoch 20/30 | Train Loss: 0.41200, Train Acc.: 86.05% | Val Loss: 0.52619, Val Acc.: 82.16% | Epoch 21/30 | Train Loss: 0.40151, Train Acc.: 87.26% | Val Loss: 0.48380, Val Acc.: 83.02% | Epoch 22/30 | Train Loss: 0.39145, Train Acc.: 87.90% | Val Loss: 0.46954, Val Acc.: 84.22% | Epoch 23/30 | Train Loss: 0.38501, Train Acc.: 88.05% | Val Loss: 0.47825, Val Acc.: 83.68% | Epoch 24/30 | Train Loss: 0.37875, Train Acc.: 88.34% | Val Loss: 0.47442, Val Acc.: 83.78% | Epoch 25/30 | Train Loss: 0.36723, Train Acc.: 88.56% | Val Loss: 0.44377, Val Acc.: 84.96% | Epoch 26/30 | Train Loss: 0.36346, Train Acc.: 88.84% | Val Loss: 0.45547, Val Acc.: 84.20% | Epoch 27/30 | Train Loss: 0.35831, Train Acc.: 88.59% | Val Loss: 0.45321, Val Acc.: 84.82% | Epoch 28/30 | Train Loss: 0.35466, Train Acc.: 89.62% | Val Loss: 0.45734, Val Acc.: 84.76% | Epoch 29/30 | Train Loss: 0.35032, Train Acc.: 89.33% | Val Loss: 0.44144, Val Acc.: 85.06% | Epoch 30/30 | Train Loss: 0.33981, Train Acc.: 89.79% | Val Loss: 0.43944, Val Acc.: 85.68% | Total Training Time: 23.84 min Test Accuracy: 86.08%
In [16]:
# --- Final Evaluation ---
test_accuracy = calculate_accuracy(test_loader)
print(f'\nTest Accuracy: {test_accuracy:.2f}%')
Test Accuracy: 86.08%
In [17]:
def plot_history(history):
"""
Plots the training and validation loss, accuracy, and learning rate
from a PyTorch history dictionary.
Args:
history (dict): Dictionary containing training metrics.
Expected keys: 'train_loss', 'val_loss', 'train_acc', 'val_acc', 'lrs'.
"""
epochs = range(len(history['train_loss']))
plt.figure(figsize=(18, 5))
# Plot 1: Loss
plt.subplot(1, 3, 1)
plt.plot(epochs, history['train_loss'], label='Training Loss')
plt.plot(epochs, history['val_loss'], label='Validation Loss')
plt.title('Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
# plt.grid(True)
# Plot 2: Accuracy
plt.subplot(1, 3, 2)
plt.plot(epochs, history['train_acc'], label='Training Accuracy')
plt.plot(epochs, history['val_acc'], label='Validation Accuracy')
plt.title('Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
# plt.grid(True)
plt.tight_layout()
plt.show()
# plt.savefig()
plot_history(history)
In [18]:
from sklearn.metrics import confusion_matrix
import seaborn as sns
def _compute_confusion_matrix(model, data_loader, device):
"""
Computes a confusion matrix for the given model and data.
Uses scikit-learn for robustness and speed.
"""
model.eval()
all_targets = []
all_predictions = []
with torch.no_grad():
for features, targets in data_loader:
features = features.to(device)
logits = model(features)
_, predicted_labels = torch.max(logits, 1)
# Move to CPU and convert to list/numpy immediately
all_targets.extend(targets.view(-1).cpu().numpy())
all_predictions.extend(predicted_labels.view(-1).cpu().numpy())
return confusion_matrix(all_targets, all_predictions)
def _plot_confusion_matrix(mat, class_names):
"""
Visualizes the confusion matrix using Seaborn for professional reporting.
"""
plt.figure(figsize=(10, 8))
sns.heatmap(mat, annot=True, fmt='d', cmap='Blues',
xticklabels=class_names, yticklabels=class_names)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix: MobileNetV2 on CIFAR-10')
plt.show()
In [19]:
class_dict = {0: 'plane',
1: 'car',
2: 'bird',
3: 'cat',
4: 'deer',
5: 'dog',
6: 'frog',
7: 'horse',
8: 'ship',
9: 'truck'}
_mat = _compute_confusion_matrix(model=model, data_loader=test_loader, device=device)
_plot_confusion_matrix(_mat, class_names=class_dict.values())
In [20]:
%watermark -iv
matplotlib : 3.10.0 numpy : 2.0.2 pandas : 2.2.2 seaborn : 0.13.2 sklearn : 1.6.1 torch : 2.9.0+cu126 torchinfo : 1.8.0 torchvision: 0.24.0+cu126
In [20]: