Explainable AI for Medical Decision Support
Module: Healthcare AI | Difficulty: Advanced
SHAP Value
LIME Explanation
Grad-CAM
where
Explainability Methods Comparison
| Method | Type | Fidelity | Stability | Speed | |--------|------|----------|-----------|-------| | SHAP | Model-agnostic | High | High | Slow | | LIME | Model-agnostic | Medium | Medium | Medium | | Grad-CAM | Gradient-based | Medium | Low | Fast | | Attention | Architecture-based | Low | Medium | Fast | | Counterfactual | Instance-based | High | High | Medium |
import torch
import torch.nn as nn
import numpy as np
class GradCAMExplainer:
def __init__(self, model, target_layer):
self.model = model
self.target_layer = target_layer
self.gradients = None
self.activations = None
target_layer.register_forward_hook(self._forward_hook)
target_layer.register_full_backward_hook(self._backward_hook)
def _forward_hook(self, module, input, output):
self.activations = output.detach()
def _backward_hook(self, module, grad_input, grad_output):
self.gradients = grad_output[0].detach()
def generate(self, input_tensor, target_class=None):
self.model.zero_grad()
output = self.model(input_tensor)
if target_class is None:
target_class = output.argmax(dim=1)
output[0, target_class].backward()
weights = self.gradients.mean(dim=[2, 3], keepdim=True)
cam = (weights * self.activations).sum(dim=1, keepdim=True)
cam = torch.relu(cam)
cam = cam / (cam.max() + 1e-8)
cam = nn.functional.interpolate(cam, size=input_tensor.shape[2:],
mode='bilinear')
return cam.squeeze().numpy()
class CounterfactualGenerator:
def __init__(self, model, learning_rate=0.01, max_iter=100):
self.model = model
self.lr = learning_rate
self.max_iter = max_iter
def generate(self, x, target_class):
x_cf = x.clone().detach().requires_grad_(True)
optimizer = torch.optim.Adam([x_cf], lr=self.lr)
for _ in range(self.max_iter):
optimizer.zero_grad()
pred = self.model(x_cf)
target_loss = -pred[0, target_class]
proximity_loss = ((x_cf - x) ** 2).sum()
loss = target_loss + 0.1 * proximity_loss
loss.backward()
optimizer.step()
return x_cf.detach()
def feature_importance_shap(model, x, num_samples=100):
baseline = torch.zeros_like(x)
importances = torch.zeros(x.shape[1])
for i in range(x.shape[1]):
with torch.no_grad():
pred_with = model(x)
pred_without = model(baseline.clone())
importances[i] = (pred_with - pred_without).item()
return importances / (importances.abs().sum() + 1e-8)
model = nn.Sequential(nn.Linear(100, 64), nn.ReLU(), nn.Linear(64, 10))
x = torch.randn(1, 100)
importances = feature_importance_shap(model, x, num_samples=50)
print(f'Top 5 features: {torch.topk(importances.abs(), 5).indices.tolist()}')
Research Insight: Explainability in medical AI faces a fundamental tension: clinically actionable explanations must be understandable to physicians, while faithful explanations may be complex. Gradient-based methods provide fast but often unreliable explanations, while perturbation methods are more faithful but computationally expensive. Hybrid approaches show promise for real-time clinical decision support.