最近由於實際需要在學習pytorch,作爲深度學習中最爲重要的反向傳播計算,pytorch用非常簡單的backward( )函數就實現了,但是在實現過程中對於其參數存在一些疑問,下面就從pytorch中反向傳播求導的計算方式,backward( )函數參數來進行說明。
這裏首先還是放出backward( )函數的pytorch文檔,因爲整個說明主要還是圍繞這個函數來進行的。
問題描述
從上面的文檔可以看到backward函數有一個奇怪的參數:grad_tensors,在實現pytorch的官方教程中可以發現:
import torch
import torch.nn as nn
x = torch.tensor([2, 3, 4], dtype=torch.float, requires_grad=True)
print(x)
y = x * 2
while y.norm() < 1000:
y = y * 2
print(y)
y.backward(torch.ones_like(y))
print(x.grad)
上面的程序的輸出爲:
tensor([2., 3., 4.], requires_grad=True)
tensor([ 512., 768., 1024.], grad_fn=<MulBackward0>)
tensor([256., 256., 256.])
這裏我們分佈來講述上面的過程:
- 創建一個張量x,並設置其 requires_grad參數爲True,程序將會追蹤所有對於該張量的操作,當完成計算後通過調用
.backward()
,自動計算所有的梯度, 這個張量的所有梯度將會自動積累到.grad
屬性。 - 創建一個關於x的函數y,由於x的requires_grad參數爲True,所以y對應的用於求導的參數
grad_fn
爲<MulBackward0>
。這是因爲在自動梯度計算中還有另外一個重要的類Function
,Tensor
和Function
互相連接並生成一個非循環圖,它表示和存儲了完整的計算曆史。 每個張量都有一個.grad_fn
屬性,這個屬性引用了一個創建了Tensor
的Function
(除非這個張量是用戶手動創建的,即,這個張量的grad_fn
是None
,例如1中創建的x的grad_fn
是None
) - 我們進行反向傳播並輸出x的梯度值,而這裏出現了一個參數
torch.ones_like(y)
即爲grad_tensors參數
,這裏便引入了我們的問題:
爲什麼在求導的過程中需要引入這個參數,如果我們不引入這個參數的話,則會報下面的錯誤:
RuntimeError: grad can be implicitly created only for scalar outputs
即爲提示我們輸出不是一個標量
下面就開始分析這個問題以及這個參數的作用。
pytorch實現反向傳播中求導的方法
這裏主要參考pytorch計算圖文章中的講解:
由上面的文章知道pytorch是動態圖機制,在訓練模型時候,每迭代一次都會構建一個新的計算圖。而計算圖其實就是代表程序中變量之間的關係。對於這個例子可以構建如下計算圖:
上面的計算圖中每一個葉子節點都是一個用戶自己創建的變量,在網絡backward時候,需要用鏈式求導法則求出網絡最後輸出的梯度,然後再對網絡進行優化,如下就是網絡的求導過程。
通過觀察上面的計算圖可以發現一個很重要的點:
pytorch在利用計算圖求導的過程中根節點都是一個標量,即一個數。當根節點即函數的因變量爲一個向量的時候,會構建多個計算圖對該向量中的每一個元素分別進行求導,這也就引出了下一節的內容
這裏順帶說一下:
pytoch構建的計算圖是動態圖,爲了節約內存,所以每次一輪迭代完也即是進行了一次backward函數計算之後計算圖就被在內存釋放,因此如果你需要多次backward只需要在第一次反向傳播時候添加一個retain_graph=True標識,讓計算圖不被立即釋放。實際上文檔中retain_graph和create_graph兩個參數作用相同,因爲前者是保持計算圖不釋放,而後者是創建計算圖,因此如果我們不想要計算圖釋放掉,將任意一個參數設置爲True都行。(這一段說明的內容與本文主要說明的內容無關,只是順帶說明一下)
backward函數
結合上面兩節的分析,可以發現,pytorch在求導的過程中,分爲下面兩種情況:
- 如果是標量對向量求導(scalar對tensor求導),那麼就可以保證上面的計算圖的根節點只有一個,此時不用引入
grad_tensors參數
,直接調用backward函數即可 - 如果是(向量)矩陣對(向量)矩陣求導(tensor對tensor求導),實際上是先求出Jacobian矩陣中每一個元素的梯度值(每一個元素的梯度值的求解過程對應上面的計算圖的求解方法),然後將這個Jacobian矩陣與
grad_tensors參數
對應的矩陣進行對應的點乘,得到最終的結果。
Jacobian矩陣參考:Jacobian矩陣和Hessian矩陣
爲什麼pytorch不支持tensor對tensor求導,而是引入grad_tensors參數
參考:PyTorch 的 backward 爲什麼有一個 grad_variables 參數?