PyTorch 笔记(13)— autograd(0.4 之前和之后版本差异)、Tensor(张量)、Gradient(梯度)
1. 背景簡(jiǎn)述
torch.autograd 是 PyTorch 中方便用戶使用,專門開發(fā)的一套自動(dòng)求導(dǎo)引擎,它能夠根據(jù)輸入和前向傳播過程自動(dòng)構(gòu)建計(jì)算圖,并執(zhí)行反向傳播。
計(jì)算圖是現(xiàn)代深度學(xué)習(xí)框架 PyTorch、TensorFlow 等的核心,它為自動(dòng)求導(dǎo)算法——反向傳播提供了理論支持。
PyTorch 的 Autograd 模塊實(shí)現(xiàn)了深度學(xué)習(xí)的算法中的反向傳播求導(dǎo)數(shù),在張量(Tensor 類)上的所有操作,Autograd 都能為他們自動(dòng)提供微分,簡(jiǎn)化了手動(dòng)計(jì)算導(dǎo)數(shù)的復(fù)雜過程。
在 0.4 以前的版本中,Pytorch 使用 Variable 類來自動(dòng)計(jì)算所有的梯度。
從 0.4 起, Variable 正式合并入 Tensor 類,通過 Variable 嵌套實(shí)現(xiàn)的自動(dòng)微分功能已經(jīng)整合進(jìn)入了Tensor 類中。雖然為了代碼的兼容性還是可以使用 Variable(tensor) 這種方式進(jìn)行嵌套,但是這個(gè)操作其實(shí)什么都沒做。
所以,以后的代碼建議直接使用 Tensor 類進(jìn)行操作,因?yàn)楣俜轿臋n中已經(jīng)將 Variable 設(shè)置成過期模塊。
要想通過 Tensor 類本身就使用 autograd 功能,只需要設(shè)置 .requries_grad=True
Variable 類中的的 grad 和 grad_fn 屬性已經(jīng)整合進(jìn)入了 Tensor 類中。
關(guān)于反向傳播的基礎(chǔ),請(qǐng)參考:淺顯易懂的計(jì)算圖
2. autograd(PyTorch 0.4 之前版本)
PyTorch 在 autograd 模塊中實(shí)現(xiàn)了計(jì)算圖的相關(guān)功能,autograd 的核心數(shù)據(jù)結(jié)構(gòu)是 Variable 。
Variable 封裝了 tensor,并記錄對(duì) tensor 的操作記錄用來構(gòu)建計(jì)算圖。
Variable 的數(shù)據(jù)結(jié)構(gòu)如下圖所示,主要包含三個(gè)屬性:
data: 保存Variable所包含的tensor;grad:保存data對(duì)應(yīng)的梯度,grad也是variable而非tensor,與data形狀一致;grad_fn:指向一個(gè)Function,這個(gè)Function用來反向傳播計(jì)算輸入的梯度,記錄variable的操作歷史,即它是什么操作的輸出,用來構(gòu)建計(jì)算圖。如果某一個(gè)變量是由用戶創(chuàng)建的,則它為葉子節(jié)點(diǎn),對(duì)應(yīng)的grad_fn為None;
Variable 的構(gòu)造函數(shù)需要傳入 tensor,同時(shí)有兩個(gè)可選參數(shù):
requires_grad(bool):是否需要對(duì)該variable進(jìn)行求導(dǎo);volatile(bool): 意為“揮發(fā)”,設(shè)置為True,構(gòu)建在該variable上的圖都不會(huì)求導(dǎo),專為推理階段設(shè)計(jì);
早期 Variable 的創(chuàng)建是需要 tensor,類似這樣:
In [8]: a = V(t.ones(3,4), requires_grad=True)
目前 Pytorch 的版本已經(jīng)可以直接這樣:
In [11]: b = t.ones(3,4).requires_grad_(True)
不區(qū)分 tensor 和 Variable ,Tensors/Variables 合并,棄用 volatile 標(biāo)志,原來若 True ,在這之后的圖都不會(huì)求導(dǎo)。
Variable 支持大部分的 tensor 支持的函數(shù),但不支持部分 inplace 函數(shù)。因?yàn)檫@些操作會(huì)修改 tensor 自身,而在反向傳播中,variable 需要緩存原來的 tensor 來計(jì)算梯度。如果想要計(jì)算各個(gè) Variable 的梯度,只需調(diào)用根節(jié)點(diǎn) variable 的 backward 方法,autograd 會(huì)自動(dòng)沿著計(jì)算圖反向傳播,計(jì)算每一個(gè)葉子節(jié)點(diǎn)的梯度。
variable.backward(grad_variables=None, retain_graph=None, create_graph=None)
主要有如下參數(shù):
grad_variables:形狀與variable一致,對(duì)于y.backward(),grad_variables相當(dāng)于鏈?zhǔn)椒▌t ?z?x\frac{\partial z}{\partial x}?x?z? = ?z?y\frac{\partial z}{\partial y}?y?z??y?x\frac{\partial y}{\partial x}?x?y? 中的 ?z?y\frac{\partial z}{\partial y}?y?z?,grad_variables也可以是tensor或序列。retain_graph:反向傳播需要緩存一些中間結(jié)果,反向傳播之后,這些緩存就被清空,可通過指定這個(gè)參數(shù)不清空緩存,用來多次反向傳播。create_graph:對(duì)反向傳播過程再次構(gòu)建計(jì)算圖,可通過backward of backward實(shí)現(xiàn)求高階導(dǎo)數(shù)。
In [1]: import torch as tIn [2]: a = t.ones(3,4).requires_grad_(True)In [3]: a
Out[3]:
tensor([[1., 1., 1., 1.],[1., 1., 1., 1.],[1., 1., 1., 1.]], requires_grad=True)In [4]: b = t.zeros(3,4)In [5]: b
Out[5]:
tensor([[0., 0., 0., 0.],[0., 0., 0., 0.],[0., 0., 0., 0.]])In [6]: c = a + bIn [7]: c
Out[7]:
tensor([[1., 1., 1., 1.],[1., 1., 1., 1.],[1., 1., 1., 1.]], grad_fn=<AddBackward0>)In [8]: d = c.sum()In [9]: d
Out[9]: tensor(12., grad_fn=<SumBackward0>)
In [10]: d.backward()In [11]: a.grad
Out[11]:
tensor([[1., 1., 1., 1.],[1., 1., 1., 1.],[1., 1., 1., 1.]])In [12]: a.requires_grad
Out[12]: TrueIn [13]: b.requires_grad
Out[13]: False此處雖然沒有指定 c 需要求導(dǎo),但 c 依賴于 a,而 a 需要求導(dǎo)因此 c 的 requires_grad 屬性會(huì)自動(dòng)設(shè)為True
In [14]: c.requires_grad
Out[14]: TrueIn [15]: a.is_leaf
Out[15]: TrueIn [16]: b.is_leaf
Out[16]: Truec 不是葉子節(jié)點(diǎn)
In [17]: c.is_leaf
Out[17]: False
3. autograd(PyTorch 0.4 之后版本)
3.1 Tensor(張量)
torch.Tensor 是這個(gè)包的核心類。如果設(shè)置它的屬性 .requires_grad 為 True,那么它將會(huì)追蹤對(duì)于該張量的所有操作。當(dāng)完成計(jì)算后可以通過調(diào)用 .backward(),來自動(dòng)計(jì)算所有的梯度。這個(gè)張量的所有梯度將會(huì)自動(dòng)累加到 .grad 屬性。
要阻止一個(gè)張量被跟蹤歷史,可以調(diào)用.detach() 方法將其與計(jì)算歷史分離,并阻止它未來的計(jì)算記錄被跟蹤。
在張量創(chuàng)建時(shí),通過設(shè)置 requires_grad=True 來告訴 Pytorch 需要對(duì)該張量進(jìn)行自動(dòng)求導(dǎo),PyTorch 會(huì)記錄該張量的每一步操作歷史并自動(dòng)計(jì)算, 以下兩種方法是等價(jià)的。
In [1]: import torch as tIn [2]: x = t.ones(2,2,requires_grad=True)In [3]: a = t.ones(2,2).requires_grad_(True)In [4]: a
Out[4]:
tensor([[1., 1.],[1., 1.]], requires_grad=True)In [5]: x
Out[5]:
tensor([[1., 1.],[1., 1.]], requires_grad=True)In [6]:
針對(duì)張量 x 做一次運(yùn)算
In [6]: y = x + 2In [7]: y
Out[7]:
tensor([[3., 3.],[3., 3.]], grad_fn=<AddBackward0>)In [8]:
y 是計(jì)算的結(jié)果,而不是用戶自己創(chuàng)建的,所以它有 grad_fn 屬性。
x 是用戶自己創(chuàng)建的,所以 grad_fn 為 None。
在張量進(jìn)行操作后,grad_fn 已經(jīng)被賦予了一個(gè)新的函數(shù),這個(gè)函數(shù)引用了一個(gè)創(chuàng)建了這個(gè) Tensor 類的Function 對(duì)象。 Tensor 和 Function 互相連接生成了一個(gè)非循環(huán)圖,它記錄并且編碼了完整的計(jì)算歷史。每個(gè)張量都有一個(gè) .grad_fn 屬性,如果這個(gè)張量是用戶手動(dòng)創(chuàng)建的那么這個(gè)張量的 grad_fn 是 None 。
In [8]: y.grad_fn
Out[8]: <AddBackward0 at 0x4bdce50>In [10]: x.grad_fnIn [11]:
對(duì) y 進(jìn)行更多操作,z=3x2+12x+12,
In [11]: z = y*y*3In [12]: z
Out[12]:
tensor([[27., 27.],[27., 27.]], grad_fn=<MulBackward0>)In [13]: z.mean()
Out[13]: tensor(27., grad_fn=<MeanBackward0>)In [14]:
.requires_grad_(...) 原地改變了現(xiàn)有張量的 requires_grad 標(biāo)志。如果沒有指定的話,默認(rèn)輸入的這個(gè)標(biāo)志是 False。
In [15]: a = t.randn(2,2)In [16]: a = ((a*3) /(a-1))In [17]: a.requires_grad
Out[17]: FalseIn [18]: a.requires_grad_(True)
Out[18]:
tensor([[ 0.6064, -11.8267],[ 0.5640, 9.0712]], requires_grad=True)In [19]: a.requires_grad
Out[19]: TrueIn [20]: b = (a*a).sum()In [21]: b.grad_fn
Out[21]: <SumBackward0 at 0x100f7490>In [22]:
3.2 Gradient(梯度)
為了防止跟蹤歷史記錄(和使用內(nèi)存),可以將代碼塊包裝在
with torch.no_grad():
中。在評(píng)估模型時(shí)特別有用,因?yàn)槟P涂赡芫哂?requires_grad = True 的可訓(xùn)練的參數(shù),但是我們不需要在此過程中對(duì)他們進(jìn)行梯度計(jì)算。
還有一個(gè)類對(duì)于 autograd 的實(shí)現(xiàn)非常重要:Function 。
Tensor 和 Function 互相連接并構(gòu)建一個(gè)非循環(huán)圖,它保存整個(gè)完整的計(jì)算過程的歷史信息。每個(gè)張量都有一個(gè) .grad_fn 屬性,該屬性引用了創(chuàng)建 Tensor自身的 Function 。(除非這個(gè)張量是用戶手動(dòng)創(chuàng)建的,即這個(gè)張量的 grad_fn 是 None )。
如果需要計(jì)算導(dǎo)數(shù),可以在 Tensor上調(diào)用 .backward()。
- 如果
Tensor是一個(gè)標(biāo)量(即它包含一個(gè)元素的數(shù)據(jù)),則不需要為backward()指定任何參數(shù); - 如果它有更多的元素,則需要指定一個(gè)
gradient參數(shù),該參數(shù)是形狀匹配的張量;
3.2.1 簡(jiǎn)單自動(dòng)求導(dǎo)
PyTorch 會(huì)自動(dòng)追蹤和記錄對(duì)與張量的所有操作,當(dāng)計(jì)算完成后調(diào)用 .backward() 方法自動(dòng)計(jì)算梯度并且將計(jì)算結(jié)果保存到 grad 屬性中。
如果 Tensor 類表示的是一個(gè)標(biāo)量(即它包含一個(gè)元素的張量),則不需要為 backward() 指定任何參數(shù),如下所示 out 是一個(gè)標(biāo)量,因此 out.backward() 和 out.backward(torch.tensor(1.)) 等價(jià) 。這種參數(shù)常出現(xiàn)在圖像分類中的單標(biāo)簽分類,輸出一個(gè)標(biāo)量代表圖像的標(biāo)簽。
因?yàn)?對(duì) y 進(jìn)行更多操作后 z=3x2+12x+12,所以 out=z/4,
In [22]: out = z.mean()In [23]: out
Out[23]: tensor(27., grad_fn=<MeanBackward0>)In [24]: out.backward()
輸出導(dǎo)數(shù) d(out)/dx=x.grad
In [25]: x.grad
Out[25]:
tensor([[4.5000, 4.5000],[4.5000, 4.5000]])
我們的得到的是一個(gè)數(shù)取值全部為 4.5 的矩陣。讓我們來調(diào)用 out 張量 O。
3.2.2 復(fù)雜自動(dòng)求導(dǎo)
如果 Tensor 類包含多個(gè)參數(shù),則需要指定一個(gè) gradient 參數(shù),它是形狀匹配的張量。
我們來看看 autograd 計(jì)算的導(dǎo)數(shù)和我們手動(dòng)推導(dǎo)的導(dǎo)數(shù)的區(qū)別。以下函數(shù)
的導(dǎo)數(shù)為:
In [23]: def f(x):...: y = x**2 * t.exp(x)...: return y...: In [24]: def gradf(x):...: dx = 2*x*t.exp(x) + x**2*t.exp(x)...: return dx...: In [25]: x = t.randn(2,3).requires_grad()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-25-95892a3a5546> in <module>
----> 1 x = t.randn(2,3).requires_grad()TypeError: 'bool' object is not callableIn [26]: x = t.randn(2,3).requires_gradIn [27]: x
Out[27]: FalseIn [28]: x = t.randn(2,3).requires_grad_()In [29]: x
Out[29]:
tensor([[-0.9131, -0.8917, 0.4434],[-1.1244, -0.1586, 0.5543]], requires_grad=True)In [30]: y = f(x)In [31]: gradf(x)
Out[31]:
tensor([[-0.3982, -0.4051, 1.6880],[-0.3198, -0.2492, 2.4649]], grad_fn=<AddBackward0>)
因?yàn)?y 不是一個(gè)標(biāo)量,所以需要輸入一個(gè)大小相同的張量作為參數(shù),這里我們用 t.ones(y.size()) 函數(shù)根據(jù) x 生成一個(gè)張量。
t.ones(y.size())
和
t.ones_like(y)
等價(jià)。
In [32]: y.backward
Out[32]:
<bound method Tensor.backward of tensor([[0.3346, 0.3260, 0.3063],[0.4107, 0.0215, 0.5349]], grad_fn=<MulBackward0>)>In [33]: y.backward(t.ones(y.size()))In [34]: x.grad
Out[34]:
tensor([[-0.3982, -0.4051, 1.6880],[-0.3198, -0.2492, 2.4649]])In [35]:
可以看到自動(dòng)求導(dǎo)和手動(dòng)求導(dǎo)結(jié)果是相等的。
3.2.3 torch.no_grad()
我們可以使用 with torch.no_grad() 上下文管理器臨時(shí)禁止對(duì)已設(shè)置 requires_grad=True 的張量進(jìn)行自動(dòng)求導(dǎo)。這個(gè)方法在測(cè)試集計(jì)算準(zhǔn)確率的時(shí)候會(huì)經(jīng)常用到,例如:
In [32]: x = t.ones(2,3, requires_grad=True)In [33]: y = 2*x*xIn [34]: y.requires_grad
Out[34]: TrueIn [35]: with t.no_grad():...: print(y.requires_grad)...:
TrueIn [36]:
這塊應(yīng)該為 False,但不知道為啥實(shí)際測(cè)試是 True,帶繼續(xù)深入了解。
使用 .no_grad() 進(jìn)行嵌套后,代碼不會(huì)跟蹤歷史記錄,也就是說保存的這部分記錄會(huì)減少內(nèi)存的使用量并且會(huì)加快少許的運(yùn)算速度。
3.3 Autograd 過程
- 當(dāng)我們執(zhí)行
z.backward()的時(shí)候。這個(gè)操作將調(diào)用z里面的grad_fn這個(gè)屬性,執(zhí)行求導(dǎo)的操作。 - 這個(gè)操作將遍歷
grad_fn的next_functions,然后分別取出里面的Function(AccumulateGrad),執(zhí)行求導(dǎo)操作。這部分是一個(gè)遞歸的過程直到最后類型為葉子節(jié)點(diǎn)。 - 計(jì)算出結(jié)果以后,將結(jié)果保存到他們對(duì)應(yīng)的
variable這個(gè)變量所引用的對(duì)象(x和y)的grad這個(gè)屬性里面。 - 求導(dǎo)結(jié)束。所有的葉節(jié)點(diǎn)的
grad變量都得到了相應(yīng)的更新
最終當(dāng)我們執(zhí)行完 z.backward() 之后,x 和 y 里面的 grad 值就得到了更新。
3. 擴(kuò)展 Autograd
如果需要自定義 autograd 擴(kuò)展新的功能,就需要擴(kuò)展 Function 類。因?yàn)?Function 使用 autograd 來計(jì)算結(jié)果和梯度,并對(duì)操作歷史進(jìn)行編碼。 在 Function類 中最主要的方法就是 forward() 和 backward() 它們分別代表了前向傳播和反向傳播。
一個(gè)自定義的 Function 需要一下三個(gè)方法:
-
__init__ (optional):如果這個(gè)操作需要額外的參數(shù)則需要定義這個(gè)Function的構(gòu)造函數(shù),不需要的話可以忽略。 -
forward():執(zhí)行前向傳播的計(jì)算代碼 -
backward():反向傳播時(shí)梯度計(jì)算的代碼。 參數(shù)的個(gè)數(shù)和forward返回值的個(gè)數(shù)一樣,每個(gè)參數(shù)代表傳回到此操作的梯度。
In [37]: ...: # 引入Function便于擴(kuò)展...: from torch.autograd.function import FunctionIn [38]: # 定義一個(gè)乘以常數(shù)的操作(輸入?yún)?shù)是張量)...: # 方法必須是靜態(tài)方法,所以要加上@staticmethod ...: class MulConstant(Function):...: @staticmethod ...: def forward(ctx, tensor, constant):...: # ctx 用來保存信息這里類似self,并且ctx的屬性可以在backward中調(diào)用...: ctx.constant=constant...: return tensor *constant...: @staticmethod...: def backward(ctx, grad_output):...: # 返回的參數(shù)要與輸入的參數(shù)一樣....: # 第一個(gè)輸入為3x3的張量,第二個(gè)為一個(gè)常數(shù)...: # 常數(shù)的梯度必須是 None....: return grad_output, None...: In [39]:
定義完我們的新操作后,我們來進(jìn)行測(cè)試
In [40]: a=t.rand(3,3,requires_grad=True)In [41]: a
Out[41]:
tensor([[0.2859, 0.6373, 0.3489],[0.7932, 0.1416, 0.0118],[0.2317, 0.8374, 0.2620]], requires_grad=True)In [42]: b=MulConstant.apply(a,5)In [43]: b
Out[43]:
tensor([[1.4294, 3.1866, 1.7447],[3.9661, 0.7079, 0.0591],[1.1584, 4.1870, 1.3099]], grad_fn=<MulConstantBackward>)In [44]:
反向傳播,返回值不是標(biāo)量,所以 backward 方法需要參數(shù)。
In [45]: b.backward(t.ones_like(a))In [46]: a.grad
Out[46]:
tensor([[1., 1., 1.],[1., 1., 1.],[1., 1., 1.]])In [47]:
參考:
- https://github.com/zergtant/pytorch-handbook/blob/master/chapter2/2.1.2-pytorch-basics-autograd.ipynb
- https://pytorch.apachecn.org/docs/1.4/blitz/autograd_tutorial.html
總結(jié)
以上是生活随笔為你收集整理的PyTorch 笔记(13)— autograd(0.4 之前和之后版本差异)、Tensor(张量)、Gradient(梯度)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问个电影,历史,亚特兰蒂斯相关的
- 下一篇: PyTorch 笔记(14)— nn.m