參考自《Pytorch autograd,backward詳解》:
1 Tensor
Pytorch中所有的計算其實都可以回歸到Tensor上,所以有必要重新認識一下Tensor。
如果我們需要計算某個Tensor的導數,那么我們需要設置其.requires_grad
屬性為True
。為方便說明,在本文中對於這種我們自己定義的變量,我們稱之為葉子節點(leaf nodes),而基於葉子節點得到的中間或最終變量則可稱之為結果節點。
另外一個Tensor中通常會記錄如下圖中所示的屬性:
data
: 即存儲的數據信息requires_grad
: 設置為True
則表示該 Tensor 需要求導grad
: 該 Tensor 的梯度值,每次在計算 backward 時都需要將前一時刻的梯度歸零,否則梯度值會一直累加,這個會在后面講到。grad_fn
: 葉子節點通常為 None,只有結果節點的 grad_fn 才有效,用於指示梯度函數是哪種類型。is_leaf
: 用來指示該 Tensor 是否是葉子節點。
舉例:
x = torch.rand(3, requires_grad=True) y = x ** 2 z = x + x print( 'x requires grad: {}, is leaf: {}, grad: {}, grad_fn: {}.' .format(x.requires_grad, x.is_leaf, x.grad, x.grad_fn) ) print( 'y requires grad: {}, is leaf: {}, grad: {}, grad_fn: {}.' .format(y.requires_grad, y.is_leaf, y.grad, y.grad_fn) ) print( 'z requires grad: {}, is leaf: {}, grad: {}, grad_fn: {}.' .format(z.requires_grad, z.is_leaf, z.grad, z.grad_fn) )
運行結果:
x requires grad: True, is leaf: True, grad: None, grad_fn: None. y requires grad: True, is leaf: False, grad: None, grad_fn: <PowBackward0 object at 0x0000021A3002CD88>. z requires grad: True, is leaf: False, grad: None, grad_fn: <AddBackward0 object at 0x0000021A3002CD88>.
2 torch.autograd.backward
如下代碼:
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
這么一個參數。它的作用相當於“權重”。
先看一個例子:
x = torch.ones(2,requires_grad=True) z = x + 2 z.backward() >>> ... RuntimeError: grad can be implicitly created only for scalar outputs
上面的報錯信息意思是只有對標量輸出它才會計算梯度,而求一個矩陣對另一矩陣的導數束手無策。
$X = \begin{bmatrix} x_0 & x_1 \end{bmatrix} \Rightarrow Z = \begin{bmatrix} x_0 + 2 & x_1 + 2 \end{bmatrix} \Rightarrow \frac{\partial Z}{\partial X} = ?$
那么我們只要想辦法把 $Z$ 轉變成一個標量不就好了?比如我們可以對 $Z$ 求和,然后用求和得到的標量在分別對 $x_0, x_1$ 求導,這樣不會對結果有影響,例如:
$Z_{sum} = \sum z_i = x_0 + x_1 + 4$
$\frac{\partial Z_{sum}}{\partial x_0} = \frac{\partial Z_{sum}}{\partial x_1} = 1$
x = torch.ones(2,requires_grad=True) z = x + 2 z.sum().backward() print(x.grad) >>> tensor([1., 1.])
而grad_tensors
這個參數就扮演了幫助求和的作用。
換句話說,就是對 $Z$ 和一個權重張量grad_tensors
進行 hadamard product 后求和。這也是 grad_tensors 需要與傳入的 tensor 大小一致的原因。
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.])
3 torch.autograd.grad
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
,如果沒有指定的話則報錯。
注意該函數返回的是 tuple 類型。