Autograd
本篇文章是本人對(duì)Pytorch官方教程的原創(chuàng)翻譯(原文鏈接)僅供學(xué)習(xí)交流使用,轉(zhuǎn)載請(qǐng)注明出處!
autograd
是Pytorch搭建神經(jīng)網(wǎng)絡(luò)最關(guān)鍵的包。它可以自動(dòng)計(jì)算tensor操作產(chǎn)生的微分,也就是說,autograd是一個(gè)define-by-run的框架,可以自動(dòng)對(duì)你的網(wǎng)絡(luò)進(jìn)行反向傳播。
在聲明一個(gè)tesnor時(shí),可以指定參數(shù).requires_grad=True
開啟自動(dòng)求導(dǎo),這樣Pytorch就會(huì)跟蹤它的所有操作,在tensor運(yùn)算完成后,可以調(diào)用.backward()
方法計(jì)算梯度,tesnor的梯度存放在它的.grad
屬性當(dāng)中。
.detach()
方法可以取消對(duì)tensor的梯度追蹤,這樣Pytorch就會(huì)把tensor從追蹤記錄中移除,不再繼續(xù)追蹤。
為了節(jié)約內(nèi)存、提高效率,可以在代碼塊前注明with torch.no_grad()
,因?yàn)橛行┳兞侩m然requires_grad=True
但其實(shí)并不需要計(jì)算梯度。
在autograd包中,還有一個(gè)非常重要的類就是Function
,除了用戶基于數(shù)據(jù)直接創(chuàng)建的Tensor(像a=torch.Tensor([1, 2, 3,])
這樣),其他的Tensor必然是根據(jù)某些Tensor通過運(yùn)算得到的,F(xiàn)unction類就記錄了這一運(yùn)算過程,并存儲(chǔ)在.grad_fn
屬性中。
當(dāng)需要計(jì)算梯度時(shí),首先需要調(diào)用y.backward()
如果y是一個(gè)標(biāo)量的話,則無需傳參,否則,必須傳入一個(gè)與y規(guī)模相同的tensor。
這是因?yàn)椋赼utograd包中,實(shí)際計(jì)算的是vector-Jacobian積。也就是給定任意的向量 ,計(jì)算
與Jacobian矩陣
的積
。如果
恰好是某個(gè)標(biāo)量函數(shù)
的梯度,即:
Jacobian矩陣:
其中:
是關(guān)于
的多元函數(shù),即:
那么根據(jù)鏈?zhǔn)椒▌t,v-J積的結(jié)果即是關(guān)于
的梯度:
定理:
,因此計(jì)算
等價(jià)于
vector-Jacobian積的這種特性使得模型非常容易擴(kuò)展。
接下來看一些例子:
import torch
初始化一個(gè)tensor,并將它的requires_grad
屬性設(shè)為True
,追蹤它的運(yùn)算。
x = torch.ones(2, 2, requires_grad=True)
print(x)
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
創(chuàng)建一個(gè)tensory = x + 2
y = x + 2
print(y)
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
是由x
計(jì)算得到的,所以它的.grad_fn
不為空
print(y.grad_fn)
<AddBackward0 object at 0x0000028CCEE770C8>
做一些其他運(yùn)算:
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_(...)
方法可以改變tensor的requires_grad
屬性。默認(rèn)情況下,requires_grad = False
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
False
True
<SumBackward0 object at 0x0000028CCEE83CC8>
接下來嘗試使用自動(dòng)求導(dǎo)機(jī)制,上文中使用到的out
是一個(gè)標(biāo)量,那么對(duì)out反向傳播則可以直接調(diào)用out.backward()
,等價(jià)于out.backward(torch.tensor(1))
out.backward()
print(x.grad)
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
檢驗(yàn):
x = torch.rand(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000: #norm是L-p范數(shù),默認(rèn)求2范數(shù)
y = y * 2
print(y)
tensor([939.6540, 998.4269, 6.6829], grad_fn=<MulBackward0>)
L-P范數(shù)
此時(shí)y
不再是標(biāo)量,自動(dòng)求導(dǎo)機(jī)制不能直接計(jì)算Jacobian行列式,反向傳播得到的.grad
是vector-Jacobian積。
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
with torch.no_grad():
可以臨時(shí)取消對(duì)代碼塊內(nèi)的tensor的追蹤。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
print((x ** 2).requires_grad)
True
True
False
True
.detach()
方法可以將tensor從自動(dòng)求導(dǎo)機(jī)制中隔離出來,得到的新tensor將不再需要求導(dǎo)。
print(x.requires_grad)
# y和x數(shù)據(jù)相同,不需要求導(dǎo)
# y不是x的拷貝,對(duì)y的修改也會(huì)影響x
# 如果直接令y = x,那么是不會(huì)取消追蹤的
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all()) # 對(duì)比全部數(shù)據(jù)
True
False
tensor(True)