> 文章列表 > CAM类激活映射 |神经网络可视化 | 热力图

CAM类激活映射 |神经网络可视化 | 热力图

CAM类激活映射 |神经网络可视化 | 热力图

文章目录

    • 前言:
    • 安装库:
    • 分类案例--ResNet50
    • 分割案例
      • AttributeError: ‘tuple‘ object has no attribute ‘cpu‘
      • RuntimeError: grad can be implicitly created only for scalar outputs
      • TypeError: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.
      • 完整代码

前言:

本篇文章只是教程,不涉及原理,感兴趣可以自行搜索
如图,热力图可以很好的反映出网络究竟注意图片的哪一部分
CAM类激活映射 |神经网络可视化 | 热力图
github官方教程:
https://github.com/jacobgil/pytorch-grad-cam
参考博客:
https://blog.csdn.net/u014264373/article/details/85415921
https://blog.csdn.net/u014264373/article/details/116302678
但还是遇到了很多报错,解决过程记录如下:

安装库:

pip install grad-cam

分类案例–ResNet50

案例图片:
CAM类激活映射 |神经网络可视化 | 热力图

案例代码:
这个代码是可以跑通的,将图片保存到你本地,然后设置好路径即可。
(需要下载ResNet预训练模型)

from pytorch_grad_cam import GradCAM, ScoreCAM, GradCAMPlusPlus, AblationCAM, XGradCAM, EigenCAM, FullGrad
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
from pytorch_grad_cam.utils.image import show_cam_on_image
from torchvision.models import resnet50
import torchvision
import torch
from matplotlib import pyplot as plt
import numpy as npdef myimshows(imgs, titles=False, fname="test.jpg", size=6):lens = len(imgs)fig = plt.figure(figsize=(size * lens, size))if titles == False:titles = "0123456789"for i in range(1, lens + 1):cols = 100 + lens * 10 + iplt.xticks(())plt.yticks(())plt.subplot(cols)if len(imgs[i - 1].shape) == 2:plt.imshow(imgs[i - 1], cmap='Reds')else:plt.imshow(imgs[i - 1])plt.title(titles[i - 1])plt.xticks(())plt.yticks(())plt.savefig(fname, bbox_inches='tight')plt.show()def tensor2img(tensor, heatmap=False, shape=(224, 224)):np_arr = tensor.detach().numpy()  # [0]# 对数据进行归一化if np_arr.max() > 1 or np_arr.min() < 0:np_arr = np_arr - np_arr.min()np_arr = np_arr / np_arr.max()# np_arr=(np_arr*255).astype(np.uint8)if np_arr.shape[0] == 1:np_arr = np.concatenate([np_arr, np_arr, np_arr], axis=0)np_arr = np_arr.transpose((1, 2, 0))return np_arrpath = "../examples/both.png"
bin_data = torchvision.io.read_file(path)  # 加载二进制数据
img = torchvision.io.decode_image(bin_data) / 255  # 解码成CHW的图片
img = img.unsqueeze(0)  # 变成BCHW的数据,B==1; squeeze
input_tensor = torchvision.transforms.functional.resize(img, [224, 224])model = resnet50(pretrained=True)
target_layers = [model.layer4[-1]]  # 如果传入多个layer,cam输出结果将会取均值# cam = GradCAM(model=model, target_layers=target_layers, use_cuda=False)
with GradCAM(model=model, target_layers=target_layers, use_cuda=False) as cam:# targets = [ClassifierOutputTarget(386), ClassifierOutputTarget(386)]  # 指定查看class_num为386的热力图targets = None  # 选定目标类别,如果不设置,则默认为分数最高的那一类# aug_smooth=True, eigen_smooth=True 使用图像增强是热力图变得更加平滑grayscale_cams = cam(input_tensor=input_tensor, targets=targets)  # targets=None 自动调用概率最大的类别显示for grayscale_cam, tensor in zip(grayscale_cams, input_tensor):# 将热力图结果与原图进行融合rgb_img = tensor2img(tensor)visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)myimshows([rgb_img, grayscale_cam, visualization], ["image", "cam", "image + cam"])

最后出来的结果应该就是这样一张图。
CAM类激活映射 |神经网络可视化 | 热力图

分割案例

如果上面的代码你跑通了
那么如何为自己的网络生成热力图呢?
有几个需要注意的点:(最后会附上完整代码)

首先,切换成你的网络了模型加载就不说了,这个自己搞好。
然后,你的网络是否是在gpu上跑的,如果是
输入数据要放gpu上

path = './test_img/yu.jpg'
bin_data = torchvision.io.read_file(path)  # 加载二进制数据
img = torchvision.io.decode_image(bin_data) / 255  # 解码成CHW的图片
img = img.unsqueeze(0)  # 变成BCHW的数据,B==1 squeeze
img_tensor = torchvision.transforms.functional.resize(img, [352, 352])
img_tensor = img_tensor.cuda()   # 加一句这个

