DEV Community

wellallyTech
wellallyTech

Posted on

Beyond Black-Box AI: Building an Explainable Skin Lesion Classifier with EfficientNet-V2 and Grad-CAM 🩺🔬

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]
Enter fullscreen mode Exit fullscreen mode

🛠️ 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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."
    }
Enter fullscreen mode Exit fullscreen mode

🌟 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:

  1. Transfer Learning allows us to leverage massive datasets (ImageNet) for specialized tasks like dermatology.
  2. PyTorch Lightning keeps our code clean and separate from the engineering boilerplate.
  3. 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)