PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

本文深入探讨了PyTorch中的Hook机制,包括Variable和nn.Module的Hook注册与使用,以及它们在前向和反向传播过程中的作用。通过具体代码示例,详细解析了Hook如何帮助我们理解和修改梯度计算。
部署运行你感兴趣的模型镜像

PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解

PyTorch入门实战教程

在看pytorch官方文档的时候,发现在nn.Module部分和Variable部分均有hook的身影。感到很神奇,因为在使用tensorflow的时候没有碰到过这个词。所以打算一探究竟。

Variable 的 hook

register_hook(hook)

注册一个backward钩子。

每次gradients被计算的时候,这个hook都被调用。hook应该拥有以下签名:

hook不应该修改它的输入,但是它可以返回一个替代当前梯度的新梯度。

这个函数返回一个 句柄(handle)。它有一个方法 handle.remove(),可以用这个方法将hook从module移除。

例子:

输出:

nn.Module的hook

register_forward_hook(hook)

在module上注册一个forward hook。

这里要注意的是,hook 只能注册到 Module 上,即,仅仅是简单的 op 包装的 Module,而不是我们继承 Module时写的那个类,我们继承 Module写的类叫做 Container。

每次调用forward()计算输出的时候,这个hook就会被调用。它应该拥有以下签名:

hook不应该修改 input和output的值。 这个函数返回一个 句柄(handle)。它有一个方法 handle.remove(),可以用这个方法将hook从module移除。

看这个解释可能有点蒙逼,但是如果要看一下nn.Module的源码怎么使用hook的话,那就乌云尽散了。

先看 register_forward_hook

result = self.forward(input, **kwargs)
for hook in self._forward_hooks.values():
#将注册的hook拿出来用
hook_result = hook(self, input, result)

return result

1
2
3
4
5
6
7
def call ( self , input , kwargs ) :
   result = self . forward ( input , * kwargs )
   for hook in self . _forward_hooks . values ( ) :
       #将注册的hook拿出来用
       hook_result = hook ( self , input , result )
   . . .
   return result

可以看到,当我们执行model(x)的时候,底层干了以下几件事:

  • 调用 forward 方法计算结果
  • 判断有没有注册 forward_hook,有的话,就将 forward 的输入及结果作为hook的实参。然后让hook自己干一些不可告人的事情。

看到这,我们就明白hook签名的意思了,还有为什么hook不能修改input的output的原因。

小例子:

def for_hook(module, input, output):
print(module)
for val in input:
print(“input val:”,val)
for out_val in output:
print(“output val:”, out_val)

class Model(nn.Module):
def init(self):
super(Model, self).init()
def forward(self, x):

    return x+1

model = Model()
x = Variable(torch.FloatTensor([1]), requires_grad=True)
handle = model.register_forward_hook(for_hook)
print(model(x))
handle.remove()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch
from torch import nn
import torch . functional as F
from torch . autograd import Variable
 
def for_hook ( module , input , output ) :
     print ( module )
     for val in input :
         print ( “input val:” , val )
     for out_val in output :
         print ( “output val:” , out_val )
 
class Model ( nn . Module ) :
     def init ( self ) :
         super ( Model , self ) . init ( )
     def forward ( self , x ) :
 
         return x + 1
 
model = Model ( )
x = Variable ( torch . FloatTensor ( [ 1 ] ) , requires_grad = True )
handle = model . register_forward_hook ( for_hook )
print ( model ( x ) )
handle . remove ( )

register_backward_hook

在module上注册一个bachward hook。此方法目前只能用在Module上,不能用在Container上,当Module的forward函数中只有一个Function的时候,称为Module,如果Module包含其它Module,称之为Container。

每次计算module的inputs的梯度的时候,这个hook会被调用。hook应该拥有下面的signature。

如果module有多个输入输出的话,那么grad_input grad_output将会是个tuple。

hook不应该修改它的arguments,但是它可以选择性的返回关于输入的梯度,这个返回的梯度在后续的计算中会替代grad_input。

这个函数返回一个句柄(handle)。它有一个方法 handle.remove(),可以用这个方法将hook从module移除。

