13.hook函数与CAM可视化


本课程来自深度之眼deepshare.net,部分截图来自课程视频。

内容简介

hook函数概念

Hook函数机制:不改变主体,实现额外功能,像一个挂件,挂钩,hook。有四个。。。具体是在module类中的call中调用。
特征图,非叶子节点的梯度,在运算过程中是会被释放掉的,如果需要提取这些变量,就要用到hook函数。

  1. torch.Tensor.register_hook(hook)
  2. torch.nn.Module.register_forward_hook
  3. torch.nn.Module.register_forward_pre_hook
  4. torch.nn.Module.register_backward_hook

Tensor.register_hook

功能:注册一个反向传播hook函数
Hook函数仅一个输入参数,为张量的梯度
简单的说就是在提取梯度的时候,要自己定义个hook函数F,然后把这个F作为参数传入到register_hook里面。函数F的输入参数为梯度。下面的几个hook也是这个套路。

Module.register_forward_hook

功能:注册module的前向传播hook函数
参数:
·module:当前网络层
·input:当前网络层输入数据
·output:当前网络层输出数据
下面的实例就是要演示卷积的过程,然后把特征图保存下来。
在这里插入图片描述

flag = 1
if flag:

    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(1, 2, 3)
            self.pool1 = nn.MaxPool2d(2, 2)

        def forward(self, x):
            x = self.conv1(x)
            x = self.pool1(x)
            return x
	
	#这里就是自定义的函数,后面要注册它
    def forward_hook(module, data_input, data_output):
        fmap_block.append(data_output)
        input_block.append(data_input)

    def forward_pre_hook(module, data_input):
        print("forward_pre_hook input:{}".format(data_input))

    def backward_hook(module, grad_input, grad_output):
        print("backward hook input:{}".format(grad_input))
        print("backward hook output:{}".format(grad_output))

    # 初始化网络
    net = Net()
    net.conv1.weight[0].detach().fill_(1)
    net.conv1.weight[1].detach().fill_(2)
    net.conv1.bias.data.detach().zero_()

    # 注册hook
    fmap_block = list()
    input_block = list()
    net.conv1.register_forward_hook(forward_hook)
    net.conv1.register_forward_pre_hook(forward_pre_hook)
    net.conv1.register_backward_hook(backward_hook)

    # inference
    fake_img = torch.ones((1, 1, 4, 4))   # batch size * channel * H * W
    output = net(fake_img)

	# 要看backward_hook就要自己构造一个loss,不然看不了
    loss_fnc = nn.L1Loss()
    target = torch.randn_like(output)
    loss = loss_fnc(target, output)
    loss.backward()

    # 观察
    print("output shape: {}\noutput value: {}\n".format(output.shape, output))
    print("feature maps shape: {}\noutput value: {}\n".format(fmap_block[0].shape, fmap_block[0]))
    print("input shape: {}\ninput value: {}".format(input_block[0][0].shape, input_block[0]))

输出结果和图中的设定是一样的,注意输出分别是9和18,接下来是feature map,shape是1,2,2,2,表示有两个22的map,然后每个map和图中一样分别都是9和18组成。
在这里插入图片描述
这里吧输入也记录下来了,shape是1,1,4,4,就是一个4
4的tensor。内容都是1。
在这里插入图片描述

Module.register_forward_pre_hook

功能:注册module前向传播的hook函数
参数:
·module:当前网络层
·input:当前网络层输入数据

Module.register_backward_hook

功能:注册module反向传播的hook函数
参数:
·module:当前网络层
·grad_input:当前网络层输入梯度数据
·grad_output:当前网络层输出梯度数据

hook函数与特征图提取

对alexnet进行特征图提取,上节课中是手工干这个事情,现在用hook来做一遍

# -*- coding:utf-8 -*-
"""
@file name  : hook_fmap_vis.py
# @author   : TingsongYu https://github.com/TingsongYu
@date       : 2019-10-28
@brief      : 采用hook函数可视化特征图
"""
import torch.nn as nn
import numpy as np
from PIL import Image
import torchvision.transforms as transforms
import torchvision.utils as vutils
from torch.utils.tensorboard import SummaryWriter
from tools.common_tools import set_seed
import torchvision.models as models

set_seed(1)  # 设置随机种子

