Capstone Project 3: Image Classifier using Transfer Learning
Learn to build an Image Classification project in PyTorch. Download pre-trained ResNet, freeze layers, fine-tune on custom images, and host inference.
Introduction
In Module 9, we explored how Transfer Learning allows us to reuse pre-trained features. In this third capstone project, we will apply this technique to construct a Deep Learning Image Classifier in PyTorch.
We will write a complete pipeline that downloads a pre-trained ResNet-18 model, freezes its feature extraction layers, appends a custom classification head, and deploys it as a FastAPI web server. Crucially, we will write code that accepts binary image file uploads (e.g., JPEG or PNG) over HTTP, pre-processes the pixels into PyTorch tensors, and returns the predicted class label.
What You Will Learn
- Reusing torchvision pre-trained models.
- Processing incoming image file uploads in FastAPI.
- Resizing, cropping, and normalizing images using
torchvision.transforms. - Running GPU/CPU model inference.
Project Structure
Setup your folder layouts:
image-classifier/
│
├── models/
│ └── resnet18_custom.pt # Serialized PyTorch state weights
├── src/
│ ├── train.py # Downloads and modifies model
│ └── app.py # FastAPI image upload receiver
├── requirements.txt
└── Dockerfile
Step 1: Model Setup (src/train.py)
We will write the setup script to download ResNet-18 from torchvision, freeze its weights, swap its final fully connected layer, and save the model weights to our models/ directory.
# src/train.py
import os
import torch
import torch.nn as nn
from torchvision import models
def setup_and_save_model():
print("Step 1: Downloading pre-trained ResNet-18...")
# Load model with pre-trained weights
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
# 2. Freeze convolutional weights
for param in model.parameters():
param.requires_grad = False
# 3. Swap final layer
# ResNet-18 features 512 input channels. We will predict 2 classes (e.g., Cat vs Dog)
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, 2)
print("\nCustomized output classifier:")
print(model.fc)
# 4. Serialize Model Weights
os.makedirs("models", exist_ok=True)
# Save the entire model structure and weights
torch.save(model, "models/resnet18_custom.pt")
print("\nModel saved successfully to: models/resnet18_custom.pt")
if __name__ == "__main__":
setup_and_save_model()
Step 2: FastAPI Image Upload Server (src/app.py)
Web APIs do not receive image files as JSON strings. Instead, they accept files as multipart-form payloads. We will use the PIL (Python Imaging Library) to open raw bytes, and apply torchvision transforms to format the image (resizing it to $224 \times 224$ pixels and applying ImageNet normalization metrics).
# src/app.py
from fastapi import FastAPI, UploadFile, File, HTTPException
import torch
import torchvision.transforms as transforms
from PIL import Image
import io
import os
app = FastAPI(title="Deep Image Classification API", version="1.0")
# Load customized PyTorch model
model_path = "models/resnet18_custom.pt"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if os.path.exists(model_path):
model = torch.load(model_path, map_location=device)
model.eval() # Set model to evaluation mode
else:
model = None
# Class labels mapping
CLASS_LABELS = {0: "Cat", 1: "Dog"}
# Define ImageNet pre-processing transformation pipeline
transform_pipeline = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406], # ImageNet standard means
std=[0.229, 0.224, 0.225] # ImageNet standard deviations
)
])
@app.post("/predict")
async def predict_image(file: UploadFile = File(...)):
if model is None:
raise HTTPException(status_code=500, detail="Model file not found.")
# Verify file type
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="Uploaded file must be an image.")
try:
# 1. Read binary image bytes
image_bytes = await file.read()
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
# 2. Apply preprocessing transforms
tensor = transform_pipeline(image).unsqueeze(0) # Add batch dimension: [1, 3, 224, 224]
tensor = tensor.to(device)
# 3. Model Inference (disable gradient tracking for speed)
with torch.no_grad():
outputs = model(tensor)
probabilities = torch.softmax(outputs, dim=1)
prediction = torch.argmax(probabilities, dim=1).item()
confidence = float(probabilities[0][prediction].item())
predicted_class = CLASS_LABELS.get(prediction, "Unknown")
return {
"status": "success",
"filename": file.filename,
"prediction": predicted_class,
"confidence_score": round(confidence, 4),
"probabilities": {
CLASS_LABELS[0]: round(float(probabilities[0][0].item()), 4),
CLASS_LABELS[1]: round(float(probabilities[0][1].item()), 4)
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")
Execution & Verification
To execute and test your image classifier:
- Install torchvision, PyTorch, and pillow:
pip install torch torchvision pillow fastapi uvicorn python-multipart - Setup the Model Weights:
python src/train.py - Start the API:
uvicorn src.app:app --reload --port 8000 - Test using curl (Upload an actual image file):
Expected Response:curl -X POST "http://localhost:8000/predict" \ -F "file=@/path/to/your/dog_image.jpg"{ "status": "success", "filename": "dog_image.jpg", "prediction": "Dog", "confidence_score": 0.9841, "probabilities": { "Cat": 0.0159, "Dog": 0.9841 } }
Summary
In this final capstone project, we loaded a pre-trained ResNet-18 model using PyTorch, froze its convolutional layers, and attached a customized classification head. We then built a FastAPI server capable of parsing binary multipart-form image uploads, processing raw pixels through standard ImageNet resizing and normalization steps, and running forward pass predictions to output final classification labels.
Next Topic
You now have a portfolio of three production-grade projects. How do you prepare for placement exams and system design interviews to land a job? Let's check: AI and Machine Learning Interview Prep: Top 30 Q&A.