pytorch求导
前言:構(gòu)建深度學(xué)習(xí)模型的基本流程就是:搭建計(jì)算圖,求得損失函數(shù),然后計(jì)算損失函數(shù)對模型參數(shù)的導(dǎo)數(shù),再利用梯度下降法等方法來更新參數(shù)。搭建計(jì)算圖的過程,稱為“正向傳播”,這個(gè)是需要我們自己動手的,因?yàn)槲覀冃枰O(shè)計(jì)我們模型的結(jié)構(gòu)。由損失函數(shù)求導(dǎo)的過程,稱為“反向傳播”,求導(dǎo)是件辛苦事兒,所以自動求導(dǎo)基本上是各種深度學(xué)習(xí)框架的基本功能和最重要的功能之一,PyTorch也不例外,后面有時(shí)間會寫一下tensorflow和mxnet框架的自動求導(dǎo)。
默認(rèn)已經(jīng)具備了導(dǎo)數(shù)相關(guān)知識,會矩陣,向量,標(biāo)量之間的求導(dǎo)法則。
一、pytorch自動求導(dǎo)初步認(rèn)識
比如有一個(gè)函數(shù),y=x的平方(y=x2),在x=3的時(shí)候它的導(dǎo)數(shù)為6,我們通過代碼來演示這樣一個(gè)過程。
x = torch.tensor(3.0, requires_grad=True) y = torch.pow(x, 2) print(x.requires_grad) print(y.requires_grad) y.backward() print(x.grad)最終的運(yùn)行結(jié)果為:
True
True
tensor(6.)? ?#這和我們自己算的是一模一樣的。
這里有一些關(guān)鍵點(diǎn),
1.1 tensor的創(chuàng)建與屬性設(shè)置
先來看一下tensor的定義:
tensor(data, dtype=None, device=None, requires_grad=False) -> Tensor參數(shù):data: (array_like): tensor的初始值. 可以是列表,元組,numpy數(shù)組,標(biāo)量等;dtype: tensor元素的數(shù)據(jù)類型device: 指定CPU或者是GPU設(shè)備,默認(rèn)是Nonerequires_grad:是否可以求導(dǎo),即求梯度,默認(rèn)是False,即不可導(dǎo)的(1)tensor對象的requires_grad屬性
每一個(gè)tensor都有一個(gè)requires_grad屬性,表示這個(gè)tensor是否是可求導(dǎo)的,如果是true則可以求導(dǎo),否則不能求導(dǎo),語法格式為:
x.requires_grad? ? 判斷一個(gè)tensor是否可以求導(dǎo),返回布爾值
需要注意的是,只有當(dāng)所有的“葉子變量”,即所謂的leaf variable都是不可求導(dǎo)的,那函數(shù)y才是不能求導(dǎo)的,什么是leaf variable呢?這其實(shí)涉及到“計(jì)算圖”相關(guān)的知識,但是我們通過下面的例子一下就能明白了,如下:
#創(chuàng)建一個(gè)二元函數(shù),即z=f(x,y)=x2+y2,x可求導(dǎo),y設(shè)置不可求導(dǎo) x=torch.tensor(3.0,requires_grad=True) y=torch.tensor(4.0,requires_grad=False) z=torch.pow(x,2)+torch.pow(y,2)#判斷x,y是否是可以求導(dǎo)的 print(x.requires_grad) print(y.requires_grad) print(z.requires_grad)#求導(dǎo),通過backward函數(shù)來實(shí)現(xiàn) z.backward() #查看導(dǎo)數(shù),也即所謂的梯度 print(x.grad) print(y.grad)'''運(yùn)行結(jié)果為: True # x是可導(dǎo)的 False # y是不可導(dǎo)的 True # z是可導(dǎo)的,因?yàn)樗幸粋€(gè) leaf variable 是可導(dǎo)的,即x可導(dǎo) tensor(6.) # x的導(dǎo)數(shù) None # 因?yàn)閥不可導(dǎo),所以是none '''如果是上面的 leaf variable變量x也設(shè)置為不可導(dǎo)的,那么z也不可導(dǎo),因?yàn)閤、y均不可導(dǎo),那么z自然不可導(dǎo)了。
(2)leaf variable(也是tensor)的requires_grad_()方法
如果某一個(gè)葉子變量,開始時(shí)不可導(dǎo)的,后面想設(shè)置它可導(dǎo),或者反過來,該怎么辦呢?tensor提供了一個(gè)方法,即
x.requires_grad_(True/False)? ?設(shè)置tensor的可導(dǎo)與不可導(dǎo),注意后面有一個(gè)下劃線哦!
但是需要注意的是,我只能夠設(shè)置葉子變量,即leaf variable的這個(gè)方法,否則會出現(xiàn)以下錯(cuò)誤:
RuntimeError: you can only change requires_grad flags of leaf variables.
1.2 函數(shù)的求導(dǎo)方法——y.backward()方法
上面只演示了簡單函數(shù)的求導(dǎo)法則,
需要注意的是:如果出現(xiàn)了復(fù)合函數(shù),比如 y是x的函數(shù),z是y的函數(shù),f是z的函數(shù),那么在求導(dǎo)的時(shí)候,會使用 f.backwrad()只會默認(rèn)求f對于葉子變量leaf variable的導(dǎo)數(shù)值,而對于中間變量y、z的導(dǎo)數(shù)值是不知道的,直接通過x.grad是知道的、y.grad、z.grad的值為none。
下面來看一下這個(gè)函數(shù)backward的定義:
backward(gradient=None, retain_graph=None, create_graph=False)它的三個(gè)參數(shù)都是可選的,上面的示例中還沒有用到任何一個(gè)參數(shù),關(guān)于這三個(gè)參數(shù),我后面會詳細(xì)說到,這里先跳過。
1.3 查看求得的導(dǎo)數(shù)的值——x.grad屬性
通過tensor的grad屬性查看所求得的梯度值。
總結(jié):
(1)torch.tensor()設(shè)置requires_grad關(guān)鍵字參數(shù)
(2)查看tensor是否可導(dǎo),x.requires_grad 屬性
(3)設(shè)置葉子變量 leaf variable的可導(dǎo)性,x.requires_grad_()方法
(4)自動求導(dǎo)方法 y.backward() ,直接調(diào)用backward()方法,只會計(jì)算對計(jì)算圖葉節(jié)點(diǎn)的導(dǎo)數(shù)。
(4)查看求得的到數(shù)值, x.grad 屬性
易錯(cuò)點(diǎn):
為什么上面的標(biāo)量x的值是3.0和4.0,而不是整數(shù)呢?這是因?yàn)?#xff0c;要想使x支持求導(dǎo),必須讓x為浮點(diǎn)類型,也就是我們給初始值的時(shí)候要加個(gè)點(diǎn):“.”。不然的話,就會報(bào)錯(cuò)。 即,不能定義[1,2,3],而應(yīng)該定義成[1.,2.,3.],前者是整數(shù),后者才是浮點(diǎn)數(shù),浮點(diǎn)數(shù)才能求導(dǎo)。
二、求導(dǎo)的核心函數(shù)——backwrad函數(shù)詳解
2.1 默認(rèn)的求導(dǎo)規(guī)則
在pytorch里面,默認(rèn):只能是【標(biāo)量】對【標(biāo)量】,或者【標(biāo)量】對向【量/矩陣】求導(dǎo)!這個(gè)很關(guān)鍵,很重要!
(1)標(biāo)量對標(biāo)量求導(dǎo)
參見上面的例子,x,y,z都是標(biāo)量,所以求導(dǎo)過程也很簡單,不再贅述。
(2)標(biāo)量對向量/矩陣求導(dǎo)
為什么標(biāo)量對于向量/矩陣是默認(rèn)的呢?因?yàn)樵谏疃葘W(xué)習(xí)中,我們一般在求導(dǎo)的時(shí)候是對損失函數(shù)求導(dǎo),損失函數(shù)一般都是一個(gè)標(biāo)量,即講所有項(xiàng)的損失加起來,但是參數(shù)又往往是向量或者是矩陣,所以這就是默認(rèn)的了。看下面的例子。
比如有一個(gè)輸入層為3節(jié)點(diǎn)的輸入層,輸出層為一個(gè)節(jié)點(diǎn)的輸出層,這樣一個(gè)簡單的神經(jīng)網(wǎng)絡(luò),針對以組樣本而言,有
X=(x1,x2,x3)=(1.5,2.5,3.5),X是(1,3)維的,輸出層的權(quán)值矩陣為W=(w1,w2,w3)T=(0.2,0.4,0.6)T,這里表示初始化的權(quán)值矩陣,T表示轉(zhuǎn)置,則W表示的是(3,1)維度,偏置項(xiàng)為b=0.1,是一個(gè)標(biāo)量,則可以構(gòu)建一個(gè)模型如下:
Y=XW+b,其中W,b就是要求倒數(shù)的變量,這里Y是一個(gè)標(biāo)量,W是向量,b是標(biāo)量,W,b是葉節(jié)點(diǎn),leaf variable,
將上面展開得到:
Y=x1*w1+x2*w2*x3*w3+b? ?(這里的1,2,3是下標(biāo),不是次方哦!難得用公式截圖)
自己手動計(jì)算得到,
Y對w1的導(dǎo)數(shù)為1.5
Y對w2的導(dǎo)數(shù)為2.5
Y對w3的導(dǎo)數(shù)為3.5
Y對b的導(dǎo)數(shù)為1
下面我們來驗(yàn)證一下:
#創(chuàng)建一個(gè)多元函數(shù),即Y=XW+b=Y=x1*w1+x2*w2*x3*w3+b,x不可求導(dǎo),W,b設(shè)置可求導(dǎo) X=torch.tensor([1.5,2.5,3.5],requires_grad=False) W=torch.tensor([0.2,0.4,0.6],requires_grad=True) b=torch.tensor(0.1,requires_grad=True) Y=torch.add(torch.dot(X,W),b)#判斷每個(gè)tensor是否是可以求導(dǎo)的 print(X.requires_grad) print(W.requires_grad) print(b.requires_grad) print(Y.requires_grad)#求導(dǎo),通過backward函數(shù)來實(shí)現(xiàn) Y.backward() #查看導(dǎo)數(shù),也即所謂的梯度 print(W.grad) print(b.grad)'''運(yùn)行結(jié)果為: False True True True tensor([1.5000, 2.5000, 3.5000]) tensor(1.) '''我們發(fā)現(xiàn)這和我們自己算的結(jié)果是一樣的。
(3)標(biāo)量對向量/矩陣求導(dǎo)的進(jìn)一步理解
比如有下面的一個(gè)復(fù)合函數(shù),而且是矩陣,定義如下:
''' x 是一個(gè)(2,3)的矩陣,設(shè)置為可導(dǎo),是葉節(jié)點(diǎn),即leaf variable y 是中間變量,由于x可導(dǎo),所以y可導(dǎo) z 是中間變量,由于x,y可導(dǎo),所以z可導(dǎo) f 是一個(gè)求和函數(shù),最終得到的是一個(gè)標(biāo)量scaler '''x = torch.tensor([[1.,2.,3.],[4.,5.,6.]],requires_grad=True) y = torch.add(x,1) z = 2*torch.pow(y,2) f = torch.mean(z)則x,y,z,f實(shí)際上的函數(shù)關(guān)系如下:
可見現(xiàn)在我么自己都可以手動求出函數(shù)f對于x11,x12,x13,x21,x22,x23的導(dǎo)數(shù)了,那我們通過torch來試一試。
print(x.requires_grad) print(y.requires_grad) print(z.requires_grad) print(f.requires_grad) print('===================================') f.backward() print(x.grad)'''運(yùn)行結(jié)果為: True True True True =================================== tensor([[1.3333, 2.0000, 2.6667],[3.3333, 4.0000, 4.6667]]) '''現(xiàn)在我們是不是更加了解自動求導(dǎo)的規(guī)則了呢?
標(biāo)量如何對標(biāo)量、向量、矩陣求導(dǎo)數(shù)!!!
2.2 向量/矩陣對向量/矩陣求導(dǎo)——通過backward的第一個(gè)參數(shù)gradient來實(shí)現(xiàn)
(1)求導(dǎo)的一個(gè)規(guī)則
比如有下面的例子:
''' x 是一個(gè)(2,3)的矩陣,設(shè)置為可導(dǎo),是葉節(jié)點(diǎn),即leaf variable y 也是一個(gè)(2,3)的矩陣,即 y=x2+x (x的平方加x) 實(shí)際上,就是要y的各個(gè)元素對相對應(yīng)的x求導(dǎo) '''x = torch.tensor([[1.,2.,3.],[4.,5.,6.]],requires_grad=True) y = torch.add(torch.pow(x,2),x)gradient=torch.tensor([[1.0,1.0,1.0],[1.0,1.0,1.0]])y.backward(gradient)print(x.grad)'''運(yùn)行結(jié)果為: tensor([[ 3., 5., 7.],[ 9., 11., 13.]]) '''這其實(shí)跟我們自己算的是一樣的,
相較于上面的標(biāo)量對于向量或者是矩陣求導(dǎo),關(guān)鍵是backward()函數(shù)的第一個(gè)參數(shù)gradient,那么這個(gè)參數(shù)是什么意思呢?
為了搞清楚傳入的這個(gè)gradient參數(shù)到底做了什么工作,我們進(jìn)一步做一個(gè)實(shí)驗(yàn),有下面的一個(gè)向量對向量的求導(dǎo),即
x = torch.tensor([1.,2.,3.],requires_grad=True) y = torch.pow(x,2)gradient=torch.tensor([1.0,1.0,1.0]) y.backward(gradient) print(x.grad) '''得到的結(jié)果: tensor([2., 4., 6.]) 這和我們期望的是一樣的 '''因?yàn)檫@里的gradient參數(shù)全部是1,所以看不出差別,現(xiàn)在更改一下gradient的值,如下:
gradient=torch.tensor([1.0,0.1,0.01])'''輸出為: tensor([2.0000, 0.4000, 0.0600]) '''從結(jié)果上來看,就是第二個(gè)導(dǎo)數(shù)縮小了十倍,第三個(gè)導(dǎo)數(shù)縮小了100倍,這個(gè)倍數(shù)和gradient里面的數(shù)字是息息相關(guān)的。
如果你想讓不同的分量有不同的權(quán)重,從效果上來理解確實(shí)是這樣子的,比如我是三個(gè)loss,loss1,loss2,loss3,它們的權(quán)重可能是不一樣的,我們就可以通過它來設(shè)置,即
dy/dx=0.1*dy1/dx+1.0*dy2/dx+0.0001*dy3/dx。
需要注意的是,gradient的維度是和最終的需要求導(dǎo)的那個(gè)y的維度是一樣的,從上面的兩個(gè)例子也可以看出來。
總結(jié):gradient參數(shù)的維度與最終的函數(shù)y保持一樣的形狀,每一個(gè)元素表示當(dāng)前這個(gè)元素所對應(yīng)的權(quán)
?
2.3?自動求導(dǎo)函數(shù)backward的第二、第三個(gè)參數(shù)
(1)保留運(yùn)算圖——retain_graph
在構(gòu)建函數(shù)關(guān)系的時(shí)候,特別是多個(gè)復(fù)合函數(shù)的時(shí)候,會有一個(gè)運(yùn)算圖,比如下面:
則有如下一些函數(shù)關(guān)系:
p=f(y)——>y=f(x)
q=f(z)——>z=f(x)
一個(gè)計(jì)算圖在進(jìn)行反向求導(dǎo)之后,為了節(jié)省內(nèi)存,這個(gè)計(jì)算圖就銷毀了。 如果你想再次求導(dǎo),就會報(bào)錯(cuò)。
就比如這里的例子而言,
你先求p求導(dǎo),那么這個(gè)過程就是反向的p對y求導(dǎo),y對x求導(dǎo)。 求導(dǎo)完畢之后,這三個(gè)節(jié)點(diǎn)構(gòu)成的計(jì)算子圖就會被釋放:
那么計(jì)算圖就只剩下z、q了,已經(jīng)不完整,無法求導(dǎo)了。 所以這個(gè)時(shí)候,無論你是想再次運(yùn)行p.backward()還是q.backward(),都無法進(jìn)行,因?yàn)閤已經(jīng)被銷毀了,報(bào)錯(cuò)如下:
RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.
那怎么辦呢?遇到這種問題,我們可以通過設(shè)置 retain_graph=True 來保留計(jì)算圖,
即更改你的backward函數(shù),添加參數(shù)retain_graph=True,重新進(jìn)行backward,這個(gè)時(shí)候你的計(jì)算圖就被保留了,不會報(bào)錯(cuò)。 但是這樣會吃內(nèi)存!,尤其是,你在大量迭代進(jìn)行參數(shù)更新的時(shí)候,很快就會內(nèi)存不足,所以這個(gè)參數(shù)在絕大部分情況下是不要去使用的。
(2)高階導(dǎo)數(shù)——create_graph
create_graph參數(shù)的資料現(xiàn)在很少,我也還沒有搜尋到一些更詳細(xì)的用法,它的官方描述是這樣的:
更高層次的計(jì)算圖會創(chuàng)建出來,允許計(jì)算高階導(dǎo)數(shù),如二階導(dǎo)數(shù)、三階導(dǎo)數(shù)等等,下面有一個(gè)簡單的小例子:
x = torch.tensor(5.0,requires_grad=True) y = torch.pow(x,3)grad_x = torch.autograd.grad(y, x, create_graph=True) print(grad_x) # dy/dx = 3 * x2,即75grad_grad_x = torch.autograd.grad(grad_x[0],x) print(grad_grad_x) # 二階導(dǎo)數(shù) d(2x)/dx = 30'''運(yùn)行結(jié)果為: (tensor(75., grad_fn=<MulBackward0>),) (tensor(30.),) '''三、關(guān)于向量對向量求導(dǎo)的解釋
補(bǔ)充說明:關(guān)于向量對向量求梯度的進(jìn)一步繞論:
比如說下面一個(gè)三維向量求梯度:
然后,要計(jì)算z關(guān)于x或者y的梯度,需要將一個(gè)外部梯度傳遞給z.backward()函數(shù),如下所示:
?z.backward(torch.FloatTensor([1.0,?1.0,?1.0])反向函數(shù)傳遞的張量就像梯度加權(quán)輸出的權(quán)值。從數(shù)學(xué)上講,這是一個(gè)向量乘以非標(biāo)量張量的雅可比矩陣(本文將進(jìn)一步討論),因此它幾乎總是一個(gè)維度的單位張量,與 backward張量相同,除非需要計(jì)算加權(quán)輸出。
注意 :向后圖是由autograd類在向前傳遞過程中自動動態(tài)創(chuàng)建的。Backward()只是通過將其參數(shù)傳遞給已經(jīng)生成的反向圖來計(jì)算梯度。
數(shù)學(xué)—雅克比矩陣和向量
從數(shù)學(xué)上講,autograd類只是一個(gè)雅可比向量積計(jì)算引擎。雅可比矩陣是一個(gè)非常簡單的單詞,它表示兩個(gè)向量所有可能的偏導(dǎo)數(shù)。它是一個(gè)向量相對于另一個(gè)向量的梯度。
注意:在這個(gè)過程中,PyTorch從不顯式地構(gòu)造整個(gè)雅可比矩陣。直接計(jì)算JVP (Jacobian vector product)通常更簡單、更有效。
如果一個(gè)向量X = [x1, x2,…xn]通過f(X) = [f1, f2,…fn]來計(jì)算其他向量,則雅可比矩陣(J)包含以下所有偏導(dǎo)組合:
注意:雅可比矩陣實(shí)現(xiàn)的是 n維向量 到 m 維向量的映射。
雅克比矩陣
上面的矩陣表示f(X)相對于X的梯度。
假設(shè)一個(gè)啟用PyTorch梯度的張量X:
X = [x1,x2,…,xn](假設(shè)這是某個(gè)機(jī)器學(xué)習(xí)模型的權(quán)值)
X經(jīng)過一些運(yùn)算形成一個(gè)向量Y
Y = f(X) = [y1, y2,…,ym]
然后使用Y計(jì)算標(biāo)量損失l。假設(shè)向量v恰好是標(biāo)量損失l關(guān)于向量Y的梯度,如下:(注意體會這句話,這個(gè)很重要!)
向量v稱為grad_tensor(梯度張量),并作為參數(shù)傳遞給backward() 函數(shù)。
為了得到損失的梯度l關(guān)于權(quán)重X的梯度,雅可比矩陣J是向量乘以向量v
這種計(jì)算雅可比矩陣并將其與向量v相乘的方法使PyTorch能夠輕松地為非標(biāo)量輸出提供外部梯度。
四、求導(dǎo)的另外兩種方法
4.1 方法一:通過?torch.autograd.backward()求導(dǎo)
前面介紹的求導(dǎo)的基本公式為:
y.backward(grad_tensors=None,?retain_graph=None,?create_graph=False),這三個(gè)參數(shù)我在前面的文章里面已經(jīng)說了,
參考前面的第一篇文章,反向求導(dǎo)它等價(jià)于:
torch.autograd.backward(tensors,grad_tensors=None,?retain_graph=None,?create_graph=False), 這里的tensors參數(shù)就相當(dāng)于是y,
所以:
y.backward() #標(biāo)量y? 等價(jià)于
torch.autograd.backward(y)。
需要注意的是,這個(gè)函數(shù)只是提供求導(dǎo)功能,并不返回值,返回的總是None,如下例子:
import torchx=torch.tensor([1.0,2.0,3.0],requires_grad=True) y=torch.tensor([4.0,5.0,6.0],requires_grad=True)z=torch.sum(torch.pow(x,2)+torch.pow(y,3)) # z=x2+y3torch.autograd.backward([z]) # 求導(dǎo),等價(jià)于z.backward()print(x.grad) # 獲取求導(dǎo)的結(jié)果 print(y.grad) ''' tensor([2., 4., 6.]) tensor([ 48., 75., 108.]) '''注意事項(xiàng):
(1)該方法只負(fù)責(zé)求導(dǎo),返回的總是None,
(2)當(dāng)向量對向量求導(dǎo)的時(shí)候,需要傳遞參數(shù)grad_tensor,這個(gè)參數(shù)的含義其實(shí)和前一篇文章的y.backward()里面的那個(gè)是一個(gè)含義;
(3)retain_graph=None,?create_graph=False 也和前面的含義是一樣的
4.2 方法二:通過torch.autograd.grad()來求導(dǎo)
除了前面的兩種方法來求導(dǎo)以外,即
y.backward()
torch.autograd.backward(y)? 這兩種方法
還有一種方法,即通過torch.autograd.grad()來求導(dǎo),先來看一下這個(gè)函數(shù)的定義。
def grad(outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False,only_inputs=True, allow_unused=False): ''' outputs : 函數(shù)的因變量,即需要求導(dǎo)的那個(gè)函數(shù),在本例子中,為z,當(dāng)然,他可以是一個(gè)tensor,也可以是幾個(gè)tensor,如[tensor1,tensor2,tensor3...] inputs : 函數(shù)的自變量,在本例中,即對應(yīng)的是[x,y],他可以是一個(gè)tensor,也可以是幾個(gè)tensor,如[tensor1,tensor2,tensor3...] grad_output : 這個(gè)參數(shù)和前面兩種方法中的grad_tensors是同樣的含義,當(dāng)出現(xiàn)向量對向量求導(dǎo)的時(shí)候需要指定該參數(shù) '''依然以這個(gè)例子而言,來看一下怎么做:
import torchx=torch.tensor([1.0,2.0,3.0],requires_grad=True) y=torch.tensor([4.0,5.0,6.0],requires_grad=True)z=torch.sum(torch.pow(x,2)+torch.pow(y,3)) # z=x2+y3print(torch.autograd.grad(z,[x,y])) # 求導(dǎo),并且返回值 ''' (tensor([2., 4., 6.]), tensor([ 48., 75., 108.])) '''注意事項(xiàng):
該函數(shù)會自動完成求導(dǎo)過程,而且會自動返回對于每一個(gè)自變量求導(dǎo)的結(jié)果。這是和前面不一樣的地方。
五、求中間節(jié)點(diǎn)導(dǎo)數(shù)的兩種方法
在Pytorch的計(jì)算圖中,只有葉結(jié)點(diǎn)的梯度會被保存下來,中間結(jié)點(diǎn)(包括輸出結(jié)點(diǎn))的梯度會在使用后被自動釋放以節(jié)省內(nèi)存,例如: import torchx = torch.Tensor([0, 1, 2, 3]).requires_grad_() y = torch.Tensor([4, 5, 6, 7]).requires_grad_() w = torch.Tensor([1, 2, 3, 4]).requires_grad_() z = x+yo = w.matmul(z) o.backward()print('x.requires_grad:', x.requires_grad) # True print('y.requires_grad:', y.requires_grad) # True print('z.requires_grad:', z.requires_grad) # True print('w.requires_grad:', w.requires_grad) # True print('o.requires_grad:', o.requires_grad) # Trueprint('x.grad:', x.grad) # tensor([1., 2., 3., 4.]) print('y.grad:', y.grad) # tensor([1., 2., 3., 4.]) print('w.grad:', w.grad) # tensor([ 4., 6., 8., 10.]) print('z.grad:', z.grad) # None print('o.grad:', o.grad) # None由于z和o為中間變量,因此在反向傳播完成計(jì)算得到了葉結(jié)點(diǎn)x,y,w的梯度后就自動釋放了,所以打印出來的結(jié)果為None.如果想要獲得中間結(jié)點(diǎn)的梯度值,可以通過以下兩種辦法.
5.1 方法一:設(shè)置變量的?retain_grad ()方法
同樣是上面的例子,加入兩行代碼: import torchx = torch.Tensor([0, 1, 2, 3]).requires_grad_() y = torch.Tensor([4, 5, 6, 7]).requires_grad_() w = torch.Tensor([1, 2, 3, 4]).requires_grad_() z = x+y z.retain_grad()o = w.matmul(z) o.backward() o.retain_grad()print('x.requires_grad:', x.requires_grad) # True print('y.requires_grad:', y.requires_grad) # True print('z.requires_grad:', z.requires_grad) # True print('w.requires_grad:', w.requires_grad) # True print('o.requires_grad:', o.requires_grad) # Trueprint('x.grad:', x.grad) # tensor([1., 2., 3., 4.]) print('y.grad:', y.grad) # tensor([1., 2., 3., 4.]) print('w.grad:', w.grad) # tensor([ 4., 6., 8., 10.]) print('z.grad:', z.grad) # tensor([1., 2., 3., 4.]) print('o.grad:', o.grad) # tensor(1.)但是這種加retain_grad()的方法會增加內(nèi)存的占用(pytorch設(shè)計(jì)的本意中,之所以把這些中間結(jié)點(diǎn)的梯度釋放搞卸磨殺驢,就是不希望這部分結(jié)點(diǎn)的梯度占用存儲空間),并不是一個(gè)好的辦法,對此另外一種方法,就是使用hook保存中間結(jié)點(diǎn)的梯度.
5.2 方法二:使用?register_hook ()方法調(diào)用hook函數(shù)
對于中間結(jié)點(diǎn) z,hook的使用方式為 z.register_hook(hook_fn),其中 hook_fn為用戶自定義的鉤子函數(shù),其簽名為: hook_fn(grad) -> Tensor or None函數(shù)的輸入為z的梯度,輸出為一個(gè)Tensor或者None.需要注意的是,反向傳播時(shí)梯度傳播到z結(jié)點(diǎn),在繼續(xù)向前傳播之前會先進(jìn)入hook_fn函數(shù)并進(jìn)行運(yùn)算.如果hook_fn函數(shù)的返回值為None,則不改變z的梯度值,如果hook_fn函數(shù)的返回值為Tensor,則將會用該Tensor的值代替原來計(jì)算得到的z的梯度,繼續(xù)向前反向傳播.
下面的例子僅僅打印z的梯度值,并不改變其梯度值,
運(yùn)行結(jié)果為:
=====Start backprop===== tensor([1., 2., 3., 4.]) =====End backprop===== x.grad: tensor([1., 2., 3., 4.]) y.grad: tensor([1., 2., 3., 4.]) w.grad: tensor([ 4., 6., 8., 10.]) z.grad: None下面的例子通過hook_fn改變了結(jié)點(diǎn)z的值,
import torchx = torch.Tensor([0, 1, 2, 3]).requires_grad_() y = torch.Tensor([4, 5, 6, 7]).requires_grad_() w = torch.Tensor([1, 2, 3, 4]).requires_grad_() z = x + y# =================== def hook_fn(grad):g = 2 * gradprint(g)return gz.register_hook(hook_fn) # ===================o = w.matmul(z)print('=====Start backprop=====') o.backward() print('=====End backprop=====')print('x.grad:', x.grad) print('y.grad:', y.grad) print('w.grad:', w.grad) print('z.grad:', z.grad)運(yùn)行結(jié)果為,
=====Start backprop===== tensor([2., 4., 6., 8.]) =====End backprop===== x.grad: tensor([2., 4., 6., 8.]) y.grad: tensor([2., 4., 6., 8.]) w.grad: tensor([ 4., 6., 8., 10.]) z.grad: None結(jié)果顯示,首先z本身的梯度變成了原來的兩倍,并使用改變后的梯度值繼續(xù)計(jì)算前向結(jié)點(diǎn)的梯度,使得葉節(jié)點(diǎn)的梯度也變成了原來的2倍.
同一個(gè)結(jié)點(diǎn)還可以注冊多個(gè)鉤子函數(shù),其執(zhí)行順序?yàn)楹瘮?shù)的注冊順序,下面的例子,
運(yùn)行結(jié)果為,
=====Start backprop===== tensor([2., 4., 6., 8.]) =====End backprop===== x.grad: tensor([2., 4., 6., 8.]) y.grad: tensor([2., 4., 6., 8.]) w.grad: tensor([ 4., 6., 8., 10.]) z.grad: None————————————————
版權(quán)聲明:本文部分為CSDN博主「LoveMIss-Y」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
本文一.二.三部分轉(zhuǎn)載自:
原文鏈接1:https://blog.csdn.net/qq_27825451/article/details/89393332
本文四部分轉(zhuǎn)載自:
原文鏈接2:https://blog.csdn.net/qq_27825451/article/details/89553479
總結(jié)
- 上一篇: matlab图像处理——平滑滤波
- 下一篇: 这位教授2 年一篇 Science,再获