07 自動(dòng)求導(dǎo)【動(dòng)手學(xué)深度學(xué)習(xí)v2】

向量鏈?zhǔn)椒▌t

例1:

- 線性回歸的例子:利用鏈?zhǔn)椒▌t對(duì)線性回歸求導(dǎo)
- <x,w>:x 和 w 做內(nèi)積
- 上圖中求 z 對(duì) w 的導(dǎo)數(shù)時(shí),第二項(xiàng) b 對(duì) a 的導(dǎo)數(shù)在代入時(shí)應(yīng)該應(yīng)該寫作 d (a - y) ,圖中少了一對(duì)括號(hào)
例2:

- 上圖中求 z 對(duì) w 的導(dǎo)數(shù)時(shí),第二項(xiàng) b 對(duì) a 的導(dǎo)數(shù)在代入時(shí)應(yīng)該應(yīng)該寫作 d (a - y) ,圖中少了一對(duì)括號(hào)
- 圖中 ||b|| 表示求 b 的范數(shù)
自動(dòng)求導(dǎo)
計(jì)算一個(gè)函數(shù)在指定值上的導(dǎo)數(shù)
自動(dòng)求導(dǎo)與符號(hào)求導(dǎo)、數(shù)值求導(dǎo)的區(qū)別

- 符號(hào)求導(dǎo):指定一個(gè)函數(shù),然后求函數(shù)的導(dǎo)數(shù)(這里可以理解為對(duì)某一個(gè)函數(shù)進(jìn)行求導(dǎo),求出其導(dǎo)函數(shù),而不是一個(gè)具體的值),它是一個(gè)顯式的計(jì)算
- 數(shù)值求導(dǎo):給定一個(gè)未知函數(shù)(不知道其具體表達(dá)式),然后利用數(shù)值去擬合它的導(dǎo)數(shù)(這里有點(diǎn)像利用極限去求導(dǎo)的過(guò)程)
計(jì)算圖
雖然使用pytorch不用去理解計(jì)算圖,但是有必要知道其內(nèi)部的工作原理,便于使用其它框架(如tensorflow等)時(shí)去理解計(jì)算圖

- 加入中間變量 a 、 b ,將他們做成一個(gè)基本的計(jì)算子
- 每個(gè)圓圈表示一個(gè)操作,也可以表示成為一個(gè)輸入
顯式構(gòu)造
(Tensorflow / Theano / MXNet)
from mxnet import sym
a = sym.var()
b = sym.var()
c = 2 * a + b
# bind data into a and b later
- 此處會(huì)報(bào)錯(cuò)“No module named 'mxnet'”,直接在conda環(huán)境下使用 pip install mxnet 或者沐神之前用的在程序第一行使用 !pip install mxnet 這行代碼進(jìn)行安裝也可
輸出:
TypeError: var() missing 1 required positional argument: 'name'
- 這里會(huì)提示缺少一個(gè)位置參數(shù)
- 這里應(yīng)該不是一段完整的可運(yùn)行的代碼,只是為了介紹顯式構(gòu)造
隱式構(gòu)造
(PyTorch / MXNet)
from mxnet import autograd , nd
with autograd.record():
? ? a = nd.ones((2,1))
? ? b = nd.ones((2,1))
? ? c = 2 * a + b
輸出:
- 沒(méi)有輸出,但也不會(huì)報(bào)錯(cuò)
自動(dòng)求導(dǎo)的兩種模式

反向積累


反向累積總結(jié)

復(fù)雜度

- 內(nèi)存復(fù)雜度:深度神經(jīng)網(wǎng)絡(luò)耗費(fèi)GPU資源的根源,做梯度的時(shí)候需要存儲(chǔ)前面的運(yùn)行結(jié)果
- 正向累積:不管神經(jīng)網(wǎng)絡(luò)的深度有多深都不需要存儲(chǔ)任何的結(jié)果,但是因?yàn)樾枰獙?duì)每一層計(jì)算梯度,所以每計(jì)算一個(gè)梯度都需要重新掃描一遍,計(jì)算復(fù)雜度太高
自動(dòng)求導(dǎo)的實(shí)現(xiàn)

