在使用pytorch的autograd的時候,難免會遇到一兩個的坑等著小伙伴們?nèi)ヌ?。今天小編總結了一些常見的pytorch的autograd的坑、小伙伴們在看完這篇總結后可不能在跳坑了哦。
關于Variable和Tensor
舊版本的Pytorch中,Variable是對Tensor的一個封裝;在Pytorch大于v0.4的版本后,Varible和Tensor合并了,意味著Tensor可以像舊版本的Variable那樣運行,當然新版本中Variable封裝仍舊可以用,但是對Varieble操作返回的將是一個Tensor。
import torch as t
from torch.autograd import Variable
a = t.ones(3,requires_grad=True)
print(type(a))
#輸出:<class 'torch.Tensor'>
a=Variable(a)
print(type(a))
#輸出仍舊是:<class 'torch.Tensor'>
print(a.volatile)
#輸出:__main__:1: UserWarning: volatile was removed (Variable.volatile is always False)
a.volatile=True
print(a.volatile)
#輸出:__main__:1: UserWarning: volatile was removed (Variable.volatile is always False)
#現(xiàn)版本pytorch中移除了volatile這個屬性,即volatile總是false
葉子節(jié)點leaf
對于那些不是任何函數(shù)(Function)的輸出,由用戶創(chuàng)建的節(jié)點稱為葉子節(jié)點,葉子節(jié)點的grad_fn為None。
import torch as t
a = t.ones(3,requires_grad=True)
b = t.rand(3,requires_grad=True)
a,a.is_leaf
#輸出:(tensor([1., 1., 1.], requires_grad=True), True)
b
#輸出:(tensor([0.4254, 0.8763, 0.5901], requires_grad=True), True)
c = a*b
c.is_leaf
#輸出:False.說明c不是葉子節(jié)點
a.grad_fn
#輸出:None.葉子節(jié)點的grad_fn為None.
c.grad_fn
#輸出:<MulBackward0 object at 0x7fa45c406278>
autograd操作
首先Tensor是默認不需要求導的,即requires_grad默認為False。
import torch as t
a = t.ones(3)
a.requires_grad
#輸出:False.Tensor默認不需要求導
如果某一個節(jié)點requires_grad被設置為True,那么所有依賴它的節(jié)點requires_grad都為True。
import torch as t
a = t.ones(3)
b = t.ones(3,requires_grad=True)
b.requires_grad
#輸出:True
c = a + b
c.requires_grad
#輸出:True.雖然c沒有指定需要求導,然是c依賴于b,而b需要求導,所以c.requires_grad=True
只有scalar才能進行反向backward()操作,并且backward對于葉節(jié)點的grad的是累加的。當只進行計算操作不做backward,葉節(jié)點的grad不發(fā)生變化。
更正一下,并不是只有scaler才能進行backward操作,矩陣和向量也可以,只不過backward()中要添加對應維度的參數(shù)。
import torch as t
a = t.ones(3,requires_grad=True)
b = t.rand(3,requires_grad=True)
a,b
#輸出:(tensor([1., 1., 1.], requires_grad=True),
#tensor([0.9373, 0.0556, 0.6426], requires_grad=True))
c = a*b
c
#輸出:tensor([0.9373, 0.0556, 0.6426], grad_fn=<MulBackward0>)
c.backward(retain_graph=True)
#輸出:RuntimeError: grad can be implicitly created only for scalar outputs
#只有數(shù)值scalar才能進行backward操作
d = c.sum()
d.backward(retain_graph=True)
#retain_graph=True是為了保存中間緩存,否則再次backward的時候會報錯
a.grad
#輸出:tensor([0.9373, 0.0556, 0.6426])
b.grad
#輸出:tensor([1., 1., 1.])
#backward后a和b的grad產(chǎn)生了數(shù)值
e = c.sum()
e.backward(retain_graph=True)
b.grad
#輸出:tensor([2., 2., 2.]).b的grad進行了兩次backward后進行了累加.
f = c.sum()
b.grad
#輸出:tensor([2., 2., 2.])
#只進行計算不backward,梯度不更新
Tensor.data和Tensor.detach()
如過tensor的數(shù)值需要參與計算又不想?yún)⑴c到計算圖的更新中,計算的時候可以用tensor.data,這樣既能利用tensor的數(shù)值,又不會更新梯度。
import torch as t
a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
a.data.requires_grad
#輸出:False. a.data獨立于計算圖之外
c = a.data * b.data
d = c.sum()
d.backward()
#輸出:RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
#因為獨立于計算圖之外,requires_grad = False所以不能backward()
當tensor.data被修改的時候,tensor也會同步的被修改,此時用該tensor進行計算并backward的時候梯度的值就不再準確了,因為tensor已經(jīng)被修改了!
import torch as t
a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()
a.data.sigmoid_()
#輸出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311]])
#雖然對a.data進行sigmoid操作,但是a的值已經(jīng)被修改了.
d.backward()
b.grad
#輸出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311]])
#b的grad不準了,本來應該都是1!
為了避免因為對tensor.data修改導致grad變化的情況,可以利用tensor.detach,同樣可以保證tensor不參與到計算圖當中,但是當tensor的值被改變的時候,再進行backward就會報錯而不會有先前的因為tensor的值被改變而導致不準的情況了。
import torch as t
a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a * b
d = c.sum()
a_ = a.detach()
a_.sigmoid_()
a
#輸出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311],
# [0.7311, 0.7311, 0.7311, 0.7311]], requires_grad=True)
#a的值已經(jīng)發(fā)生了改變
d.backward()
#報錯:RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
#因為a的值被修改了,所以不能再進行backward
推薦用tensor.detach的方式而不是tensor.data的方式,因為這樣更保險!
autograd.grad和hook
在計算的時候有時候我們可能會用到非葉節(jié)點的grad,但是非葉節(jié)點的grad在backward之后就會被自動清空:
import torch as t
a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()
d.backward()
a.grad
#輸出:tensor([[0.3114, 0.3017, 0.8461, 0.6899],
# [0.3878, 0.8712, 0.2406, 0.7396],
# [0.6369, 0.0907, 0.4984, 0.5058]])
c.grad
#輸出:None
#c為非葉子節(jié)點,計算后被清空
可以用autograd.grad和hook來處理這種情況:
#利用autograd.grad獲取中間節(jié)點梯度
t.autograd.grad(d,c)
#輸出:(tensor([[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]]),)
#利用hook獲取中間節(jié)點梯度
import torch as t
a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()
def print_grad(grad):
print(grad)
#給c注冊hook
c_hook = c.register_hook(print_grad)
d.backward()
#輸出:tensor([[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]])
#移除鉤子
c_hook.remove()
補充:關于Pytorch中autograd和backward的一些筆記
1 Tensor
Pytorch中所有的計算其實都可以回歸到Tensor上,所以有必要重新認識一下Tensor。
如果我們需要計算某個Tensor的導數(shù),那么我們需要設置其.requires_grad屬性為True。為方便說明,在本文中對于這種我們自己定義的變量,我們稱之為葉子節(jié)點(leaf nodes),而基于葉子節(jié)點得到的中間或最終變量則可稱之為結果節(jié)點。
另外一個Tensor中通常會記錄如下圖中所示的屬性:
data
: 即存儲的數(shù)據(jù)信息
requires_grad
: 設置為True則表示該 Tensor 需要求導
grad: 該 Tensor 的梯度值,每次在計算 backward 時都需要將前一時刻的梯度歸零,否則梯度值會一直累加,這個會在后面講到。
grad_fn
: 葉子節(jié)點通常為 None,只有結果節(jié)點的 grad_fn 才有效,用于指示梯度函數(shù)是哪種類型。
is_leaf
: 用來指示該 Tensor 是否是葉子節(jié)點。
舉例:
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 是一個標量,當調(diào)用它的 backward 方法后會根據(jù)鏈式法則自動計算出葉子節(jié)點的梯度值。
但是如果遇到 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
: 通常在調(diào)用一次 backward 后,pytorch 會自動把計算圖銷毀,所以要想對某個變量重復調(diào)用 backward,則需要將該參數(shù)設置為True
create_graph
: 當設置為True的時候可以用來計算更高階的梯度
grad_variables
: 這個官方說法是 grad_variables' is deprecated. Use 'grad_tensors' instead. 也就是說這個參數(shù)后面版本中應該會丟棄,直接使用grad_tensors就好了。
pytorch設計了grad_tensors這么一個參數(shù)。它的作用相當于“權重”。
先看一個例子:
x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward()
>>> ...
RuntimeError: grad can be implicitly created only for scalar outputs
上面的報錯信息意思是只有對標量輸出它才會計算梯度,而求一個矩陣對另一矩陣的導數(shù)束手無策。
x = torch.ones(2,requires_grad=True)
z = x + 2
z.sum().backward()
print(x.grad)
>>> tensor([1., 1.])
而grad_tensors這個參數(shù)就扮演了幫助求和的作用。
換句話說,就是對 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)
看了前面的內(nèi)容后再看這個函數(shù)就很好理解了,各參數(shù)作用如下:
outputs
: 結果節(jié)點,即被求導數(shù)
inputs
: 葉子節(jié)點
grad_outputs
: 類似于backward方法中的grad_tensors
retain_graph
: 同上
create_graph
: 同上
only_inputs
: 默認為True,如果為True,則只會返回指定input的梯度值。 若為False,則會計算所有葉子節(jié)點的梯度,并且將計算得到的梯度累加到各自的.grad屬性上去。
allow_unused
: 默認為False, 即必須要指定input,如果沒有指定的話則報錯。
注意該函數(shù)返回的是 tuple 類型。
以上就是Pytorch的autograd使用總結,希望能給大家一個參考,也希望大家多多支持W3Cschool。如有錯誤或未考慮完全的地方,望不吝賜教。