鉤子編程(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 的導數)