# ----------------------------------- feature map visualization -----------------------------------
# flag = 0
flag = 1
if flag:
    writer = SummaryWriter(comment='test_your_comment', filename_suffix="_test_your_filename_suffix")

    # 数据
    path_img = "./lena.png"     # your path to image
    normMean = [0.49139968, 0.48215827, 0.44653124]
    normStd = [0.24703233, 0.24348505, 0.26158768]

    norm_transform = transforms.Normalize(normMean, normStd)
    img_transforms = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        norm_transform
    ])

    img_pil = Image.open(path_img).convert('RGB')
    if img_transforms is not None:
        img_tensor = img_transforms(img_pil)
    img_tensor.unsqueeze_(0)    # chw --> bchw

    # 模型
    alexnet = models.alexnet(pretrained=True)

    # 注册hook
    fmap_dict = dict()#定义一个字典用于存储特征图
    for name, sub_module in alexnet.named_modules():#对alexnet下面所有的子网络层进行循环

        if isinstance(sub_module, nn.Conv2d):#判断是否是卷积层,如果是则注册一个hook
            key_name = str(sub_module.weight.shape)#用模型的shape来作为字典中的key
            fmap_dict.setdefault(key_name, list())

            n1, n2 = name.split(".")

            def hook_func(m, i, o):#hook函数
                key_name = str(m.weight.shape)
                fmap_dict[key_name].append(o)

            #这里第一个_modules是feature,第二个_modules是0.1.2.3.4...
            alexnet._modules[n1]._modules[n2].register_forward_hook(hook_func)#注册hook

    # forward
    output = alexnet(img_tensor)

    # add image上节的内容,写入event file并进行tensorboard的可视化
    for layer_name, fmap_list in fmap_dict.items():
        fmap = fmap_list[0]
        fmap.transpose_(0, 1)

        nrow = int(np.sqrt(fmap.shape[0]))
        fmap_grid = vutils.make_grid(fmap, normalize=True, scale_each=True, nrow=nrow)
        writer.add_image('feature map in {}'.format(layer_name), fmap_grid, global_step=322)

在tensorboard中打开event file
在这里插入图片描述
在这里插入图片描述
这里是由于激活函数和上节课中使用的不一样,所有特征图也有所不一样。

CAM and Grad-CAM

CAM(class activation map,类激活图)

文献:CAM:《Learning Deep Features for Discriminative Localization》
下面例子是识别图片中的狗狗属于啥品种,那是为什么这个模型会把图中的狗狗分为|Australian terrier这个类别呢,就可以使用CAM来进行可视化。
在这里插入图片描述
CAM的思想是对模型的每一个特征图进行加权求和(注意力机制有木有,就是上面多个特征图乘以 w 1 , w 2 , . . . w n w_1,w_2,...w_n w1,w2,...wn),来看模型更关注图像的什么位置,上图里,明显可以看到更关注狗狗的区域(特别是狗头部分)。如何获得W,就是这个算法的关键:先要对特征图进行global average pool全局平均池化GAP,一张图对应一个神经元,然后接一个FC层进行输出,得到这个类别的每个神经元的权重就是CAM。
CAM有一个缺点:他的网络最后输出部分必须要有一个GAP,才能得到我们想要的权值,所以我们在分析实例的时候,还需要改动最后的GAP,再重新进行训练。
针对上面的缺点,有一个新的解决方案:

Grad-CAM

CAM的改进版,利用梯度作为特征图权重,无需改动和重新训练网络模型
文献:Grad-CAM:Visual Explanations from Deep Networks via Gradient-based Localization
在这里插入图片描述
梯度的feature map的每一个图可以做平均,然后每一个图对应一个权重,注意看那两个箭头。然后将每个权重和原始的feature map进行加权平均操作,然后经过一个ReLU函数,得到最后的结果。
下面是一个实例,是做的CIFA10的类激活图可视化。
在这里插入图片描述
可以看到,模型关注是区域在于蓝天部分(第二排的红色部分),并没有学习到飞机本身的特征。
因此会出现第四个小图,也预测为飞机
在这里插入图片描述
如果把一个可以正确预测为汽车的图片进行resize后,粘贴到蓝天图片中模型不关注的左边中间。
分析与代码:https://zhuanlan.zhihu.com/p/75894080
小结:Grad-CAM可以让我们知道模型是否正确的学习到具体对象的特征。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值