本系列介紹了入門PyTorch所需要了解的內容。本文主要參考文獻:《Deep Learning with PyTorch: A 60 Minute Blitz》(PyTorch深度學習60分鐘快速入門),更新於2019.06.12。
PyTorch中所有神經網絡的核心是autograd
包,這個包提供所有在張量上進行自動求導的操作。這個過程是define-by-run的,也就是反向傳播過程由代碼運行方式定義,而且每次迭代都可以不同。
Tensor
torch.Tensor
是這個包的重要類別。如果將其屬性.requires_grad
設成True
,則會跟蹤發生在這個張量上的所有操作。在計算結束後,可以通過調用.backward()
進行梯度的自動計算。這個張量的梯度被積累在屬性.grad
中。
要停止一個張量參與反向傳播,可以調用.detach()
函數使其脫離反向傳播計算過程。
如果要停止反向傳播和內存利用,可以將整個代碼用代碼塊torch.no_grad()
,這個對於模型的評估格外有用,因爲模型中可能存在可訓練的參數(requires_grad=True
),但是卻不希望計算它們的梯度。
對於反向傳播很重要的另一個類別時Function
。
Tensor
和Function
之間是相互聯繫的,並且構建了一個非週期圖(acyclic graph),記錄了計算的完整歷史。每個張量都有個.grad_fn
屬性來引用創造這個Tensor
的Function
(除了由用戶定義的張量,這些張量的grad_fn
是·None`)。
可以通過調用Tensor
上的.backward()
實現求導。但是如果這個張量是標量(只有一個元素數據),不需要對backward()
單獨定義任何變量;但是元素很多的時候,需要制定gradient
變量來保證張量的維度保持不變。
import torch
創建一個張量並設定requires_grad=True
來跟蹤實施在上面的計算:
x = torch.ones(2,2,requires_grad=True)
print(x)
進行一個張量操作:
y = x + 2
print(y)
y
是通過某個操作定義的變量,因此具有grad_fn
:
print(y.grad_fn)
進一步對y
進行操作:
z = y * y * 3
out = z.mean()
print(z.out)
requires_grad_( ... )
可以in-place地改變已有張量的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)
Gradients
現在開始反向傳播。由於out
包括的是一個標量,out.backward()
等同於out.backward(torch.tensor(1.))
。
out.backward()
顯示導數d(out)/dx:
print(x.grad)
這裏應該得到4.5組成的矩陣。稱out
張量爲o
,可以得到
且。
因此,,因此。
數學上,如果有一個值爲向量的函數,對的梯度是一個雅可比矩陣(Jacobian matrix)【在向量微積分中,雅可比矩陣是一階偏導數以一定方式排列成的矩陣,其行列式稱爲雅可比行列式。】:
整體而言,torch.autograd
是一個用於計算向量-雅克比乘積的機器(an engine for computing vector-Jacobian product)。即給定任意向量,計算乘積。如果恰好是一個標量函數的梯度,即,那麼根據chain rule,vector-Jacobian乘積應該是相對於的梯度:
注意,是行向量,通過可以轉換成列向量。
這個vector-Jacobian乘積使得將外部梯度送入一個沒有標量輸出的模型變得非常容易。
現在來具體看一個例子;
x = torch.randn(3,requires_grad=True)
y = x* 2
while y.data.norm() < 1000:
y = y * 2
print(y)
終端結果:
此時,y
已經不是一個標量了,torch.autograd
沒有辦法直接計算完整的Jacobian,但是我們其實只需要vector-Jacobian乘積,那麼簡單地將向量以變量的形式反向傳播就可以:
v = torch.tensor(p0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
終端結果:
也可以通過將代碼塊融入with torch.no_grad():
中實現.requires_grad=True
下的反向梯度傳播:
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
終端結果:
代碼總運行時間3.013秒。
輔助閱讀材料:【文檔學習】Pytorch——torch.autorgrad包
更多內容,歡迎加入星球討論。