【深度学习总结】热力图-Grad-CAM使用

Grad-CAM使用

介绍

Grad-CAM,全称为Gradient-weighted Class Activation Mapping,是一种用于深度学习模型可视化的技术,特别是在卷积神经网络(CNN)中。它通过生成热力图来展示模型在做出决策时关注的区域,从而提供模型决策过程的可视化解释。
在这里插入图片描述
官方的代码pytorch-grad-cam中,里面也集成了其他热力图可视化方法。

源码解读

下面对pytorch-grad-cam的核心代码进行解读。

以语义分割的GradCAM为例,当我们使用它时,代码通常为:

from pytorch_grad_cam import GradCAMclass SemanticSegmentationTarget:def __init__(self, category, mask):self.category = categoryself.mask = torch.from_numpy(mask)if torch.cuda.is_available():self.mask = self.mask.cuda()def __call__(self, model_output):return (model_output[self.category, :, : ] * self.mask).sum()target_layers = [model.model.backbone.layer4]
targets = [SemanticSegmentationTarget(car_category, car_mask_float)]
with GradCAM(model=model,target_layers=target_layers,use_cuda=torch.cuda.is_available()) as cam:grayscale_cam = cam(input_tensor=input_tensor,targets=targets)[0, :]

其中model是网络模型,target_layers是需要可视化的层,input_tensor是图片数据,targets是想要最大化的目标,这里就是对属于“汽车”类别的所有像素的预测进行求和。

BaseCAM

pytorch_grad_cam库中,GradCAM继承的类就是BaseCAM,代码在:base_cam.py,GradCAM的代码在grad_cam.py,它只是实现了一个get_cam_weights函数。

准备工作

BaseCAMforward函数中,它的参数为:

  • input_tensor:输入数据
  • targets:想要最大化的目标,通常是一个nn.Module类列表

它首先设置输入数据的梯度以及得到模型的梯度和激活值,如下:

self.outputs = outputs = self.activations_and_grads(input_tensor)

其中activations_and_grads是专门抓取模型的梯度和激活值的,这个后面会讲,它返回的是模型的输出。

如果你没有提供targets,它会按照分类模型的标准来构建:

if targets is None:target_categories = np.argmax(outputs.cpu().data.numpy(), axis=-1)targets = [ClassifierOutputTarget(category) for category in target_categories]

接下来就会根据target来计算损失,然后进行梯度反向传播:

if self.uses_gradients:self.model.zero_grad()loss = sum([target(output) for target, output in zip(targets, outputs)])loss.backward(retain_graph=True)

此时目标层的梯度和激活值已经保存在self.activations_and_grads中。

热力图计算

然后就是计算每层的热力图,这是最重要的。先获取目标层的梯度和激活值,以及特征的大小:

activations_list = [a.cpu().data.numpy() for a in self.activations_and_grads.activations]
grads_list = [g.cpu().data.numpy() for g in self.activations_and_grads.gradients]
target_size = self.get_target_width_height(input_tensor)

然后遍历每个目标层,获取对应的梯度和激活值:

layer_activations = activations_list[i]
layer_grads = grads_list[i]

接着就是计算热力图,先获取对应的权重:

weights = self.get_cam_weights(input_tensor, target_layer, targets, activations, grads)

不同的激活图方法会有不同的实现,GradCAM的做法就是对梯度进行平均:

# 2D image
if len(grads.shape) == 4:return np.mean(grads, axis=(2, 3))
# 3D image
elif len(grads.shape) == 5:return np.mean(grads, axis=(2, 3, 4))

得到权重后,对激活值进行加权,如下:

# 2D conv
if len(activations.shape) == 4:weighted_activations = weights[:, :, None, None] * activations
# 3D conv
elif len(activations.shape) == 5:weighted_activations = weights[:, :, None, None, None] * activations

然后,对加权的值在通道维度进行求和,得到最终的激活图,如果指定了平滑,还会使用平衡方法:

if eigen_smooth:cam = get_2d_projection(weighted_activations)else:cam = weighted_activations.sum(axis=1)

最后取第一维最大的作为最终的激活图,并将激活图变成跟输入数据一样的大小:

cam = np.maximum(cam, 0)scaled = scale_cam_image(cam, target_size)

得到所有目标层的激活图后,将它们在通过维度进行拼接,然后取平均值,得到最终的结果:

