In the world of Computer Vision and digital health, "accuracy" is only half the battle. When it comes to Skin Lesion Classification, a doctor doesn't just need to know what the model thinks; they need to know why it thinks that.
Today, we are bridging the gap between raw pixels and clinical trust. We will build a high-performance screening system using Transfer Learning with EfficientNet-V2, organized with PyTorch Lightning, and—most importantly—made transparent using Grad-CAM (Gradient-weighted Class Activation Mapping). This approach transforms a "black-box" neural network into an explainable assistant that highlights specific pathological regions.
🏗️ The System Architecture
Before diving into the code, let's look at the data flow. We are building a pipeline that takes a dermoscopic image and returns both a diagnostic prediction and a visual heatmap.
graph TD
A[User Uploads Skin Image] --> B[FastAPI Backend]
B --> C[Preprocessing & Normalization]
C --> D[EfficientNet-V2 Backbone]
D --> E[Global Average Pooling]
E --> F[Softmax Prediction]
D -.-> G[Grad-CAM Generator]
G --> H[Heatmap Overlay]
F --> I[Final Result Package]
H --> I
I --> J[Frontend UI / Doctor's Dashboard]
🛠️ Tech Stack & Prerequisites
- EfficientNet-V2: Our powerhouse feature extractor (optimized for speed and parameter efficiency).
- PyTorch Lightning: To keep our training code modular and scalable.
- Grad-CAM: To visualize the "attention" of our convolutional layers.
- FastAPI: To serve the model with high performance.
1. Defining the Explainable Classifier
We use EfficientNet-V2-S as our backbone. Why? Because it offers a better trade-off between accuracy and training speed compared to traditional ResNets, especially for medical images where fine details matter.
import torch
import torch.nn as nn
from torchvision import models
import pytorch_lightning as pl
class SkinLesionModel(pl.LightningModule):
def __init__(self, num_classes=7):
super().__init__()
# Load pre-trained EfficientNet-V2
self.backbone = models.efficientnet_v2_s(weights='DEFAULT')
# Modify the head for our specific number of skin lesion categories
in_features = self.backbone.classifier[1].in_features
self.backbone.classifier[1] = nn.Linear(in_features, num_classes)
self.criterion = nn.CrossEntropyLoss()
def forward(self, x):
return self.backbone(x)
def training_step(self, batch, batch_idx):
x, y = batch
logits = self(x)
loss = self.criterion(logits, y)
self.log("train_loss", loss)
return loss
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=1e-4)
2. Visualizing with Grad-CAM
Grad-CAM uses the gradients of any target concept (like the "Melanoma" class) flowing into the final convolutional layer to produce a localization map highlighting the important regions in the image.
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
from pytorch_grad_cam.utils.image import show_cam_on_image
def generate_explanation(model, input_tensor, target_category_idx):
# We target the last convolutional block of EfficientNet-V2
target_layers = [model.backbone.features[-1]]
cam = GradCAM(model=model, target_layers=target_layers)
# Generate the heatmap for the predicted class
targets = [ClassifierOutputTarget(target_category_idx)]
grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0, :]
return grayscale_cam
3. Serving via FastAPI
Now, let's wrap this in a production-ready API. We'll return the predicted class and the heatmap image encoded in base64.
from fastapi import FastAPI, UploadFile, File
import io
from PIL import Image
import torchvision.transforms as T
app = FastAPI()
model = SkinLesionModel.load_from_checkpoint("best_model.ckpt").eval()
preprocess = T.Compose([
T.Resize((224, 224)),
T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
@app.post("/predict")
async def predict(file: UploadFile = File(...)):
# 1. Load Image
image_bytes = await file.read()
image = Image.open(io.BytesIO(image_bytes)).convert('RGB')
input_tensor = preprocess(image).unsqueeze(0)
# 2. Inference
with torch.no_grad():
output = model(input_tensor)
probs = torch.nn.functional.softmax(output[0], dim=0)
pred_idx = torch.argmax(probs).item()
# 3. Explainability (Grad-CAM)
heatmap = generate_explanation(model, input_tensor, pred_idx)
return {
"prediction": pred_idx,
"confidence": float(probs[pred_idx]),
"message": "Heatmap generated successfully for clinical verification."
}
🌟 The "Official" Way to Build AI Systems
While this tutorial provides a solid foundation for local experimentation, moving medical AI to production requires rigorous handling of data privacy (HIPAA/GDPR), model versioning, and drift monitoring.
For more advanced patterns, production-ready AI architectures, and deep dives into the future of health-tech engineering, I highly recommend checking out the official guides at WellAlly Blog. They cover everything from high-performance GPU orchestration to building secure patient-data pipelines. 🥑
🚀 Conclusion
By combining EfficientNet-V2 with Grad-CAM, we've built more than just a classifier; we've built a transparent screening tool.
Key Takeaways:
- Transfer Learning allows us to leverage massive datasets (ImageNet) for specialized tasks like dermatology.
- PyTorch Lightning keeps our code clean and separate from the engineering boilerplate.
- Explainability is not optional in healthcare; it is a requirement for adoption.
What are you building with Vision models? Drop a comment below or share your latest project! Happy coding! 💻🔥
Top comments (0)