栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

PyTorch中的hook机制在module层面的解读

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

PyTorch中的hook机制在module层面的解读

        先说结论:1. hook机制主要有三种表现形式,即register_hook()、register_forward_hook()、register_backward_hook()。register_hook()主要应用在tensor层面,后两者主要用在module层面。

        2. 以module层面为例,通俗来讲,hook的作用就是就是在不改变module定义源码的前提下,获取模型某一层的属性。register_forward_hook()主要是获取前向传播的一些属性,例如某一层的name、某一层的input feature map或output feature map等;register_backward_hook()主要是获取反向传播的属性,主要是梯度。

一、Hook机制提出的意义

       在Pytorch中,模型在计算过程中会自动舍弃计算的中间结果,所以想要获取这些数值就需要使用hook函数。例如,对于复合函数f[g(x)]而言,若要求f关于的导数,根据链式法则必然要先求f关于g的导数,但是在PyTorch中不会保留f关于g的导数这一中间计算结果。因此,在不改变网络的定义代码的前提下,也不需要在forward函数中return某个感兴趣层的输出,利用hook来获取某一层的属性。

二、Hook机制的应用举例

<1>使用注意事项

        register_forward_hook()函数必须在forward()函数调用之后被使用,因为这个函数源码注释显示这个函数“ it will not have effect on forward since this is called after :func:`forward` is called”。

<2>实验代码①

        使用方式为module.register_forward_hook(hook_fn),其中hook_fn的声明为:

hook_fn(module, input, output) -> Tensor or None

        函数的输入分别为module模块,输入,输出。hook可以修改输出值,使用方法类似于Tensor的Hook。

def hook_forward_fn(module, input, output):
    print(f"module: {module}")
    print(f"input: {input}")
    print(f"output: {output}")


class CBR(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = nn.Conv2d(3, 1, 1)
        self.bn = nn.BatchNorm2d(1)

        self.conv.register_forward_hook(hook_forward_fn)
        self.bn.register_forward_hook(hook_forward_fn)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        return torch.relu(x)

net = CBR()
x = torch.rand(1, 3, 2, 2, requires_grad=True)
y = net(x).mean()
y.backward()

输出结果为:

module: Conv2d(3, 1, kernel_size=(1, 1), stride=(1, 1))
input: (tensor([[[[0.1824, 0.0140],
          [0.3478, 0.8911]],

         [[0.3780, 0.3915],
          [0.3625, 0.1337]],

         [[0.5159, 0.2859],
          [0.5742, 0.0458]]]], requires_grad=True),)
output: tensor([[[[-0.3373, -0.2838],
          [-0.3712, -0.3378]]]], grad_fn=)
module: BatchNorm2d(1, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
input: (tensor([[[[-0.3373, -0.2838],
          [-0.3712, -0.3378]]]], grad_fn=),)
output: tensor([[[[-0.1507,  1.5480],
          [-1.2299, -0.1675]]]], grad_fn=)

<3>实验代码②

        使用方式为module.register_backward_hook)hook_fn),其中hook_fn的声明为:

hook_fn(module, grad_input, grad_output) -> Tensor or None

        函数的输入分别为module模块,输入端的grad,输出端的grad,这里称呼的输入与输出是站在前向传播的角度的。如果模块有多个输入与输出的话, 其grad_input和grad_output可以是tuple类型。 与forward不同的是,backward传播的时候,不仅反向传递input和output的grad,还会传递模块Parameter的grad,因此比如fc模块,其grad_input 是一个三元组的tuple,(对bias的梯度,对输入的梯度,对w的梯度);conv模块grad_input也是一个三元组tuple,为(对输入的梯度,对w的梯度,对bias的梯度)。如果想改变梯度,可以对grad_output进行操作。

def hook_backward_fn(module, grad_input, grad_output):
    print(f"module: {module}")
    print(f"grad_output: {grad_output}")
    print(f"grad_input: {grad_input}")
    print("*"*20)

class fc(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(1, 2)
        self.fc2 = nn.Linear(2, 3)

        self.fc1.register_backward_hook(hook_backward_fn)
        self.fc2.register_backward_hook(hook_backward_fn)

    def forward(self, x):
        x = self.fc1(x)
        x = self.fc2(x)
        return x

x = torch.rand(2, 1, requires_grad=True)
net = fc()
y = net(x).mean()
y.backward()

输出结果为:

module: Linear(in_features=2, out_features=3, bias=True)
grad_output: (tensor([[0.1667, 0.1667, 0.1667],
        [0.1667, 0.1667, 0.1667]]),)
grad_input: (tensor([0.3333, 0.3333, 0.3333]), tensor([[-0.0926,  0.0243],
        [-0.0926,  0.0243]]), tensor([[-0.1773, -0.1773, -0.1773],
        [-0.0068, -0.0068, -0.0068]]))
********************
module: Linear(in_features=1, out_features=2, bias=True)
grad_output: (tensor([[-0.0926,  0.0243],
        [-0.0926,  0.0243]]),)
grad_input: (tensor([-0.1853,  0.0486]), tensor([[0.0073],
        [0.0073]]), tensor([[-0.1057,  0.0278]]))
********************

<4>实验代码③

        这里展示的是比较常见的hook_fn的定义方式:

import torch
import torch.nn as nn
import torch.nn.functional as F
 
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
 
    def forward(self, x):
        out = F.relu(self.conv1(x))     #1 
        out = F.max_pool2d(out, 2)      #2
        out = F.relu(self.conv2(out))   #3
        out = F.max_pool2d(out, 2)
        out = out.view(out.size(0), -1)
        out = F.relu(self.fc1(out))
        out = F.relu(self.fc2(out))
        out = self.fc3(out)
        return out
 
features = []
def hook(module, input, output): 
    # module: model.conv2 
    # input :in forward function  [#2]
    # output:is  [#3 self.conv2(out)]
    features.append(output.clone().detach())
    # output is saved  in a list 
 
 
net = LeNet() ## 模型实例化 
x = torch.randn(2, 3, 32, 32) ## input 
handle = net.conv2.register_forward_hook(hook) ## 获取整个Lenet模型 conv2的中间结果
y = net(x)  ## 获取的是 关于 input x 的 conv2 结果 
 
print(features[0].size()) # 即 [#3 self.conv2(out)]
handle.remove() ## hook删除 

<5>实验代码④

        若要绘制热力图,首先要获取梯度。下面附两个绘制热力图的参考链接:

        PaddlePaddle热力图绘制

        Matplotlib调用imshow()函数绘制热图

三、Reference

        实验代码参考来源:

        代码参考1

        代码参考2

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1011387.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号