你知道pytorch的backward求導的要求嗎?你想了解pytorch反向傳播的原理嗎?本文將記錄不同結果對求導參數的要求,並使用代碼詳細說明,本文借鑒它人博客對pytorch反向傳播原理進行解釋。
backward函數解釋 :
一. 如果是標量對向量求導(scalar對tensor求導),那么就可以保證上面的計算圖的根節點只有一個,此時不用引入grad_tensors參數(即梯度權重)
,直接調用backward函數即可。
代碼如下:
x = torch.ones(2, requires_grad=True) # x = [1,1]
y = 2 * x[0] ** 3+ 2 * x[1] ** 3 # y=2*x^2 ,其中 x = [1,1], y是2維
y.backward() # y'= ∂(2*x^2)/∂x = 4x
print(x.grad) # x在x=[1,1]時候的 導數值
結果如下:
二. 如果是(向量)矩陣對(向量)矩陣求導(tensor對tensor求導),實際上是先求出矩陣中每一個元素的梯度值(每一個元素的梯度值的求解過程對應下面的計算圖的求解方法), 然后將這個矩陣與grad_tensors參數(即梯度權重)
對應的矩陣進行對應的點乘,得到最終的結果。
x = torch.ones(3, requires_grad=True) # x = [1,1,1]
y = 2 * x ** 2 # y=2*x^2 ,其中 x = [1,1,1], y是3維
gradients = torch.tensor([0.1, 1.0, 0.001], dtype=torch.float) # [0.1, 1.0, 0.0001] 表示各個維度上導函數前的權重
y.backward(gradients) # y'= ∂(2*x^2)/∂x = 4x
print(x.grad) # x在x=[1,1,1]時候的 導數值
結果如下:
三.如果是(向量)矩陣對(向量)矩陣求導(tensor對tensor求導),無gradients就會報錯。
x = torch.ones(3, requires_grad=True) # x = [1,1,1]
y = 2 * x ** 2 # y=2*x^2 ,其中 x = [1,1,1], y是3維
gradients = torch.tensor([0.1, 1.0, 0.001], dtype=torch.float) # [0.1, 1.0, 0.0001] 表示各個維度上導函數前的權重
y.backward() # y'= ∂(2*x^2)/∂x = 4x
print(x.grad) # x在x=[1,1,1]時候的 導數值
結論: 反向傳播必須要各個節點的權重,因標量只有一個值,故而無需權重,而矩陣是一個向量,故而需要對應的權重。
四.backward結合Fuction函數使用方法(紅色字體很重要):
class MyReLU(torch.autograd.Function): def forward(self, input_): # 在forward中,需要定義MyReLU這個運算的forward計算過程 # 同時可以保存任何在后向傳播中需要使用的變量值 self.save_for_backward(input_) # 將輸入保存起來,在backward時使用 output = input_.clamp(min=0) # relu就是截斷負數,讓所有負數等於0 return output def backward(self, grad_output): # 根據BP算法的推導(鏈式法則),dloss / dx = (dloss / doutput) * (doutput / dx) # dloss / doutput就是輸入的參數grad_output、 # 因此只需求relu的導數,在乘以grad_outpu input_, = self.saved_tensors grad_input = grad_output.clone() grad_input[input < 0] = 0 # 上述計算的結果就是左式。即ReLU在反向傳播中可以看做一個通道選擇函數,所有未達到閾值(激活值<0)的單元的梯度都為0 return grad_input
在結合官網列子理解:
>>> class Exp(Function): >>> >>> @staticmethod >>> def forward(ctx, i): >>> result = i.exp() >>> ctx.save_for_backward(result) # 保留e^x >>> return result >>> >>> @staticmethod >>> def backward(ctx, grad_output): >>> result, = ctx.saved_tensors >>> return grad_output * result # 恰好(dloss/dout)*e^x >>> >>> #Use it by calling the apply method: >>> output = Exp.apply(input)
這里首先還是放出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.])
- 1
- 2
- 3
這里我們分布來講述上面的過程:
- 創建一個張量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是動態圖機制,在訓練模型時候,每迭代一次都會構建一個新的計算圖。而計算圖其實就是代表程序中變量之間的關系。對於y = ( a + b ) ( b + c ) y=(a+b)(b+c)y=(a+b)(b+c)這個例子可以構建如下計算圖:
上面的計算圖中每一個葉子節點都是一個用戶自己創建的變量,在網絡backward時候,需要用鏈式求導法則求出網絡最后輸出的梯度,然后再對網絡進行優化,如下就是網絡的求導過程。
通過觀察上面的計算圖可以發現一個很重要的點:
pytorch在利用計算圖求導的過程中根節點都是一個標量,即一個數。當根節點即函數的因變量為一個向量的時候,會構建多個計算圖對該向量中的每一個元素分別進行求導,這也就引出了下一節的內容
這里順帶說一下:
pytoch構建的計算圖是動態圖,為了節約內存,所以每次一輪迭代完也即是進行了一次backward函數計算之后計算圖就被在內存釋放,因此如果你需要多次backward只需要在第一次反向傳播時候添加一個retain_graph=True標識,讓計算圖不被立即釋放。實際上文檔中retain_graph和create_graph兩個參數作用相同,因為前者是保持計算圖不釋放,而后者是創建計算圖,因此如果我們不想要計算圖釋放掉,將任意一個參數設置為True都行。(這一段說明的內容與本文主要說明的內容無關,只是順帶說明一下)
參考博客:https://blog.csdn.net/sinat_28731575/article/details/90342082