Autograd:自動求導
Pytorch所有的神經網絡的核心是autograd庫. 我們先簡單瞭解一下, 然後訓練第一個神經網絡.
autograd
包提供了Tensor所有操作的自動求導(差分). 這是一個運行時定義(define-by-run)的架構, 意味着你的後向傳播是根據你的代碼怎樣運行來定義的. 每一次的迭代都可能不同.
讓我們看幾個例子.
Tensor
torch.Tensor
是這個包的核心類. 如果將其屬性attribute.requires_grad
設爲True
, 它就會記錄所有的操作. 計算完成後, 調用.backward()
, 會自動計算所有的梯度(gradients). 這個tensor的梯度會累加到.grad
屬性中.
爲了阻止tensor記錄歷史, 可以調用.detach()
將它和計算曆史脫鉤, 並阻止其下一步計算繼續記錄操作歷史.
爲了防止追蹤歷史(和使用存儲), 你可以將代碼塊放在torch.no_grad():
中. 這在評估一個模型時特別有用, 因爲模型中有可訓練的參數, 他們的requires_grad
是True
. 而在評估過程中不需要計算梯度.
還有一個類在autograd實現中特別重要, Function
.
Tensor
和Function
內部聯繫並且建立一張非環形的圖(acyclic graph). 這張圖將運算的全部歷史全部編碼. 每一個tensor都有一個.grad_fn
指向創建此tensor的Function
. 除非Tensor是用戶創建的, 其grad_fn
是None
.
如果你想計算導數, 你可以在一個Tensor上調用.backward()
. 如果Tensor是個標量(也就是隻有一個元素), 你不需要指定任何參數. 但是如果它有不止一個元素, 你需要指定一個匹配大小的tensor作爲梯度參數.
import torch
創建一個requires_grad=True
的tensor用來追蹤計算曆史.
x = torch.ones(2, 2, requires_grad=True)
print(x)
Out:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
做一個tensor運算:
y = x + 2
print(y)
Out:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
作爲一個操作的結果而被創建, 所以它有grad_fn
.
print(y.grad_fn)
Out:
<AddBackward0 object at 0x7f6a7f893e10>
在y上做更多運算.
z = y * y * 3
out = z.mean()
print(z, out)
Out:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... )
原址改變現有Tensor的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)
Out:
False
True
<SumBackward0 object at 0x7f6a7f8a2ba8>
Gradients
現在開始反向傳播. 因爲out
只有一個標量, out.backward()
等效於out.backward(torch.tensor(1.))
.
out.backward()
打印梯度 d(out)/dx
print(x.grad)
Out:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
你應該得到一個4.5的矩陣. 令tensorout
爲"". 我們得到, , . 所以, , 所以, .
在數學上, 有一個向量函數, 那麼相對於的梯度就是一個Jocobian 矩陣:
一般地, torch.autograd
是一個計算向量和Jacobian矩陣乘積的引擎. 也就是, 給定一個向量, 計算乘積. 如果是一個標量函數的梯度, 也就是,, 那麼根據鏈式法則(chain rule), 向量和Jacobian矩陣乘積的結果就是對的梯度:
(注意輸出行向量, 可以看做是J^T\cdot v所輸出的列向量)
向量-Jacobian矩陣乘積的性質可以很方便的給非標量輸出的模型傳遞外部梯度.
現在看一個向量-Jacobian矩陣乘積的例子:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
Out:
tensor([ 367.6527, -1195.3390, -546.3927], grad_fn=<MulBackward0>)
Now in this case y is no longer a scalar. torch.autograd could not compute the full Jacobian directly, but if we just want the vector-Jacobian product, simply pass the vector to backward as argument:
這裏y
不是標量. torch.autograd
不能直接計算全部的Jacobian, 但是如果需要向量-Jacobian矩陣乘積, 簡單地傳遞一個向量參數給backward
即可:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
Out:
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
設置.requires_grad=True
, 阻止autograd
追蹤Tensor的歷史. 將代碼塊放在with torch.no_grad():
裏:
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
Out:
True
True
False
或者使用detach()
獲取一個內容相同但不需要求梯度的新tensor:
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
Out:
True
False
tensor(True)
稍後閱讀:
autograd.Function
的文檔在:https://pytorch.org/docs/stable/autograd.html#function
Total running time of the script: ( 0 minutes 3.509 seconds)
公式編輯語法: https://katex.org/docs/supported.html
補充1. 如何自定義autograd 和nn?
簡單來說, 需要繼承Function基類並override forward()和backward()函數. 這兩個函數都是static的.
官方文檔:
https://pytorch.org/docs/stable/notes/extending.html
補充解釋:
https://blog.csdn.net/Hungryof/article/details/78346304
補充2. 如何理解autograd.Function和Tensor的聯繫?
pytorch0.4之前版本有Variable和Tensor的區別. 現在兩者統一, 使用Tensor即可.
torch.Tensor
有幾個屬性/方法和autograd相關:
- grad, 存儲梯度值(累加)
- requires_grad, (是否需要計算梯度)
- is_leaf, (是否葉節點, 是葉節點的tensor不從其他tensor運算得來)
- backward(), (計算當前tensor相對於葉節點tensor的梯度)
- detach(), 得到新tensor, 不再求梯度
- detach_(), 使tensor成爲一個葉節點.
- register_hook(), 註冊一個backward hook, 用以求梯度.
- retain_grad()
官方文檔:
https://pytorch.org/docs/stable/autograd.html
補充3. 如何理解自動求導和DAG圖的關係?
autograd機制能夠記錄作用於Tensor上的所有操作,生成一個動態計算圖。圖的葉子節點是輸入的數據,根節點是輸出的結果。當在根節點調用.backward()的時候就會從根到葉應用鏈式法則計算梯度。默認情況下,只有.requires_grad和is_leaf兩個屬性都爲True的節點纔會被計算導數,並存儲到grad中。
我們已經知道PyTorch使用動態計算圖(DAG)記錄計算的全過程,那麼DAG是怎樣建立的呢?一些博客認爲DAG的節點是Tensor(或說Variable),這其實是不準確的。DAG的節點是Function對象,邊表示數據依賴,從輸出指向輸入。因此Function類在PyTorch自動微分中位居核心地位,但是用戶通常不會直接去使用,導致人們對Function類瞭解並不多。
每當對Tensor施加一個運算的時候,就會產生一個Function對象,它產生運算的結果,記錄運算的發生,並且記錄運算的輸入。Tensor使用.grad_fn屬性記錄這個計算圖的入口。反向傳播過程中,autograd引擎會按照逆序,通過Function的backward依次計算梯度。
https://www.cnblogs.com/cocode/p/10746347.html
補充4. 爲什麼.grad設計成累加模式?
backward函數本身沒有返回值,它計算出來的梯度存放在葉子節點的grad屬性中。PyTorch文檔中提到,如果grad屬性不爲空,新計算出來的梯度值會直接加到舊值上面。
爲什麼不直接覆蓋舊的結果呢?這是因爲有些Tensor可能有多個輸出,那麼就需要調用多個backward。疊加的處理方式使得backward不需要考慮之前有沒有被計算過導數,只需要加上去就行了,這使得設計變得更簡單。因此我們用戶在反向傳播之前,常常需要用zero_grad函數對導數手動清零,確保計算出來的是正確的結果。