Kaggle

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.11/dist-packages (2.6.0)
Requirement already satisfied: ipython>=6.0 in /usr/local/lib/python3.11/dist-packages (from watermark) (7.34.0)
Requirement already satisfied: importlib-metadata>=1.4 in /usr/local/lib/python3.11/dist-packages (from watermark) (8.7.0)
Requirement already satisfied: setuptools in /usr/local/lib/python3.11/dist-packages (from watermark) (75.2.0)
Requirement already satisfied: zipp>=3.20 in /usr/local/lib/python3.11/dist-packages (from importlib-metadata>=1.4->watermark) (3.23.0)
Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (0.19.2)
Requirement already satisfied: decorator in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (4.4.2)
Requirement already satisfied: pickleshare in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (0.7.5)
Requirement already satisfied: traitlets>=4.2 in /usr/local/lib/python3.11/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.11/dist-packages (from ipython>=6.0->watermark) (3.0.51)
Requirement already satisfied: pygments in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (2.19.2)
Requirement already satisfied: backcall in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (0.2.0)
Requirement already satisfied: matplotlib-inline in /usr/local/lib/python3.11/dist-packages (from ipython>=6.0->watermark) (0.1.7)
Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.11/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.11/dist-packages (from jedi>=0.16->ipython>=6.0->watermark) (0.8.4)
Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.11/dist-packages (from pexpect>4.3->ipython>=6.0->watermark) (0.7.0)
Requirement already satisfied: wcwidth in /usr/local/lib/python3.11/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=6.0->watermark) (0.2.13)
In [2]:
!pip install torchinfo
Requirement already satisfied: torchinfo in /usr/local/lib/python3.11/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.11.13
IPython version      : 7.34.0

torch: 2.6.0+cu124

EfficientNet-B0 Classifier on CIFAR-10¶


Network Architecture:

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

EfficientNet-B0baselinenetwork Source


EfficientNet: Model Architecture¶

EfficientNet-B0-block

Image Source


REFERENCES:

  1. Tan, M., & Le, Q. V. (2019). EfficientNet: Rethinking model scaling for convolutional neural networks. International Conference on Machine Learning, 6105–6114
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:  2
GPU 0: Tesla T4
GPU 1: Tesla T4

0. Model Hyperparameters¶


In [7]:
##########################
### SETTINGS
##########################

# Hyperparameters
RANDOM_SEED = 123
BATCH_SIZE = 128 #256
LEARNING_RATE = 0.001
NUM_EPOCHS = 30 #50
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)

1. Data Preparation & Loading¶


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])

2. Model Creation¶


Building EfficientNet-B0 from scratch is a fantastic exercise because it introduces you to the MBConv (Mobile Inverted Bottleneck) block and Squeeze-and-Excitation (SE) optimization.

Implementation Tips for "From Scratch":¶

  • Squeeze-and-Excitation Block: This is the "secret sauce" that allows EfficientNet to weight channels dynamically.
  • MBConv Block: This combines Depthwise-Separable convolutions with the SE block and a skip connection.
  • Activation Function: EfficientNet uses SiLU (also known as Swish: ). Don't use ReLU, or it won't be a true EfficientNet.
  • Weights Initialization: When training from scratch, use Kaiming Initialization for the Conv layers to help convergence.
  • Batch Size: You can now use a larger batch size (like 128) because you aren't upscaling images to 224x224.

Note: In this scratch version, I have modified the Strides to 1 in the first few stages. This ensures that a 32x32 CIFAR-10 image doesn't shrink to 1x1 before the network is even half-finished.

In [12]:
class SqueezeExcitation(nn.Module):
    def __init__(self, in_channels, reduced_dim):
        super().__init__()
        self.se = nn.Sequential(
            nn.AdaptiveAvgPool2d(1), # Squeeze
            nn.Conv2d(in_channels, reduced_dim, 1),
            nn.SiLU(),
            nn.Conv2d(reduced_dim, in_channels, 1),
            nn.Sigmoid() # Excitation
        )

    def forward(self, x):
        return x * self.se(x)

