import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
import numpy as np
import os
Low Rank Approximation Implementation 2/4
Model Compression
Singular Value Decomposition (SVD) - Resnet50
SVD (a mathematical technique) helps us approximate complex weight matrices with simpler ones, to compress the model’s convolutional layers. * Goal: Reduce the model’s size and speed up computations without significantly sacrificing its accuracy.
Download and Load the Dataset
Using the Imagenette2-320 dataset, which is a smaller version of the ImageNet dataset. It has images that belong to just 10 classes (e.g., different breeds of dogs, cats, etc.).
from torchvision.datasets.utils import download_and_extract_archive
# Download Imagenette2-320
= "https://s3.amazonaws.com/fast-ai-imageclas/imagenette2-320.tgz"
url = "./data"
root =root)
download_and_extract_archive(url, download_root
# Set dataset path
= os.path.join(root, "imagenette2-320") dataset_path
Using downloaded and verified file: ./data\imagenette2-320.tgz
Extracting ./data\imagenette2-320.tgz to ./data
Prepare the Dataset
# Data transformation: Resize, Convert to Tensor, Normalize
= transforms.Compose([
transform 224, 224)), # Resize images to 224x224
transforms.Resize((# Convert images to PyTorch tensors
transforms.ToTensor(), 0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), # Normalize as per ImageNet pre-training
transforms.Normalize([
])
# Load dataset
= datasets.ImageFolder(root=os.path.join(dataset_path, 'train'), transform=transform)
train_dataset = datasets.ImageFolder(root=os.path.join(dataset_path, 'val'), transform=transform)
valid_dataset
# Create DataLoaders
= DataLoader(train_dataset, batch_size=32, shuffle=True)
train_loader = DataLoader(valid_dataset, batch_size=32, shuffle=False) valid_loader
Load the Pre-trained ResNet-50 Model
# Load pre-trained ResNet-50 model
= models.resnet50(pretrained=True)
model
# Move model to device (CPU or GPU if available)
= torch.device('cuda' if torch.cuda.is_available() else 'cpu')
device = model.to(device) model
Apply SVD for Model Compression
This technique reduces the size of the model by approximating its convolutional layers (layers that detect image features like edges, shapes, etc.).
def svd_compress_conv_layer(conv_layer, rank):
# Get the weight tensor of the convolutional layer
= conv_layer.weight.data
weight = weight.shape
out_channels, in_channels, h, w
# Reshape the weight tensor to a 2D matrix of shape (out_channels, in_channels * h * w)
= weight.view(out_channels, -1)
weight_reshaped
# Apply SVD to the weight matrix
= torch.svd(weight_reshaped)
U, S, V
# Keep only the top `rank` singular values/vectors
= U[:, :rank]
U_reduced = S[:rank]
S_reduced = V[:, :rank]
V_reduced
# Construct the compressed weight matrix
= torch.mm(U_reduced, torch.diag(S_reduced))
compressed_weight = torch.mm(compressed_weight, V_reduced.t())
compressed_weight
# Reshape back to the original convolutional weight shape
= compressed_weight.view(out_channels, in_channels, h, w)
compressed_weight
# Replace the original weights with the compressed weights
= compressed_weight
conv_layer.weight.data
return conv_layer
Apply SVD to Each Convolutional Layer in ResNet-50
We apply SVD to every convolutional layer in the model. By keeping only the most important components (e.g., 20 components), we make each layer smaller and therefore reduce the entire model’s size.
def compress_resnet50(model, rank):
for name, module in model.named_modules():
if isinstance(module, nn.Conv2d):
print(f"Compressing layer: {name}")
= svd_compress_conv_layer(module, rank=rank)
compressed_layer setattr(model, name, compressed_layer)
# Compress the model with a reduced rank (e.g., keep top 20 components)
=20) compress_resnet50(model, rank
Compressing layer: conv1
Compressing layer: layer1.0.conv1
Compressing layer: layer1.0.conv2
Compressing layer: layer1.0.conv3
Compressing layer: layer1.0.downsample.0
Compressing layer: layer1.1.conv1
Compressing layer: layer1.1.conv2
Compressing layer: layer1.1.conv3
Compressing layer: layer1.2.conv1
Compressing layer: layer1.2.conv2
Compressing layer: layer1.2.conv3
Compressing layer: layer2.0.conv1
Compressing layer: layer2.0.conv2
Compressing layer: layer2.0.conv3
Compressing layer: layer2.0.downsample.0
Compressing layer: layer2.1.conv1
Compressing layer: layer2.1.conv2
Compressing layer: layer2.1.conv3
Compressing layer: layer2.2.conv1
Compressing layer: layer2.2.conv2
Compressing layer: layer2.2.conv3
Compressing layer: layer2.3.conv1
Compressing layer: layer2.3.conv2
Compressing layer: layer2.3.conv3
Compressing layer: layer3.0.conv1
Compressing layer: layer3.0.conv2
Compressing layer: layer3.0.conv3
Compressing layer: layer3.0.downsample.0
Compressing layer: layer3.1.conv1
Compressing layer: layer3.1.conv2
Compressing layer: layer3.1.conv3
Compressing layer: layer3.2.conv1
Compressing layer: layer3.2.conv2
Compressing layer: layer3.2.conv3
Compressing layer: layer3.3.conv1
Compressing layer: layer3.3.conv2
Compressing layer: layer3.3.conv3
Compressing layer: layer3.4.conv1
Compressing layer: layer3.4.conv2
Compressing layer: layer3.4.conv3
Compressing layer: layer3.5.conv1
Compressing layer: layer3.5.conv2
Compressing layer: layer3.5.conv3
Compressing layer: layer4.0.conv1
Compressing layer: layer4.0.conv2
Compressing layer: layer4.0.conv3
Compressing layer: layer4.0.downsample.0
Compressing layer: layer4.1.conv1
Compressing layer: layer4.1.conv2
Compressing layer: layer4.1.conv3
Compressing layer: layer4.2.conv1
Compressing layer: layer4.2.conv2
Compressing layer: layer4.2.conv3
Fine-Tune the Compressed Model
After compression, the model needs to be retrained slightly to adjust the weights.
Compression changes the weights significantly, which may affect the model’s accuracy. Fine-tuning helps adjust these weights and bring back the accuracy closer to its original value.
= nn.CrossEntropyLoss()
criterion = optim.Adam(model.parameters(), lr=0.001)
optimizer
= 3
num_epochs
model.train()
for epoch in range(num_epochs):
= 0.0
running_loss for images, labels in train_loader:
= images.to(device), labels.to(device)
images, labels
# Zero the parameter gradients
optimizer.zero_grad()
# Forward pass
= model(images)
outputs = criterion(outputs, labels)
loss
# Backward pass and optimize
loss.backward()
optimizer.step()
+= loss.item()
running_loss
print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}")
print("Training complete.")
Epoch [1/3], Loss: 1.4015
Epoch [2/3], Loss: 0.8525
Epoch [3/3], Loss: 0.6684
Training complete.
Evaluate the Compressed Model
eval()
model.= 0
correct = 0
total
with torch.no_grad():
for images, labels in valid_loader:
= images.to(device), labels.to(device)
images, labels = model(images)
outputs = torch.max(outputs.data, 1)
_, predicted += labels.size(0)
total += (predicted == labels).sum().item()
correct
print(f'Validation Accuracy after Compression: {100 * correct / total:.2f}%')
Validation Accuracy after Compression: 72.20%