pytorch如何计算导数_PyTorch怎么用?来看这里
構(gòu)建深度學(xué)習(xí)模型的基本流程就是:搭建計(jì)算圖,求得損失函數(shù),然后計(jì)算損失函數(shù)對(duì)模型參數(shù)的導(dǎo)數(shù),再利用梯度下降法等方法來更新參數(shù)。
搭建計(jì)算圖的過程,稱為“正向傳播”,這個(gè)是需要我們自己動(dòng)手的,因?yàn)槲覀冃枰O(shè)計(jì)我們模型的結(jié)構(gòu)。由損失函數(shù)求導(dǎo)的過程,稱為“反向傳播”,求導(dǎo)是件辛苦事兒,所以自動(dòng)求導(dǎo)基本上是各種深度學(xué)習(xí)框架的基本功能和最重要的功能之一,PyTorch也不例外。
我們今天來體驗(yàn)一下PyTorch的自動(dòng)求導(dǎo)吧,好為后面的搭建模型做準(zhǔn)備。
一、設(shè)置Tensor的自動(dòng)求導(dǎo)屬性
所有的tensor都有.requires_grad屬性,都可以設(shè)置成自動(dòng)求導(dǎo)。具體方法就是在定義tensor的時(shí)候,讓這個(gè)屬性為True:
x = tensor.ones(2,4,requires_grad=True)
In [1]: import torchIn [2]: x = torch.ones(2,4,requires_grad=True)In [3]: print(x)tensor([[1., 1., 1., 1.], [1., 1., 1., 1.]], requires_grad=True)只要這樣設(shè)置了之后,后面由x經(jīng)過運(yùn)算得到的其他tensor,就都有equires_grad=True屬性了。
可以通過x.requires_grad來查看這個(gè)屬性。
In [4]: y = x + 2In [5]: print(y)tensor([[3., 3., 3., 3.], [3., 3., 3., 3.]], grad_fn=)In [6]: y.requires_gradOut[6]: True如果想改變這個(gè)屬性,就調(diào)用tensor.requires_grad_()方法:
In [22]: x.requires_grad_(False)Out[22]:tensor([[1., 1., 1., 1.], [1., 1., 1., 1.]])In [21]: print(x.requires_grad,y.requires_grad)False True這里,注意區(qū)別tensor.requires_grad和tensor.requires_grad_()兩個(gè)東西,前面是調(diào)用變量的屬性值,后者是調(diào)用內(nèi)置的函數(shù),來改變屬性。
二、求導(dǎo)
下面我們來試試自動(dòng)求導(dǎo)到底怎么樣。
我們首先定義一個(gè)計(jì)算圖(計(jì)算的步驟):
In [28]: x = torch.tensor([[1.,2.,3.],[4.,5.,6.]],requires_grad=True)In [29]: y = x+1In [30]: z = 2*y*yIn [31]: J = torch.mean(z)這里需要注意的是,要想使x支持求導(dǎo),必須讓x為浮點(diǎn)類型,也就是我們給初始值的時(shí)候要加個(gè)點(diǎn):“.”。不然的話,就會(huì)報(bào)錯(cuò)。
即,不能定義[1,2,3],而應(yīng)該定義成[1.,2.,3.],前者是整數(shù),后者才是浮點(diǎn)數(shù)。
上面的計(jì)算過程可以表示為:
好了,重點(diǎn)注意的地方來了!
x、y、z都是tensor,但是size為(2,3)的矩陣。但是J是對(duì)z的每一個(gè)元素加起來求平均,所以J是標(biāo)量。
求導(dǎo),只能是【標(biāo)量】對(duì)標(biāo)量,或者【標(biāo)量】對(duì)向量/矩陣求導(dǎo)!
所以,上圖中,只能J對(duì)x、y、z求導(dǎo),而z則不能對(duì)x求導(dǎo)。
我們不妨試一試:
- PyTorch里面,求導(dǎo)是調(diào)用.backward()方法。直接調(diào)用backward()方法,會(huì)計(jì)算對(duì)計(jì)算圖葉節(jié)點(diǎn)的導(dǎo)數(shù)。獲取求得的導(dǎo)數(shù),用.grad方法。
試圖z對(duì)x求導(dǎo):
In [31]: z.backward()# 會(huì)報(bào)錯(cuò):Traceback (most recent call last) in ()----> 1 z.backward()RuntimeError: grad can be implicitly created only for scalar outputs正確的應(yīng)該是J對(duì)x求導(dǎo):
In [33]: J.backward()In [34]: x.gradOut[34]:tensor([[1.3333, 2.0000, 2.6667], [3.3333, 4.0000, 4.6667]])檢驗(yàn)一下,求的是不是對(duì)的。
J對(duì)x的導(dǎo)數(shù)應(yīng)該是什么呢?
檢查發(fā)現(xiàn),導(dǎo)數(shù)就是:
[[1.3333, 2.0000, 2.6667],
[3.3333, 4.0000, 4.6667]]
總結(jié)一下,構(gòu)建計(jì)算圖(正向傳播,Forward Propagation)和求導(dǎo)(反向傳播,Backward Propagation)的過程就是:
三、關(guān)于backward函數(shù)的一些其他問題:
1. 不是標(biāo)量也可以用backward()函數(shù)來求導(dǎo)?
在看文檔的時(shí)候,有一點(diǎn)我半天沒搞懂:
他們給了這樣的一個(gè)例子:
我在前面不是說“只有標(biāo)量才能對(duì)其他東西求導(dǎo)”么?它這里的y是一個(gè)tensor,是一個(gè)向量。按道理不能求導(dǎo)呀。這個(gè)參數(shù)gradients是干嘛的?
但是,如果看看backward函數(shù)的說明,會(huì)發(fā)現(xiàn),里面確實(shí)有一個(gè)gradients參數(shù):
從說明中我們可以了解到:
- 如果你要求導(dǎo)的是一個(gè)標(biāo)量,那么gradients默認(rèn)為None,所以前面可以直接調(diào)用J.backward()就行了如果你要求導(dǎo)的是一個(gè)張量,那么gradients應(yīng)該傳入一個(gè)Tensor。那么這個(gè)時(shí)候是什么意思呢?
在StackOverflow有一個(gè)解釋很好:
一般來說,我是對(duì)標(biāo)量求導(dǎo),比如在神經(jīng)網(wǎng)絡(luò)里面,我們的loss會(huì)是一個(gè)標(biāo)量,那么我們讓loss對(duì)神經(jīng)網(wǎng)絡(luò)的參數(shù)w求導(dǎo),直接通過loss.backward()即可。
但是,有時(shí)候我們可能會(huì)有多個(gè)輸出值,比如loss=[loss1,loss2,loss3],那么我們可以讓loss的各個(gè)分量分別對(duì)x求導(dǎo),這個(gè)時(shí)候就采用:
loss.backward(torch.tensor([[1.0,1.0,1.0,1.0]]))
如果你想讓不同的分量有不同的權(quán)重,那么就賦予gradients不一樣的值即可,比如:
loss.backward(torch.tensor([[0.1,1.0,10.0,0.001]]))
這樣,我們使用起來就更加靈活了,雖然也許多數(shù)時(shí)候,我們都是直接使用.backward()就完事兒了。
2. 一個(gè)計(jì)算圖只能backward一次
一個(gè)計(jì)算圖在進(jìn)行反向求導(dǎo)之后,為了節(jié)省內(nèi)存,這個(gè)計(jì)算圖就銷毀了。
如果你想再次求導(dǎo),就會(huì)報(bào)錯(cuò)。
比如你定義了計(jì)算圖:
你先求p求導(dǎo),那么這個(gè)過程就是反向的p對(duì)y求導(dǎo),y對(duì)x求導(dǎo)。
求導(dǎo)完畢之后,這三個(gè)節(jié)點(diǎn)構(gòu)成的計(jì)算子圖就會(huì)被釋放:
那么計(jì)算圖就只剩下z、q了,已經(jīng)不完整,無法求導(dǎo)了。
所以這個(gè)時(shí)候,無論你是想再次運(yùn)行p.backward()還是q.backward(),都無法進(jìn)行,報(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.
好,怎么辦呢?
遇到這種問題,一般兩種情況:
1. 你的實(shí)際計(jì)算,確實(shí)需要保留計(jì)算圖,不讓子圖釋放。
那么,就更改你的backward函數(shù),添加參數(shù)retain_graph=True,重新進(jìn)行backward,這個(gè)時(shí)候你的計(jì)算圖就被保留了,不會(huì)報(bào)錯(cuò)。
但是這樣會(huì)吃內(nèi)存!,尤其是,你在大量迭代進(jìn)行參數(shù)更新的時(shí)候,很快就會(huì)內(nèi)存不足,memory out了。
2. 你實(shí)際根本沒必要對(duì)一個(gè)計(jì)算圖backward多次,而你不小心多跑了一次backward函數(shù)。
通常,你要是在IPython里面聯(lián)系PyTorch的時(shí)候,因?yàn)槟銜?huì)反復(fù)運(yùn)行一個(gè)單元格的代碼,所以很容易一不小心把backward運(yùn)行了多次,就會(huì)報(bào)錯(cuò)。這個(gè)時(shí)候,你就檢查一下代碼,防止backward運(yùn)行多次即可。
文章轉(zhuǎn)自:https://zhuanlan.zhihu.com/p/51385110
總結(jié)
以上是生活随笔為你收集整理的pytorch如何计算导数_PyTorch怎么用?来看这里的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win7变成xp风格了怎么改回_微软看了
- 下一篇: 股权投资在哪里买