然后按照上面的代码,修改这一句,改成你要查看的层:

target_layers = [model.layer4[-1]]  # 如果传入多个layer,cam输出结果将会取均值

把这个改成你要的层,然后运行一下,可能会遇到报错:

AttributeError: ‘tuple‘ object has no attribute ‘cpu‘

如果出现这个报错,可以看下你的网络最终输出是几个特征。因为是自己写的网络,有的因为训练需要,最终返回的是多个结果。

print(model(x))

如果有多个结果,会被变成一个元组。后面需要转cpu,元组tuple没有.cpu的方法,所以报错。
解决方法:
先把你的网络包装一下,你返回了多个值,选择有用的那一个就行
我这里选择了多个输出的第一个,自己视情况而定

class SegmentationModelOutputWrapper(torch.nn.Module):def __init__(self, model):super(SegmentationModelOutputWrapper, self).__init__()self.model = modeldef forward(self, x):return self.model(x)[0]  # 我这里选择了多个输出的第一个,自己视情况而定model = NetWork()
model.load_state_dict(torch.load(opt.snap_path))
# 网络加载后先包装下  修改输出
model = SegmentationModelOutputWrapper(model)

然后再运行,可能会出现报错:

RuntimeError: grad can be implicitly created only for scalar outputs

这个问题的解决办法是:
你需要去到base_cam.py这个库文件里面去
第85行有一句loss.backward(retain_graph = True)
将其修改为loss.backward(torch.ones_like(loss),retain_graph=True)

CAM类激活映射 |神经网络可视化 | 热力图

参考链接:https://blog.csdn.net/weixin_44390884/article/details/127893163

还有一个报错:

TypeError: can’t convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.

如果你的模型最后返回的特征是tensor的特征,那么需要对tensor2img做改动
CAM类激活映射 |神经网络可视化 | 热力图

np_arr = tensor.detach().numpy()  # [0]

修改为:

np_arr = tensor.cpu().detach().numpy()  # [0]

完整代码

import os
import torch
import argparse
import numpy as np
import imageio
import torchvision
import torch.nn.functional as F
import torch.backends.cudnn as cudnn
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.image import show_cam_on_image
from matplotlib import pyplot as pltdef myimshows(imgs, titles=False, fname="test.jpg", size=6):lens = len(imgs)fig = plt.figure(figsize=(size * lens, size))if titles == False:titles = "0123456789"for i in range(1, lens + 1):cols = 100 + lens * 10 + iplt.xticks(())plt.yticks(())plt.subplot(cols)if len(imgs[i - 1].shape) == 2:plt.imshow(imgs[i - 1], cmap='Reds')else:plt.imshow(imgs[i - 1])plt.title(titles[i - 1])plt.xticks(())plt.yticks(())plt.savefig(fname, bbox_inches='tight')plt.show()def tensor2img(tensor, heatmap=False, shape=(224, 224)):np_arr = tensor.cpu().detach().numpy()  # [0]if np_arr.max() > 1 or np_arr.min() < 0:np_arr = np_arr - np_arr.min()np_arr = np_arr / np_arr.max()# np_arr=(np_arr*255).astype(np.uint8)if np_arr.shape[0] == 1:np_arr = np.concatenate([np_arr, np_arr, np_arr], axis=0)np_arr = np_arr.transpose((1, 2, 0))return np_arrif __name__ == '__main__':model = NetWork()model.load_state_dict(torch.load(opt.snap_path))# torchinfo.summary(model=model,input_size=(8, 3, 352, 352))# 包装下  修改输出model = SegmentationModelOutputWrapper(model)model.eval()path = './test_img/yu.jpg'bin_data = torchvision.io.read_file(path)  # 加载二进制数据img = torchvision.io.decode_image(bin_data) / 255  # 解码成CHW的图片img = img.unsqueeze(0)  # 变成BCHW的数据,B==1 squeezeimg_tensor = torchvision.transforms.functional.resize(img, [352, 352])img_tensor = img_tensor.cuda()target_layers = [model.model.ncd]targets = Nonewith GradCAM(model=model,target_layers=target_layers,use_cuda=True) as cam:grayscale_cams = cam(input_tensor=img_tensor,targets=targets,aug_smooth=True)# cam_image = show_cam_on_image(img_rgb, grayscale_cam, use_rgb=True)for grayscale_cam, tensor in zip(grayscale_cams, img_tensor):# 将热力图结果与原图进行融合rgb_img = tensor2img(tensor)visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)myimshows([rgb_img, grayscale_cam, visualization], ["image", "cam", "image + cam"])