現(xiàn)代神經(jīng)網(wǎng)絡(luò)依靠反向傳播(Back Propogation)算法來對模型進行優(yōu)化,其本質(zhì)是大規(guī)模鏈?zhǔn)角髮?dǎo)。因此,能夠?qū)νㄟ^編程來對網(wǎng)絡(luò)參數(shù)進行求導(dǎo)是非常重要的。目前的深度學(xué)習(xí)框架神經(jīng)網(wǎng)絡(luò)如PyTorch和TensorFlow等都實現(xiàn)了自動求梯度的功能。
計算圖
計算圖(Computation Graph)是現(xiàn)代深度學(xué)習(xí)框架的核心,其為高效自動求導(dǎo)算法——反向傳播(Back Propogation)提供了理論支持。如下所示,計算圖是一種特殊的有向無環(huán)圖(DAG),用于記錄算子與變量之間的關(guān)系。
計算圖具有兩個優(yōu)勢:
a. 使用非常簡單的函數(shù)就可以組合成一個極其復(fù)雜的模型;
b. 可以實現(xiàn)自動微分。
在PyTorch中,通過記錄算子與變量之間的關(guān)系可以生成表達(dá)式對應(yīng)的計算圖。對于表達(dá)式y = wx + b,其中w、x和b是變量,+和=是算子。有DAG中,w、x和b是葉子節(jié)點(leaf node),這些節(jié)點通常由用戶自己創(chuàng)建,不依賴于其他變量。y稱為根節(jié)點,是計算圖的最終目標(biāo)。
>>> x = torch.ones(2, 2)
>>> w = torch.rand(2, 2, requires_grad=True)
>>> b = torch.rand(2, 2, requires_grad=True)
>>> y = w * x + b
>>> x.is_leaf, w.is_leaf, b.is_leaf
(True, True, True)
>>> y.is_leaf
False
>>>
自動求導(dǎo)
在創(chuàng)建tensor的時候指定requires_grad參數(shù)或者使用requires_grad_()函數(shù)來指定是否對該參數(shù)進行自動求導(dǎo)。
>>> x = torch.ones(2, 2)
>>> w = torch.rand(2, 2, requires_grad=True)
>>> b = torch.rand(2, 2, requires_grad=True)
>>> y = w * x + b
x.requires_grad未指定自動求導(dǎo),因此是False,w.requires_grad和b.requires_grad為我們的求導(dǎo)對象,因此是True。雖然未指定y.requires_grad為True,但由于y依賴于需要求導(dǎo)的w,因此y.requires_grad為True。
>>> x.requires_grad, b.requires_grad, w.requires_grad
(False, True, True)
>>> y.requires_grad
True
有了計算圖之后,對根節(jié)點調(diào)用backward()函數(shù)進行反向傳播,就能夠得到各個需要求導(dǎo)的葉子的導(dǎo)數(shù),通過grad屬性即可得到。
>>> y = w * x + b
>>> y.backward()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "F:\ProgramData\Anaconda3\lib\site-packages\torch\tensor.py", line 102, in backward
torch.autograd.backward(self, gradient, retain_graph, create_graph)
File "F:\ProgramData\Anaconda3\lib\site-packages\torch\autograd\__init__.py", line 84, in backward
grad_tensors = _make_grads(tensors, grad_tensors)
File "F:\ProgramData\Anaconda3\lib\site-packages\torch\autograd\__init__.py", line 28, in _make_grads
raise RuntimeError("grad can be implicitly created only for scalar outputs")
RuntimeError: grad can be implicitly created only for scalar outputs
如果對非標(biāo)量y求導(dǎo),函數(shù)需要額外指定grad_tensors,grad_tensors的shape必須和y的相同。
>>> weights = torch.ones(2, 2)
>>> y.backward(weights, retain_graph=True)
>>> w.grad
tensor([[1., 1.],
[1., 1.]])
此外,PyTorch中梯度是累加的,每次反向傳播之后,當(dāng)前的梯度值會累加到舊的梯度值上。
>>> y.backward(weights, retain_graph=True)
>>> w.grad
tensor([[2., 2.],
[2., 2.]])
>>> y.backward(weights, retain_graph=True)
>>> w.grad
tensor([[3., 3.],
[3., 3.]])
要清空變量當(dāng)前的梯度,可以使用zero()和zero_()函數(shù)。
>>> w.grad.zero_()
tensor([[0., 0.],
[0., 0.]])
>>> w.grad
tensor([[0., 0.],
[0., 0.]])
PS:在我們使用PyTorch構(gòu)建網(wǎng)絡(luò)時,Model會在反向傳播時會自行處理梯度更新問題,上述知識有助于理解PyToch中的自動求導(dǎo)。