Grad-CAM,即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping)
Grad-CAM,即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping),是一种用于解释卷积神经网络决策的方法。它通过可视化模型对于给定输入的关注区域来提供洞察。
原理:
Grad-CAM的关键思想是将输出类别的梯度(相对于特定卷积层的输出)与该层的输出相乘,然后取平均,得到一个“粗糙”的热力图。这个热力图可以被放大并叠加到原始图像上,以显示模型在分类时最关注的区域。
具体步骤如下:
- 选择一个卷积层作为解释的来源。通常,我们会选择网络的最后一个卷积层,因为它既包含了高级特征,也保留了空间信息。
- 前向传播图像到网络,得到你想解释的类别的得分。
- 计算此得分 相对于我们选择的卷积层 输出的梯度。
- 对于该卷积层的每个通道,使用上述梯度的全局平均值对该通道进行加权。
- 结果是一个与卷积层的空间维度相同的加权热力图。
优势
Grad-CAM的优点是它可以用于任何卷积神经网络,无需进行结构修改或重新训练。它为我们提供了一个简单但直观的方式来理解模型对于特定输入的决策。
Code
import torch
import cv2
import torch.nn.functional as F
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from PIL import Imageclass GradCAM:def __init__(self, model, target_layer):self.model = modelself.target_layer = target_layerself.feature_maps = Noneself.gradients = None# Hook layerstarget_layer.register_forward_hook(self.save_feature_maps)target_layer.register_backward_hook(self.save_gradients)def save_feature_maps(self, module, input, output):self.feature_maps = output.detach()def save_gradients(self, module, grad_input, grad_output):self.gradients = grad_output[0].detach()def generate_cam(self, image, class_idx=None):# Set model to evaluation modeself.model.eval()# Forward passoutput = self.model(image)if class_idx is None:class_idx = torch.argmax(output).item()# Zero out gradientsself.model.zero_grad()# Backward pass for target classone_hot = torch.zeros((1, output.size()[-1]), dtype=torch.float32)one_hot[0][class_idx] = 1output.backward(gradient=one_hot.cuda(), retain_graph=True)# Get pooled gradients and feature mapspooled_gradients = torch.mean(self.gradients, dim=[0, 2, 3])activation = self.feature_maps.squeeze(0)for i in range(activation.size(0)):activation[i, :, :] *= pooled_gradients[i]# Create heatmapheatmap = torch.mean(activation, dim=0).squeeze().cpu().numpy()heatmap = np.maximum(heatmap, 0)heatmap /= torch.max(heatmap)heatmap = cv2.resize(heatmap, (image.size(3), image.size(2)))heatmap = np.uint8(255 * heatmap)heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)# Superimpose heatmap on original imageoriginal_image = self.unprocess_image(image.squeeze().cpu().numpy())superimposed_img = heatmap * 0.4 + original_imagesuperimposed_img = np.clip(superimposed_img, 0, 255).astype(np.uint8)return heatmap, superimposed_imgdef unprocess_image(self, image):# Reverse the preprocessing stepmean = np.array([0.485, 0.456, 0.406])std = np.array([0.229, 0.224, 0.225])image = (((image.transpose(1, 2, 0) * std) + mean) * 255).astype(np.uint8)return imagedef visualize_gradcam(model, input_image_path, target_layer):# Load imageimg = Image.open(input_image_path)preprocess = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])input_tensor = preprocess(img).unsqueeze(0).cuda()# Create GradCAMgradcam = GradCAM(model, target_layer)heatmap, result = gradcam.generate_cam(input_tensor)plt.figure(figsize=(10,10))plt.subplot(1,2,1)plt.imshow(heatmap)plt.title('Heatmap')plt.axis('off')plt.subplot(1,2,2)plt.imshow(result)plt.title('Superimposed Image')plt.axis('off')plt.show()# Load your model (e.g., resnet20 in this case)
# model = resnet20()
# model.load_state_dict(torch.load("path_to_your_weights.pth"))
# model.to('cuda')# Visualize GradCAM
# visualize_gradcam(model, "path_to_your_input_image.jpg", model.layer3[-1])
中文注释详细版
import torch
import cv2
import torch.nn.functional as F
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from PIL import Imageclass GradCAM:def __init__(self, model, target_layer):self.model = model # 要进行Grad-CAM处理的模型self.target_layer = target_layer # 要进行特征可视化的目标层self.feature_maps = None # 存储特征图self.gradients = None # 存储梯度# 为目标层添加钩子,以保存输出和梯度target_layer.register_forward_hook(self.save_feature_maps)target_layer.register_backward_hook(self.save_gradients)def save_feature_maps(self, module, input, output):"""保存特征图"""self.feature_maps = output.detach()def save_gradients(self, module, grad_input, grad_output):"""保存梯度"""self.gradients = grad_output[0].detach()def generate_cam(self, image, class_idx=None):"""生成CAM热力图"""# 将模型设置为评估模式self.model.eval()# 正向传播output = self.model(image)if class_idx is None:class_idx = torch.argmax(output).item()# 清空所有梯度self.model.zero_grad()# 对目标类进行反向传播one_hot = torch.zeros((1, output.size()[-1]), dtype=torch.float32)one_hot[0][class_idx] = 1output.backward(gradient=one_hot.cuda(), retain_graph=True)# 获取平均梯度和特征图pooled_gradients = torch.mean(self.gradients, dim=[0, 2, 3])activation = self.feature_maps.squeeze(0)for i in range(activation.size(0)):activation[i, :, :] *= pooled_gradients[i]# 创建热力图heatmap = torch.mean(activation, dim=0).squeeze().cpu().numpy()heatmap = np.maximum(heatmap, 0)heatmap /= torch.max(heatmap)heatmap = cv2.resize(heatmap, (image.size(3), image.size(2)))heatmap = np.uint8(255 * heatmap)heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)# 将热力图叠加到原始图像上original_image = self.unprocess_image(image.squeeze().cpu().numpy())superimposed_img = heatmap * 0.4 + original_imagesuperimposed_img = np.clip(superimposed_img, 0, 255).astype(np.uint8)return heatmap, superimposed_imgdef unprocess_image(self, image):"""反预处理图像,将其转回原始图像"""mean = np.array([0.485, 0.456, 0.406])std = np.array([0.229, 0.224, 0.225])image = (((image.transpose(1, 2, 0) * std) + mean) * 255).astype(np.uint8)return imagedef visualize_gradcam(model, input_image_path, target_layer):"""可视化Grad-CAM热力图"""# 加载图像img = Image.open(input_image_path)preprocess = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])input_tensor = preprocess(img).unsqueeze(0).cuda()# 创建GradCAMgradcam = GradCAM(model, target_layer)heatmap, result = gradcam.generate_cam(input_tensor)# 显示图像和热力图plt.figure(figsize=(10,10))plt.subplot(1,2,1)plt.imshow(heatmap)plt.title('热力图')plt.axis('off')plt.subplot(1,2,2)plt.imshow(result)plt.title('叠加后的图像')plt.axis('off')plt.show()# 以下是示例代码,显示如何使用上述代码。
# 首先,你需要加载你的模型和权重。
# model = resnet20()
# model.load_state_dict(torch.load("path_to_your_weights.pth"))
# model.to('cuda')# 然后,调用`visualize_gradcam`函数来查看结果。
# visualize_gradcam(model, "path_to_your_input_image.jpg", model.layer3[-1])
论文链接:https://openaccess.thecvf.com/content_ICCV_2017/papers/Selvaraju_Grad-CAM_Visual_Explanations_ICCV_2017_paper.pdf
相关文章:

Grad-CAM,即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping)
Grad-CAM,即梯度加权类激活映射 (Gradient-weighted Class Activation Mapping),是一种用于解释卷积神经网络决策的方法。它通过可视化模型对于给定输入的关注区域来提供洞察。 原理: Grad-CAM的关键思想是将输出类别的梯度(相对于特定卷积…...
程序发布——使用pyinstaller打包识别程序为exe可执行文件 详解
当我们使用python完成项目开发后,必然面对着如何将自己的程序分享给其他人使用,这就离不开程序的打包工作。对于大多数人而言,我们还是使用windows电脑居多,因此我们在大多数场景是需要将程序打包为exe的可执行文件。笔者将在本章节详细介绍使用pyinstaller进行打包的全过程…...

Docker 使用
简介 Docker是一个开源的容器引擎,它有助于更快地交付应用。 Docker可将应用程序和基础设施层隔离,并且能将基础设施当作程序一样进行管理。使用 Docker可更快地打包、测试以及部署应用程序,并可以缩短从编写到部署运行代码的周期。 Docker…...

电脑c盘变红满了怎么清理?4个方法轻松清理!
“我的电脑才用了不到一年,现在就已经满了!电脑c盘变红满了应该怎么清理呢?有什么方法能帮我清理的干净一点吗?希望大家给我出出主意。” 随着我们使用电脑时间的增多,电脑C盘可能会变得满满当当,这会不仅会…...

【UE 材质】实现角度渐变材质、棋盘纹理材质
目标 步骤 一、角度渐变材质 1. 首先通过“Mask”节点将"Texture Coordinate" 节点的R、G通道分离 2. 通过“RemapValueRange”节点将0~1范围映射到-1~1 可以看到此时R通道效果: G通道效果: 继续补充如下节点 二、棋盘纹理材质 原视频链接&…...

[深度学习]1. 深度学习知识点汇总
本文记录了我在学习深度学习的过程中遇到过的不懂的知识点,为了方便翻阅,故将其发表于此,随时更新,供大家参考。 深度学习常见知识点 1. 测试精度和训练精度 在深度学习中,测试精度和训练精度是两个重要的指标&#…...

鲁棒优化入门(6)—Matlab+Yalmip两阶段鲁棒优化通用编程指南(上)
0.引言 上一篇博客介绍了使用Yalmip工具箱求解单阶段鲁棒优化的方法。这篇文章将和大家一起继续研究如何使用Yalmip工具箱求解两阶段鲁棒优化(默认看到这篇博客时已经有一定的基础了,如果没有可以看看我专栏里的其他文章)。关于两阶段鲁棒优化与列与约束生成算法的原…...
golang通过gorm操作sqlite设置主键自增
在 Golang 中使用 GORM 操作 SQLite 数据库时,可以通过以下步骤设置主键自增: 首先,确保已经安装了 GORM 和 SQLite 的驱动程序。你可以使用以下命令安装它们: go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite导入所…...

基于Spring Boot的企业门户网站设计与实现(Java+spring boot+MySQL)
获取源码或者论文请私信博主 演示视频: 基于Spring Boot的企业门户网站设计与实现(Javaspring bootMySQL) 使用技术: 前端:html css javascript jQuery ajax thymeleaf 微信小程序 后端:Java springboot…...

Json解析流程
一、拿到了题库 分析一下可以定义的 1、序号,用来区分题目数,每个题有唯一的序号 2、题目,就是下图的Q 3、预设的回答,下图的A 分析完我可以知道有三个字段,分别是int index、string Q、string A。 二、把字段丢到…...

Mybatis 动态SQL – 使用choose标签动态生成条件语句
之前我们介绍了if,where标签的使用;本篇我们需要在if,where标签的基础上介绍如何使用Mybatis提供的choose标签动态生成条件语句。 如果您对if,where标签动态生成条件语句不太了解,建议您先进行了解后再阅读本篇,可以参考: Mybat…...
http接口自动化测试框架实现
目录 一、测试需求描述 二、实现方法 三、Excel表格样式 四、实现代码(代码才是王道,有注释很容易就能看明白的) 一、测试需求描述 对服务后台一系列的http接口功能测试。 输入:根据接口描述构造不同的参数输入值 输出&…...

Android逆向学习(三)vscode修改smali绕过vip
Android逆向学习(三)vscode修改smali绕过vip 写在前面 这是吾爱的第二个作业,主要就是要修改smali代码,其实smali代码我感觉没有必要去学,当然主要是我本来就会汇编语言,基本上汇编语言都是一样的&#x…...
代码随想录训练营第38天|62.不同路径,63.不同路径II
代码随想录训练营第38天|62.不同路径,63.不同路径II 62.不同路径文章思路代码 63.不同路径II文章思路代码 总结 62.不同路径 文章 代码随想录|0062.不同路径 思路 d p [ i ] [ j ] { 1 , i 0 ∧ j 0 d p [ i − 1 ] [ j ] d p [ i ] [ j − 1 ] , e l s e \b…...

BlueStore BlueFS rocksdb 关联性梳理
Tag: ceph 12.2.4 BlueStore空间初始化 BlueStore磁盘空间管理 总述 OSD挂载目录基于文件系统管理,Slow、WAL、DB空间区域基于裸盘管理;Slow区域:此类空间主要用于存储对象数据,由BlueStore管理,其中分配于BlueFS空…...
PgSQL-并行查询系列-介绍[译]
PgSQL-并行查询系列-介绍 现代CPU模型拥有大量的CPU核心。多年来,数据库应用程序都是并发向数据库发送查询的。查询处理多个表的行时,若可以使用多核,则可以客观地提升性能。PgSQL 9.6引入了并行查询的新特性,开启并行查询后可以大…...

Linux以系统服务的方式启动Kafka(其他服务同理)
最终效果: 先回顾命令行的启动方式: kafka的启动 进入kafka的安装目录 1、首先启动zookeeper服务: bin/zookeeper-server-start.sh config/zookeeper.properties2、再启动kafka bin/kafka-server-start.sh config/server.properties &…...

成都瀚网科技有限公司:抖店的评论会消失吗?
抖店是抖音推出的电子商务平台。很多用户在购物后都会对产品进行评价。但有时用户可能会发现抖店评论缺失,让用户产生一些疑惑和困惑。本文将围绕这个问题提供一些答案和解决方案。 1.为什么抖店评论不见了? 首先需要明确的是,抖店评论消失可…...

优先级队列priority_queue以及仿函数的使用
目录 优先级队列priority_queuepriority_queue的模拟实现仿函数 优先级队列priority_queue 优先级队列priority_queue是一种容器适配器,根据严格的弱排序标准,它默认第一个元素总是它所包含的元素中最大的 优先级队列默认使用vector作为底层存储数据的…...

java+ssm+mysql水费管理系统
项目介绍: 使用javassmmysql开发的用户水费管理系统,系统包含超级管理员,系统管理员、用户角色,功能如下: 超级管理员:管理员管理、用户管理、用水管理(用水记录、缴费提醒)、水费…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...