cam_per_target_layer = np.concatenate(cam_per_target_layer, axis=1)
cam_per_target_layer = np.maximum(cam_per_target_layer, 0)
result = np.mean(cam_per_target_layer, axis=1)

ActivationsAndGradients类

它负责抓取目标层的激活值和梯度,代码在:activations_and_gradients.py

在BaseCAM中通过如下方式创建:

self.activations_and_grads = ActivationsAndGradients(self.model, target_layers, reshape_transform)

其中model是网络模型,target_layers是目标层,是一个nn.Module类列表。

在该类中,首先注册目标层的钩子函数:

for target_layer in target_layers:self.handles.append(target_layer.register_forward_hook(self.save_activation))# Because of https://github.com/pytorch/pytorch/issues/61519,# we don't use backward hook to record gradients.self.handles.append(target_layer.register_forward_hook(self.save_gradient))

其中register_forward_hook的用法如下:

hook_handle = layer.register_forward_hook(hook_fn)
  • layer:你想要添加 hook 的模型层(如卷积层、线性层等)。
  • hook_fn:自定义的 hook 函数,用于在前向传播过程中处理数据。

hook_fn的格式如下:hook_fn是一个带有三个参数的函数:

def hook_fn(module, input, output):# module 是当前的层# input 是层的输入,通常是一个元组# output 是层的输出pass
  • module:当前的层对象。
  • input:传递给该层的输入数据(作为元组)。
  • output:该层的输出数据。

它使用的保存梯度的函数如下:

def save_gradient(self, module, input, output):if not hasattr(output, "requires_grad") or not output.requires_grad:# You can only register hooks on tensor requires grad.return# Gradients are computed in reverse orderdef _store_grad(grad):if self.reshape_transform is not None:grad = self.reshape_transform(grad)self.gradients = [grad.cpu().detach()] + self.gradientsoutput.register_hook(_store_grad)def save_activation(self, module, input, output):activation = outputif self.reshape_transform is not None:activation = self.reshape_transform(activation)self.activations.append(activation.cpu().detach())

其中register_hook函数允许你为张量注册一个钩子函数,该钩子函数会在计算梯度时被调用。

它执行通过一个call函数:

def __call__(self, x):self.gradients = []self.activations = []return self.model(x)

官方代码使用

官方的教程看这里:pytorch-gradcam-book

CLIP特征可视化——非官方代码

CLIP分为视觉编码器和文本编码器,其中视觉编码器有ResNet和ViT,这里以ResNet为例,可视化它的特征。

首先创建钩子函数,提取激活值和梯度:

class Hook:"""Attaches to a module and records its activations and gradients."""def __init__(self, module: nn.Module):self.data = Noneself.hook = module.register_forward_hook(self.save_grad)def save_grad(self, module, input, output):self.data = outputoutput.requires_grad_(True)output.retain_grad()def __enter__(self):return selfdef __exit__(self, exc_type, exc_value, exc_traceback):self.hook.remove()@propertydef activation(self) -> torch.Tensor:return self.data@propertydef gradient(self) -> torch.Tensor:return self.data.grad

然后实现GradCAM,思路也十分简单,先计算梯度,然后根据梯度得到权重,再和激活值进行加权求和,从而得到激活图:

def gradCAM(model: nn.Module,input: torch.Tensor,target: torch.Tensor,layer: nn.Module
) -> torch.Tensor:# 梯度归0if input.grad is not None:input.grad.data.zero_()# requires_grad = {}for name, param in model.named_parameters():requires_grad[name] = param.requires_gradparam.requires_grad_(False)# 添加钩子函数assert isinstance(layer, nn.Module)with Hook(layer) as hook:        # 前向和后向传播output = model(input)output.backward(target)grad = hook.gradient.float()act = hook.activation.float()# 在空间维度进行平均池化来得到权重alpha = grad.mean(dim=(2, 3), keepdim=True)# 通道维度加权求和gradcam = torch.sum(act * alpha, dim=1, keepdim=True)# 去除负值,只想要正值gradcam = torch.clamp(gradcam, min=0)# resizegradcam = F.interpolate(gradcam,input.shape[2:],mode='bicubic',align_corners=False)# 存储梯度设置for name, param in model.named_parameters():param.requires_grad_(requires_grad[name])return gradcam

然后定义一些功能函数:

