Open In Colab

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 [ ]:
!pip install watermark
Collecting watermark
  Downloading watermark-2.6.0-py3-none-any.whl.metadata (15 kB)
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.0)
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)
Collecting jedi>=0.16 (from ipython>=6.0->watermark)
  Downloading jedi-0.19.2-py2.py3-none-any.whl.metadata (22 kB)
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)
Downloading watermark-2.6.0-py3-none-any.whl (13 kB)
Downloading jedi-0.19.2-py2.py3-none-any.whl (1.6 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 44.4 MB/s eta 0:00:00
Installing collected packages: jedi, watermark
Successfully installed jedi-0.19.2 watermark-2.6.0
In [ ]:
!pip install torchinfo
Collecting torchinfo
  Downloading torchinfo-1.8.0-py3-none-any.whl.metadata (21 kB)
Downloading torchinfo-1.8.0-py3-none-any.whl (23 kB)
Installing collected packages: torchinfo
Successfully installed torchinfo-1.8.0
In [ ]:
%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

ShuffleNetV1 Classifier on CIFAR-10¶

Network Architecture:

The network in this notebook is an implementation of the ShuffleNetV1 [1] architecture on the CIFAR-10 dataset to train an image classifier.

ShuffleNetV1-architecture

Image Source


ShuffleNetV1: Unit Block¶

ShuffleNet Unit-block

Image Source




REFERENCES:

  1. Zhang, X., Zhou, X., Lin, M., & Sun, J. (2018). ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices. Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 6848-6856.
  2. 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 [ ]:
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 [ ]:
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 [ ]:
# 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

0. Model Hyperparameters¶


In [ ]:
##########################
### 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 [ ]:
set_all_seeds(RANDOM_SEED)

1. Data Preparation & Loading¶


In [ ]:
# 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)
100%|██████████| 170M/170M [00:05<00:00, 32.8MB/s]
In [ ]:
# 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 [ ]:
# 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])

2. Model Creation¶


In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F

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)     # Reshape
    x = torch.transpose(x, 1, 2).contiguous()                            # Transpose
    x = x.view(batchsize, -1, height, width)                             # Flatten back
    return x

class ShuffleUnitV1(nn.Module):
    def __init__(self, in_channels, out_channels, stride, groups):
        super(ShuffleUnitV1, self).__init__()
        self.stride = stride
        self.groups = groups
        mid_channels = out_channels // 4

        # If stride=2, the residual path includes an Average Pool and we concat,
        # so we must subtract in_channels from the output width.
        if self.stride == 2:
            out_channels -= in_channels

        # Group Convolution 1 (1x1)
        # Note: The original paper doesn't use groups in the first 1x1 of Stage 2
        self.gconv1 = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, 1, groups=groups, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True)
        )

        # Depthwise Convolution (3x3)
        self.dwconv2 = nn.Sequential(
            nn.Conv2d(mid_channels, mid_channels, 3, stride, 1, groups=mid_channels, bias=False),
            nn.BatchNorm2d(mid_channels)
        )

        # Group Convolution 3 (1x1)
        self.gconv3 = nn.Sequential(
            nn.Conv2d(mid_channels, out_channels, 1, groups=groups, bias=False),
            nn.BatchNorm2d(out_channels)
        )

        if stride == 2:
            self.shortcut = nn.AvgPool2d(3, stride=2, padding=1)

    def forward(self, x):
        residual = x

        out = self.gconv1(x)
        out = channel_shuffle(out, self.groups)
        out = self.dwconv2(out)
        out = self.gconv3(out)

        if self.stride == 2:
            res = self.shortcut(residual)
            out = F.relu(torch.cat([out, res], dim=1))
        else:
            out = F.relu(out + residual)

        return out

