淺談Pytorch中autograd的若干(踩坑)總結(jié)
關(guān)于Variable和Tensor
舊版本的Pytorch中,Variable是對(duì)Tensor的一個(gè)封裝;在Pytorch大于v0.4的版本后,Varible和Tensor合并了,意味著Tensor可以像舊版本的Variable那樣運(yùn)行,當(dāng)然新版本中Variable封裝仍舊可以用,但是對(duì)Varieble操作返回的將是一個(gè)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這個(gè)屬性,即volatile總是false
葉子節(jié)點(diǎn)leaf
對(duì)于那些不是任何函數(shù)(Function)的輸出,由用戶創(chuàng)建的節(jié)點(diǎn)稱為葉子節(jié)點(diǎn),葉子節(jié)點(diǎn)的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.說(shuō)明c不是葉子節(jié)點(diǎn) a.grad_fn #輸出:None.葉子節(jié)點(diǎn)的grad_fn為None. c.grad_fn #輸出:<MulBackward0 object at 0x7fa45c406278>
autograd操作
首先Tensor是默認(rèn)不需要求導(dǎo)的,即requires_grad默認(rèn)為False。
import torch as t a = t.ones(3) a.requires_grad #輸出:False.Tensor默認(rèn)不需要求導(dǎo)
如果某一個(gè)節(jié)點(diǎn)requires_grad被設(shè)置為True,那么所有依賴它的節(jié)點(diǎn)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沒(méi)有指定需要求導(dǎo),然是c依賴于b,而b需要求導(dǎo),所以c.requires_grad=True
只有scalar才能進(jìn)行反向backward()操作,并且backward對(duì)于葉節(jié)點(diǎn)的grad的是累加的。當(dāng)只進(jìn)行計(jì)算操作不做backward,葉節(jié)點(diǎn)的grad不發(fā)生變化。
更正一下,并不是只有scaler才能進(jìn)行backward操作,矩陣和向量也可以,只不過(guò)backward()中要添加對(duì)應(yīng)維度的參數(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才能進(jìn)行backward操作 d = c.sum() d.backward(retain_graph=True) #retain_graph=True是為了保存中間緩存,否則再次backward的時(shí)候會(huì)報(bào)錯(cuò) 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進(jìn)行了兩次backward后進(jìn)行了累加. f = c.sum() b.grad #輸出:tensor([2., 2., 2.]) #只進(jìn)行計(jì)算不backward,梯度不更新
Tensor.data和Tensor.detach()
如過(guò)tensor的數(shù)值需要參與計(jì)算又不想?yún)⑴c到計(jì)算圖的更新中,計(jì)算的時(shí)候可以用tensor.data,這樣既能利用tensor的數(shù)值,又不會(huì)更新梯度。
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獨(dú)立于計(jì)算圖之外 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 #因?yàn)楠?dú)立于計(jì)算圖之外,requires_grad = False所以不能backward()
當(dāng)tensor.data被修改的時(shí)候,tensor也會(huì)同步的被修改,此時(shí)用該tensor進(jìn)行計(jì)算并backward的時(shí)候梯度的值就不再準(zhǔn)確了,因?yàn)閠ensor已經(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]]) #雖然對(duì)a.data進(jìn)行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不準(zhǔn)了,本來(lái)應(yīng)該都是1!
為了避免因?yàn)閷?duì)tensor.data修改導(dǎo)致grad變化的情況,可以利用tensor.detach,同樣可以保證tensor不參與到計(jì)算圖當(dāng)中,但是當(dāng)tensor的值被改變的時(shí)候,再進(jìn)行backward就會(huì)報(bào)錯(cuò)而不會(huì)有先前的因?yàn)閠ensor的值被改變而導(dǎo)致不準(zhǔn)的情況了。
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() #報(bào)錯(cuò):RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation #因?yàn)閍的值被修改了,所以不能再進(jìn)行backward
推薦用tensor.detach的方式而不是tensor.data的方式,因?yàn)檫@樣更保險(xiǎn)!
autograd.grad和hook
在計(jì)算的時(shí)候有時(shí)候我們可能會(huì)用到非葉節(jié)點(diǎn)的grad,但是非葉節(jié)點(diǎn)的grad在backward之后就會(huì)被自動(dò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() 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é)點(diǎn),計(jì)算后被清空
可以用autograd.grad和hook來(lái)處理這種情況:
#利用autograd.grad獲取中間節(jié)點(diǎn)梯度 t.autograd.grad(d,c) #輸出:(tensor([[1., 1., 1., 1.], # [1., 1., 1., 1.], # [1., 1., 1., 1.]]),) #利用hook獲取中間節(jié)點(diǎn)梯度 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注冊(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()
補(bǔ)充:關(guān)于Pytorch中autograd和backward的一些筆記
1 Tensor
Pytorch中所有的計(jì)算其實(shí)都可以回歸到Tensor上,所以有必要重新認(rèn)識(shí)一下Tensor。
如果我們需要計(jì)算某個(gè)Tensor的導(dǎo)數(shù),那么我們需要設(shè)置其.requires_grad屬性為True。為方便說(shuō)明,在本文中對(duì)于這種我們自己定義的變量,我們稱之為葉子節(jié)點(diǎn)(leaf nodes),而基于葉子節(jié)點(diǎn)得到的中間或最終變量則可稱之為結(jié)果節(jié)點(diǎn)。
另外一個(gè)Tensor中通常會(huì)記錄如下圖中所示的屬性:
data
: 即存儲(chǔ)的數(shù)據(jù)信息
requires_grad
: 設(shè)置為True則表示該 Tensor 需要求導(dǎo)
grad: 該 Tensor 的梯度值,每次在計(jì)算 backward 時(shí)都需要將前一時(shí)刻的梯度歸零,否則梯度值會(huì)一直累加,這個(gè)會(huì)在后面講到。
grad_fn
: 葉子節(jié)點(diǎn)通常為 None,只有結(jié)果節(jié)點(diǎn)的 grad_fn 才有效,用于指示梯度函數(shù)是哪種類型。
is_leaf
: 用來(lái)指示該 Tensor 是否是葉子節(jié)點(diǎn)。
舉例:
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) )
運(yùn)行結(jié)果:
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.)
當(dāng) z 是一個(gè)標(biāo)量,當(dāng)調(diào)用它的 backward 方法后會(huì)根據(jù)鏈?zhǔn)椒▌t自動(dòng)計(jì)算出葉子節(jié)點(diǎn)的梯度值。
但是如果遇到 z 是一個(gè)向量或者是一個(gè)矩陣的情況,這個(gè)時(shí)候又該怎么計(jì)算梯度呢?這種情況我們需要定義grad_tensor來(lái)計(jì)算矩陣的梯度。
在介紹為什么使用之前我們先看一下源代碼中backward的接口是如何定義的:
torch.autograd.backward( tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)
tensor
: 用于計(jì)算梯度的 tensor。也就是說(shuō)這兩種方式是等價(jià)的:torch.autograd.backward(z) == z.backward()
grad_tensors
: 在計(jì)算非標(biāo)量的梯度時(shí)會(huì)用到。他其實(shí)也是一個(gè)tensor,它的shape一般需要和前面的tensor保持一致。
retain_graph
: 通常在調(diào)用一次 backward 后,pytorch 會(huì)自動(dòng)把計(jì)算圖銷毀,所以要想對(duì)某個(gè)變量重復(fù)調(diào)用 backward,則需要將該參數(shù)設(shè)置為True
create_graph
: 當(dāng)設(shè)置為True的時(shí)候可以用來(lái)計(jì)算更高階的梯度
grad_variables
: 這個(gè)官方說(shuō)法是 grad_variables' is deprecated. Use 'grad_tensors' instead. 也就是說(shuō)這個(gè)參數(shù)后面版本中應(yīng)該會(huì)丟棄,直接使用grad_tensors就好了。
pytorch設(shè)計(jì)了grad_tensors這么一個(gè)參數(shù)。它的作用相當(dāng)于“權(quán)重”。
先看一個(gè)例子:
x = torch.ones(2,requires_grad=True) z = x + 2 z.backward() >>> ... RuntimeError: grad can be implicitly created only for scalar outputs
上面的報(bào)錯(cuò)信息意思是只有對(duì)標(biāo)量輸出它才會(huì)計(jì)算梯度,而求一個(gè)矩陣對(duì)另一矩陣的導(dǎo)數(shù)束手無(wú)策。
x = torch.ones(2,requires_grad=True) z = x + 2 z.sum().backward() print(x.grad) >>> tensor([1., 1.])
而grad_tensors這個(gè)參數(shù)就扮演了幫助求和的作用。
換句話說(shuō),就是對(duì) Z 和一個(gè)權(quán)重張量grad_tensors進(jìn)行 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)容后再看這個(gè)函數(shù)就很好理解了,各參數(shù)作用如下:
outputs
: 結(jié)果節(jié)點(diǎn),即被求導(dǎo)數(shù)
inputs
: 葉子節(jié)點(diǎn)
grad_outputs
: 類似于backward方法中的grad_tensors
retain_graph
: 同上
create_graph
: 同上
only_inputs
: 默認(rèn)為True,如果為True,則只會(huì)返回指定input的梯度值。 若為False,則會(huì)計(jì)算所有葉子節(jié)點(diǎn)的梯度,并且將計(jì)算得到的梯度累加到各自的.grad屬性上去。
allow_unused
: 默認(rèn)為False, 即必須要指定input,如果沒(méi)有指定的話則報(bào)錯(cuò)。
注意該函數(shù)返回的是 tuple 類型。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Python使用plt.boxplot()函數(shù)繪制箱圖、常用方法以及含義詳解
箱線圖一般用來(lái)展現(xiàn)數(shù)據(jù)的分布,如上下四分位值、中位數(shù)等,也可以直觀地展示異常點(diǎn),下面這篇文章主要給大家介紹了關(guān)于Python使用plt.boxplot()函數(shù)繪制箱圖、常用方法以及含義詳解的相關(guān)資料,需要的朋友可以參考下2022-08-08Python編寫Windows Service服務(wù)程序
這篇文章主要為大家詳細(xì)介紹了Python編寫Windows Service服務(wù)程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Python中號(hào)稱神仙的六個(gè)內(nèi)置函數(shù)詳解
這篇文章主要介紹了Python中號(hào)稱神仙的六個(gè)內(nèi)置函數(shù),今天分享的這6個(gè)內(nèi)置函數(shù),在使用?Python?進(jìn)行數(shù)據(jù)分析或者其他復(fù)雜的自動(dòng)化任務(wù)時(shí)非常方便,需要的朋友可以參考下2022-05-05Flask快速實(shí)現(xiàn)分頁(yè)效果示例
本文主要介紹了Flask快速實(shí)現(xiàn)分頁(yè)效果示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Python實(shí)現(xiàn)在Excel文件中寫入圖表
這篇文章主要為大家介紹了如何利用Python語(yǔ)言實(shí)現(xiàn)在Excel文件中寫入一個(gè)比較簡(jiǎn)單的圖表,文中的實(shí)現(xiàn)方法講解詳細(xì),快動(dòng)手嘗試一下吧2022-05-05python實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ping工具方法
今天小編就為大家分享一篇python實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ping工具方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01