import torch
x = torch.arange(4.0)
x
輸出:
tensor([0., 1., 2., 3.])
在計(jì)算 y 關(guān)于 向量x 的梯度之前,需要一個(gè)地方來(lái)存儲(chǔ)梯度
x.requires_grad_(True)
# 等價(jià)于 x = torch.arange(4.0,requires_grad=True)
x.grad # 默認(rèn)值是None
然后計(jì)算y
y = 2 * torch.dot(x,x)
y
- y 等于 x 與 x 的內(nèi)積的兩倍
輸出:
tensor(28., grad_fn=<DotBackward0>)
- grad_fn:這里是隱式構(gòu)造圖,所以有一個(gè)求梯度的函數(shù),告知 y 是從 x 計(jì)算過(guò)來(lái)的
通過(guò)調(diào)用反向傳播函數(shù)來(lái)自動(dòng)計(jì)算 y 關(guān)于 x 每個(gè)分量的梯度
y.backward()
x.grad
- y.backward():求導(dǎo)
- 利用 x.grad 來(lái)訪問(wèn)導(dǎo)數(shù)
輸出:
tensor([ 0., 4., 8., 12.])
x.grad == 4 * x
- 驗(yàn)證一下結(jié)果
輸出:
tensor([True, True, True, True])
計(jì)算 x 的另一個(gè)函數(shù)
# 在默認(rèn)情況下,PyTorch會(huì)累積梯度,因此在進(jìn)行另外一個(gè)函數(shù)之前需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad
- PyTorch中下劃線表示重寫內(nèi)容,zero_表示把所有的梯度清零
輸出:
tensor([1., 1., 1., 1.])
深度學(xué)習(xí)中,目的不是計(jì)算微分矩陣,而是批量中每個(gè)樣本單獨(dú)計(jì)算的偏導(dǎo)數(shù)之和
- 深度學(xué)習(xí)中很少對(duì)一個(gè)向量進(jìn)行求導(dǎo),而只是對(duì)一個(gè)標(biāo)量進(jìn)行求導(dǎo)
- 絕大部分情況下會(huì)先進(jìn)行求和,再進(jìn)行求導(dǎo),這樣子就是一個(gè)標(biāo)量
# 對(duì)非標(biāo)量調(diào)用 backward 需要傳入一個(gè) gradient 參數(shù)
x.grad.zero_()
y = x * x
# 等價(jià)于 y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
- y.detach():把 y 當(dāng)作一個(gè)常數(shù)而不是一個(gè)關(guān)于 x 的函數(shù)
輸出;
tensor([0., 2., 4., 6.])
- 以后將會(huì)用于在一些場(chǎng)景下將網(wǎng)絡(luò)中的參數(shù)固定
即使構(gòu)建函數(shù)的計(jì)算圖需要通過(guò)Python控制流(例如:條件、循環(huán)或任意函數(shù)調(diào)用),仍然可以計(jì)算得到變量的梯度
def f(a):
? ? b = a * 2
? ? while b.norm() < 1000:
? ? ? ? b = b * 2
? ? if b.sum() > 0:
? ? ? ? c = b
? ? else:
? ? ? ? c = 100 * b
? ? return c
a = torch.randn(size = (),requires_grad=True)
d = f(a)
d.backward()
- size=():表示是一個(gè)標(biāo)量
- require_grad=True:表示需要梯度
- 在計(jì)算的時(shí)候,torch會(huì)把整個(gè)計(jì)算圖存下來(lái),然后倒著做一遍
- 隱式計(jì)算的好處在于對(duì)于控制流做的更好一些,但是速度比較慢
Q&A
1、ppt上隱式構(gòu)造和顯式構(gòu)造看起來(lái)為什么差不多?
- 看上去是差不多,但是顯式構(gòu)造是先把整個(gè)計(jì)算寫出來(lái),然后再給值
- 用python來(lái)實(shí)現(xiàn)一個(gè)函數(shù)和用數(shù)學(xué)來(lái)實(shí)現(xiàn)一個(gè)函數(shù)是不一樣的
- 很多情況下,在使用的時(shí)候顯式構(gòu)造非常不方便
2、需要正向和反向都要算一遍嗎?
- 在神經(jīng)網(wǎng)絡(luò)中求梯度的時(shí)候,需要正著算一遍,然后再反著算一遍
- 自動(dòng)求導(dǎo)的時(shí)候只需要反著算一遍,不需要正著再算一遍
3、為什么Pytorch會(huì)默認(rèn)累積梯度?
- 反向傳播的時(shí)候需要把內(nèi)存結(jié)果存起來(lái),因此耗費(fèi)內(nèi)存比較多
- PyTorch對(duì)內(nèi)存的管理不太好,如果對(duì)于一個(gè)很大的批量,如果無(wú)法一次計(jì)算全計(jì)算出來(lái),則可以將其劃分成多次計(jì)算,然后進(jìn)行累加,從而得到正確的結(jié)果
- 當(dāng)weight在不同的模型之間進(jìn)行共享時(shí)也是有好處的餓
4、為什么是0246?是這么理解嗎:x^2對(duì)x求導(dǎo)
2 * x 等于0246
5、為什么深度學(xué)習(xí)中一般對(duì)標(biāo)量求導(dǎo)而不是對(duì)矩陣或者向量,如果我的loss是包含向量或者矩陣,那求導(dǎo)之前是不是要把它們變成標(biāo)量?
- 因?yàn)閘oss通常是一個(gè)標(biāo)量
- 如果loss是一個(gè)向量的話,隨著神經(jīng)網(wǎng)絡(luò)深度的增加,它會(huì)變成一個(gè)很大的張量,無(wú)法進(jìn)行計(jì)算
6、mxnet不用gluo的時(shí)候,除了用sym構(gòu)建一個(gè)網(wǎng)絡(luò)層,有什么自動(dòng)求導(dǎo)的好的方法嗎?比如我構(gòu)建了一個(gè)loss公式后
跳過(guò)了。。。
7、今天講的求導(dǎo),會(huì)在pytorch里如何實(shí)現(xiàn)代碼呢?
- 講了實(shí)現(xiàn),但是沒(méi)有講具體公式的實(shí)現(xiàn),有興趣可以自己試一下
8、多個(gè)loss分別反向的時(shí)候是不是需要累積梯度?
- 是的,神經(jīng)網(wǎng)絡(luò)中有多個(gè)loss的話,是需要進(jìn)行累積的,這也是為什么pytorch默認(rèn)是累加梯度的
9、為什么獲取.grad前需要backward?
- 不去backward的話就不會(huì)去計(jì)算梯度,這件事情占用很多的內(nèi)存,所以需要手動(dòng)backward計(jì)算梯度
10、求導(dǎo)過(guò)程是不是都是有向圖,也就是可以用樹狀結(jié)構(gòu)來(lái)表示,有沒(méi)有其它環(huán)狀的圖結(jié)構(gòu)?
- 有,例如循環(huán)神經(jīng)網(wǎng)絡(luò)就能夠變成一個(gè)環(huán)狀的圖結(jié)構(gòu)
11、pytorch或mxnet框架審計(jì)上可以實(shí)現(xiàn)矢量的求導(dǎo)嗎?
----end----