Pytorch可视化模型任意中间层的类激活热力图(Grad-CAM)

本文中绘制类激活热力图的基本方法参考自:https://blog.csdn.net/sinat_37532065/article/details/103362517

在该方法的基础上,本文添加了如下功能:根据网络层的名字,可视化模型指定层的类激活热力图。

本文使用的backbone network为Resnet50,运行时需要根据实际使用的网络来修改以下两个参数

1. "layers_names":包含模型所有网络层名的列表,层名顺序与模型结构一致。

2. "out_layer_name":指定的网络层名,使用该层输出的特征图来绘制热力图。

pytorch获取中间变量的梯度的方法可以看这里:https://blog.csdn.net/PanYHHH/article/details/113436204

完整代码如下,解释都在注释里:

# coding: utf-8
import os
import cv2
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

import torch
import torch.autograd as autograd
import torchvision.transforms as transforms

from senet.se_resnet import FineTuneSEResnet50


# 训练过的模型路径
resume_path = r"D:\TJU\GBDB\set113\cross_validation\test1\epoch_0257_checkpoint.pth.tar"
# 输入图像路径
single_img_path = r'D:\TJU\GBDB\set113\CAM\temp.jpg'
# 绘制的热力图存储路径
save_path = r'D:\TJU\GBDB\set113\CAM\temp_layer4.jpg'

# 网络层的层名列表, 需要根据实际使用网络进行修改
layers_names = ['conv1', 'bn1', 'relu', 'maxpool', 'layer1', 'layer2', 'layer3', 'layer4', 'avgpool']
# 指定层名
out_layer_name = "layer4"

features_grad = 0


# 为了读取模型中间参数变量的梯度而定义的辅助函数
def extract(g):
    global features_grad
    features_grad = g


def draw_CAM(model, img_path, save_path, transform=None, visual_heatmap=False, out_layer=None):
    """
    绘制 Class Activation Map
    :param model: 加载好权重的Pytorch model
    :param img_path: 测试图片路径
    :param save_path: CAM结果保存路径
    :param transform: 输入图像预处理方法
    :param visual_heatmap: 是否可视化原始heatmap(调用matplotlib)
    :return:
    """
    # 读取图像并预处理
    global layer2
    img = Image.open(img_path).convert('RGB')
    if transform:
        img = transform(img).cuda()
    img = img.unsqueeze(0)  # (1, 3, 448, 448)

    # model转为eval模式
    model.eval()

    # 获取模型层的字典
    layers_dict = {layers_names[i]: None for i in range(len(layers_names))}
    for i, (name, module) in enumerate(model.features._modules.items()):
        layers_dict[layers_names[i]] = module

    # 遍历模型的每一层, 获得指定层的输出特征图
    # features: 指定层输出的特征图, features_flatten: 为继续完成前端传播而设置的变量
    features = img
    start_flatten = False
    features_flatten = None
    for name, layer in layers_dict.items():
        if name != out_layer and start_flatten is False:    # 指定层之前
            features = layer(features)
        elif name == out_layer and start_flatten is False:  # 指定层
            features = layer(features)
            start_flatten = True
        else:   # 指定层之后
            if features_flatten is None:
                features_flatten = layer(features)
            else:
                features_flatten = layer(features_flatten)

    features_flatten = torch.flatten(features_flatten, 1)
    output = model.classifier(features_flatten)

    # 预测得分最高的那一类对应的输出score
    pred = torch.argmax(output, 1).item()
    pred_class = output[:, pred]

    # 求中间变量features的梯度
    # 方法1
    # features.register_hook(extract)
    # pred_class.backward()
    # 方法2
    features_grad = autograd.grad(pred_class, features, allow_unused=True)[0]

    grads = features_grad  # 获取梯度
    pooled_grads = torch.nn.functional.adaptive_avg_pool2d(grads, (1, 1))
    # 此处batch size默认为1,所以去掉了第0维(batch size维)
    pooled_grads = pooled_grads[0]
    features = features[0]
    print("pooled_grads:", pooled_grads.shape)
    print("features:", features.shape)
    # features.shape[0]是指定层feature的通道数
    for i in range(features.shape[0]):
        features[i, ...] *= pooled_grads[i, ...]

    # 计算heatmap
    heatmap = features.detach().cpu().numpy()
    heatmap = np.mean(heatmap, axis=0)
    heatmap = np.maximum(heatmap, 0)
    heatmap /= np.max(heatmap)

    # 可视化原始热力图
    if visual_heatmap:
        plt.matshow(heatmap)
        plt.show()

    img = cv2.imread(img_path)  # 用cv2加载原始图像
    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))  # 将热力图的大小调整为与原始图像相同
    heatmap = np.uint8(255 * heatmap)  # 将热力图转换为RGB格式
    heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)  # 将热力图应用于原始图像
    superimposed_img = heatmap * 0.7 + img  # 这里的0.4是热力图强度因子
    cv2.imwrite(save_path, superimposed_img)  # 将图像保存到硬盘


