本文适合深度学习炼丹师、模型可解释性入门、CV 算法工程师内容Hook 原理 Grad-CAM 公式 从零手写 Grad-CAM 工程库使用环境PyTorch torchvision OpenCV matplotlib浙大疏锦行一、前言为什么我们需要 Grad-CAMCNN 模型强大但黑盒问题一直存在模型为什么把这张图分类为 “狗”它到底看了图片的哪些区域Grad-CAMGradient-weighted Class Activation Mapping就是目前最通用、最稳定、无需修改网络的CNN 可视化神器。它能生成一张热力图红色 模型最关注的区域蓝色 模型几乎不关注而 Grad-CAM 的核心技术就是PyTorch Hook。二、Hook 到底是什么最通俗讲解2.1 回调函数Callback你把一个函数丢给别人别人在合适的时候帮你调用。这就是回调 hook 的本质。2.2 Lambda轻量级回调lambda grad: print(grad.shape)适合在 Hook 里写简单逻辑。2.3 PyTorch 两大 Hook最重要1Module Hook模块钩子注册在网络层上用来抓前向输出特征图反向梯度module.register_forward_hook(forward_hook) module.register_backward_hook(backward_hook)2Tensor Hook张量钩子只抓梯度tensor.register_hook(hook_fn)三、Grad-CAM 原理公式 白话3.1 四步走前向传播拿到最后一个卷积层的特征图 A反向传播对目标类别求导得到梯度计算每个通道的权重梯度全局平均池化αk​H×W1​∑i,j​∂Ai,j,k​∂yc​加权求和 ReLU 上采样得到最终 CAMLGrad−CAM​ReLU(∑αk​⋅Ak​)四、实战从零实现 Grad-CAM完整代码4.1 导入库import torch import torch.nn as nn import torchvision.models as models import cv2 import numpy as np import matplotlib.pyplot as plt from PIL import Image device torch.device(cuda if torch.cuda.is_available() else cpu)4.2 Grad-CAM 类含 Hook 注册class GradCAM: def __init__(self, model, target_layer): self.model model.eval() self.target_layer target_layer self.gradients None self.activations None self._register_hooks() def _forward_hook(self, module, inp, outp): self.activations outp.detach() def _backward_hook(self, module, grad_inp, grad_outp): self.gradients grad_outp[0].detach() def _register_hooks(self): self.target_layer.register_forward_hook(self._forward_hook) self.target_layer.register_backward_hook(self._backward_hook) def generate_cam(self, x, class_idxNone): out self.model(x.to(device)) if class_idx is None: class_idx out.argmax(dim1).item() self.model.zero_grad() loss out[0, class_idx] loss.backward() # 权重梯度 GAP weights self.gradients.mean(dim(2, 3), keepdimTrue) cam (weights * self.activations).sum(1, keepdimTrue) cam torch.relu(cam) cam - cam.min() cam / cam.max() cam nn.functional.interpolate( cam, sizex.shape[2:], modebilinear, align_cornersFalse ) return cam.squeeze().cpu().numpy()4.3 图像预处理def preprocess(img_path): tfm nn.Sequential( nn.Resize((224, 224)), nn.ToTensor(), nn.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ) img Image.open(img_path).convert(RGB) return tfm(img).unsqueeze(0)4.4 可视化def show_result(img_path, cam): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (224, 224)) / 255.0 heatmap cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)[..., ::-1] heatmap heatmap / 255.0 fusion 0.5 * heatmap 0.5 * img fusion np.clip(fusion, 0, 1) plt.subplot(121), plt.imshow(img), plt.axis(off), plt.title(原图) plt.subplot(122), plt.imshow(fusion), plt.axis(off), plt.title(Grad-CAM) plt.show()4.5 运行model models.resnet50(pretrainedTrue).to(device) target_layer model.layer4[-1] cam GradCAM(model, target_layer) x preprocess(test.jpg) # 换成你的图片 heatmap cam.generate_cam(x) show_result(test.jpg, heatmap)五、工程版使用 pytorch_grad_cam 库最推荐5.1 安装pip install grad-cam5.2 极简代码from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image model models.resnet50(pretrainedTrue).to(device).eval() target_layers [model.layer4[-1]] cam GradCAM(modelmodel, target_layerstarget_layers)后续和上面流程一致非常稳定、支持各种模型。六、常见问题避坑指南热力图全黑没开梯度Hook 没注册成功目标层不是卷积层热力图很模糊换更深的卷积层使用 bilinear 上采样结果不对图像归一化是否正确类别索引是否正确七、总结Hook PyTorch 可解释性的灵魂前向抓特征反向抓梯度。Grad-CAM 最通用的 CNN 可视化方法不修改网络、不需要额外训练。本文代码✅ 可直接跑✅ 可直接发 CSDN✅ 可直接用于项目