钩子方法有四种:
火炬..()
火炬.nn..k()
火炬.nn..ok()
torch.nn.._hook()。
1、手电筒..(挂钩)
用于导出指定张量的梯度,或者修改梯度值。
import torch
def grad_hook(grad):
grad *= 2
x = torch.tensor([2., 2., 2., 2.], requires_grad=True)
y = torch.pow(x, 2)
z = torch.mean(y)
h = x.register_hook(grad_hook)
z.backward()
print(x.grad)
h.remove() # removes the hook
>>> tensor([2., 2., 2., 2.])
注意:(1)上面的代码是有效的,但是如果写成grad = grad * 2就会无效,因为此时没有对grad进行本地操作,新的grad值并没有传递给指定的梯度。为了安全起见,最好在 def 语句中指定 grad。现在:
def grad_hook(grad):
grad = grad * 2
return grad
(2) 可以使用()方法取消钩子。注意,()必须在()之后,因为梯度计算只有在执行()语句时才开始,而在x.()处它只“注册”了一个grad hook。此时没有计算,而是执行 取消这个钩子就行了,然后()这个钩子就不起作用了。
(3)如果类中定义了钩子函数,则必须先在输入参数中添加self,即
def grad_hook(self, grad):
...
2、torch.nn..k(,输入,输出)
用于导出指定子模块(可以是layer、等nn.type)的输入输出张量,但只能修改输出。它常用于导出或修改卷积特征图。
inps, outs = [],[]
def layer_hook(module, inp, out):
inps.append(inp[0].data.cpu().numpy())
outs.append(out.data.cpu().numpy())
hook = net.layer1.register_forward_hook(layer_hook)
output = net(input)
hook.remove()
注意:(1)由于模块可以有多个输入,因此输入是元组类型,需要先提取出来再进行操作;输出是元组类型,可以直接使用。
(2)导出后不要放到显存上,除非你有A100。
(3)只能修改输出out的值,不能修改输入inp的值(不能返回,本地修改无效)。修改时最好以表单形式返回,如:
def layer_hook(self, module, inp, out):
out = self.lam * out + (1 - self.lam) * out[self.indices]
return out
这段代码在mixup中使用,混合中间层特征,实现数据增强,其中self.lam是[0,1]概率值,self.lam是[0,1]概率值。是最后一个序列号。
3、torch.nn.._hook(, in)
用于导出或修改指定子模块的输入张量。
def pre_hook(module, inp):
inp0 = inp[0]
inp0 = inp0 * 2
inp = tuple([inp0])
return inp
hook = net.layer1.register_forward_pre_hook(pre_hook)
output = net(input)
hook.remove()
注意:(1)inp值是tuple类型,所以需要先提取张量,然后进行其他操作,然后将其转换为tuple并返回。
(2)这句话只有在执行=net(input)时才会被调用。 ()可以放在调用后取消钩子。
4、torch.nn..ok(, , )
用于导出指定子模块的输入和输出张量的梯度,但只能修改输入张量的梯度(即只能返回gin),不能修改输出张量的梯度。
gouts = []
def backward_hook(module, gin, gout):
print(len(gin),len(gout))
gouts.append(gout[0].data.cpu().numpy())
gin0,gin1,gin2 = gin
gin1 = gin1*2
gin2 = gin2*3
gin = tuple([gin0,gin1,gin2])
return gin
hook = net.layer1.register_backward_hook(backward_hook)
loss.backward()
hook.remove()
注意:
(1) 和都是元组,必须先展开。修改的时候,执行操作然后再把tuple放回去。
(2) 该钩子函数是在()语句中调用的,因此()应放在()之后,以取消钩子。