if __name__ == '__main__':
    transform = transforms.Compose([
        transforms.Resize(448),
        transforms.ToTensor(),
        transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
    ])
    # 构建模型并加载预训练参数
    seresnet50 = FineTuneSEResnet50(num_class=113).cuda()
    checkpoint = torch.load(resume_path)
    seresnet50.load_state_dict(checkpoint['state_dict'])
    draw_CAM(seresnet50, single_img_path, save_path, transform=transform, visual_heatmap=True, out_layer=out_layer_name)

热力图绘制如下,输入图像是随便找的网图:

原图像:

layer1:  layer2: layer3: layer4:

 

 

  • 23
    点赞
  • 222
    收藏
    觉得还不错? 一键收藏
  • 46
    评论
以下是基于自己创建的CNN回归模型Grad-CAM可视化激活热力图PyTorch实现代码: ```python import torch import torch.nn.functional as F import cv2 import numpy as np class GradCAM: def __init__(self, model, target_layer): self.model = model self.target_layer = target_layer self.feature_maps = None self.gradient = None self.activation_maps = None def forward(self, x): self.feature_maps = [] self.gradient = [] for name, module in self.model.named_modules(): x = module(x) if name == self.target_layer: x.register_hook(self.gradient_hook) self.feature_maps.append(x) elif "conv" in name: self.feature_maps.append(x) self.activation_maps = x return self.activation_maps def gradient_hook(self, grad): self.gradient.append(grad) def backward(self): gradient = self.gradient[0] feature_maps = self.feature_maps[-1] batch_size, channel, height, width = feature_maps.shape weights = F.adaptive_avg_pool2d(gradient, (1, 1)) weights = weights.view(batch_size, channel) activation_maps = feature_maps.view(batch_size, channel, -1) weights = weights.unsqueeze(-1) cam = torch.bmm(activation_maps, weights) cam = F.relu(cam) cam = cam.view(batch_size, 1, height, width) cam = F.interpolate(cam, size=(224, 224), mode='bilinear', align_corners=False) cam = cam.squeeze() return cam.detach().cpu().numpy() model = YourCNNModel() gradcam = GradCAM(model, 'conv2') # target_layer is the layer you want to visualize # input image img = cv2.imread('image.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) img = np.transpose(img, (2, 0, 1)) img = np.expand_dims(img, axis=0) img = torch.from_numpy(img).float() # forward pass and backward pass output = model(img) cam = gradcam.forward(img) cam = gradcam.backward() # plot the heat map plt.imshow(img.squeeze().permute(1, 2, 0)) plt.imshow(cam, alpha=0.5, cmap='jet') plt.show() ``` 其中,`GradCAM`实现了Grad-CAM算法的前向传播和反向传播,`model`是你自己创建的CNN回归模型,`target_layer`是你想要可视化的卷积层名称。在使用时,需要将输入像转换为PyTorch张量,然后调用`gradcam.forward()`得到卷积层的特征和梯度,再调用`gradcam.backward()`得到激活热力图。最后,将输入像和激活热力图叠加在一起即可得到可视化结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值