class MBConv(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride, expand_ratio, se_ratio=0.25):
        super().__init__()
        expand_channels = in_channels * expand_ratio
        self.use_residual = (stride == 1 and in_channels == out_channels)

        layers = []
        # 1. Expansion phase
        if expand_ratio != 1:
            layers.append(nn.Conv2d(in_channels, expand_channels, 1, bias=False))
            layers.append(nn.BatchNorm2d(expand_channels))
            layers.append(nn.SiLU())

        # 2. Depthwise phase
        layers.append(nn.Conv2d(expand_channels, expand_channels, kernel_size, 
                                stride, kernel_size//2, groups=expand_channels, bias=False))
        layers.append(nn.BatchNorm2d(expand_channels))
        layers.append(nn.SiLU())

        # 3. Squeeze and Excitation
        se_reduced_dim = int(in_channels * se_ratio)
        layers.append(SqueezeExcitation(expand_channels, se_reduced_dim))

        # 4. Pointwise phase (Projection)
        layers.append(nn.Conv2d(expand_channels, out_channels, 1, bias=False))
        layers.append(nn.BatchNorm2d(out_channels))

        self.block = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_residual:
            return x + self.block(x)
        return self.block(x)

class EfficientNetB0(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        # Initial Conv: Changed stride 2 -> 1 for CIFAR-10
        self.stem = nn.Sequential(
            nn.Conv2d(3, 32, 3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(32),
            nn.SiLU()
        )

        # Configuration: [expand_ratio, channels, repeats, stride, kernel_size]
        self.config = [
            [1, 16, 1, 1, 3],
            [6, 24, 2, 1, 3], # Stride 1 for CIFAR (Standard is 2)
            [6, 40, 2, 2, 5],
            [6, 80, 3, 2, 3],
            [6, 112, 3, 1, 5],
            [6, 192, 4, 2, 5],
            [6, 320, 1, 1, 3],
        ]

        layers = []
        in_channels = 32
        for expand_ratio, out_channels, repeats, stride, kernel_size in self.config:
            for i in range(repeats):
                layers.append(MBConv(in_channels, out_channels, kernel_size, 
                                     stride if i == 0 else 1, expand_ratio))
                in_channels = out_channels
        self.layers = nn.Sequential(*layers)

        # Head
        self.head = nn.Sequential(
            nn.Conv2d(320, 1280, 1, bias=False),
            nn.BatchNorm2d(1280),
            nn.SiLU(),
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
            nn.Dropout(0.2),
            nn.Linear(1280, num_classes)
        )

        # Initialize Weights
        self._initialize_weights()
        
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # Using fan_out for depthwise/pointwise layers
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        x = self.stem(x)
        x = self.layers(x)
        x = self.head(x)
        return x

# Instantiate
model = EfficientNetB0().to(device)
In [13]:
# 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[13]:
====================================================================================================================================================================================
Layer (type:depth-idx)                                  Input Shape               Output Shape              Param #                   Kernel Shape              Mult-Adds
====================================================================================================================================================================================
EfficientNetB0                                          [128, 3, 32, 32]          [128, 10]                 --                        --                        --
├─Sequential: 1-1                                       [128, 3, 32, 32]          [128, 32, 32, 32]         --                        --                        --
│    └─Conv2d: 2-1                                      [128, 3, 32, 32]          [128, 32, 32, 32]         864                       [3, 3]                    113,246,208
│    └─BatchNorm2d: 2-2                                 [128, 32, 32, 32]         [128, 32, 32, 32]         64                        --                        8,192
│    └─SiLU: 2-3                                        [128, 32, 32, 32]         [128, 32, 32, 32]         --                        --                        --
├─Sequential: 1-2                                       [128, 32, 32, 32]         [128, 320, 4, 4]          --                        --                        --
│    └─MBConv: 2-4                                      [128, 32, 32, 32]         [128, 16, 32, 32]         --                        --                        --
│    │    └─Sequential: 3-1                             [128, 32, 32, 32]         [128, 16, 32, 32]         1,448                     --                        104,940,544
│    └─MBConv: 2-5                                      [128, 16, 32, 32]         [128, 24, 32, 32]         --                        --                        --
│    │    └─Sequential: 3-2                             [128, 16, 32, 32]         [128, 24, 32, 32]         6,004                     --                        616,729,088
│    └─MBConv: 2-6                                      [128, 24, 32, 32]         [128, 24, 32, 32]         --                        --                        --
│    │    └─Sequential: 3-3                             [128, 24, 32, 32]         [128, 24, 32, 32]         10,710                    --                        1,076,159,232
│    └─MBConv: 2-7                                      [128, 24, 32, 32]         [128, 40, 16, 16]         --                        --                        --
│    │    └─Sequential: 3-4                             [128, 24, 32, 32]         [128, 40, 16, 16]         15,350                    --                        760,017,664
│    └─MBConv: 2-8                                      [128, 40, 16, 16]         [128, 40, 16, 16]         --                        --                        --
│    │    └─Sequential: 3-5                             [128, 40, 16, 16]         [128, 40, 16, 16]         31,290                    --                        826,533,120
│    └─MBConv: 2-9                                      [128, 40, 16, 16]         [128, 80, 8, 8]           --                        --                        --
│    │    └─Sequential: 3-6                             [128, 40, 16, 16]         [128, 80, 8, 8]           37,130                    --                        490,343,680
│    └─MBConv: 2-10                                     [128, 80, 8, 8]           [128, 80, 8, 8]           --                        --                        --
│    │    └─Sequential: 3-7                             [128, 80, 8, 8]           [128, 80, 8, 8]           102,900                   --                        667,322,880
│    └─MBConv: 2-11                                     [128, 80, 8, 8]           [128, 80, 8, 8]           --                        --                        --
│    │    └─Sequential: 3-8                             [128, 80, 8, 8]           [128, 80, 8, 8]           102,900                   --                        667,322,880
│    └─MBConv: 2-12                                     [128, 80, 8, 8]           [128, 112, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-9                             [128, 80, 8, 8]           [128, 112, 8, 8]          126,004                   --                        856,074,752
│    └─MBConv: 2-13                                     [128, 112, 8, 8]          [128, 112, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-10                            [128, 112, 8, 8]          [128, 112, 8, 8]          208,572                   --                        1,376,030,208
│    └─MBConv: 2-14                                     [128, 112, 8, 8]          [128, 112, 8, 8]          --                        --                        --
│    │    └─Sequential: 3-11                            [128, 112, 8, 8]          [128, 112, 8, 8]          208,572                   --                        1,376,030,208
│    └─MBConv: 2-15                                     [128, 112, 8, 8]          [128, 192, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-12                            [128, 112, 8, 8]          [128, 192, 4, 4]          262,492                   --                        920,509,952
│    └─MBConv: 2-16                                     [128, 192, 4, 4]          [128, 192, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-13                            [128, 192, 4, 4]          [128, 192, 4, 4]          587,952                   --                        979,900,416
│    └─MBConv: 2-17                                     [128, 192, 4, 4]          [128, 192, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-14                            [128, 192, 4, 4]          [128, 192, 4, 4]          587,952                   --                        979,900,416
│    └─MBConv: 2-18                                     [128, 192, 4, 4]          [128, 192, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-15                            [128, 192, 4, 4]          [128, 192, 4, 4]          587,952                   --                        979,900,416
│    └─MBConv: 2-19                                     [128, 192, 4, 4]          [128, 320, 4, 4]          --                        --                        --
│    │    └─Sequential: 3-16                            [128, 192, 4, 4]          [128, 320, 4, 4]          717,232                   --                        1,244,174,336
├─Sequential: 1-3                                       [128, 320, 4, 4]          [128, 10]                 --                        --                        --
│    └─Conv2d: 2-20                                     [128, 320, 4, 4]          [128, 1280, 4, 4]         409,600                   [1, 1]                    838,860,800
│    └─BatchNorm2d: 2-21                                [128, 1280, 4, 4]         [128, 1280, 4, 4]         2,560                     --                        327,680
│    └─SiLU: 2-22                                       [128, 1280, 4, 4]         [128, 1280, 4, 4]         --                        --                        --
│    └─AdaptiveAvgPool2d: 2-23                          [128, 1280, 4, 4]         [128, 1280, 1, 1]         --                        --                        --
│    └─Flatten: 2-24                                    [128, 1280, 1, 1]         [128, 1280]               --                        --                        --
│    └─Dropout: 2-25                                    [128, 1280]               [128, 1280]               --                        --                        --
│    └─Linear: 2-26                                     [128, 1280]               [128, 10]                 12,810                    --                        1,639,680
====================================================================================================================================================================================
Total params: 4,020,358
Trainable params: 4,020,358
Non-trainable params: 0
Total mult-adds (Units.GIGABYTES): 14.88
====================================================================================================================================================================================
Input size (MB): 1.57
Forward/backward pass size (MB): 3408.01
Params size (MB): 16.08
Estimated Total Size (MB): 3425.66
====================================================================================================================================================================================

3. Model Training, Testing & Evaluation¶


In [14]:
criterion = nn.CrossEntropyLoss()
# optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=5e-4)
optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE, weight_decay=5e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=NUM_EPOCHS, eta_min=1e-6)

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()
    train_loss, train_correct, train_total = 0.0, 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()
        
        train_loss += loss.item() * inputs.size(0)
        _, predicted = outputs.max(1)
        train_total += labels.size(0)
        train_correct += predicted.eq(labels).sum().item()

    train_loss = train_loss / len(train_subset)
    train_acc = 100. * train_correct / train_total
    
    # Calculate validation loss and accuracy
    model.eval()
    val_loss, val_correct, val_total = 0.0, 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)
            _, predicted = outputs.max(1)
            val_total += labels.size(0)
            val_correct += predicted.eq(labels).sum().item()
    
    val_loss = val_loss / len(val_subset)
    val_acc = 100. * val_correct / val_total
    
    scheduler.step()
    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.60032, Train Acc.: 39.72% | Val Loss: 1.33364, Val Acc.: 51.76% | LR: 0.00099726
Epoch 2/30 | Train Loss: 1.08361, Train Acc.: 61.32% | Val Loss: 1.01320, Val Acc.: 64.06% | LR: 0.00098908
Epoch 3/30 | Train Loss: 0.85020, Train Acc.: 70.32% | Val Loss: 0.90244, Val Acc.: 68.76% | LR: 0.00097555
Epoch 4/30 | Train Loss: 0.72896, Train Acc.: 75.08% | Val Loss: 0.76207, Val Acc.: 74.44% | LR: 0.00095682
Epoch 5/30 | Train Loss: 0.64968, Train Acc.: 77.83% | Val Loss: 0.73072, Val Acc.: 74.96% | LR: 0.00093308
Epoch 6/30 | Train Loss: 0.59704, Train Acc.: 79.57% | Val Loss: 0.64726, Val Acc.: 78.36% | LR: 0.00090460
Epoch 7/30 | Train Loss: 0.55662, Train Acc.: 81.15% | Val Loss: 0.64641, Val Acc.: 77.54% | LR: 0.00087170
Epoch 8/30 | Train Loss: 0.51604, Train Acc.: 82.41% | Val Loss: 0.64870, Val Acc.: 78.00% | LR: 0.00083473
Epoch 9/30 | Train Loss: 0.48318, Train Acc.: 83.56% | Val Loss: 0.56256, Val Acc.: 80.92% | LR: 0.00079410
Epoch 10/30 | Train Loss: 0.45845, Train Acc.: 84.54% | Val Loss: 0.52241, Val Acc.: 82.26% | LR: 0.00075025
Epoch 11/30 | Train Loss: 0.43246, Train Acc.: 85.22% | Val Loss: 0.49367, Val Acc.: 83.10% | LR: 0.00070366
Epoch 12/30 | Train Loss: 0.41405, Train Acc.: 85.94% | Val Loss: 0.50176, Val Acc.: 82.34% | LR: 0.00065485
Epoch 13/30 | Train Loss: 0.38834, Train Acc.: 86.86% | Val Loss: 0.45898, Val Acc.: 83.82% | LR: 0.00060435
Epoch 14/30 | Train Loss: 0.36786, Train Acc.: 87.43% | Val Loss: 0.44432, Val Acc.: 84.96% | LR: 0.00055271
Epoch 15/30 | Train Loss: 0.34615, Train Acc.: 88.17% | Val Loss: 0.44099, Val Acc.: 85.02% | LR: 0.00050050
Epoch 16/30 | Train Loss: 0.32895, Train Acc.: 88.79% | Val Loss: 0.43182, Val Acc.: 84.74% | LR: 0.00044829
Epoch 17/30 | Train Loss: 0.30636, Train Acc.: 89.34% | Val Loss: 0.40617, Val Acc.: 85.92% | LR: 0.00039665
Epoch 18/30 | Train Loss: 0.28627, Train Acc.: 90.12% | Val Loss: 0.39009, Val Acc.: 87.10% | LR: 0.00034615
Epoch 19/30 | Train Loss: 0.26854, Train Acc.: 90.80% | Val Loss: 0.37416, Val Acc.: 87.66% | LR: 0.00029734
Epoch 20/30 | Train Loss: 0.24797, Train Acc.: 91.64% | Val Loss: 0.36167, Val Acc.: 88.02% | LR: 0.00025075
Epoch 21/30 | Train Loss: 0.23367, Train Acc.: 92.00% | Val Loss: 0.35960, Val Acc.: 87.66% | LR: 0.00020690
Epoch 22/30 | Train Loss: 0.21770, Train Acc.: 92.38% | Val Loss: 0.34750, Val Acc.: 88.14% | LR: 0.00016627
Epoch 23/30 | Train Loss: 0.20236, Train Acc.: 93.12% | Val Loss: 0.33933, Val Acc.: 88.48% | LR: 0.00012930
Epoch 24/30 | Train Loss: 0.18630, Train Acc.: 93.76% | Val Loss: 0.33489, Val Acc.: 88.66% | LR: 0.00009640
Epoch 25/30 | Train Loss: 0.17299, Train Acc.: 94.14% | Val Loss: 0.32322, Val Acc.: 89.18% | LR: 0.00006792
Epoch 26/30 | Train Loss: 0.16390, Train Acc.: 94.46% | Val Loss: 0.34044, Val Acc.: 88.68% | LR: 0.00004418
Epoch 27/30 | Train Loss: 0.15627, Train Acc.: 94.68% | Val Loss: 0.31890, Val Acc.: 89.86% | LR: 0.00002545
Epoch 28/30 | Train Loss: 0.14808, Train Acc.: 95.14% | Val Loss: 0.32634, Val Acc.: 89.26% | LR: 0.00001192
Epoch 29/30 | Train Loss: 0.14205, Train Acc.: 95.34% | Val Loss: 0.32047, Val Acc.: 89.54% | LR: 0.00000374
Epoch 30/30 | Train Loss: 0.14286, Train Acc.: 95.24% | Val Loss: 0.32164, Val Acc.: 89.18% | LR: 0.00000100

Total Training Time: 36.49 min

Test Accuracy: 89.93%
In [15]:
# --- Final Evaluation ---
test_accuracy = calculate_accuracy(test_loader)
print(f'\nTest Accuracy: {test_accuracy:.2f}%')
Test Accuracy: 89.93%
In [16]:
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 [17]:
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 [18]:
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 [19]:
%watermark -iv
matplotlib : 3.7.2
numpy      : 1.26.4
pandas     : 2.2.3
seaborn    : 0.12.2
sklearn    : 1.2.2
torch      : 2.6.0+cu124
torchinfo  : 1.8.0
torchvision: 0.21.0+cu124

In [ ]: