不依赖第三方库:手写Grad-CAM生成胸片热力图 一、Grad-CAM解决什么问题假设模型判断一张胸片属于“细菌性肺炎”我们不仅希望得到概率还希望知道模型重点关注了图像中的哪些区域。Grad-CAM 的基本过程是保存目标卷积层输出的特征图对目标类别分数执行反向传播保存目标分数对特征图的梯度对梯度在空间维度上求平均得到通道权重对特征图加权求和并执行 ReLU将低分辨率激活图插值到原图大小。需要强调AI 文字解释来自 CNN 的结构化结果Grad-CAM 展示的是 CNN 的关注区域。热力图不等于病灶分割AI 解释也不能证明分类一定正确两者都不能代替医生诊断。二、注册前向和反向钩子classGradCAM:def__init__(self,model,target_layer):self.modelmodel self.activationsNoneself.gradientsNoneself.fwd_handletarget_layer.register_forward_hook(self._save_activation)self.bwd_handletarget_layer.register_full_backward_hook(self._save_gradient)def_save_activation(self,module,inputs,output):self.activationsoutput.detach()def_save_gradient(self,module,grad_input,grad_output):self.gradientsgrad_output[0].detach()前向钩子保存特征图反向钩子保存梯度。使用完以后应移除钩子避免同一个模型被反复挂载而造成内存问题。三、计算类激活图def__call__(self,x,class_idxNone):self.model.zero_grad()logitsself.model(x)probstorch.softmax(logits,dim1)[0]ifclass_idxisNone:class_idxint(logits.argmax(dim1).item())scorelogits[0,class_idx]score.backward()weightsself.gradients.mean(dim(2,3),keepdimTrue)camtorch.relu((weights*self.activations).sum(dim1,keepdimTrue))camtorch.nn.functional.interpolate(cam,sizex.shape[-2:],modebilinear,align_cornersFalse,)camcam[0,0]cam(cam-cam.min())/(cam.max()-cam.min()1e-8)returncam.detach().cpu().numpy(),class_idx,probs.detach().cpu().numpy()这里不能把整个过程放进torch.no_grad()因为 Grad-CAM 必须反向传播目标类别的梯度。普通分类推理可以关闭梯度但生成热力图需要单独执行一次带梯度的前向和反向过程。四、不同网络如何选择目标层Grad-CAM 通常选择最后一个卷积阶段因为该位置兼具较强语义信息和一定空间结构。defget_target_layer(model,name):ifname.startswith(resnet):returnmodel.layer4[-1]ifname.startswith(densenet):returnmodel.features.denseblock4raiseValueError(未知模型结构)ResNet18 和 ResNet50 都可以选择layer4的最后一个残差块DenseNet121 可以选择最后一个 dense block。五、叠加到原图cam_resizedcv2.resize(cam,(width,height))heatmapcv2.applyColorMap(np.uint8(255*cam_resized),cv2.COLORMAP_JET,)heatmapcv2.cvtColor(heatmap,cv2.COLOR_BGR2RGB)overlay(heatmap.astype(np.float32)*0.45original.astype(np.float32)*0.55)overlaynp.clip(overlay,0,255).astype(np.uint8)热力图权重过高会遮挡胸片细节因此项目使用 0.45 的热力图权重和 0.55 的原图权重。六、计算关注区域项目进一步把 224×224 的热力图划分为左上、右上、左下、右下四块计算每块平均响应。由于医学影像的左右方向遵循患者视角图像左侧对应患者右侧显示文字时需要进行方向映射。如果最高区域与第二高区域的响应差很小则不武断地判断某一侧而是标记为“双侧肺野”。七、常见问题1. 热力图全黑检查目标层是否正确、是否执行了backward()以及是否错误使用了torch.no_grad()。2. 热力图总关注图像边缘可能存在数据集偏差、裁剪不当、文字标记或设备边框泄漏。此时应检查训练数据而不是简单修改颜色。3. 热力图和病灶位置不一致Grad-CAM 解释的是模型决策不是医学标注。模型可能利用了错误线索这恰好是可解释性分析能够发现的问题。八、总结手写 Grad-CAM 的代码并不复杂真正重要的是正确选择目标层、保留梯度、及时移除钩子并正确理解热力图的边界。它适合作为辅助解释工具但不能代替专业医学判断。参考标签Grad-CAMPyTorch可解释人工智能OpenCV医学影像热力图