PyTorch 笔记(13)— autograd(0.4 之前和之后版本差异)、Tensor(张量)、Gradient(梯度)
1. 背景簡述
torch.autograd 是 PyTorch 中方便用戶使用,專門開發的一套自動求導引擎,它能夠根據輸入和前向傳播過程自動構建計算圖,并執行反向傳播。
計算圖是現代深度學習框架 PyTorch、TensorFlow 等的核心,它為自動求導算法——反向傳播提供了理論支持。
PyTorch 的 Autograd 模塊實現了深度學習的算法中的反向傳播求導數,在張量(Tensor 類)上的所有操作,Autograd 都能為他們自動提供微分,簡化了手動計算導數的復雜過程。
在 0.4 以前的版本中,Pytorch 使用 Variable 類來自動計算所有的梯度。
從 0.4 起, Variable 正式合并入 Tensor 類,通過 Variable 嵌套實現的自動微分功能已經整合進入了Tensor 類中。雖然為了代碼的兼容性還是可以使用 Variable(tensor) 這種方式進行嵌套,但是這個操作其實什么都沒做。
所以,以后的代碼建議直接使用 Tensor 類進行操作,因為官方文檔中已經將 Variable 設置成過期模塊。
要想通過 Tensor 類本身就使用 autograd 功能,只需要設置 .requries_grad=True
Variable 類中的的 grad 和 grad_fn 屬性已經整合進入了 Tensor 類中。
關于反向傳播的基礎,請參考:淺顯易懂的計算圖
2. autograd(PyTorch 0.4 之前版本)
PyTorch 在 autograd 模塊中實現了計算圖的相關功能,autograd 的核心數據結構是 Variable 。
Variable 封裝了 tensor,并記錄對 tensor 的操作記錄用來構建計算圖。
Variable 的數據結構如下圖所示,主要包含三個屬性:
data: 保存Variable所包含的tensor;grad:保存data對應的梯度,grad也是variable而非tensor,與data形狀一致;grad_fn:指向一個Function,這個Function用來反向傳播計算輸入的梯度,記錄variable的操作歷史,即它是什么操作的輸出,用來構建計算圖。如果某一個變量是由用戶創建的,則它為葉子節點,對應的grad_fn為None;
Variable 的構造函數需要傳入 tensor,同時有兩個可選參數:
requires_grad(bool):是否需要對該variable進行求導;volatile(bool): 意為“揮發”,設置為True,構建在該variable上的圖都不會求導,專為推理階段設計;
早期 Variable 的創建是需要 tensor,類似這樣:
In [8]: a = V(t.ones(3,4), requires_grad=True)
目前 Pytorch 的版本已經可以直接這樣:
In [11]: b = t.ones(3,4).requires_grad_(True)
不區分 tensor 和 Variable ,Tensors/Variables 合并,棄用 volatile 標志,原來若 True ,在這之后的圖都不會求導。
Variable 支持大部分的 tensor 支持的函數,但不支持部分 inplace 函數。因為這些操作會修改 tensor 自身,而在反向傳播中,variable 需要緩存原來的 tensor 來計算梯度。如果想要計算各個 Variable 的梯度,只需調用根節點 variable 的 backward 方法,autograd 會自動沿著計算圖反向傳播,計算每一個葉子節點的梯度。
variable.backward(grad_variables=None, retain_graph=None, create_graph=None)
主要有如下參數:
grad_variables:形狀與variable一致,對于y.backward(),grad_variables相當于鏈式法則 ?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:反向傳播需要緩存一些中間結果,反向傳播之后,這些緩存就被清空,可通過指定這個參數不清空緩存,用來多次反向傳播。create_graph:對反向傳播過程再次構建計算圖,可通過backward of backward實現求高階導數。
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 需要求導,但 c 依賴于 a,而 a 需要求導因此 c 的 requires_grad 屬性會自動設為True
In [14]: c.requires_grad
Out[14]: TrueIn [15]: a.is_leaf
Out[15]: TrueIn [16]: b.is_leaf
Out[16]: Truec 不是葉子節點
In [17]: c.is_leaf
Out[17]: False
3. autograd(PyTorch 0.4 之后版本)
3.1 Tensor(張量)
torch.Tensor 是這個包的核心類。如果設置它的屬性 .requires_grad 為 True,那么它將會追蹤對于該張量的所有操作。當完成計算后可以通過調用 .backward(),來自動計算所有的梯度。這個張量的所有梯度將會自動累加到 .grad 屬性。
要阻止一個張量被跟蹤歷史,可以調用.detach() 方法將其與計算歷史分離,并阻止它未來的計算記錄被跟蹤。
在張量創建時,通過設置 requires_grad=True 來告訴 Pytorch 需要對該張量進行自動求導,PyTorch 會記錄該張量的每一步操作歷史并自動計算, 以下兩種方法是等價的。
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]:
針對張量 x 做一次運算
In [6]: y = x + 2In [7]: y
Out[7]:
tensor([[3., 3.],[3., 3.]], grad_fn=<AddBackward0>)In [8]:
y 是計算的結果,而不是用戶自己創建的,所以它有 grad_fn 屬性。
x 是用戶自己創建的,所以 grad_fn 為 None。
在張量進行操作后,grad_fn 已經被賦予了一個新的函數,這個函數引用了一個創建了這個 Tensor 類的Function 對象。 Tensor 和 Function 互相連接生成了一個非循環圖,它記錄并且編碼了完整的計算歷史。每個張量都有一個 .grad_fn 屬性,如果這個張量是用戶手動創建的那么這個張量的 grad_fn 是 None 。
In [8]: y.grad_fn
Out[8]: <AddBackward0 at 0x4bdce50>In [10]: x.grad_fnIn [11]:
對 y 進行更多操作,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_(...) 原地改變了現有張量的 requires_grad 標志。如果沒有指定的話,默認輸入的這個標志是 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(梯度)
為了防止跟蹤歷史記錄(和使用內存),可以將代碼塊包裝在
with torch.no_grad():
中。在評估模型時特別有用,因為模型可能具有 requires_grad = True 的可訓練的參數,但是我們不需要在此過程中對他們進行梯度計算。
還有一個類對于 autograd 的實現非常重要:Function 。
Tensor 和 Function 互相連接并構建一個非循環圖,它保存整個完整的計算過程的歷史信息。每個張量都有一個 .grad_fn 屬性,該屬性引用了創建 Tensor自身的 Function 。(除非這個張量是用戶手動創建的,即這個張量的 grad_fn 是 None )。
如果需要計算導數,可以在 Tensor上調用 .backward()。
- 如果
Tensor是一個標量(即它包含一個元素的數據),則不需要為backward()指定任何參數; - 如果它有更多的元素,則需要指定一個
gradient參數,該參數是形狀匹配的張量;
3.2.1 簡單自動求導
PyTorch 會自動追蹤和記錄對與張量的所有操作,當計算完成后調用 .backward() 方法自動計算梯度并且將計算結果保存到 grad 屬性中。
如果 Tensor 類表示的是一個標量(即它包含一個元素的張量),則不需要為 backward() 指定任何參數,如下所示 out 是一個標量,因此 out.backward() 和 out.backward(torch.tensor(1.)) 等價 。這種參數常出現在圖像分類中的單標簽分類,輸出一個標量代表圖像的標簽。
因為 對 y 進行更多操作后 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(out)/dx=x.grad
In [25]: x.grad
Out[25]:
tensor([[4.5000, 4.5000],[4.5000, 4.5000]])
我們的得到的是一個數取值全部為 4.5 的矩陣。讓我們來調用 out 張量 O。
3.2.2 復雜自動求導
如果 Tensor 類包含多個參數,則需要指定一個 gradient 參數,它是形狀匹配的張量。
我們來看看 autograd 計算的導數和我們手動推導的導數的區別。以下函數
的導數為:
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 不是一個標量,所以需要輸入一個大小相同的張量作為參數,這里我們用 t.ones(y.size()) 函數根據 x 生成一個張量。
t.ones(y.size())
和
t.ones_like(y)
等價。
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]:
可以看到自動求導和手動求導結果是相等的。
3.2.3 torch.no_grad()
我們可以使用 with torch.no_grad() 上下文管理器臨時禁止對已設置 requires_grad=True 的張量進行自動求導。這個方法在測試集計算準確率的時候會經常用到,例如:
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]:
這塊應該為 False,但不知道為啥實際測試是 True,帶繼續深入了解。
使用 .no_grad() 進行嵌套后,代碼不會跟蹤歷史記錄,也就是說保存的這部分記錄會減少內存的使用量并且會加快少許的運算速度。
3.3 Autograd 過程
- 當我們執行
z.backward()的時候。這個操作將調用z里面的grad_fn這個屬性,執行求導的操作。 - 這個操作將遍歷
grad_fn的next_functions,然后分別取出里面的Function(AccumulateGrad),執行求導操作。這部分是一個遞歸的過程直到最后類型為葉子節點。 - 計算出結果以后,將結果保存到他們對應的
variable這個變量所引用的對象(x和y)的grad這個屬性里面。 - 求導結束。所有的葉節點的
grad變量都得到了相應的更新
最終當我們執行完 z.backward() 之后,x 和 y 里面的 grad 值就得到了更新。
3. 擴展 Autograd
如果需要自定義 autograd 擴展新的功能,就需要擴展 Function 類。因為 Function 使用 autograd 來計算結果和梯度,并對操作歷史進行編碼。 在 Function類 中最主要的方法就是 forward() 和 backward() 它們分別代表了前向傳播和反向傳播。
一個自定義的 Function 需要一下三個方法:
-
__init__ (optional):如果這個操作需要額外的參數則需要定義這個Function的構造函數,不需要的話可以忽略。 -
forward():執行前向傳播的計算代碼 -
backward():反向傳播時梯度計算的代碼。 參數的個數和forward返回值的個數一樣,每個參數代表傳回到此操作的梯度。
In [37]: ...: # 引入Function便于擴展...: from torch.autograd.function import FunctionIn [38]: # 定義一個乘以常數的操作(輸入參數是張量)...: # 方法必須是靜態方法,所以要加上@staticmethod ...: class MulConstant(Function):...: @staticmethod ...: def forward(ctx, tensor, constant):...: # ctx 用來保存信息這里類似self,并且ctx的屬性可以在backward中調用...: ctx.constant=constant...: return tensor *constant...: @staticmethod...: def backward(ctx, grad_output):...: # 返回的參數要與輸入的參數一樣....: # 第一個輸入為3x3的張量,第二個為一個常數...: # 常數的梯度必須是 None....: return grad_output, None...: In [39]:
定義完我們的新操作后,我們來進行測試
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]:
反向傳播,返回值不是標量,所以 backward 方法需要參數。
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
總結
以上是生活随笔為你收集整理的PyTorch 笔记(13)— autograd(0.4 之前和之后版本差异)、Tensor(张量)、Gradient(梯度)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 问个电影,历史,亚特兰蒂斯相关的
- 下一篇: 钻石烟多少钱啊?