钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
Hook 是 PyTorch 中一个十分有用的特性。利用它,我们可以不必改变网络输入输出的结构,方便地获取、改变网络中间层变量的值和梯度。这个功能被广泛用于可视化神经网络中间层的 feature、gradient,从而诊断神经网络中可能出现的问题,分析网络有效性。本文将结合代码,由浅入深地介绍 pytorch 中 hook 的用法。文章将分为以下三个部分:
- Hook for Tensors :针对 Tensor 的 hook
- Hook for Modules:针对例如 nn.Conv2d、nn.Linear等网络模块的 hook
- Guided Backpropagation:利用 Hook 实现的一段神经网络可视化代码
Hook for Tensors
上面的计算图中,x y w 为叶子节点,而 z 为中间变量
在 PyTorch 的计算图(computation graph)中,只有叶子结点(leaf nodes)的变量会保留梯度。而所有中间变量的梯度只被用于反向传播,一旦完成反向传播,中间变量的梯度就将自动释放,从而节约内存。如下面这段代码所示:
import torch
x = torch.Tensor([0, 1, 2, 3]).requires_grad_()
y = torch.Tensor([4, 5, 6, 7]).requires_grad_()
w = torch.Tensor([1, 2, 3, 4]).requires_grad_()
z = x+y
# z.retain_grad()
o = w.matmul(z)
o.backward()
# o.retain_grad()
print('x.requires_grad:', x.requires_grad) # True
print('y.requires_grad:', y.requires_grad) # True
print('z.requires_grad:', z.requires_grad) # True
print('w.requires_grad:', w.requires_grad) # True
print('o.requires_grad:', o.requires_grad) # True
print('x.grad:', x.grad) # tensor([1., 2., 3., 4.])
print('y.grad:', y.grad) # tensor([1., 2., 3., 4.])
print('w.grad:', w.grad) # tensor([ 4., 6., 8., 10.])
print('z.grad:', z.grad) # None
print('o.grad:', o.grad) # None
由于 z 和 o 为中间变量(并非直接指定数值的变量,而是由别的变量计算得到的变量),它们虽然 requires_grad 的参数都是 True,但是反向传播后,它们的梯度并没有保存下来,而是直接删除了,因此是 None。如果想在反向传播之后保留它们的梯度,则需要特殊指定:把上面代码中的z.retain_grad() 和 o.retain_grad的注释去掉,可以得到它们对应的梯度,运行结果如下所示:
x.requires_grad: True
y.requires_grad: True
z.requires_grad: True
w.requires_grad: True
o.requires_grad: True
x.grad: tensor([1., 2., 3., 4.])
y.grad: tensor([1., 2., 3., 4.])
w.grad: tensor([ 4., 6., 8., 10.])
z.grad: tensor([1., 2., 3., 4.])
o.grad: tensor(1.)
但是,这种加 retain_grad() 的方案会增加内存占用,并不是个好办法,对此的一种替代方案,就是用 hook 保存中间变量的梯度。
对于中间变量z,hook 的使用方式为:z.register_hook(hook_fn),其中 hook_fn为一个用户自定义的函数:
hook_fn(grad) -> Tensor or None
即向函数hook_fn输入参数grad后,输出有两种情况:一是Tensor,而是None(它的输入为待hook的中间变量 z 的梯度,输出为一个 Tensor 或者是 None)。
当输出为None时,一般用于获取这个中间变量的梯度并将其打印;当输出为Tensor时,一般将这个中间变量原计算出来的梯度结果替换成这个输出的Tensor,并用于接下来的梯度计算过程。由此可见,hook_fn这一函数的定义是很自由的:
- 输出的结果是由用户进行定义的,同时输出的结果决定了这个hook的功能:打印中间变量的梯度or替换计算得到的中间变量的梯度。
- 当输出的结果为Tensor时,可以在hook_fn中对函数传入的、由BP算法计算得到的中间变量的梯度进行变换,得到一个新的Tensor用于替代原有的计算得到的Tensor作为新的梯度,并用于之后的梯度计算过程。所以在如何将这个原有的中间变量对应的梯度进行变换上有很大的自由。比如线性变换,将这个梯度放大多少倍,完全是由用户自己决定的。
反向传播时,梯度传播到变量 z,再继续向前传播之前,将会传入 hook_fn。如果hook_fn的返回值是 None,那么梯度将不改变,继续向前传播,如果 hook_fn的返回值是 Tensor 类型,则该 Tensor 将取代 z 原有的梯度,向前传播。
关于hook_fn()函数的代码如下:
# case1: hook_fn的输出为None时:
def hook_fn(grad): # 这个grad为BP算法计算得到的调用hook_fn函数的中间变量的梯度值
print(grad) # 打印计算得到的该变量对应的gradient值
#return None # 可以不用加这句,返回结果依然是None
# case2: hook_fn的输出为Tensor时:
def hook_fn(grad):
grad_ = grad * 2 # 将该变量计算得到的gradient值放大两倍
print(grad_) # 打印经过变换后的梯度值(或用于替代原先梯度值的新梯度值)
return grad_ # 将经过变换后的gradient值返回
注意hook的使用方法:
Tensor_name.register_hook(hook_fn)
使用hook并运行代码观察梯度结果:
# case1: hook_fn() return None, and hook only print the gradient of the tensor
import torch
def hook_fn(grad):
print(grad)
x = torch.Tensor([0, 1, 2, 3]).requires_grad_()
y = torch.Tensor([4, 5, 6, 7]).requires_grad_()
w = torch.Tensor([1, 2, 3, 4]).requires_grad_()
z = x+y
z.register_hook(hook_fn)
o = w.matmul(z)
o.backward()
print('x.grad:', x.grad)
print('y.grad:', y.grad)
print('w.grad:', w.grad)
print('z.grad:', z.grad)
# case2: hook_fn() return the tensor, and use this tensor to replace the original gradient
import torch
def hook_fn(grad):
grad_ = grad * 2
print(grad_)
return grad_
x = torch.Tensor([0, 1, 2, 3]).requires_grad_()
y = torch.Tensor([4, 5, 6, 7]).requires_grad_()
w = torch.Tensor([1, 2, 3, 4]).requires_grad_()
z = x+y
z.register_hook(hook_fn)
o = w.matmul(z)
o.backward()
print('x.grad:', x.grad)
print('y.grad:', y.grad)
print('w.grad:', w.grad)
print('z.grad:', z.grad)
运行结果如下:
# case1: hook_fn returns None
tensor([1., 2., 3., 4.])
x.grad: tensor([1., 2., 3., 4.])
y.grad: tensor([1., 2., 3., 4.])
w.grad: tensor([ 4., 6., 8., 10.])
z.grad: None
# case2: hook_fn returns tensor
tensor([2., 4., 6., 8.])
x.grad: tensor([2., 4., 6., 8.])
y.grad: tensor([2., 4., 6., 8.])
w.grad: tensor([ 4., 6., 8., 10.])
z.grad: None
可见,hook_fn返回tensor时,z的gradinet值变为原先的两倍,同时x y的gradient值也变为原先的两倍。
Hook for Modules
网络模块 module 不像上一节中的 Tensor,拥有显式的变量名可以直接访问,而是被封装在神经网络中间。我们通常只能获得网络整体的输入和输出,对于夹在网络中间的模块,我们不但很难得知它输入/输出的梯度,甚至连它输入输出的数值都无法获得。除非设计网络时,在 forward 函数的返回值中包含中间 module 的输出,或者用很麻烦的办法,把网络按照 module 的名称拆分再组合,让中间层提取的 feature 暴露出来。
为了解决这个麻烦,PyTorch 设计了两种 hook:register_forward_hook 和register_backward_hook,分别用来获取正/反向传播时,中间层模块输入和输出的 feature/gradient,大大降低了获取模型内部信息流的难度。
register forward hook
register_forward_hook的作用是获取前向传播过程中,各个网络模块的输入和输出。对于模块module,其使用方式为:module_name.register_forward_hook(hook_fn_forward) 。其中hook_fn_forward为用户自定义函数:
hook_fn_forward(module, input, output) -> None
它的输入变量分别为:模块,模块的输入,模块的输出,和对 Tensor 的 hook 不同,forward hook 不返回任何值,也就是说不能用它来修改输入或者输出的值,但借助这个 hook,我们可以方便地用预训练的神经网络提取特征,而不用改变预训练网络的结构。(直观地理解,因为并不设计反向传播梯度计算的过程,前向过程仅仅是需要观察输入与输出,故对于grad_fn返回None即可,返回Tensor并无用处。具体的原因可以参考源码。)
实现代码如下:
import torch
from torch import nn
# 首先我们定义一个模型
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.fc1 = nn.Linear(3, 4)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(4, 1)
self.initialize()
# 为了方便验证,我们将指定特殊的weight和bias
def initialize(self):
with torch.no_grad():
# 因为在这里只需一个前向的过程,所以不需要autograd机制
# 可以使用torch.nn.Parmaeter()来指定网络中某一变量的参数值
self.fc1.weight = torch.nn.Parameter(
torch.Tensor([[1., 2., 3.],
[-4., -5., -6.],
[7., 8., 9.],
[-10., -11., -12.]]))
self.fc1.bias = torch.nn.Parameter(torch.Tensor([1.0, 2.0, 3.0, 4.0]))
self.fc2.weight = torch.nn.Parameter(torch.Tensor([[1.0, 2.0, 3.0, 4.0]]))
self.fc2.bias = torch.nn.Parameter(torch.Tensor([1.0]))
def forward(self, x):
o = self.fc1(x)
o = self.relu1(o)
o = self.fc2(o)
return o
# 全局变量,用于存储中间层的 feature
total_feat_out = []
total_feat_in = []
# 定义 forward hook function
def hook_fn_forward(module, input, output):
print(module) # 用于区分模块
print('input', input) # 首先打印出来
print('output', output)
total_feat_out.append(output) # 然后分别存入全局 list 中
total_feat_in.append(input)
model = Model()
modules = model.named_children() #
for name, module in modules:
module.register_forward_hook(hook_fn_forward)
# module.register_backward_hook(hook_fn_backward)
# 注意下面代码中 x 的维度,对于linear module,输入一定是大于等于二维的
# (第一维是 batch size)。在 forward hook 中看不出来,但是 backward hook 中,
# 得到的梯度完全不对。
x = torch.Tensor([[1.0, 1.0, 1.0]]).requires_grad_()
o = model(x)
o.backward()
print('==========Saved inputs and outputs==========')
for idx in range(len(total_feat_in)):
print('input: ', total_feat_in[idx])
print('output: ', total_feat_out[idx])
输出结果如下所示:
Linear(in_features=3, out_features=4, bias=True)
input (tensor([[1., 1., 1.]], requires_grad=True),)
output tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>)
ReLU()
input (tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>),)
output tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>)
Linear(in_features=4, out_features=1, bias=True)
input (tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>),)
output tensor([[89.]], grad_fn=<AddmmBackward>)
==========Saved inputs and outputs==========
input: (tensor([[1., 1., 1.]], requires_grad=True),)
output: tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>)
input: (tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>),)
output: tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>)
input: (tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>),)
output: tensor([[89.]], grad_fn=<AddmmBackward>)
register backward hook
和register_forward_hook相似,register_backward_hook 的作用是获取神经网络反向传播过程中,各个模块输入端和输出端的梯度值。对于模块 module,其使用方式为:module.register_backward_hook(hook_fn_backward) 。其中hook_fn_backward为用户自定义函数:
hook_fn_backward(module, input_grad, output_grad) -> Tensor or None
它的输入变量分别为:模块,模块输入端的梯度,模块输出端的梯度。需要注意的是,这里的输入端和输出端,是站在前向传播的角度的,而不是反向传播的角度。例如线性模块:o=W*x+b,其输入端为 W,x 和 b,输出端为 o。所以这里提到的输入端,是前向的输入端,但是对于反向传播过程来说,他实际上是对应的输出端。如果模块有多个输入或者输出的话,grad_input和grad_output可以是 tuple 类型。对于线性模块:o=W*x+b ,它的输入端包括了W、x 和 b 三部分,因此 grad_input 就是一个包含三个元素的 tuple。
当hook_fn_backward()输出为None时,其对于module.register_backward_hook()的作用和hook_fn_forward()对于module.register_forward_hook()的作用一致。返回None时,并不修改值,仅仅用于打印中间层变量参数的值,所以在hook_fn_backward()函数中只需要对函数参数input和output打印并存放进提前设置好的全局列表中。
hook_fn_backward()返回None时,实现代码如下所示:
import torch
from torch import nn
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.fc1 = nn.Linear(3, 4)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(4, 1)
self.initialize()
def initialize(self):
with torch.no_grad():
self.fc1.weight = torch.nn.Parameter(
torch.Tensor([[1., 2., 3.],
[-4., -5., -6.],
[7., 8., 9.],
[-10., -11., -12.]]))
self.fc1.bias = torch.nn.Parameter(torch.Tensor([1.0, 2.0, 3.0, 4.0]))
self.fc2.weight = torch.nn.Parameter(torch.Tensor([[1.0, 2.0, 3.0, 4.0]]))
self.fc2.bias = torch.nn.Parameter(torch.Tensor([1.0]))
def forward(self, x):
o = self.fc1(x)
o = self.relu1(o)
o = self.fc2(o)
return o
total_grad_out = []
total_grad_in = []
def hook_fn_backward(module, grad_input, grad_output):
print(module) # 为了区分模块
# 为了符合反向传播的顺序,我们先打印 grad_output
print('grad_output', grad_output)
# 再打印 grad_input
print('grad_input', grad_input)
# 保存到全局变量
total_grad_in.append(grad_input)
total_grad_out.append(grad_output)
model = Model()
modules = model.named_children()
# 注意只能对简单模块进行register_backward_hook()
for name, module in modules:
module.register_backward_hook(hook_fn_backward)
x = torch.Tensor([[1.0, 1.0, 1.0]]).requires_grad_()
o = model(x)
o.backward()
print('==========Saved inputs and outputs==========')
for idx in range(len(total_grad_in)):
print('grad output: ', total_grad_out[idx])
print('grad input: ', total_grad_in[idx])
输出结果如下所示:
Linear(in_features=3, out_features=4, bias=True)
input (tensor([[1., 1., 1.]], requires_grad=True),)
output tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>)
ReLU()
input (tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>),)
output tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>)
Linear(in_features=4, out_features=1, bias=True)
input (tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>),)
output tensor([[89.]], grad_fn=<AddmmBackward>)
==========Saved inputs and outputs==========
input: (tensor([[1., 1., 1.]], requires_grad=True),)
output: tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>)
input: (tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>),)
output: tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>)
input: (tensor([[ 7., 0., 27., 0.]], grad_fn=<ThresholdBackward0>),)
output: tensor([[89.]], grad_fn=<AddmmBackward>)
待补全部分:hook_fn_backward()的输出为Tensor时。
其实有实现输出为Tensor的hook_fn_backward(),在实现中将grad_in * 2并返回。但是最终运行结果报错,报错提示时原本期待的值为3,但是实际得到的值为6。所以在没有查看register_backward_hook()的源码的情况下,不知道应该如何具体操作才能有效地更改grad_in即反向传播过程中的输出梯度值。这个留给之后需要的时候再进行研究。
以下给出完整的实现代码:
import torch import torch.nn as nn # hook def hook_fn(grad_in): print('grad_in_hook_fn', grad_in) return None def hook_fn_forward(module, input, output): #print(module) #print('input>>>', input) #print('output>>>', output) total_feat_out.append(output) total_feat_in.append(input) return None def hook_fn_backward_N(module, grad_input, grad_output): # the input is the ``input`` in forward propagation # input is a tuple of (w, b, x) when the model is y = w * x +b # hook_fn_backward can return a tensor as a new grad_input to replace the calculated grad_input # pay attention to the concept of ``input`` and ``output`` print(module) #print('grad_in', grad_input) #print('grad_out', grad_output) total_grad_in_N.append(grad_input) total_grad_out_N.append(grad_output) return None def hook_fn_backward_T(module, grad_input, grad_output): #print(module) grad_in = grad_input * 2 total_grad_in_T.append(grad_input) total_grad_out_T.append(grad_output) return grad_in # model class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.fc1 = nn.Linear(3, 4) self.relu1 = nn.ReLU() self.fc2 = nn.Linear(4, 1) #self.initialize() # 为了方便验证,我们将指定特殊的weight和bias ''' def initialize(self): with torch.no_grad(): self.fc1.weight = torch.nn.Parameter( torch.Tensor([[1., 2., 3.], [-4., -5., -6.], [7., 8., 9.], [-10., -11., -12.]])) self.fc1.bias = torch.nn.Parameter(torch.Tensor([1.0, 2.0, 3.0, 4.0])) self.fc2.weight = torch.nn.Parameter(torch.Tensor([[1.0, 2.0, 3.0, 4.0]])) self.fc2.bias = torch.nn.Parameter(torch.Tensor([1.0])) ''' def forward(self, x): o = self.fc1(x) o = self.relu1(o) o = self.fc2(o) return o net = Model() # using hook and execute the forward and backward propagation process total_feat_out = [] total_feat_in = [] total_grad_in_N = [] total_grad_out_N = [] total_grad_in_T = [] total_grad_out_T = [] # net_name.named_children() input_data = (torch.tensor([1., 1., 1.])).unsqueeze(0) #print(input_data.size()) input_data.requires_grad=True modules = net2.named_children() for name, module in modules: module.register_backward_hook(hook_fn_backward_N) module.register_backward_hook(hook_fn_backward_T) # forward propagation output_data = net2(input_data) output_data.register_hook(hook_fn) output_data.backward()
输出结果如下所示:
grad_in_hook_fn tensor([[1.]]) Linear(in_features=4, out_features=1, bias=True) Linear(in_features=4, out_features=1, bias=True) RuntimeError: hook 'hook_fn_backward_T' has returned an incorrect number of values (got 6, but expected 3)
注意
- hook_fn_backward()与hook_fn_forward()的不同
- 在 forward hook 中,input 是 x,而不包括 W 和 b。
- 返回 Tensor 或者 None,backward hook 函数不能直接改变它的输入变量,但是可以返回新的 grad_input,反向传播到它上一个模块。
- register_backward_hook只能操作简单模块,而不能操作包含多个子模块的复杂模块。如果对复杂模块用了 backward hook,那么我们只能得到该模块最后一次简单操作的梯度信息。对于上面的代码稍作修改,不再遍历各个子模块,而是把 model 整体绑在一个 hook_fn_backward()上:
代码及输出结果如下所示。有输出结果可知程序只输出了 fc2 的梯度信息。
# 代码修改部分
model = Model()
model.register_backward_hook(hook_fn_backward)
# 程序输出结果
Model(
(fc1): Linear(in_features=3, out_features=4, bias=True)
(relu1): ReLU()
(fc2): Linear(in_features=4, out_features=1, bias=True)
)
grad_output (tensor([[1.]]),)
grad_input (tensor([1.]), tensor([[1., 2., 3., 4.]]), tensor([[ 7.],
[ 0.],
[27.],
[ 0.]]))
==========Saved inputs and outputs==========
grad output: (tensor([[1.]]),)
grad input: (tensor([1.]), tensor([[1., 2., 3., 4.]]), tensor([[ 7.],
[ 0.],
[27.],
[ 0.]]))
- 同样注意先register再进行forward/backward propagation process
Guided Backpropagation
通过上文的介绍,我们已经掌握了PyTorch 中各种 hook 的使用方法。接下来,我们将用这个技术写一小段代码,来可视化预训练的神经网络。
Guided Backpropagation 算法来自 ICLR 2015 的文章:Striving for Simplicity: The All Convolutional Net
补充
1.
关于Module.register_forward_hook()和Module.register_backward_hook()的使用。一定要注意这只是完成了一个hook的注册(register),并没有实现对hook_fn_forward()/hook_fn_backward()的调用,只有当传入输入数据完成前向传播的过程后才会实现对hook_fn_forward()/hook_fn_backward()的调用。所以也只有在前向传播过程完成之后列表中才会存放中间变量对应参数。此时才可以对其进行读取。同时注意,hook的注册要在进行前向/反向传播过程之前进行(这一点很关键,否则会出现``list``为空的情况)。
验证代码及结果如下所示:
import torch
from torch import nn
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.fc1 = nn.Linear(3, 4)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(4, 1)
self.initialize()
def initialize(self):
with torch.no_grad():
self.fc1.weight = torch.nn.Parameter(
torch.Tensor([[1., 2., 3.],
[-4., -5., -6.],
[7., 8., 9.],
[-10., -11., -12.]]))
self.fc1.bias = torch.nn.Parameter(torch.Tensor([1.0, 2.0, 3.0, 4.0]))
self.fc2.weight = torch.nn.Parameter(torch.Tensor([[1.0, 2.0, 3.0, 4.0]]))
self.fc2.bias = torch.nn.Parameter(torch.Tensor([1.0]))
def forward(self, x):
o = self.fc1(x)
o = self.relu1(o)
o = self.fc2(o)
return o
# 全局变量,用于存储中间层的 feature
total_feat_out = []
total_feat_in = []
def hook_fn_forward(module, input, output):
total_feat_out.append(output) # 然后分别存入全局 list 中
total_feat_in.append(input)
model = Model()
modules = model.named_children() #
for name, module in modules:
module.register_forward_hook(hook_fn_forward)
# module.register_backward_hook(hook_fn_backward)
x = torch.Tensor([[1.0, 1.0, 1.0]]).requires_grad_()
o = model(x)
o.backward()
print('==========Saved inputs and outputs==========')
print(len(total_feat_in))
for idx in range(len(total_feat_in)):
print('input: ', total_feat_in[idx])
print('output: ', total_feat_out[idx])
对应的输出结果如下:
==========Saved inputs and outputs==========
3
input: (tensor([[1., 1., 1.]], requires_grad=True),)
output: tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>)
input: (tensor([[ 7., -13., 27., -29.]], grad_fn=<AddmmBackward>),)
output: tensor([[ 7., 0., 27., 0.]], grad_fn=<ReluBackward0>)
input: (tensor([[ 7., 0., 27., 0.]], grad_fn=<ReluBackward0>),)
output: tensor([[89.]], grad_fn=<AddmmBackward>)
当将代码中的前向传播过程注释掉,输出结果如下所示。
# 将前向传播过程注释掉
'''
x = torch.Tensor([[1.0, 1.0, 1.0]]).requires_grad_()
o = model(x)
o.backward()
'''
# 将前向传播过程注释掉后的输出结果
==========Saved inputs and outputs==========
0
2.
backward hook 在全连接层和卷积层有许多表现不一致的地方
- 形状
- 在卷积层中,weight 的梯度和 weight 的形状相同
- 在全连接层中,weight 的梯度的形状是 weight 形状的转秩(观察上文中代码的输出可以验证)
- grad_input tuple 中各梯度的顺序
- 在卷积层中,bias 的梯度位于tuple 的末尾:grad_input = (对feature的导数,对权重 W 的导数,对 bias 的导数)
- 在全连接层中,bias 的梯度位于 tuple 的开头:grad_input=(对 bias 的导数,对 feature 的导数,对 W 的导数)
- 当 batchsize>1时,对 bias 的梯度处理不同
- 在卷积层,对 bias 的梯度为整个 batch 的数据在 bias 上的梯度之和:grad_input = (对feature的导数,对权重 W 的导数,对 bias 的导数)
- 在全连接层,对 bias 的梯度是分开的,bach 中每条数据,对应一个 bias 的梯度:grad_input = ((data1 对 bias 的导数,data2 对 bias 的导数 ...),对 feature 的导数,对 W 的导数)