記得深度學習三巨頭之一Yann LeCun曾經說過:“深度學習已死,可微編程永生”,就是說深度學習只是一種計算範式,而背後的可微分編程,具有更廣闊的應用前景。在這裏我們將探索PyTorch中的自動微分技術。
Tensor函數
PyTorch中的自動微分是基於Tensor的,以Tensor作爲參數的函數,實際上是將Tensor中的每個元素應用該函數。只講概念比較抽象,我們來看一個具體的例子。
我們定義x∈R5,定義如下函數:
y=f(x)=⎣⎢⎢⎢⎢⎡f(x1)f(x2)f(x3)f(x4)f(x5)⎦⎥⎥⎥⎥⎤
下面的程序我們以x2爲例:
import torch
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
y = x ** 2
print(y)
在深度學習中,有三種最常見的情況:標量對向量的微分、向量對向量的微分、向量對矩陣的微分,其實還有張量對張量的微分,因爲在實際中比較少見,所以這裏就不再討論了。
標量對向量的微分
通常神經網絡的代價函數會是一個標量,對於多分類問題,神經網絡的輸出爲一個向量,這時就需要求標量對向量的微分。我們定義如下函數:
y=i=1∑5xi2
我們知道求微分可以採用解析法、數值法和自動微分法,由於這個問題比較簡單,我們可以直接應用解析法:
∂xi∂y=2xi,i=1,2,3,4,5
我們定義標量對向量的微分爲:
∂x∂y=[∂x1∂y∂x2∂y∂x3∂y∂x4∂y∂x5∂y]∈R1×n,x∈Rn
我們可以通過PyTorch中的自動微分來完成這一任務:
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True, dtype=torch.float32)
y = torch.sum(x ** 2)
print(y.item())
y.backward()
print('shape:{0}; {1}'.format(x.grad.size(), x.grad))
第1行:
- 第1行:我們在定義Tensor時,如果加入requires_grad,表明我們需要求對其的微分;
- 第2行:計算函數值,在實際應用中就是神經網絡的前向傳播過程;
- 第4行:調用自動微分,就是實際應用中的神經網絡的反向傳播過程;
我們可以看到打印的結果與我們用解析法求出的內容一致。
向量對向量微分
實際上向量對向量微分叫做Jacobian矩陣,向量y∈Rm對向量x∈Rn的微分定義爲:
∂x∂y=⎣⎢⎢⎢⎡∂x1∂y1∂x1∂y2...∂x1∂ym∂x2∂y1∂x2∂y2...∂x2∂ym............∂xn∂y1∂xn∂y2...∂xn∂ym⎦⎥⎥⎥⎤∈Rm×n
我們假設有如下向量,例如是神經網絡某層的輸入向量:
zl=⎣⎢⎢⎢⎢⎡1.02.03.04.05.0⎦⎥⎥⎥⎥⎤
經過該層的激活函數(sigmoid函數)之後,得到本層的輸出向量:
al=⎣⎢⎢⎢⎢⎡z12z22z13z14z15⎦⎥⎥⎥⎥⎤
因爲本神經元的輸入只會影響本神經元的輸出,因此我們的al如上所示。根據解析法可得其Jacobian矩陣爲:
∂zl∂al=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡∂z1l∂a1l00000∂z2l∂a2l00000∂z3l∂a3l00000∂z3l∂a3l00000∂z3l∂a3l⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
其實際上只有對角線上有值,因此在PyTorch中,爲了更高效的處理問題,我們通常不需要原始的Jacobian矩陣,而是將其乘以一個與al同維且元素均爲1的向量,得到的結果向量的元素爲對角線上的元素。
∂zl∂al⋅⎣⎢⎢⎢⎢⎡1.01.01.01.01.0⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡∂z1l∂a1l00000∂z2l∂a2l00000∂z3l∂a3l00000∂z3l∂a3l00000∂z3l∂a3l⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤⋅⎣⎢⎢⎢⎢⎡1.01.01.01.01.0⎦⎥⎥⎥⎥⎤=⋅⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡∂z1l∂a1l∂z2l∂a2l∂z3l∂a2l∂z4l∂a4l∂z5l∂a5l⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
在PyTorch中代碼如下所示:
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True)
y = x**2
y.backward(torch.ones_like(y))
print('y_x:{0}'.format(x.grad))
向量對矩陣求微分
在深度學習中,經常會出現上一層的輸入向量zl∈RNl對連接權值Wl∈RNl×Nl−1的微分,公式爲:
zl=Wl⋅al−1+bl
其中al−1∈RNl−1,bl∈RNl。
通過解析法,我們可以得到其微分公式爲:
∂Wl∂zl=⎣⎢⎢⎡a1l−1a1l−1...a1l−1a2l−1a2l−1...a2l−1............al−1l−1al−1l−1...al−1l−1⎦⎥⎥⎤=⎣⎢⎢⎡(al−1)T(al−1)T...(al−1)T⎦⎥⎥⎤∈RNl×Nl−1
PyTorch代碼如下所示:
a_l_1 = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], requires_grad=True)
W_l = torch.tensor([
[101.0, 102.0, 103.0, 104.0, 105.0],
[201.0, 202.0, 203.0, 204.0, 205.0],
[301.0, 302.0, 303.0, 304.0, 305.0]
], requires_grad=True)
b_l = torch.tensor([1001.0, 1002.0, 1003.0], requires_grad=True)
z_l = torch.matmul(W_l, a_l_1) + b_l
z_l.backward(torch.ones_like(z_l))
print('y_x:{0}'.format(W_l.grad))
''' 打印輸出
y_x:tensor([[1., 2., 3., 4., 5.],
[1., 2., 3., 4., 5.],
[1., 2., 3., 4., 5.]])
'''
上述程序的打印結果與理論分析的結果一致,證明我們的求法是正確的。