def normalize(x: np.ndarray) -> np.ndarray:# Normalize to [0, 1].x = x - x.min()if x.max() > 0:x = x / x.max()return x# Modified from: https://github.com/salesforce/ALBEF/blob/main/visualization.ipynb
def getAttMap(img, attn_map, blur=True):if blur:attn_map = filters.gaussian_filter(attn_map, 0.02*max(img.shape[:2]))attn_map = normalize(attn_map)cmap = plt.get_cmap('jet')attn_map_c = np.delete(cmap(attn_map), 3, 2)attn_map = 1*(1-attn_map**0.7).reshape(attn_map.shape + (1,))*img + \(attn_map**0.7).reshape(attn_map.shape+(1,)) * attn_map_creturn attn_mapdef viz_attn(img, attn_map, blur=True):_, axes = plt.subplots(1, 2, figsize=(10, 5))axes[0].imshow(img)axes[1].imshow(getAttMap(img, attn_map, blur))for ax in axes:ax.axis("off")plt.show()def load_image(img_path, resize=None):image = Image.open(img_path).convert("RGB")if resize is not None:image = image.resize((resize, resize))return np.asarray(image).astype(np.float32) / 255.

最后将这些集成:

image_url = 'https://images2.minutemediacdn.com/image/upload/c_crop,h_706,w_1256,x_0,y_64/f_auto,q_auto,w_1100/v1554995050/shape/mentalfloss/516438-istock-637689912.jpg' image_caption = 'the cat' 
clip_model = "RN50" #["RN50", "RN101", "RN50x4", "RN50x16"]
saliency_layer = "layer4"  #["layer4", "layer3", "layer2", "layer1"]
blur = Truedevice = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load(clip_model, device=device, jit=False)# 下载图片
image_path = 'image.png'
urllib.request.urlretrieve(image_url, image_path)
# 预处理
image_input = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
image_np = load_image(image_path, model.visual.input_resolution)
text_input = clip.tokenize([image_caption]).to(device)# 计算热力图
attn_map = gradCAM(model.visual,image_input,model.encode_text(text_input).float(),getattr(model.visual, saliency_layer)
)
attn_map = attn_map.squeeze().detach().cpu().numpy()viz_attn(image_np, attn_map, blur)

最终的效果为:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/443216.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hotspot是什么?

Hotspot 简单来说,JVM的一种。 一、HotSpot 的官方定义 HotSpot 是 Oracle 公司开发的一个高性能的 Java 虚拟机(JVM)。它通过一系列先进的技术和优化手段,为 Java 应用程序提供高效的运行环境,实现了跨平台的代码执行…

【JS】判断快乐数

思路 这里主要是需要熟悉对取值各个位数上的单数操作,也就是数字拆分方法: 转化为字符串,使用split方法 // 将数字转换为字符串,以便拆分为单个数字 let arr ( (totalCount || n)).split(); 使用数学运算符 let sum 0; // 初始…

第二十二天|回溯算法| 理论基础,77. 组合(剪枝),216. 组合总和III,17. 电话号码的字母组合

目录 回溯算法理论基础 1.题目分类 2.理论基础 3.回溯法模板 补充一个JAVA基础知识 什么时候用ArrayList什么时候用LinkedList 77. 组合 未剪枝优化 剪枝优化 216. 组合总和III 17. 电话号码的字母组合 回溯法的一个重点理解:细细理解这句话!…

《Linux从小白到高手》理论篇:Linux的进程管理详解

本篇将介绍Linux的进程管理相关知识,并将深入介绍Linux的进程间相互通信。 进程就是运行中的程序,一个运行着的程序,可能有多个进程。 比如Oracle DB,启动Oracle实例服务后,就会有多个进程。 Linux进程分类 在 Linux…

五、Python基础语法(程序的输入和输出)

一、输入 输入:输入就是获取键盘输入的数据,使用input()函数。代码会从上往下执行,当遇到input()函数,就会暂停执行,输入内容后,敲回车键,表示本次的输入结束。input函数得到的数据类型都是字符…

Kali Linux中安装配置影音资源下载神器Amule

一、Debian系列Linux安装amule命令: sudo apt update sudo apt-get install amule amule-utils 二、配置Amule的要点: 1、首次运行Amule,提示是否下载服务器列表,点击是。 2、搜索选项的类型选择全球,类型的默认选项…

cs61b学习 part3

