DAY 42 Grad-CAM 与 Hook 函数

本文适合:深度学习炼丹师、模型可解释性入门、CV 算法工程师内容:Hook 原理 + Grad-CAM 公式 + 从零手写 Grad-CAM + 工程库使用环境:PyTorch + torchvision + OpenCV + matplotlib


@浙大疏锦行


一、前言:为什么我们需要 Grad-CAM?

CNN 模型强大,但黑盒问题一直存在:模型为什么把这张图分类为 "狗"?它到底看了图片的哪些区域?

Grad-CAM(Gradient-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(最重要)

1)Module Hook(模块钩子)

注册在网络层上,用来抓:

  • 前向输出(特征图)
  • 反向梯度
python 复制代码
module.register_forward_hook(forward_hook)
module.register_backward_hook(backward_hook)

2)Tensor Hook(张量钩子)

只抓梯度:

python 复制代码
tensor.register_hook(hook_fn)

三、Grad-CAM 原理(公式 + 白话)

3.1 四步走

  1. 前向传播拿到最后一个卷积层的特征图 A

  2. 反向传播对目标类别求导,得到梯度

  3. 计算每个通道的权重梯度全局平均池化:αk​=H×W1​∑i,j​∂Ai,j,k​∂yc​

  4. 加权求和 + ReLU + 上采样得到最终 CAM:LGrad−CAM​=ReLU(∑αk​⋅Ak​)


四、实战:从零实现 Grad-CAM(完整代码)

4.1 导入库

python 复制代码
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 注册)

python 复制代码
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_idx=None):
        out = self.model(x.to(device))

        if class_idx is None:
            class_idx = out.argmax(dim=1).item()

        self.model.zero_grad()
        loss = out[0, class_idx]
        loss.backward()

        # 权重:梯度 GAP
        weights = self.gradients.mean(dim=(2, 3), keepdim=True)
        cam = (weights * self.activations).sum(1, keepdim=True)
        cam = torch.relu(cam)

        cam -= cam.min()
        cam /= cam.max()

        cam = nn.functional.interpolate(
            cam, size=x.shape[2:], mode="bilinear", align_corners=False
        )
        return cam.squeeze().cpu().numpy()

4.3 图像预处理

python 复制代码
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 可视化

python 复制代码
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 运行

python 复制代码
model = models.resnet50(pretrained=True).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 安装

python 复制代码
pip install grad-cam

5.2 极简代码

python 复制代码
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.image import show_cam_on_image

model = models.resnet50(pretrained=True).to(device).eval()
target_layers = [model.layer4[-1]]

cam = GradCAM(model=model, target_layers=target_layers)

后续和上面流程一致,非常稳定、支持各种模型。


六、常见问题(避坑指南)

  1. 热力图全黑

    • 没开梯度
    • Hook 没注册成功
    • 目标层不是卷积层
  2. 热力图很模糊

    • 换更深的卷积层
    • 使用 bilinear 上采样
  3. 结果不对

    • 图像归一化是否正确
    • 类别索引是否正确

七、总结

  1. Hook = PyTorch 可解释性的灵魂前向抓特征,反向抓梯度。

  2. Grad-CAM = 最通用的 CNN 可视化方法不修改网络、不需要额外训练。

  3. 本文代码✅ 可直接跑✅ 可直接发 CSDN✅ 可直接用于项目

相关推荐
咖啡星人k5 小时前
2026 多模态大模型:AI 如何“看图+读字+听音“三合一,MonkeyCode 免费上手
人工智能·深度学习·神经网络·计算机视觉·自然语言处理
johnsong6 小时前
深度拆解:150M循环模型如何用循环推理击败大Transformer
android·深度学习·transformer
txg6666 小时前
LLM 驱动漏洞传播验证:TransferFuzz-Pro 如何自动调试“继承“来的安全债
人工智能·深度学习·安全
Rocky Ding*7 小时前
【三年面试五年模拟】2026-08-18_哔哩哔哩_AI应用岗Agent开发一面面经(含完整答案)
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·ai agent
2601_957879337 小时前
Seedance排队太久怎么办?2026免排队AI视频工具与替代方案怎么选
大数据·人工智能·深度学习
a187927218318 小时前
从一条直线到大模型输出一个token(九):输出矩阵与多层堆叠
深度学习·ai·transformer·token·注意力机制·deepseek·多层堆叠
jay神8 小时前
YOLO模型什么时候应该加注意力机制?
人工智能·深度学习·yolo·分类·回归·毕业设计
sel_98 小时前
【多轮对话论文导读(二)】多轮对话与LLM Agent论文阅读:长期记忆、多轮评估与Agent训练
人工智能·深度学习·算法·语言模型·自然语言处理
AI云海8 小时前
深度学习_全连接网络实现回归任务
人工智能·深度学习·回归
wangchen_08 小时前
PyTorch
人工智能·pytorch·python