日常都是無腦使用backward,每次看到別人的代碼裏使用諸如autograd.grad這種方法的時候就有點抵觸,今天花了點時間瞭解了一下原理,寫下筆記以供之後參考。如下筆記基於Pytorch1.0html
Pytorch中全部的計算其實均可以迴歸到Tensor上,因此有必要從新認識一下Tensor。若是咱們須要計算某個Tensor的導數,那麼咱們須要設置其.requires_grad
屬性爲True
。爲方便說明,在本文中對於這種咱們本身定義的變量,咱們稱之爲葉子節點(leaf nodes),而基於葉子節點獲得的中間或最終變量則可稱之爲結果節點。例以下面例子中的x
則是葉子節點,y
則是結果節點。node
x = torch.rand(3, requires_grad=True) y = x**2 z = x + x
另一個Tensor中一般會記錄以下圖中所示的屬性:python
data
: 即存儲的數據信息requires_grad
: 設置爲True
則表示該Tensor須要求導grad
: 該Tensor的梯度值,每次在計算backward時都須要將前一時刻的梯度歸零,不然梯度值會一直累加,這個會在後面講到。grad_fn
: 葉子節點一般爲None,只有結果節點的grad_fn纔有效,用於指示梯度函數是哪一種類型。例如上面示例代碼中的y.grad_fn=<PowBackward0 at 0x213550af048>, z.grad_fn=<AddBackward0 at 0x2135df11be0>
is_leaf
: 用來指示該Tensor是不是葉子節點。有以下代碼:微信
x = torch.tensor(1.0, requires_grad=True) y = torch.tensor(2.0, requires_grad=True) z = x**2+y z.backward() print(z, x.grad, y.grad) >>> tensor(3., grad_fn=<AddBackward0>) tensor(2.) tensor(1.)
能夠z是一個標量,當調用它的backward方法後會根據鏈式法則自動計算出葉子節點的梯度值。機器學習
可是若是遇到z是一個向量或者是一個矩陣的狀況,這個時候又該怎麼計算梯度呢?這種狀況咱們須要定義grad_tensor
來計算矩陣的梯度。在介紹爲何使用以前咱們先看一下源代碼中backward的接口是如何定義的:函數
torch.autograd.backward( tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)
tensor
: 用於計算梯度的tensor。也就是說這兩種方式是等價的:torch.autograd.backward(z) == z.backward()
grad_tensors
: 在計算矩陣的梯度時會用到。他其實也是一個tensor,shape通常須要和前面的tensor
保持一致。retain_graph
: 一般在調用一次backward後,pytorch會自動把計算圖銷燬,因此要想對某個變量重複調用backward,則須要將該參數設置爲True
create_graph
: 當設置爲True
的時候能夠用來計算更高階的梯度grad_variables
: 這個官方說法是grad_variables' is deprecated. Use 'grad_tensors' instead.也就是說這個參數後面版本中應該會丟棄,直接使用grad_tensors
就行了。好了,參數大體做用都介紹了,下面咱們看看pytorch爲何設計了grad_tensors
這麼一個參數,以及它有什麼用呢?學習
仍是用代碼作個示例ui
x = torch.ones(2,requires_grad=True) z = x + 2 z.backward() >>> ... RuntimeError: grad can be implicitly created only for scalar outputs
當咱們運行上面的代碼的話會報錯,報錯信息爲RuntimeError: grad can be implicitly created only for scalar outputs。spa
上面的報錯信息意思是隻有對標量輸出它纔會計算梯度,而求一個矩陣對另外一矩陣的導數一籌莫展。scala
$$ X = \left[\begin{array}{cc} x_0 & x_1 \ \end{array}\right] ,,,,,,,,,
Z=X+2=\left[\begin{array}{cc} x_0+2 & x_1+2 \ \end{array}\right] \Rightarrow \frac{\partial{Z}}{\partial{X}}=? $$
那麼咱們只要想辦法把矩陣轉變成一個標量不就行了?好比咱們能夠對z求和,而後用求和獲得的標量在對x求導,這樣不會對結果有影響,例如:
$$ \begin{align} &Z_{sum}=\sum{z_i}=x_0+x_1+8 \notag \ &\text{then} ,,,,, \frac{\partial{Z_{sum}}}{\partial{x_0}}=\frac{\partial{Z_{sum}}}{\partial{x_1}}=1 \notag \end{align} $$
咱們能夠看到對z求和後再計算梯度沒有報錯,結果也與預期同樣:
x = torch.ones(2,requires_grad=True) z = x + 2 z.sum().backward() print(x.grad) >>> tensor([1., 1.])
咱們再仔細想一想,對z求和不就是等價於z點乘一個同樣維度的全爲1的矩陣嗎?即$sum(Z)=dot(Z,I)$,而這個I也就是咱們須要傳入的grad_tensors
參數。(點乘只是相對於一維向量而言的,對於矩陣或更高爲的張量,能夠看作是對每個維度作點乘)
代碼以下:
x = torch.ones(2,requires_grad=True) z = x + 2 z.backward(torch.ones_like(z)) # grad_tensors須要與輸入tensor大小一致 print(x.grad) >>> tensor([1., 1.])
弄個再複雜一點的:
x = torch.tensor([2., 1.], requires_grad=True).view(1, 2) y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True) z = torch.mm(x, y) print(f"z:{z}") z.backward(torch.Tensor([[1., 0]]), retain_graph=True) print(f"x.grad: {x.grad}") print(f"y.grad: {y.grad}") >>> z:tensor([[5., 8.]], grad_fn=<MmBackward>) x.grad: tensor([[1., 3.]]) y.grad: tensor([[2., 0.], [1., 0.]])
結果解釋以下:
總結:
說了這麼多,grad_tensors
的做用其實能夠簡單地理解成在求梯度時的權重,由於可能不一樣值的梯度對結果影響程度不一樣,因此pytorch弄了個這種接口,而沒有固定爲全是1。引用自知乎上的一個評論:若是從最後一個節點(總loss)來backward,這種實現(torch.sum(y*w))的意義就具體化爲 multiple loss term with difference weights 這種需求了吧。
torch.autograd.grad( outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=False)
看了前面的內容後在看這個函數就很好理解了,各參數做用以下:
outputs
: 結果節點,即被求導數inputs
: 葉子節點grad_outputs
: 相似於backward
方法中的grad_tensors
retain_graph
: 同上create_graph
: 同上only_inputs
: 默認爲True
, 若是爲True
, 則只會返回指定input
的梯度值。 若爲False
,則會計算全部葉子節點的梯度,而且將計算獲得的梯度累加到各自的.grad
屬性上去。allow_unused
: 默認爲False
, 即必需要指定input
,若是沒有指定的話則報錯。<footer style="color:white;;background-color:rgb(24,24,24);padding:10px;border-radius:10px;"><br> <h3 style="text-align:center;color:tomato;font-size:16px;" id="autoid-2-0-0"><br> <footer style="color:white;;background-color:rgb(24,24,24);padding:10px;border-radius:10px;"><br> <h3 style="text-align:center;color:tomato;font-size:16px;" id="autoid-2-0-0"><br> <center> <span>微信公衆號:AutoML機器學習</span><br> <img src="https://ask.qcloudimg.com/draft/1215004/21ra82axnz.jpg" style="width:200px;height:200px"> </center> <b>MARSGGBO</b><b style="color:white;"><span style="font-size:25px;">♥</span>原創</b><br> <span>若有意合做或學術討論歡迎私戳聯繫~<br>郵箱:marsggbo@foxmail.com</span> <br><br> <b style="color:white;"><br> 2020-01-23 17:45:35 <p></p> </b><p><b style="color:white;"></b><br> </p></h3><br> </footer> <br><br> <br><br> <b style="color:white;"><br> 2019-9-18<p></p> </b><p><b style="color:white;"></b><br> </p></h3><br> </footer>