Image Classification Pipelines
Building a complete image classification pipeline requires careful attention to data loading, augmentation, training loops, and evaluation.
Dataset and DataLoader
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import os
class ImageClassificationDataset(Dataset):
def __init__(self, root_dir, transform=None, is_train=True):
self.root_dir = root_dir
self.transform = transform
self.is_train = is_train
# Load class names
self.classes = sorted(os.listdir(root_dir))
self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)}
# Build file list
self.samples = []
for cls in self.classes:
cls_dir = os.path.join(root_dir, cls)
for img_name in os.listdir(cls_dir):
if img_name.endswith(('.jpg', '.png', '.jpeg')):
self.samples.append((os.path.join(cls_dir, img_name), self.class_to_idx[cls]))
def __len__(self):
return len(self.samples)
def __getitem__(self, idx):
img_path, label = self.samples[idx]
image = Image.open(img_path).convert('RGB')
if self.transform:
image = self.transform(image)
return image, label
Augmentation Pipeline
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
transforms.RandomRotation(15),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
Training Loop
def train_epoch(model, loader, criterion, optimizer, device):
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return running_loss / len(loader), 100. * correct / total
Validation and Evaluation
@torch.no_grad()
def validate(model, loader, criterion, device):
model.eval()
running_loss = 0.0
correct = 0
total = 0
all_preds = []
all_labels = []
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
all_preds.extend(predicted.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
return running_loss / len(loader), 100. * correct / total, all_preds, all_labels
Complete Training Script
def train_model(model, train_loader, val_loader, epochs, device, save_path):
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
best_acc = 0.0
for epoch in range(epochs):
train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
val_loss, val_acc, _, _ = validate(model, val_loader, criterion, device)
scheduler.step()
print(f"Epoch {epoch+1}/{epochs}")
print(f" Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%")
print(f" Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%")
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), save_path)
print(f" -> Saved best model (Acc: {best_acc:.2f}%)")
return model
Using Pre-trained Models
from torchvision import models
# Transfer learning with pre-trained ResNet
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
# Replace classifier
num_features = model.fc.in_features
model.fc = nn.Sequential(
nn.Dropout(0.3),
nn.Linear(num_features, 10) # 10 classes
)
# Freeze early layers
for param in model.conv1.parameters():
param.requires_grad = False
for param in model.layer1.parameters():
param.requires_grad = False
# Usage
train_model(model, train_loader, val_loader, epochs=30, device='cuda', save_path='best_model.pth')
A well-structured pipeline enables reproducible experiments and easy debugging.