class ShuffleNetV1(nn.Module):
    def __init__(self, groups=3, num_classes=10):
        super(ShuffleNetV1, self).__init__()

        # Configuration for groups=3 (standard V1 setup)
        if groups == 1:
            out_channels = [24, 144, 288, 576]
        elif groups == 2:
            out_channels = [24, 200, 400, 800]
        elif groups == 3:
            out_channels = [24, 240, 480, 960]
        elif groups == 4:
            out_channels = [24, 272, 544, 1088]
        elif groups == 8:
            out_channels = [24, 384, 768, 1536]
        else:
            raise ValueError("Unsupported group count for this simple implementation.")

        # CIFAR-10 Adaptation: 3x3 conv instead of 7x7/MaxPool
        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, groups)
        self.stage3 = self._make_stage(out_channels[1], out_channels[2], 8, groups)
        self.stage4 = self._make_stage(out_channels[2], out_channels[3], 4, groups)

        self.fc = nn.Linear(out_channels[3], num_classes)

    def _make_stage(self, in_channels, out_channels, repeats, groups):
        layers = []
        # First unit of stage always has stride 2
        layers.append(ShuffleUnitV1(in_channels, out_channels, 2, groups))
        # Subsequent units have stride 1
        for _ in range(repeats - 1):
            layers.append(ShuffleUnitV1(out_channels, out_channels, 1, groups))
        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 = F.adaptive_avg_pool2d(x, (1, 1))
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x
In [ ]:
model = ShuffleNetV1().to(device)
In [ ]:
# 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[ ]:
=====================================================================================================================================================================
Layer (type:depth-idx)                   Input Shape               Output Shape              Param #                   Kernel Shape              Mult-Adds
=====================================================================================================================================================================
ShuffleNetV1                             [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, 240, 16, 16]        --                        --                        --
│    └─ShuffleUnitV1: 2-4                [128, 24, 32, 32]         [128, 240, 16, 16]        --                        --                        --
│    │    └─Sequential: 3-1              [128, 24, 32, 32]         [128, 60, 32, 32]         600                       --                        62,929,920
│    │    └─Sequential: 3-2              [128, 60, 32, 32]         [128, 60, 16, 16]         660                       --                        17,710,080
│    │    └─Sequential: 3-3              [128, 60, 16, 16]         [128, 216, 16, 16]        4,752                     --                        141,613,056
│    │    └─AvgPool2d: 3-4               [128, 24, 32, 32]         [128, 24, 16, 16]         --                        3                         --
│    └─ShuffleUnitV1: 2-5                [128, 240, 16, 16]        [128, 240, 16, 16]        --                        --                        --
│    │    └─Sequential: 3-5              [128, 240, 16, 16]        [128, 60, 16, 16]         4,920                     --                        157,301,760
│    │    └─Sequential: 3-6              [128, 60, 16, 16]         [128, 60, 16, 16]         660                       --                        17,710,080
│    │    └─Sequential: 3-7              [128, 60, 16, 16]         [128, 240, 16, 16]        5,280                     --                        157,347,840
│    └─ShuffleUnitV1: 2-6                [128, 240, 16, 16]        [128, 240, 16, 16]        --                        --                        --
│    │    └─Sequential: 3-8              [128, 240, 16, 16]        [128, 60, 16, 16]         4,920                     --                        157,301,760
│    │    └─Sequential: 3-9              [128, 60, 16, 16]         [128, 60, 16, 16]         660                       --                        17,710,080
│    │    └─Sequential: 3-10             [128, 60, 16, 16]         [128, 240, 16, 16]        5,280                     --                        157,347,840
│    └─ShuffleUnitV1: 2-7                [128, 240, 16, 16]        [128, 240, 16, 16]        --                        --                        --
│    │    └─Sequential: 3-11             [128, 240, 16, 16]        [128, 60, 16, 16]         4,920                     --                        157,301,760
│    │    └─Sequential: 3-12             [128, 60, 16, 16]         [128, 60, 16, 16]         660                       --                        17,710,080
│    │    └─Sequential: 3-13             [128, 60, 16, 16]         [128, 240, 16, 16]        5,280                     --                        157,347,840
├─Sequential: 1-3                        [128, 240, 16, 16]        [128, 480, 8, 8]          --                        --                        --
│    └─ShuffleUnitV1: 2-8                [128, 240, 16, 16]        [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-14             [128, 240, 16, 16]        [128, 120, 16, 16]        9,840                     --                        314,603,520
│    │    └─Sequential: 3-15             [128, 120, 16, 16]        [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-16             [128, 120, 8, 8]          [128, 240, 8, 8]          10,080                    --                        78,704,640
│    │    └─AvgPool2d: 3-17              [128, 240, 16, 16]        [128, 240, 8, 8]          --                        3                         --
│    └─ShuffleUnitV1: 2-9                [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-18             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-19             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-20             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-10               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-21             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-22             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-23             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-11               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-24             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-25             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-26             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-12               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-27             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-28             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-29             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-13               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-30             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-31             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-32             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-14               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-33             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-34             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-35             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
│    └─ShuffleUnitV1: 2-15               [128, 480, 8, 8]          [128, 480, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-36             [128, 480, 8, 8]          [128, 120, 8, 8]          19,440                    --                        157,317,120
│    │    └─Sequential: 3-37             [128, 120, 8, 8]          [128, 120, 8, 8]          1,320                     --                        8,878,080
│    │    └─Sequential: 3-38             [128, 120, 8, 8]          [128, 480, 8, 8]          20,160                    --                        157,409,280
├─Sequential: 1-4                        [128, 480, 8, 8]          [128, 960, 4, 4]          --                        --                        --
│    └─ShuffleUnitV1: 2-16               [128, 480, 8, 8]          [128, 960, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-39             [128, 480, 8, 8]          [128, 240, 8, 8]          38,880                    --                        314,634,240
│    │    └─Sequential: 3-40             [128, 240, 8, 8]          [128, 240, 4, 4]          2,640                     --                        4,485,120
│    │    └─Sequential: 3-41             [128, 240, 4, 4]          [128, 480, 4, 4]          39,360                    --                        78,766,080
│    │    └─AvgPool2d: 3-42              [128, 480, 8, 8]          [128, 480, 4, 4]          --                        3                         --
│    └─ShuffleUnitV1: 2-17               [128, 960, 4, 4]          [128, 960, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-43             [128, 960, 4, 4]          [128, 240, 4, 4]          77,280                    --                        157,347,840
│    │    └─Sequential: 3-44             [128, 240, 4, 4]          [128, 240, 4, 4]          2,640                     --                        4,485,120
│    │    └─Sequential: 3-45             [128, 240, 4, 4]          [128, 960, 4, 4]          78,720                    --                        157,532,160
│    └─ShuffleUnitV1: 2-18               [128, 960, 4, 4]          [128, 960, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-46             [128, 960, 4, 4]          [128, 240, 4, 4]          77,280                    --                        157,347,840
│    │    └─Sequential: 3-47             [128, 240, 4, 4]          [128, 240, 4, 4]          2,640                     --                        4,485,120
│    │    └─Sequential: 3-48             [128, 240, 4, 4]          [128, 960, 4, 4]          78,720                    --                        157,532,160
│    └─ShuffleUnitV1: 2-19               [128, 960, 4, 4]          [128, 960, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-49             [128, 960, 4, 4]          [128, 240, 4, 4]          77,280                    --                        157,347,840
│    │    └─Sequential: 3-50             [128, 240, 4, 4]          [128, 240, 4, 4]          2,640                     --                        4,485,120
│    │    └─Sequential: 3-51             [128, 240, 4, 4]          [128, 960, 4, 4]          78,720                    --                        157,532,160
├─Linear: 1-5                            [128, 960]                [128, 10]                 9,610                     --                        1,230,080
=====================================================================================================================================================================
Total params: 913,378
Trainable params: 913,378
Non-trainable params: 0
Total mult-adds (Units.GIGABYTES): 5.33
=====================================================================================================================================================================
Input size (MB): 1.57
Forward/backward pass size (MB): 1854.42
Params size (MB): 3.65
Estimated Total Size (MB): 1859.64
=====================================================================================================================================================================

3. Model Training, Testing & Evaluation¶


In [ ]:
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.56867, Train Acc.: 49.15% | Val Loss: 1.47229, Val Acc.: 46.52% | 
Epoch 2/30 | Train Loss: 1.17268, Train Acc.: 58.58% | Val Loss: 1.21637, Val Acc.: 57.28% | 
Epoch 3/30 | Train Loss: 0.97019, Train Acc.: 69.07% | Val Loss: 0.93374, Val Acc.: 67.46% | 
Epoch 4/30 | Train Loss: 0.83978, Train Acc.: 70.36% | Val Loss: 0.90918, Val Acc.: 68.10% | 
Epoch 5/30 | Train Loss: 0.75056, Train Acc.: 75.06% | Val Loss: 0.77564, Val Acc.: 73.12% | 
Epoch 6/30 | Train Loss: 0.68110, Train Acc.: 77.66% | Val Loss: 0.70453, Val Acc.: 75.82% | 
Epoch 7/30 | Train Loss: 0.63895, Train Acc.: 79.36% | Val Loss: 0.68369, Val Acc.: 76.62% | 
Epoch 8/30 | Train Loss: 0.59735, Train Acc.: 79.64% | Val Loss: 0.71107, Val Acc.: 75.40% | 
Epoch 9/30 | Train Loss: 0.56836, Train Acc.: 81.02% | Val Loss: 0.64375, Val Acc.: 77.04% | 
Epoch 10/30 | Train Loss: 0.54416, Train Acc.: 82.16% | Val Loss: 0.61841, Val Acc.: 78.48% | 
Epoch 11/30 | Train Loss: 0.52127, Train Acc.: 83.37% | Val Loss: 0.59265, Val Acc.: 79.32% | 
Epoch 12/30 | Train Loss: 0.49705, Train Acc.: 81.67% | Val Loss: 0.64391, Val Acc.: 77.66% | 
Epoch 13/30 | Train Loss: 0.48378, Train Acc.: 84.99% | Val Loss: 0.54836, Val Acc.: 81.02% | 
Epoch 14/30 | Train Loss: 0.46446, Train Acc.: 84.90% | Val Loss: 0.56053, Val Acc.: 81.46% | 
Epoch 15/30 | Train Loss: 0.44531, Train Acc.: 83.18% | Val Loss: 0.58697, Val Acc.: 79.42% | 
Epoch 16/30 | Train Loss: 0.43608, Train Acc.: 85.70% | Val Loss: 0.53800, Val Acc.: 81.38% | 
Epoch 17/30 | Train Loss: 0.41942, Train Acc.: 85.35% | Val Loss: 0.52966, Val Acc.: 81.42% | 
Epoch 18/30 | Train Loss: 0.41390, Train Acc.: 84.20% | Val Loss: 0.57512, Val Acc.: 80.56% | 
Epoch 19/30 | Train Loss: 0.40408, Train Acc.: 86.59% | Val Loss: 0.52014, Val Acc.: 81.92% | 
Epoch 20/30 | Train Loss: 0.39485, Train Acc.: 85.43% | Val Loss: 0.54740, Val Acc.: 80.96% | 
Epoch 21/30 | Train Loss: 0.38756, Train Acc.: 88.16% | Val Loss: 0.47956, Val Acc.: 83.82% | 
Epoch 22/30 | Train Loss: 0.37431, Train Acc.: 87.66% | Val Loss: 0.49141, Val Acc.: 82.74% | 
Epoch 23/30 | Train Loss: 0.37214, Train Acc.: 87.47% | Val Loss: 0.50267, Val Acc.: 83.20% | 
Epoch 24/30 | Train Loss: 0.36423, Train Acc.: 86.70% | Val Loss: 0.53721, Val Acc.: 82.20% | 
Epoch 25/30 | Train Loss: 0.35413, Train Acc.: 88.21% | Val Loss: 0.48620, Val Acc.: 83.74% | 
Epoch 26/30 | Train Loss: 0.35015, Train Acc.: 86.56% | Val Loss: 0.53099, Val Acc.: 82.32% | 
Epoch 27/30 | Train Loss: 0.34699, Train Acc.: 88.85% | Val Loss: 0.46388, Val Acc.: 84.40% | 
Epoch 28/30 | Train Loss: 0.33591, Train Acc.: 87.17% | Val Loss: 0.52713, Val Acc.: 82.62% | 
Epoch 29/30 | Train Loss: 0.33443, Train Acc.: 88.99% | Val Loss: 0.45196, Val Acc.: 84.60% | 
Epoch 30/30 | Train Loss: 0.32403, Train Acc.: 88.04% | Val Loss: 0.49852, Val Acc.: 83.54% | 

Total Training Time: 121.55 min

Test Accuracy: 84.31%
In [ ]:
# --- Final Evaluation ---
test_accuracy = calculate_accuracy(test_loader)
print(f'\nTest Accuracy: {test_accuracy:.2f}%')
Test Accuracy: 84.31%
In [ ]:
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)
No description has been provided for this image
In [ ]:
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 [ ]:
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())
No description has been provided for this image
In [ ]:
%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 [ ]: