【Pytorch with fastai】第 18 章 :使用 CAM 进行 CNN 解释

news2025/1/11 19:39:30

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

CAM 和 Hooks

渐变CAM

结论


现在我们知道如何从头开始构建几乎任何东西,让我们使用这些知识来创建全新的(并且非常有用!)功能:类激活映射。它让我们深入了解 CNN 为什么做出这样的预测。

在此过程中,我们将了解 PyTorch 的一个我们以前从未见过的方便功能,即hook,我们将应用本书其余部分介绍的许多概念。如果你真的想测试你对本书材料的理解,在你读完本章后,试着把它放在一边,自己从头开始重新创造这些想法(不要偷看!)。

CAM 和 Hooks

激活图(CAM) 由 Bolei Zhou 等人介绍。在 “为判别性本地化学习深层特征”中。它使用最后的输出 卷积层(就在平均池化层之前)与预测一起为我们提供了模型做出决定的原因的热图可视化。这是一个有用的解释工具。

更准确地说,在最后一个卷积层的每个位置,我们有与最后一个线性层一样多的过滤器。因此,我们可以计算这些激活与最终权重的点积,以获得特征图上每个位置的用于做出决定的特征的分数。

我们将需要一种方法来在模型训练时访问模型内​​部的激活。在 PyTorch 中,这可以通过hook来完成。Hooks 相当于 PyTorch 的 fastai 的回调。Learner然而,钩子允许您将代码注入前向和后向计算本身,而不是让您像 fastai 回调一样将代码注入训练循环。我们可以将一个钩子附加到模型的任何层,它会在我们计算输出(前向钩子)或 反向传播(后向钩子)期间执行。前向钩子是一个接受三样东西的函数——一个模块、它的输入和它的输出——它可以执行你想要的任何行为。(fastai 还提供了一个方便 HookCallback的,我们不会在这里介绍,但看看 fastai 文档;它使使用钩子更容易一些。)

为了说明,我们将使用我们在第 1 章中训练的相同猫狗模型 :

path = untar_data(URLs.PETS)/'images'
def is_cat(x): return x[0].isupper()
dls = ImageDataLoaders.from_name_func(
    path, get_image_files(path), valid_pct=0.2, seed=21,
    label_func=is_cat, item_tfms=Resize(224))
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(1)
epochtrain_lossvaild_losserror_ratetime
00.1419870.0188230.00744200:16
epochtrain_lossvaild_losserror_ratetime
00.0509340.0153660.00676600:21

首先,我们将获取一张猫图片和一批数据:

img = PILImage.create(image_cat())
x, = first(dls.test_dl([img]))

对于 CAM,我们要存储最后一个卷积层的激活值。我们把我们的钩子函数放在一个类中,这样它就有一个我们可以稍后访问的状态,并且只存储输出的副本:

class Hook():
    def hook_func(self, m, i, o): self.stored = o.detach().clone()

然后我们可以实例化 一个Hook并将其附加到我们想要的层,即 CNN 主体的最后一层:

hook_output = Hook()
hook = learn.model[0].register_forward_hook(hook_output.hook_func)

现在我们可以抓取一批并通过我们的模型提供它:

with torch.no_grad(): output = learn.model.eval()(x)

我们可以访问我们存储的激活:

act = hook_output.stored[0]

让我们也仔细检查我们的预测:

F.softmax(output, dim=-1)
tensor([[7.3566e-07, 1.0000e+00]], device='cuda:0')

我们知道0(for False) 是“狗”,因为类在 fastai 中自动排序,但我们仍然可以通过查看来仔细检查dls.vocab

dls.vocab
(#2) [False,True]

所以,我们的模型非常有信心这是一张猫的照片。

为了将我们的权重矩阵(2 乘以激活数)与激活(批量大小按行按列)进行点积,我们使用自定义einsum

act.shape
torch.Size([512, 7, 7])
cam_map = torch.einsum('ck,kij->cij', learn.model[1][-1].weight, act)
cam_map.shape
torch.Size([2, 7, 7])

对于我们批次中的每张图像,对于每个类别,我们都会得到一个 7×7 的特征图,告诉我们哪里的激活值更高,哪里的激活值更低。这将使我们看到图片的哪些区域影响了模型的决定。

例如,我们可以找出哪些区域使模型决定这只动物是一只猫(请注意,DataLoader我们需要输入decodex因为它已经被索引时不维护类型TensorImage——这可能会在您阅读本文时修复):

x_dec = TensorImage(dls.train.decode((x,))[0][0])
_,ax = plt.subplots()
x_dec.show(ctx=ax)
ax.imshow(cam_map[1].detach().cpu(), alpha=0.6, extent=(0,224,224,0),
              interpolation='bilinear', cmap='magma');

在这种情况下,亮黄色区域对应高激活,紫色区域对应低激活。在这种情况下,我们可以看到头部和前爪是让模型确定这是一张猫图片的两个主要区域。

一旦你完成了你的钩子,你应该删除它,否则它可能会泄漏一些内存:

hook.remove()

这就是为什么让 Hook类成为上下文管理器通常是个好主意,在您输入时注册挂钩 它并在您退出时将其删除。上下文管理器是一种 Python 结构,__enter__当对象在with 子句中创建时以及__exit__在子句末尾调用with。例如,这就是 Python 处理with open(...) as f:结构的方式,您经常会在打开文件时看到这种结构,而无需close(f)在末尾显式显示。

如果我们定义Hook如下

class Hook():
    def __init__(self, m):
        self.hook = m.register_forward_hook(self.hook_func)
    def hook_func(self, m, i, o): self.stored = o.detach().clone()
    def __enter__(self, *args): return self
    def __exit__(self, *args): self.hook.remove()

我们可以这样安全地使用它:

with Hook(learn.model[0]) as hook:
    with torch.no_grad(): output = learn.model.eval()(x.cuda())
    act = hook.stored

fastaiHook为您提供了这个类,以及一些其他方便的类,使使用钩子更容易。

此方法很有用,但仅适用于最后一层。Gradient CAM是解决这个问题的一个变体。

渐变CAM

我们刚刚看到的方法让我们只计算具有最后激活的热图,因为一旦我们有了我们的特征,我们就必须将它们相乘 通过最后的权重矩阵。这不适用于网络的内层。2016 年论文 “Grad-CAM:你为什么这么说?”中介绍的变体 由 Ramprasaath R. Selvaraju 等人撰写。使用所需类别的最终激活的梯度。如果你还记得一点关于反向传递的知识,最后一层输出相对于该层输入的梯度等于层权重,因为它是一个线性层。

对于更深的层,我们仍然需要梯度,但它们不再只等于权重。我们必须计算它们。PyTorch 在向后传递期间为我们计算了每一层的梯度 ,但它们没有存储(张量除外,其中requires_gradis True)。但是,我们可以在向后传递上注册一个钩子,PyTorch 会将梯度作为参数提供给它,因此我们可以将它们存储在那里。为此,我们将使用一个类似于 的HookBwdHook,但拦截和存储梯度而不是激活:

class HookBwd():
    def __init__(self, m):
        self.hook = m.register_backward_hook(self.hook_func)
    def hook_func(self, m, gi, go): self.stored = go[0].detach().clone()
    def __enter__(self, *args): return self
    def __exit__(self, *args): self.hook.remove()

然后对于类索引1(对于True,即“猫”),我们像以前一样截取最后一个卷积层的特征,并计算我们类的输出激活的梯度。我们不能只调用 output.backward,因为梯度仅对标量(通常是我们的损失)有意义,并且output是 2 阶张量。但是,如果我们选择一个图像(我们将使用0)和一个类别(我们将使用1),我们可以计算我们喜欢的任何权重或激活的梯度,相对于该单个值,使用output[0,cls].backward。我们的钩子拦截我们将用作权重的梯度:

cls = 1
with HookBwd(learn.model[0]) as hookg:
    with Hook(learn.model[0]) as hook:
        output = learn.model.eval()(x.cuda())
        act = hook.stored
    output[0,cls].backward()
    grad = hookg.stored

Grad-CAM 的权重由特征图上的梯度平均值给出。然后就和之前一模一样了:

w = grad[0].mean(dim=[1,2], keepdim=True)
cam_map = (w * act[0]).sum(0)
_,ax = plt.subplots()
x_dec.show(ctx=ax)
ax.imshow(cam_map.detach().cpu(), alpha=0.6, extent=(0,224,224,0),
              interpolation='bilinear', cmap='magma');

Grad-CAM 的新颖之处在于我们可以在任何层上使用它。例如,这里我们在倒数第二个 ResNet 组的输出上使用它:

with HookBwd(learn.model[0][-2]) as hookg:
    with Hook(learn.model[0][-2]) as hook:
        output = learn.model.eval()(x.cuda())
        act = hook.stored
    output[0,cls].backward()
    grad = hookg.stored
w = grad[0].mean(dim=[1,2], keepdim=True)
cam_map = (w * act[0]).sum(0)

我们现在可以查看该层的激活图:

_,ax = plt.subplots()
x_dec.show(ctx=ax)
ax.imshow(cam_map.detach().cpu(), alpha=0.6, extent=(0,224,224,0),
              interpolation='bilinear', cmap='magma');

结论

模型解释是一个活跃的研究领域,在这简短的一章中,我们只是触及了可能的表面。类激活图通过显示对给定预测最负责的图像区域,让我们深入了解模型预测特定结果的原因。这可以帮助我们分析误报并找出我们的训练中缺少什么样的数据来避免它们。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Design a Facebook NewsFeed

title: Notes of System Design No.06 — Design a Facebook’s Newsfeed description: Design a Facebook’s Newsfeed ’ date: 2022-05-13 18:01:58 tags: 系统设计 categories: 系统设计 00. What is News Feed? 01.Functional Requirement 02. Non-Functional Requireme…

面试刷题---计算机网络部分(一)

1 请描述 TCP/IP 协议中主机与主机之间通信的三要素 答: IP 地址(IP address) 子网掩码(subnet mask) IP 路由(IP router) 扩展: TCP/IP定义:TCP/IP是基于TCP和IP这两个…

iHRM 人力资源管理系统_第11章_刷脸登录

iHRM 人力资源管理系统_第11章_刷脸登录 文章目录iHRM 人力资源管理系统_第11章_刷脸登录第11章 刷脸登录1 浅谈人工智能1.1 人工智能的概述1.2 人工智能的应用领域1.3 基于人工智能的刷脸登录介绍2 百度云AI概述2.1 概述2.2 百度云AI的开发步骤2.3 百度云AI的注册与认证3 百度…

web大作业 静态网页(地下城与勇士 10页 带视频)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材,DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 游戏官网 | 游戏网站 | 电竞游戏 | 游戏介绍 | 等网站的设计与制作 | HTML期末大学生网页设计作业,Web大学生网页 HTML:结构 …

开源免费的对象存储Minio

什么是Minio? Minio是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合存储大容量、非结构化的数据。例如,图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是…

机器学习:卷积神经网络

卷积神经网络卷积神经网络的结构及原理卷积层池化层激活函数全连接层反馈运算使用MNIST数据集进行代码解析数据介绍实现流程代码实现卷积神经网络的结构及原理 卷积层 卷积运算一个重要的特点就是:通过卷积运算,可以使原信号特征增强,并且降…

服务器常用的异常及性能排查

服务器常用的异常及性能排查 使用 top 命令查看性能指标 top 命令使用详细介绍:传送门 查看Tasks total 进程数 正常我们在使用过程中对每天的一个进程数大概是有一个谱的,比如正常就是1百多个,突然暴增几百,那就很明显这里有…

计算机网络:运输层

运输层 运输层主要解决了应用进程之间的通信,称之为端到端协议 1.运输层概述 计算机网-------络体系结构的角度 AP:应用进程之间的简称 2. 运输层端口号、复用与分用的概念 2.1 端口号 2.2 发送方的复用(multiplexing)和接收方的分用&…

【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day10

大家好,我是陶然同学,软件工程大三明年实习。认识我的朋友们知道,我是科班出身,学的还行,但是对面试掌握不够,所以我将用这100多天更新Java面试题🙃🙃。 不敢苟同,相信大…

锐捷RLDP理论及实验讲解

RLDP概念 RLDP(Rapid Link Detection Protocol)是一个用于快速检测以太网链路故障的链路协议,包括环路链路故障、单向链路故障、双向链路故障等 工作原理 RLDP定义了两种协议报文:探测报文(Probe)和探测响…

【Java第32期】:Spring 中普通Maven项目的创建

作者:有只小猪飞走啦 博客地址:https://blog.csdn.net/m0_62262008?typeblog 内容:Spring 中普通Maven项目的创建 文章目录前言一,创建Spring项目1.创建一个普通的Maven项目2,添加Spring框架3,添加启动类…

【数据库系统概论】关系数据理论、范式

数据库一二三范式简单解释 第一范式 一个关系模式应当是一个五元组。 R(U,D,DOM,F)R(U,D,DOM,F)R(U,D,DOM,F) 这里: 关系名RRR是符号化的元组语义UUU为一组属性DDD为属性组UUU中的属性所来自的域DOMDOMDOM为属性到域的映射FFF为属性组UUU上的一组数据依赖 由于D…

RabbitMQ_概述

RabbitMQ大致工作流程图 解释 Producer:生产者 Consumer:消费者 Connection:AMQP协议连接 Channel:信道,进行消息读写的通道,RabbitMQ的绝大部分操作在信道完成;客户端可以建立多个信道&…

用 AWTK 和 AWPLC 快速开发嵌入式应用程序 (4)- 自定义功能块(上)

AWPLC 目前还处于开发阶段的早期,写这个系列文章的目的,除了用来验证目前所做的工作外,还希望得到大家的指点和反馈。如果您有任何疑问和建议,请在评论区留言。 1. 背景 AWTK 全称 Toolkit AnyWhere,是 ZLG 开发的开源…

PTA题目 两个数的简单计算器

本题要求编写一个简单计算器程序,可根据输入的运算符,对2个整数进行加、减、乘、除或求余运算。题目保证输入和输出均不超过整型范围。 输入格式: 输入在一行中依次输入操作数1、运算符、操作数2,其间以1个空格分隔。操作数的数…

跟艾文学编程《Python基础》(7)pandas数据分析

作者: 艾文,计算机硕士学位,企业内训讲师和金牌面试官,公司资深算法专家,现就职BAT一线大厂。邮箱: 1121025745qq.com博客:https://wenjie.blog.csdn.net/内容:跟艾文学编程《Python…

汉字风格迁移篇---W-net:基于深度神经网络的一次任意风格汉字生成

文章目录一、摘要二、提出原因已有的一些模型解决方案依然存在的限制三、介绍与创新四、模型介绍预处理w-net结构优化策略和损失函数五、实验实验设置用zi2zi作为基线具体实现1、 W-Net训练期间的超参数设置如下:2、一些细节处理模型评估W-net、zi2zi-v1、zi2zi-v2不…

第2-3-7章 个人网盘服务接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.8 导入其他接口代码5.8.1 接口导入-分页查询附件5.8.2 接口导入-根据业务类型/业务id查询附件5.9 导入网盘服务接口5.9.1 导入FileController5.9.2 导入StatisticsController5.9.3 导入FileRestManager5.9.4 导入FileService5.9.5 导入FileServiceImpl5.9.6 扩展File…

面向OLAP的列式存储DBMS-8-[ClickHouse]的常用聚合函数

ClickHouse 中的常用聚合函数 1 聚合函数 ClickHouse 中的聚合函数,因为和关系型数据库的相似性,本来聚合函数不打算说的,但是 ClickHouse 提供了很多关系型数据库中没有的函数,所以我们还是从头了解一下。 1.1 count count&…

Vue3 用src动态引入本地图片

💭💭 ✨: Vue3 用src动态引入本地图片   💟:东非不开森的主页   💜: 躲起来的星星也在努力发光 你也要💜💜   🌸: 如有错误或不足之处,希望可以指正&#…