Assignment 4: Application of Neural Networks for Image Processing¶
In this assignment, you will complete three main tasks to deepen your understanding of neural networks, specifically using Convolutional Neural Networks (CNNs) for image processing:
Image Classification with CNN (30 points)
Create an image classifier using PyTorch, building and training a CNN model to classify images accurately.Image Denoising with CNN (45 points)
Develop a denoising model using PyTorch, leveraging a CNN to remove noise from images and enhance visual quality.Kaggle Competition (25 + 20 points)
Participate in a Kaggle competition where you will apply your skills in image classification on a challenging dataset.
%%capture
!uv pip install torch torchvision numpy matplotlib scipy tqdm safetensors
# Importing necessary libraries
import os
from datetime import datetime
import torch # Core PyTorch library for tensor operations
import torch.nn as nn # Neural network components
import torch.optim as optim # Optimization algorithms
import numpy as np # Numerical operations
import matplotlib.pyplot as plt # Plotting|
from torchvision import datasets, transforms # Datasets and transformations for computer vision
from torch.utils.data import DataLoader, Dataset, random_split, Subset # Data handling utilities
from tqdm import tqdm
from safetensors.torch import save_model, load_model
SVHN Dataset¶
In this assignment, you will work with the Street View House Numbers (SVHN) dataset to develop a denoising algorithm and a classification task. The SVHN dataset consists of over 600,000 color images of house numbers collected from Google Street View images. Each image is a 32x32 pixel RGB image capturing digits (0-9) in real-world scenarios. The dataset can be found at this link.
For this assignment, you will use:
- Training set: 73,257 images
- Testing set: 26,032 images
# Define transformations for the dataset
transform = transforms.ToTensor()
# Load the training and test datasets
trainset = datasets.SVHN(root='./data', split='train', download=True, transform=transform)
testset = datasets.SVHN(root='./data', split='test', download=True, transform=transform)
# Set batch size as a parameter
batch_size = 64
# Function to create data loaders with a specified batch size
def get_dataloader(dataset, batch_size, shuffle):
return torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=shuffle)
# Create data loaders for training and testing
trainloader = get_dataloader(trainset, batch_size=batch_size, shuffle=True)
testloader = get_dataloader(testset, batch_size=batch_size, shuffle=False)
Using downloaded and verified file: ./data/train_32x32.mat Using downloaded and verified file: ./data/test_32x32.mat
Visualization of SVHN dataset¶
# Define the class labels for the SVHN dataset
classes = [str(i) for i in range(10)]
# Function to display a grid of images with specified rows, columns, and class labels
def show_images_grid(dataloader, rows, cols):
# Get a batch of training images and labels
dataiter = iter(dataloader)
images, labels = next(dataiter)
fig, axes = plt.subplots(rows, cols, figsize=(cols * 2, rows * 2))
idx = 0
for i in range(rows):
for j in range(cols):
if idx < len(images):
npimg = images[idx].numpy()
axes[i, j].imshow(np.transpose(npimg, (1, 2, 0)))
axes[i, j].set_title(f'Class: {classes[labels[idx]]}')
axes[i, j].axis('off')
idx += 1
plt.tight_layout()
plt.show()
# Display a 2x5 grid of images from trainloader
show_images_grid(trainloader, rows=2, cols=5)
The del
keyword deletes variables from memory, freeing up space. This is useful in large projects, especially deep learning, where datasets and data loaders use significant memory. Removing these variables ensures available memory for later code and helps avoid conflicts with similar names.
del trainset, testset, trainloader, testloader, transform, batch_size, get_dataloader
Task 1: SVHN Image Classification Using a Convolutional Neural Network (CNN) (30 points) ¶
In this task, you will complete the implementation of a CNN model and use it to classify images from the SVHN dataset. Your goal is to build an accurate model that can recognize and classify house number digits in real-world images.
When available, a GPU can accelerate neural network training with parallel computations, ideal for large datasets and complex models.
# Set up device for training (use GPU if available)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Using device: {device}')
Using device: cuda
Hyper-Parameters:
batch_size = 64 # Batch size for data loading
learning_rate = 0.05 # Learning rate for optimizer
num_epochs = 20 # Number of training epochs
momentum = 0.9
weight_decay = 1e-4
Loading the dataset
# Define transformations for SVHN dataset
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4377, 0.4438, 0.4728), (0.1980, 0.2010, 0.1970)),
])
# Load training and test datasets with transformations
train_dataset = datasets.SVHN(root='./data', split='train', download=True, transform=transform)
test_dataset = datasets.SVHN(root='./data', split='test', download=True, transform=transform)
# Create data loaders with specified batch size
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
Using downloaded and verified file: ./data/train_32x32.mat Using downloaded and verified file: ./data/test_32x32.mat
A (15 points) Build the CNN Model¶
A convolutional layer can be implemented in pytorch like
nn.Conv2d(in_channels=, out_channels=, kernel_size=, padding=, stride=).
Complete the CNN model implementation with the following architecture:
Convolutional Layers:
- Three convolutional layers with:
kernel_size=3
,stride=1
,padding=1
- Output channels:
- First layer: 32 channels
- Second layer: 64 channels
- Third layer: 128 channels
- Each convolutional layer is followed by:
- Batch normalization to stabilize and speed up training
- ReLU activation for non-linearity
- Max pooling (
kernel_size=2
,stride=2
) to reduce spatial dimensions
- Three convolutional layers with:
Fully Connected Layers:
- Flatten the output from the convolutional layers.
- First fully connected layer with 128 units and ReLU activation.
- Final fully connected layer with 10 units for classification (one for each class in SVHN).
# utilities to serde with safetensors
def save_pretrained(model, name='model', *, base_path='./model'):
os.makedirs(base_path, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Create filename with model name and timestamp
filename = f'{name}_{timestamp}.safetensors'
filepath = os.path.join(base_path, filename)
# Save the model
save_model(model, filepath)
print(f'Model saved to {filepath}')
return filepath
class PretrainedMixin:
@classmethod
def from_pretrained(cls, filepath, device='cuda'):
model = cls().to(device)
load_model(model, filepath)
model.eval()
return model
def save_pretrained(self, base_path='./model'):
save_pretrained(self, name=self.__class__.__qualname__, base_path=base_path)
class SVHNClassifier(nn.Module, PretrainedMixin):
def __init__(self):
super(SVHNClassifier, self).__init__()
# not specified in spec, but add dropout for stability
self.convblock1 = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
self.convblock2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
self.convblock3 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# Calculate input size for the first fully connected layer
# Input image: 32x32
# After 3 max pooling layers (32 -> 16 -> 8 -> 4)
# With 128 channels: 128 * 4 * 4 = 2048
self.fc = nn.Sequential(nn.Linear(128 * 4 * 4, 128), nn.ReLU(), nn.Linear(128, 10))
def forward(self, x):
x = self.convblock1(x)
x = self.convblock2(x)
x = self.convblock3(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
B (15 points) Train the network¶
In the part, we are going to train the SVHN_CNN model to classify SVHN dataset.
- Finish implementing train and test functions.
- Include last epoch results for train/test loss and train/test accuracy in your report
- Plot the train/test losses and train/test accuracies using the plot_metrics function.
# Initialize model and move to device
model = SVHNClassifier().to(device)
# Define loss function and optimizer
criterion = nn.CrossEntropyLoss() # CrossEntropy includes softmax
if True:
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
if False:
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum, weight_decay=weight_decay)
# Define function to evaluate model performance
def evaluate(model, data_loader, criterion, epoch, num_epochs, *, ncols=100):
model.eval() # Set model to evaluation mode
total_loss = 0
correct = 0
total = 0
with (
torch.no_grad(),
tqdm(data_loader, desc=f'eval {epoch + 1}/{num_epochs}', ncols=ncols) as eval_data_loader,
): # No gradient calculation for evaluation
for data, targets in eval_data_loader:
# Move data to device
data = data.to(device)
targets = targets.to(device)
# Forward pass
outputs = model(data)
loss = criterion(outputs, targets)
# Accumulate loss
total_loss += loss.item() * data.size(0)
# Calculate accuracy
_, predicted = torch.max(outputs.data, 1)
total += targets.size(0)
correct += (predicted == targets).sum().item()
# Calculate average loss and accuracy
avg_loss = total_loss / total
accuracy = 100 * correct / total
return accuracy, avg_loss
# Training the model
train_losses, test_losses = [], []
train_accuracies, test_accuracies = [], []
# bar stuff
ncols = 100
for epoch in range(num_epochs):
model.train() # Set model to training mode
running_loss = 0.0
total = 0
correct = 0
# Training loop with tqdm progress bar
with tqdm(
enumerate(train_loader), total=len(train_loader), desc=f'epoch {epoch + 1}/{num_epochs}', ncols=ncols
) as batch_pbar:
for i, (data, targets) in batch_pbar:
# Move data to device
data = data.to(device)
targets = targets.to(device)
# Zero the gradients
optimizer.zero_grad()
# Forward pass
outputs = model(data)
loss = criterion(outputs, targets)
# Backward pass and optimize
loss.backward()
optimizer.step()
# Track training statistics
running_loss += loss.item() * data.size(0)
_, predicted = torch.max(outputs.data, 1)
total += targets.size(0)
correct += (predicted == targets).sum().item()
# Update batch progress bar
batch_pbar.set_postfix({'batch_loss': loss.item()})
# Calculate average training loss and accuracy for this epoch
train_loss = running_loss / total
train_accuracy = 100 * correct / total
# Evaluate on train and test sets after each epoch
test_accuracy, test_loss = evaluate(model, test_loader, criterion, epoch + 1, num_epochs, ncols=ncols)
# Print epoch statistics
print('-' * 60)
print(f'epoch {epoch + 1:02d}/{num_epochs:02d} - Train Loss: {train_loss:.6f}, Train Acc: {train_accuracy:.2f}%')
print(f' - Test Loss: {test_loss:.6f}, Test Acc: {test_accuracy:.2f}%')
print('-' * 60)
# Store losses and accuracies
train_losses.append(train_loss)
test_losses.append(test_loss)
train_accuracies.append(train_accuracy)
test_accuracies.append(test_accuracy)
epoch 1/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 91.35it/s, batch_loss=0.283] eval 2/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 114.90it/s]
------------------------------------------------------------ epoch 01/20 - Train Loss: 0.738209, Train Acc: 76.31% - Test Loss: 0.460860, Test Acc: 86.17% ------------------------------------------------------------
epoch 2/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 91.66it/s, batch_loss=0.448] eval 3/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 112.73it/s]
------------------------------------------------------------ epoch 02/20 - Train Loss: 0.363855, Train Acc: 89.01% - Test Loss: 0.348315, Test Acc: 89.72% ------------------------------------------------------------
epoch 3/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 91.11it/s, batch_loss=0.373] eval 4/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 114.52it/s]
------------------------------------------------------------ epoch 03/20 - Train Loss: 0.294560, Train Acc: 91.20% - Test Loss: 0.375101, Test Acc: 88.85% ------------------------------------------------------------
epoch 4/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 92.84it/s, batch_loss=0.072] eval 5/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 114.35it/s]
------------------------------------------------------------ epoch 04/20 - Train Loss: 0.249752, Train Acc: 92.66% - Test Loss: 0.316894, Test Acc: 90.64% ------------------------------------------------------------
epoch 5/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 93.01it/s, batch_loss=0.282] eval 6/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 112.55it/s]
------------------------------------------------------------ epoch 05/20 - Train Loss: 0.218510, Train Acc: 93.53% - Test Loss: 0.290605, Test Acc: 91.79% ------------------------------------------------------------
epoch 6/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 89.42it/s, batch_loss=0.249] eval 7/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 113.38it/s]
------------------------------------------------------------ epoch 06/20 - Train Loss: 0.193034, Train Acc: 94.37% - Test Loss: 0.387319, Test Acc: 88.32% ------------------------------------------------------------
epoch 7/20: 100%|██████████████████████████████| 1145/1145 [00:12<00:00, 90.50it/s, batch_loss=0.11] eval 8/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 112.97it/s]
------------------------------------------------------------ epoch 07/20 - Train Loss: 0.171169, Train Acc: 94.97% - Test Loss: 0.298162, Test Acc: 91.44% ------------------------------------------------------------
epoch 8/20: 100%|██████████████████████████████| 1145/1145 [00:12<00:00, 89.51it/s, batch_loss=0.22] eval 9/20: 100%|████████████████████████████████████████████████| 407/407 [00:03<00:00, 111.44it/s]
------------------------------------------------------------ epoch 08/20 - Train Loss: 0.150749, Train Acc: 95.62% - Test Loss: 0.325905, Test Acc: 90.91% ------------------------------------------------------------
epoch 9/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 91.04it/s, batch_loss=0.057] eval 10/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 115.42it/s]
------------------------------------------------------------ epoch 09/20 - Train Loss: 0.132484, Train Acc: 96.16% - Test Loss: 0.293228, Test Acc: 91.89% ------------------------------------------------------------
epoch 10/20: 100%|████████████████████████████| 1145/1145 [00:12<00:00, 92.75it/s, batch_loss=0.159] eval 11/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 115.97it/s]
------------------------------------------------------------ epoch 10/20 - Train Loss: 0.115858, Train Acc: 96.65% - Test Loss: 0.312531, Test Acc: 91.62% ------------------------------------------------------------
epoch 11/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 91.81it/s, batch_loss=0.0474] eval 12/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 114.73it/s]
------------------------------------------------------------ epoch 11/20 - Train Loss: 0.098826, Train Acc: 97.15% - Test Loss: 0.304291, Test Acc: 92.22% ------------------------------------------------------------
epoch 12/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 89.11it/s, batch_loss=0.17] eval 13/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 114.80it/s]
------------------------------------------------------------ epoch 12/20 - Train Loss: 0.086760, Train Acc: 97.44% - Test Loss: 0.329752, Test Acc: 91.54% ------------------------------------------------------------
epoch 13/20: 100%|█████████████████████████████| 1145/1145 [00:12<00:00, 90.16it/s, batch_loss=0.13] eval 14/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 115.73it/s]
------------------------------------------------------------ epoch 13/20 - Train Loss: 0.075698, Train Acc: 97.75% - Test Loss: 0.328805, Test Acc: 91.87% ------------------------------------------------------------
epoch 14/20: 100%|███████████████████████████| 1145/1145 [00:13<00:00, 86.67it/s, batch_loss=0.0395] eval 15/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 113.70it/s]
------------------------------------------------------------ epoch 14/20 - Train Loss: 0.064567, Train Acc: 98.08% - Test Loss: 0.330988, Test Acc: 91.90% ------------------------------------------------------------
epoch 15/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 91.50it/s, batch_loss=0.0425] eval 16/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 114.75it/s]
------------------------------------------------------------ epoch 15/20 - Train Loss: 0.052779, Train Acc: 98.51% - Test Loss: 0.361344, Test Acc: 91.63% ------------------------------------------------------------
epoch 16/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 92.88it/s, batch_loss=0.0297] eval 17/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 116.03it/s]
------------------------------------------------------------ epoch 16/20 - Train Loss: 0.044054, Train Acc: 98.78% - Test Loss: 0.373459, Test Acc: 91.55% ------------------------------------------------------------
epoch 17/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 89.22it/s, batch_loss=0.0369] eval 18/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 116.14it/s]
------------------------------------------------------------ epoch 17/20 - Train Loss: 0.035443, Train Acc: 99.04% - Test Loss: 0.371599, Test Acc: 92.22% ------------------------------------------------------------
epoch 18/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 92.12it/s, batch_loss=0.0643] eval 19/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 111.15it/s]
------------------------------------------------------------ epoch 18/20 - Train Loss: 0.027670, Train Acc: 99.29% - Test Loss: 0.429213, Test Acc: 91.15% ------------------------------------------------------------
epoch 19/20: 100%|███████████████████████████| 1145/1145 [00:12<00:00, 89.28it/s, batch_loss=0.0241] eval 20/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 113.56it/s]
------------------------------------------------------------ epoch 19/20 - Train Loss: 0.024244, Train Acc: 99.34% - Test Loss: 0.415622, Test Acc: 91.67% ------------------------------------------------------------
epoch 20/20: 100%|████████████████████████████| 1145/1145 [00:12<00:00, 91.23it/s, batch_loss=0.012] eval 21/20: 100%|███████████████████████████████████████████████| 407/407 [00:03<00:00, 113.90it/s]
------------------------------------------------------------ epoch 20/20 - Train Loss: 0.025504, Train Acc: 99.22% - Test Loss: 0.410858, Test Acc: 91.92% ------------------------------------------------------------
def plot_metrics(train_metrics, test_metrics, metric_name):
plt.figure(figsize=(8, 6))
epochs = np.arange(len(train_metrics))
plt.plot(epochs, train_metrics, label=f'Train {metric_name}', color='blue')
plt.plot(epochs, test_metrics, label=f'Test {metric_name}', color='red')
plt.xlabel('Epochs')
plt.ylabel(metric_name)
plt.title(f'{metric_name} over Epochs')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# Plot loss
plot_metrics(train_losses, test_losses, 'Loss')
# Plot accuracy
plot_metrics(train_accuracies, test_accuracies, 'Accuracy')
if False:
model.save_pretrained()
Task 2: SVHN Denoising with Convolution Neural Network (CNN) (45 points) ¶
Convolutional Neural Network (CNN) have also been quite successful in the field of image processing. In this part, you are asked to finish the implementation of the CNN model and use the model to denoise images from SVHN.
The objective of tasks in the assignment is to train a network that, given a noisy image, recovers the original image. Therefore, each training point consists of the input (noisy image) and the expected output (true image).
Again, we remove these variables ensures available memory for later code and helps avoid conflicts with similar names.
del (
device,
batch_size,
learning_rate,
num_epochs,
train_dataset,
test_dataset,
train_loader,
test_loader,
transform,
SVHNClassifier,
model,
criterion,
optimizer,
evaluate,
train_losses,
test_losses,
train_accuracies,
test_accuracies,
show_images_grid,
classes,
)
# Set up device for training (use GPU if available)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'Using device: {device}')
Using device: cuda
Create the Noisy dataset¶
Step 1: Defining a Custom Noise Transformation¶
First, we define a custom transformation that applies noise to each image by randomly dropping pixels. This transformation will be used to generate a noisy version of every image in the dataset.
# Custom transformation to add noise by dropping pixels with a given probability for each channel independently
class AddNoiseTransform:
def __init__(self, drop_prob=0.1):
self.drop_prob = drop_prob
def __call__(self, img):
channel_mask = (torch.rand(img.shape) > self.drop_prob).float()
noisy_img = img * channel_mask
return noisy_img
Step 2: Creating a Dataset of Paired Images: Original and Noisy¶
We define a dataset that pairs each original image with a corresponding noisy image. This dataset is structured so that each data sample includes both the clean image (as the label) and the noisified version (as the feature). We then create PyTorch dataloaders where each element contains a pair of original and noisy images.
# PairDataset class that returns a pair of images: (original, noisy)
class PairDataset(Dataset):
def __init__(self, dataset_origin, dataset_noisy):
assert len(dataset_origin) == len(dataset_noisy), 'Datasets must be of the same length'
self.dataset_origin = dataset_origin
self.dataset_noisy = dataset_noisy
def __getitem__(self, index):
clean_img, _ = self.dataset_origin[index]
noisy_img, _ = self.dataset_noisy[index]
return clean_img, noisy_img
def __len__(self):
return len(self.dataset_origin)
# Function to create paired dataset for specific classes
def create_paired_dataloader(classes, num_samples=1500, batch_size=64, drop_prob=0.5, split='train'):
"""
Creates a DataLoader for a paired dataset with noisy and clean images from specified classes.
Parameters:
- classes (list): List of class labels to include (e.g., [0, 1, 2, 3, 4] or [5, 6, 7, 8, 9]).
- num_samples (int): Number of samples to use from the filtered classes.
- batch_size (int): Batch size for the DataLoader.
- drop_prob (float): Drop probability for AddNoiseTransform.
- split (str): Dataset split to load ('train' or 'test').
Returns:
- DataLoader: A DataLoader for the paired dataset.
"""
# Define transformations
transform_clean = transforms.ToTensor()
transform_noisy = transforms.Compose([transforms.ToTensor(), AddNoiseTransform(drop_prob=drop_prob)])
# Load the specified dataset split
dataset_clean_full = datasets.SVHN(root='./data', split=split, download=True, transform=transform_clean)
dataset_noisy_full = datasets.SVHN(root='./data', split=split, download=True, transform=transform_noisy)
# Filter to include only samples with specified classes
indices = [i for i, (_, label) in enumerate(dataset_clean_full) if label in classes]
# Take only the first `num_samples` samples after filtering
subset_indices = indices[:num_samples]
# Create clean and noisy subsets
dataset_clean = Subset(dataset_clean_full, subset_indices)
dataset_noisy = Subset(dataset_noisy_full, subset_indices)
# Create the paired dataset
paired_dataset = PairDataset(dataset_clean, dataset_noisy)
# DataLoader for paired dataset
paired_dataloader = DataLoader(paired_dataset, batch_size=batch_size, shuffle=True)
return paired_dataloader
Task A (10 points): Implementing a CNN for Image Denoising¶
In this task, you will implement a Convolutional Neural Network (CNN) to map noisy images to their original, clean versions. This architecture consists of two convolutional layers.
Architecture Details¶
First Convolutional Layer
- Setup:
kernel_size=3
,padding=1
,stride=1
to maintain image dimensions. - Input: 32x32x3 images.
- Output: Feature maps of size 32x32x30.
- Activation: Apply ReLU for non-linearity.
- Setup:
Second Convolutional Layer
- Setup: Same kernel size, padding, and stride as above.
- Output: 32x32x3 to match the original image dimensions.
- Activation: Sigmoid to constrain values between 0 and 1.
Hyperparameters
input_channels=3
,output_channels=3
,feature_maps=30
.
Implementation Requirements¶
Using these guidelines, implement the CNN model in PyTorch with torch.nn.Conv2d
for the layers, and torch.nn.ReLU
and torch.nn.Sigmoid
for activations.
class ImageDenoisingCNN(nn.Module, PretrainedMixin):
def __init__(self):
super(ImageDenoisingCNN, self).__init__()
# First Convolutional Layer
# Input: 32x32x3 -> Output: 32x32x30
self.conv1 = nn.Conv2d(in_channels=3, out_channels=30, kernel_size=3, padding=1, stride=1)
self.relu = nn.ReLU()
# Second Convolutional Layer
# Input: 32x32x30 -> Output: 32x32x3
self.conv2 = nn.Conv2d(in_channels=30, out_channels=3, kernel_size=3, padding=1, stride=1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
# First conv layer with ReLU
x = self.conv1(x)
x = self.relu(x)
# Second conv layer with Sigmoid
x = self.conv2(x)
x = self.sigmoid(x)
return x
To understand the model’s complexity, we can calculate the total number of trainable parameters in ImageDenoisingCNN. Each parameter contributes to the model's ability to learn patterns in the data.
model = ImageDenoisingCNN()
# Calculate and print the number of trainable parameters
num_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print('Total number of trainable parameters in ImageDenoisingCNN:', num_params)
Total number of trainable parameters in ImageDenoisingCNN: 1653
Task B (20 points): Denoising SVHN Images¶
This task involves building and evaluating a model that can remove noise from SVHN images in the first 5 classes (0 through 4). The model is trained and tested on a dataset of noisy images paired with their clean counterparts, using only 1,500 samples per class.
Objectives:
- Complete the training function: This function should return the average training and test losses for each epoch.
- Complete the testing function: This function should compute the average test loss across all batches.
- Denoise SVHN Images: Train the model to denoise images from the first 5 classes.
Include the last epoch train/test values and last epoch sample images in your report.
# Display a single row of images with specified columns
def show_images_grid2(images, title, cols):
fig, axes = plt.subplots(1, cols, figsize=(cols * 2, 2))
if cols == 1:
axes = [axes]
for idx in range(cols):
if idx < len(images):
img = images[idx].numpy().transpose((1, 2, 0))
axes[idx].imshow(img)
axes[idx].set_title(title)
axes[idx].axis('off')
plt.tight_layout()
plt.show()
def train(train_loader, test_loader, model, epochs, loss_function, optimizer, device='cuda'):
"""
Train the model on the training dataset and evaluate it on the test dataset.
"""
# Move model to the specified device
model = model.to(device)
train_loss_epochs = []
test_loss_epochs = []
for epoch in range(epochs):
model.train()
train_loss_batches = []
# Use context manager for batch progress bar
with tqdm(
enumerate(train_loader), total=len(train_loader), desc=f'epoch {epoch + 1}/{epochs}', ncols=100
) as batch_pbar:
for batch_idx, (clean_images, noisy_images) in batch_pbar:
# Move data to device
clean_images = clean_images.to(device)
noisy_images = noisy_images.to(device)
# Zero the gradients
optimizer.zero_grad()
# Forward pass
denoised_images = model(noisy_images)
loss = loss_function(denoised_images, clean_images)
# Backward pass and optimize
loss.backward()
optimizer.step()
# Track batch loss
train_loss_batches.append(loss.item())
batch_pbar.set_postfix({'batch_loss': loss.item()})
# Display sample results every 5 epochs, at the last batch
if epoch % 5 == 0 and batch_idx == len(train_loader) - 1:
show_images_grid2(clean_images[:5].detach().cpu(), title='Clean', cols=5)
show_images_grid2(noisy_images[:5].detach().cpu(), title='Noisy', cols=5)
show_images_grid2(denoised_images[:5].detach().cpu(), title='Denoised', cols=5)
# Calculate average training loss for the epoch
train_loss_epoch = np.mean(train_loss_batches)
train_loss_epochs.append(train_loss_epoch)
# Evaluate model on test set
test_loss_epoch = evaluate(test_loader, model, loss_function, epoch + 1, num_epochs, device=device)
test_loss_epochs.append(test_loss_epoch)
return train_loss_epochs, test_loss_epochs
def evaluate(dataloader, model, loss_function, epoch, num_epochs, device='cuda'):
"""
Evaluate the model on the test dataset and return the average loss.
"""
model.eval()
test_losses = []
with torch.no_grad():
with tqdm(dataloader, desc=f'eval {epoch}/{num_epochs}', ncols=100) as eval_pbar:
for clean_images, noisy_images in eval_pbar:
# Move data to device
clean_images = clean_images.to(device)
noisy_images = noisy_images.to(device)
# Forward pass
denoised_images = model(noisy_images)
loss = loss_function(denoised_images, clean_images)
# Track batch loss
test_losses.append(loss.item())
return np.mean(test_losses)
learning_rate = 0.05
batch_size = 64
drop_rate = 0.3
num_samples = 1500
num_epochs = 100
# Setup code
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Initialize model, criterion, and optimizer
model = ImageDenoisingCNN().to(device)
criterion = nn.MSELoss() # Mean Squared Error loss for image reconstruction
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# Create data loaders for first 5 classes (0-4)
first_five_classes = list(range(5)) # [0, 1, 2, 3, 4]
paired_trainloader_first_five = create_paired_dataloader(
classes=first_five_classes, num_samples=num_samples, batch_size=batch_size, drop_prob=drop_rate, split='train'
)
paired_testloader_first_five = create_paired_dataloader(
classes=first_five_classes, num_samples=num_samples, batch_size=batch_size, drop_prob=drop_rate, split='test'
)
# Train the model
print('Starting training...')
train_loss_epochs, test_loss_epochs = train(
paired_trainloader_first_five, paired_testloader_first_five, model, num_epochs, criterion, optimizer, device
)
Using downloaded and verified file: ./data/train_32x32.mat Using downloaded and verified file: ./data/train_32x32.mat Using downloaded and verified file: ./data/test_32x32.mat Using downloaded and verified file: ./data/test_32x32.mat Starting training...
epoch 1/100: 88%|███████████████████████████▏ | 21/24 [00:00<00:00, 67.47it/s, batch_loss=0.0138]
epoch 1/100: 100%|███████████████████████████████| 24/24 [00:00<00:00, 34.06it/s, batch_loss=0.0138] eval 1/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.86it/s] epoch 2/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.86it/s, batch_loss=0.00674] eval 2/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.98it/s] epoch 3/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 68.84it/s, batch_loss=0.00603] eval 3/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.88it/s] epoch 4/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.63it/s, batch_loss=0.00606] eval 4/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.44it/s] epoch 5/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.30it/s, batch_loss=0.00501] eval 5/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.14it/s] epoch 6/100: 88%|██████████████████████████▎ | 21/24 [00:00<00:00, 69.40it/s, batch_loss=0.00683]
epoch 6/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 34.39it/s, batch_loss=0.00683] eval 6/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.16it/s] epoch 7/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 67.89it/s, batch_loss=0.00721] eval 7/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.22it/s] epoch 8/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.38it/s, batch_loss=0.00439] eval 8/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.08it/s] epoch 9/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.79it/s, batch_loss=0.00523] eval 9/100: 100%|██████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.28it/s] epoch 10/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.22it/s, batch_loss=0.00595] eval 10/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.78it/s] epoch 11/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 68.39it/s, batch_loss=0.00472]
epoch 11/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.40it/s, batch_loss=0.00472] eval 11/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.16it/s] epoch 12/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.70it/s, batch_loss=0.00628] eval 12/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.32it/s] epoch 13/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.69it/s, batch_loss=0.00538] eval 13/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.19it/s] epoch 14/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.71it/s, batch_loss=0.00437] eval 14/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.43it/s] epoch 15/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 70.75it/s, batch_loss=0.0044] eval 15/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.31it/s] epoch 16/100: 92%|██████████████████████████▌ | 22/24 [00:00<00:00, 69.51it/s, batch_loss=0.00433]
epoch 16/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.89it/s, batch_loss=0.00433] eval 16/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.53it/s] epoch 17/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.90it/s, batch_loss=0.00498] eval 17/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.70it/s] epoch 18/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.07it/s, batch_loss=0.00435] eval 18/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.04it/s] epoch 19/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.30it/s, batch_loss=0.00425] eval 19/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.67it/s] epoch 20/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.51it/s, batch_loss=0.00375] eval 20/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.25it/s] epoch 21/100: 67%|████████████████████ | 16/24 [00:00<00:00, 70.11it/s, batch_loss=0.0033]
epoch 21/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 34.96it/s, batch_loss=0.0033] eval 21/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.22it/s] epoch 22/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.83it/s, batch_loss=0.00432] eval 22/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.70it/s] epoch 23/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.46it/s, batch_loss=0.00363] eval 23/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.22it/s] epoch 24/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.51it/s, batch_loss=0.00387] eval 24/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.92it/s] epoch 25/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.31it/s, batch_loss=0.00339] eval 25/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.97it/s] epoch 26/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 68.08it/s, batch_loss=0.00419]
epoch 26/100: 100%|█████████████████████████████| 24/24 [00:01<00:00, 21.25it/s, batch_loss=0.00419] eval 26/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 72.61it/s] epoch 27/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 61.17it/s, batch_loss=0.00372] eval 27/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 71.89it/s] epoch 28/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.79it/s, batch_loss=0.00317] eval 28/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.65it/s] epoch 29/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 67.36it/s, batch_loss=0.00296] eval 29/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.80it/s] epoch 30/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.64it/s, batch_loss=0.00253] eval 30/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.53it/s] epoch 31/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 69.38it/s, batch_loss=0.00385]
epoch 31/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.47it/s, batch_loss=0.00385] eval 31/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.06it/s] epoch 32/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.42it/s, batch_loss=0.0049] eval 32/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.53it/s] epoch 33/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.00it/s, batch_loss=0.00414] eval 33/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.90it/s] epoch 34/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.02it/s, batch_loss=0.00431] eval 34/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.77it/s] epoch 35/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.26it/s, batch_loss=0.00469] eval 35/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 76.89it/s] epoch 36/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 65.60it/s, batch_loss=0.00452]
epoch 36/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 33.07it/s, batch_loss=0.00452] eval 36/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 73.71it/s] epoch 37/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 60.16it/s, batch_loss=0.00346] eval 37/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 74.22it/s] epoch 38/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 60.31it/s, batch_loss=0.00322] eval 38/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 76.56it/s] epoch 39/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.06it/s, batch_loss=0.00401] eval 39/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.02it/s] epoch 40/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.78it/s, batch_loss=0.00301] eval 40/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.72it/s] epoch 41/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 68.81it/s, batch_loss=0.00295]
epoch 41/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.45it/s, batch_loss=0.00295] eval 41/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.82it/s] epoch 42/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.76it/s, batch_loss=0.00298] eval 42/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.64it/s] epoch 43/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.02it/s, batch_loss=0.00276] eval 43/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.32it/s] epoch 44/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.59it/s, batch_loss=0.00411] eval 44/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.92it/s] epoch 45/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.99it/s, batch_loss=0.00257] eval 45/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.98it/s] epoch 46/100: 67%|███████████████████▎ | 16/24 [00:00<00:00, 70.07it/s, batch_loss=0.00373]
epoch 46/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.92it/s, batch_loss=0.00373] eval 46/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.89it/s] epoch 47/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.43it/s, batch_loss=0.00356] eval 47/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.18it/s] epoch 48/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.79it/s, batch_loss=0.00337] eval 48/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.94it/s] epoch 49/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.41it/s, batch_loss=0.00293] eval 49/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.98it/s] epoch 50/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.83it/s, batch_loss=0.00311] eval 50/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.95it/s] epoch 51/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 64.14it/s, batch_loss=0.00317]
epoch 51/100: 100%|█████████████████████████████| 24/24 [00:01<00:00, 23.04it/s, batch_loss=0.00317] eval 51/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.59it/s] epoch 52/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 61.97it/s, batch_loss=0.00324] eval 52/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.42it/s] epoch 53/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.93it/s, batch_loss=0.00438] eval 53/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.70it/s] epoch 54/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.73it/s, batch_loss=0.00321] eval 54/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.74it/s] epoch 55/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.55it/s, batch_loss=0.00262] eval 55/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.24it/s] epoch 56/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 69.88it/s, batch_loss=0.00385]
epoch 56/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.66it/s, batch_loss=0.00385] eval 56/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.14it/s] epoch 57/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 70.57it/s, batch_loss=0.0028] eval 57/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.32it/s] epoch 58/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 65.61it/s, batch_loss=0.0032] eval 58/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 74.31it/s] epoch 59/100: 100%|███████████████████████████████| 24/24 [00:00<00:00, 64.09it/s, batch_loss=0.003] eval 59/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.15it/s] epoch 60/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 67.10it/s, batch_loss=0.00395] eval 60/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.20it/s] epoch 61/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 65.46it/s, batch_loss=0.00316]
epoch 61/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 32.73it/s, batch_loss=0.00316] eval 61/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 69.58it/s] epoch 62/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 65.07it/s, batch_loss=0.00292] eval 62/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.57it/s] epoch 63/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.97it/s, batch_loss=0.00304] eval 63/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.80it/s] epoch 64/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.71it/s, batch_loss=0.00285] eval 64/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.89it/s] epoch 65/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.69it/s, batch_loss=0.00426] eval 65/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.82it/s] epoch 66/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 69.31it/s, batch_loss=0.00322]
epoch 66/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.63it/s, batch_loss=0.00322] eval 66/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.66it/s] epoch 67/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.81it/s, batch_loss=0.00579] eval 67/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.30it/s] epoch 68/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 70.73it/s, batch_loss=0.0031] eval 68/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.68it/s] epoch 69/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.20it/s, batch_loss=0.00303] eval 69/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.71it/s] epoch 70/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.00it/s, batch_loss=0.00304] eval 70/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.81it/s] epoch 71/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 67.98it/s, batch_loss=0.00338]
epoch 71/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.26it/s, batch_loss=0.00338] eval 71/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 75.27it/s] epoch 72/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.11it/s, batch_loss=0.00328] eval 72/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.81it/s] epoch 73/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.41it/s, batch_loss=0.00291] eval 73/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.19it/s] epoch 74/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 68.99it/s, batch_loss=0.00306] eval 74/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.13it/s] epoch 75/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 69.18it/s, batch_loss=0.0036] eval 75/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.43it/s] epoch 76/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 64.78it/s, batch_loss=0.00286]
epoch 76/100: 100%|█████████████████████████████| 24/24 [00:01<00:00, 21.61it/s, batch_loss=0.00286] eval 76/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 75.75it/s] epoch 77/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.01it/s, batch_loss=0.00471] eval 77/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 75.53it/s] epoch 78/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.14it/s, batch_loss=0.00326] eval 78/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.46it/s] epoch 79/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 66.64it/s, batch_loss=0.00291] eval 79/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.37it/s] epoch 80/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 67.80it/s, batch_loss=0.00283] eval 80/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.52it/s] epoch 81/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 66.44it/s, batch_loss=0.00301]
epoch 81/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 33.69it/s, batch_loss=0.00301] eval 81/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.37it/s] epoch 82/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.24it/s, batch_loss=0.00293] eval 82/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.74it/s] epoch 83/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 71.67it/s, batch_loss=0.0032] eval 83/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.34it/s] epoch 84/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.82it/s, batch_loss=0.00299] eval 84/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.28it/s] epoch 85/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.19it/s, batch_loss=0.00303] eval 85/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.11it/s] epoch 86/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 68.25it/s, batch_loss=0.00327]
epoch 86/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.32it/s, batch_loss=0.00327] eval 86/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.51it/s] epoch 87/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 70.87it/s, batch_loss=0.0033] eval 87/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.99it/s] epoch 88/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.58it/s, batch_loss=0.00403] eval 88/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.21it/s] epoch 89/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.02it/s, batch_loss=0.00315] eval 89/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.74it/s] epoch 90/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.51it/s, batch_loss=0.00399] eval 90/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.65it/s] epoch 91/100: 88%|█████████████████████████▍ | 21/24 [00:00<00:00, 68.47it/s, batch_loss=0.00338]
epoch 91/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 34.45it/s, batch_loss=0.00338] eval 91/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.15it/s] epoch 92/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.35it/s, batch_loss=0.00362] eval 92/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 77.68it/s] epoch 93/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.29it/s, batch_loss=0.00276] eval 93/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.67it/s] epoch 94/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.04it/s, batch_loss=0.00315] eval 94/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.91it/s] epoch 95/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 71.90it/s, batch_loss=0.00315] eval 95/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.64it/s] epoch 96/100: 92%|███████████████████████████▌ | 22/24 [00:00<00:00, 69.72it/s, batch_loss=0.0027]
epoch 96/100: 100%|██████████████████████████████| 24/24 [00:00<00:00, 34.90it/s, batch_loss=0.0027] eval 96/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 80.53it/s] epoch 97/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.63it/s, batch_loss=0.00307] eval 97/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.39it/s] epoch 98/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 69.79it/s, batch_loss=0.00271] eval 98/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.21it/s] epoch 99/100: 100%|█████████████████████████████| 24/24 [00:00<00:00, 70.38it/s, batch_loss=0.00367] eval 99/100: 100%|█████████████████████████████████████████████████| 24/24 [00:00<00:00, 79.09it/s] epoch 100/100: 100%|████████████████████████████| 24/24 [00:00<00:00, 70.95it/s, batch_loss=0.00302] eval 100/100: 100%|████████████████████████████████████████████████| 24/24 [00:00<00:00, 78.81it/s]
Task C (5 points): Plotting Training and Testing Losses Over Epochs¶
To visualize the learning process of your model, plot the training and testing losses over each epoch. This allows you to evaluate model performance over time and helps identify potential issues, such as overfitting or underfitting.
Include this plot in your report.
# Create the plot
plt.figure(figsize=(10, 6))
# Plot training and test losses
epochs = range(1, len(train_loss_epochs) + 1)
plt.plot(epochs, train_loss_epochs, label='Training Loss', color='blue', linestyle='-')
plt.plot(epochs, test_loss_epochs, label='Test Loss', color='red', linestyle='-')
# Customize the plot
plt.title('Training and Test Losses Over Time', fontsize=14, pad=15)
plt.xlabel('Epochs', fontsize=12)
plt.ylabel('Loss (MSE)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=10)
# Add minor gridlines
plt.minorticks_on()
plt.grid(True, which='minor', linestyle=':', alpha=0.4)
# Adjust layout and display
plt.tight_layout()
plt.show()
# Print final losses
print(f'Final Training Loss: {train_loss_epochs[-1]:.6f}')
print(f'Final Test Loss: {test_loss_epochs[-1]:.6f}')
Final Training Loss: 0.003326 Final Test Loss: 0.003811
Task D (10 points): Denoising Last 5 Classes, Reporting Loss, and Visualization¶
Use your model to denoise images from classes 5-9 of the SVHN dataset. Report the test loss and visualize clean, noisy, and denoised images side by side. Include these results in your report.
# Load test data for classes 5-9 with 1,500 samples per class
last_five_classes = list(range(5, 10)) # [5, 6, 7, 8, 9]
paired_testloader_last_five = create_paired_dataloader(
classes=last_five_classes, num_samples=num_samples, batch_size=batch_size, drop_prob=drop_rate, split='test'
)
print(f'Number of batches in paired_testloader_last_five: {len(paired_testloader_last_five)}')
# Evaluate model on the last 5 classes test set
model.eval()
test_loss = 0
total_batches = 0
# Store some examples for visualization
example_clean = None
example_noisy = None
example_denoised = None
with torch.no_grad():
for batch_idx, (clean_images, noisy_images) in enumerate(paired_testloader_last_five):
# Move images to device
clean_images = clean_images.to(device)
noisy_images = noisy_images.to(device)
# Get model predictions
denoised_images = model(noisy_images)
# Calculate loss
batch_loss = criterion(denoised_images, clean_images)
test_loss += batch_loss.item()
total_batches += 1
# Store the first batch for visualization
if batch_idx == 0:
example_clean = clean_images
example_noisy = noisy_images
example_denoised = denoised_images
# Calculate average test loss
average_test_loss = test_loss / total_batches
print(f'Average Test Loss on classes 5-9: {average_test_loss:.6f}')
Using downloaded and verified file: ./data/test_32x32.mat Using downloaded and verified file: ./data/test_32x32.mat Number of batches in paired_testloader_last_five: 24 Average Test Loss on classes 5-9: 0.003754
# Move images to CPU for visualization and detach from computational graph
example_clean = example_clean.cpu().detach()
example_noisy = example_noisy.cpu().detach()
example_denoised = example_denoised.cpu().detach()
show_images_grid2(example_clean[:5], title='Clean Images', cols=5)
show_images_grid2(example_noisy[:5], title='Noisy Images', cols=5)
show_images_grid2(example_denoised[:5], title='Denoised Images', cols=5)
plt.tight_layout()
plt.show()
# Save example images if needed
save_path = 'denoising_examples_last_five.png'
plt.savefig(save_path)
print(f'Example images saved to {save_path}')
<Figure size 640x480 with 0 Axes>
Example images saved to denoising_examples_last_five.png
<Figure size 640x480 with 0 Axes>
Task 3: Kaggle Competition (25 + 20 Bonus Points) ¶
For this task, you will participate in a Kaggle competition. Follow the steps below to maximize your performance and gain additional points:
Account Setup: Create a Kaggle account if you don’t already have one, and log in. Make sure to include your Kaggle username in both your report and code submission for verification purposes.
Competition Details: Access the competition at the link provided below and thoroughly read the description and requirements. Implement your solution in a separate file (not in this notebook) and aim to achieve the highest score possible on the leaderboard.
Submission on A2L: Ensure your A2L submission includes:
- Final Code: Upload your final code file used for the Kaggle competition.
- Report (
kaggle.pdf
): Provide a detailed report explaining your model and approach, along with the code used in your last Kaggle submission.- Start the report with your final accuracy score.
- Clearly mention your Kaggle username for verification.
- Submission File (
submission.csv
): Upload the finalsubmission.csv
file used in the competition as a record of your performance.
Bonus Points: Additional points will be awarded to the top-performing students on the competition leaderboard.