从上边描述来看,backward hook似乎可以帮助我们处理一下计算完的梯度。看下面nn.Module中register_backward_hook方法的实现,和register_forward_hook方法的实现几乎一样,都是用字典把注册的hook保存起来。

先看个例子来看一下hook的参数代表了什么:

x=Variable(torch.FloatTensor([[1, 2, 3]]),requires_grad=True)
mod=Linear(3, 1, bias=False)
mod.register_backward_hook(bh) # 在这里给module注册了backward hook

out=mod(x)
out.register_hook(lambda grad: 0.1grad) #在这里给variable注册了 hook
out.backward()
print([’
’]20)
print(“x.grad”, x.grad)
print(mod.weight.grad)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import torch
from torch . autograd import Variable
from torch . nn import Parameter
import torch . nn as nn
import math
def bh ( m , gi , go ) :
     print ( “Grad Input” )
     print ( gi )
     print ( “Grad Output” )
     print ( go )
     return gi [ 0 ] 0 , gi [ 1 ] 0
class Linear ( nn . Module ) :
     def init ( self , in_features , out_features , bias = True ) :
         super ( Linear , self ) . init ( )
         self . in_features = in_features
         self . out_features = out_features
         self . weight = Parameter ( torch . Tensor ( out_features , in_features ) )
         if bias :
             self . bias = Parameter ( torch . Tensor ( out_features ) )
         else :
             self . register_parameter ( ‘bias’ , None )
         self . reset_parameters ( )
 
     def reset_parameters ( self ) :
         stdv = 1. / math . sqrt ( self . weight . size ( 1 ) )
         self . weight . data . uniform_ ( - stdv , stdv )
         if self . bias is not None :
             self . bias . data . uniform_ ( - stdv , stdv )
 
     def forward ( self , input ) :
         if self . bias is None :
             return self . _backend . Linear ( ) ( input , self . weight )
         else :
             return self . _backend . Linear ( ) ( input , self . weight , self . bias )
 
x = Variable ( torch . FloatTensor ( [ [ 1 , 2 , 3 ] ] ) , requires_grad = True )
mod = Linear ( 3 , 1 , bias = False )
mod . register_backward_hook ( bh ) # 在这里给module注册了backward hook
 
out = mod ( x )
out . register_hook ( lambda grad : 0.1 grad ) #在这里给variable注册了 hook
out . backward ( )
print ( [ ] 20 )
print ( “x.grad” , x . grad )
print ( mod . weight . grad )

输出:

Variable containing:
0 0 0
[torch.FloatTensor of size 1x3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Grad Input
( Variable containing :
1.00000e - 02
   5.1902 - 2.3778 - 4.4071
[ torch . FloatTensor of size 1x3 ]
, Variable containing :
0.1000    0.2000    0.3000
[ torch . FloatTensor of size 1x3 ]
)
Grad Output
( Variable containing :
0.1000
[ torch . FloatTensor of size 1x1 ]
, )
[ ’’ , , '’ , , '’ , , '’ , , '’ , , '’ , , '’ , , '’ , , '’ , , '’ , ' ]
x . grad Variable containing :
0 - 0 - 0
[ torch . FloatTensor of size 1x3 ]
 
Variable containing :
0    0    0
[ torch . FloatTensor of size 1x3 ]

可以看出,grad_in保存的是,此模块Function方法的输入的值的梯度。grad_out保存的是,此模块forward方法返回值的梯度。我们不能在grad_in上直接修改,但是我们可以返回一个新的new_grad_in作为Function方法inputs的梯度。

上述代码对variable和module同时注册了backward hook,这里要注意的是,无论是module hook还是variable hook,最终还是注册到Function上的。这点通过查看Varible的register_hook源码和Module的__call__源码得知。

Module的register_backward_hook的行为在未来的几个版本可能会改变

BP过程中Function中的动作可能是这样的:

关于pytorch run_backward()的可能实现猜测为:

中间Variable的梯度在BP的过程中是保存到GradBuffer中的(C++源码中可以看到), BP完会释放. 如果retain_grads=True的话,就不会被释放。

文章来源:Keith

PyTorch入门实战教程

发表评论 取消回复

电子邮件地址不会被公开。 必填项已用标注

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

PyTorch 2.5

PyTorch 2.5

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值