如果你有许多list,这里将会是大量的时间,我指的是对于单向链表查找时间复杂度O(N)相对于数组O(1)的时间复杂度会慢一些 所以这究竟是顺序表的编写还是链表的改进? IntList public class IntList {public int first;public IntList rest;public IntLis…

后端增删改查的基本应用——一个简单的货物管理系统

最终效果,如图所示: 如果想要进行修改操作,可点击某栏修改选项,会在本表格下方弹出修改的具体操作界面(点击前隐藏),并且目前的信息可复现在修改框内。 本篇文章通过该项目将后端和前端结合起来…

编译链接的过程发生了什么?

一:程序的翻译环境和执行环境 在 ANSI C 的任何一种实现中,存在两个不同的环境。 第 1 种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第 2 种是执行环境,它用于实际执行代码 也就是说:↓ 1&#xff1…

微信小程序启动不起来,报错凡是以~/包名/*.js路径的文件,都找不到,试过网上一切方法,最终居然这么解决的,【避坑】命运的齿轮开始转动

app.json "resolveAlias": {"~/*": "/*"},文件代码也没有问题,网上的方法试过来了,大模型AI也问过遍,熬夜到凌晨2点半,最不可思议的是居然是因为微信开发者工具版本的问题,我真的是笑死…

网站排名,让网站快速有排名的几个方法

要让网站快速获得并提升排名,需要综合运用一系列专业策略和技术,这些策略涵盖了内容优化、技术调整、外链建设、用户体验提升等多个方面。以下是让网站快速有排名的几个方法: 1.内容为王:创造高质量、有价值的内容 -深入…

南京大学《软件分析》李越, 谭添——1. 导论

导论 主要概念: soundcompletePL领域概述 动手学习 本节无 文章目录 导论1. PL(Programming Language) 程序设计语言1.1 程序设计语言的三大研究方向1.2 与静态分析相关方向的介绍与对比静态程序分析动态软件测试形式化(formal)语义验证(verification) 2. 静态分析:2.1莱斯…

Redis数据库与GO(一):安装,string,hash

安装包地址:https://github.com/tporadowski/redis/releases 建议下载zip版本,解压即可使用。解压后,依次打开目录下的redis-server.exe和redis-cli.exe,redis-cli.exe用于输入指令。 一、基本结构 如图,redis对外有个…

k8s的安装和部署

配置三台主机,分别禁用各个主机上的swap,并配置解析 systemctl mask swap.target swapoff -a vim /etc/fstab配置这三个主机上的主机以及harbor仓库的主机 所有主机设置docker的资源管理模式为system [rootk8s-master ~]# vim /etc/docker/daemon.json…

为什么推荐你一定要弄懂千门八将108局,学会做局思维的人有多么的厉害?

在纷繁复杂的社会与商业环境中,能够洞悉事物本质、预见趋势并巧妙布局的人,往往能在竞争中脱颖而出,成为时代的弄潮儿。而“千门八将108局”这一古老而深邃的智慧体系,不仅蕴含了中国传统文化中对于策略、心理学、人际交往的深刻理…

PCL 提取点云边界

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 计算法向量 2.1.2 提取边界点 2.1.3 可视化边界点 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法与项目实战案例汇总&#xff0…

动手学深度学习(李沐)PyTorch 第 6 章 卷积神经网络

李宏毅-卷积神经网络CNN 如果使用全连接层:第一层的weight就有3*10^7个 观察 1:检测模式不需要整张图像 很多重要的pattern只要看小范围即可 简化1:感受野 根据观察1 可以做第1个简化,卷积神经网络会设定一个区域&#xff0c…

SolarWinds中如何添加华为交换机实现网络管理

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部 下午好,我的网工朋友。 SolarWinds作为一款广受好评的网络管理软件,它提供了全面的网络配置、监控和管理解决方案&#x…

组织病理学图像中的再识别|文献速递--基于多模态-半监督深度学习的病理学诊断与病灶分割

Title 题目 Re-identification from histopathology images 组织病理学图像中的再识别 01 文献速递介绍 在光学显微镜下评估苏木精-伊红(H&E)染色切片是肿瘤病理诊断中的标准程序。随着全片扫描仪的出现,玻片切片可以被数字化为所谓…

【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象

文章目录 请求1. 传递单个参数注意事项1 . 正常传递参数2 . 不传递 age 参数3 . 传递参数类型不匹配 2. 传递多个参数3. 传递对象 请求 访问不同的路径,就是发送不同的请求。在发送请求时,可能会带一些参数,所以学习 Spring 的请求&#xff…