天天看點

PyTorch 自動求導機制【附示例代碼】Pytorch 自動求導機制

Pytorch 自動求導機制

文章目錄

  • Pytorch 自動求導機制
    • 1. 簡單的自動求導(輸出是1維)
    • 2. 多元數組的自動求導(輸出是多元)
    • 3. 多次自動求導

自動求導是 PyTorch 中非常重要的特性,能夠讓我們避免手動去計算非常複雜的導數,這能夠極大地減少了我們構模組化型的時間。

每個Tensor都有個标志:

requires_grad

,它都允許從梯度計算中精細地排除子圖,并可以提高效率。

requires_grad=True

要求梯度

requires_grad=False

不要求梯度

什麼是排除子圖?

答: 排除沒必要的梯度計算。

requires_grad

如果有一個單一的輸入操作需要梯度,它的輸出也需要梯度。相反,隻有所有輸入都不需要梯度,輸出才不需要。如果其中所有的變量都不需要梯度進行,後向計算不會在子圖中執行。

步驟:

  • 導入Variable并指定

    requires_grad=True

    ,
  • y.backward()

  • x.grad

1. 簡單的自動求導(輸出是1維)

import torch
from torch.autograd import Variable

x = torch.Tensor([2])
# tensor 變成Variable
x = Variable(x,requires_grad=True)
           
y.backward()
print(x.grad)
           
tensor([12.])
           

例2: z = ( x + 2 ) 2 + 3 z = (x + 2)^2 + 3 z=(x+2)2+3, z z z對 x x x進行求導。

x = Variable(torch.Tensor([2]), requires_grad=True)
y = x + 2
z = y ** 2 + 3
print(z)
           
tensor([19.], grad_fn=<AddBackward0>)
           
# 使用自動求導
z.backward()
print(x.grad)
           
tensor([8.])
           

例3:更複雜的例子

x = Variable(torch.randn(5, 10), requires_grad=True)
y = Variable(torch.randn(5, 5), requires_grad=True)
w = Variable(torch.randn(10, 5), requires_grad=True)

out = torch.mean(y - torch.mm(x, w)) # torch.matmul 是做矩陣乘法或者的使用torch.mm()
print(out)
out.backward()
           
tensor(-0.8683, grad_fn=<MeanBackward0>)
           
# 得到 x 的梯度
print(x.grad)
           
tensor([[ 0.0479,  0.0470,  0.0192,  0.1219,  0.2274, -0.1751,  0.0755,  0.0062,
         -0.1455,  0.0992],
        [ 0.0479,  0.0470,  0.0192,  0.1219,  0.2274, -0.1751,  0.0755,  0.0062,
         -0.1455,  0.0992],
        [ 0.0479,  0.0470,  0.0192,  0.1219,  0.2274, -0.1751,  0.0755,  0.0062,
         -0.1455,  0.0992],
        [ 0.0479,  0.0470,  0.0192,  0.1219,  0.2274, -0.1751,  0.0755,  0.0062,
         -0.1455,  0.0992],
        [ 0.0479,  0.0470,  0.0192,  0.1219,  0.2274, -0.1751,  0.0755,  0.0062,
         -0.1455,  0.0992]])
           
# 得到 y 的的梯度
print(y.grad)
           
tensor([[0.0400, 0.0400, 0.0400, 0.0400, 0.0400],
        [0.0400, 0.0400, 0.0400, 0.0400, 0.0400],
        [0.0400, 0.0400, 0.0400, 0.0400, 0.0400],
        [0.0400, 0.0400, 0.0400, 0.0400, 0.0400],
        [0.0400, 0.0400, 0.0400, 0.0400, 0.0400]])
           
# 得到 w 的梯度
print(w.grad)
           
tensor([[0.0219, 0.0219, 0.0219, 0.0219, 0.0219],
        [0.0160, 0.0160, 0.0160, 0.0160, 0.0160],
        [0.0847, 0.0847, 0.0847, 0.0847, 0.0847],
        [0.1473, 0.1473, 0.1473, 0.1473, 0.1473],
        [0.0931, 0.0931, 0.0931, 0.0931, 0.0931],
        [0.0924, 0.0924, 0.0924, 0.0924, 0.0924],
        [0.1073, 0.1073, 0.1073, 0.1073, 0.1073],
        [0.0393, 0.0393, 0.0393, 0.0393, 0.0393],
        [0.0226, 0.0226, 0.0226, 0.0226, 0.0226],
        [0.0419, 0.0419, 0.0419, 0.0419, 0.0419]])
           

2. 多元數組的自動求導(輸出是多元)

例4:多元數組的自動求導機制

m = Variable(torch.FloatTensor([[2, 3]]), requires_grad=True) # 建構一個 1 x 2 的矩陣
n = Variable(torch.zeros(1, 2)) # 建構一個相同大小的 0 矩陣
print(m)
print(n)
           
tensor([[2., 3.]], requires_grad=True)
tensor([[0., 0.]])
           
# 通過 m 中的值計算新的 n 中的值
n[0, 0] = m[0, 0] ** 2
n[0, 1] = m[0, 1] ** 3
print(n)
           
tensor([[ 4., 27.]], grad_fn=<CopySlices>)
           
import numpy as np
a =np.array([[1,2,3],[4,5,6]]) 

a[1,1]==a[1][1] #True
# 隻有二維數組或矩陣,tensor才能這樣取數
           
True
           

n = ( n 0 ,   n 1 ) = ( m 0 2 ,   m 1 3 ) = ( 2 2 ,   3 3 ) n = (n_0,\ n_1) = (m_0^2,\ m_1^3) = (2^2,\ 3^3) n=(n0​, n1​)=(m02​, m13​)=(22, 33),對 n n n進行反向傳播,也就是 n n n對 m m m的導數。

∂ n ∂ m = ∂ ( n 0 ,   n 1 ) ∂ ( m 0 ,   m 1 ) \frac{\partial n}{\partial m} = \frac{\partial (n_0,\ n_1)}{\partial (m_0,\ m_1)} ∂m∂n​=∂(m0​, m1​)∂(n0​, n1​)​

在 PyTorch 中,如果要調用自動求導,需要往

backward()

中傳入一個參數,這個參數的形狀和 n 一樣大,比如是 ( w 0 ,   w 1 ) (w_0,\ w_1) (w0​, w1​),那麼自動求導的結果就是:

∂ n ∂ m 0 = w 0 ∂ n 0 ∂ m 0 + w 1 ∂ n 1 ∂ m 0 \frac{\partial n}{\partial m_0} = w_0 \frac{\partial n_0}{\partial m_0} + w_1 \frac{\partial n_1}{\partial m_0} ∂m0​∂n​=w0​∂m0​∂n0​​+w1​∂m0​∂n1​​

∂ n ∂ m 1 = w 0 ∂ n 0 ∂ m 1 + w 1 ∂ n 1 ∂ m 1 \frac{\partial n}{\partial m_1} = w_0 \frac{\partial n_0}{\partial m_1} + w_1 \frac{\partial n_1}{\partial m_1} ∂m1​∂n​=w0​∂m1​∂n0​​+w1​∂m1​∂n1​​

n.backward(torch.ones_like(n)) # 将 (w0, w1) 取成 (1, 1)
print(m.grad)
           
tensor([[ 4., 27.]])
           
關鍵:為什麼要

torch.ones_like(n)

?【向量求導】

n不是一個标量,是一個向量,需要傳入一個維數一樣的1向量,使得梯度是每個分量的求梯度的和。

驗證:

∂ n ∂ m 0 = w 0 ∂ n 0 ∂ m 0 + w 1 ∂ n 1 ∂ m 0 = 2 m 0 + 0 = 2 × 2 = 4 \frac{\partial n}{\partial m_0} = w_0 \frac{\partial n_0}{\partial m_0} + w_1 \frac{\partial n_1}{\partial m_0} = 2 m_0 + 0 = 2 \times 2 = 4 ∂m0​∂n​=w0​∂m0​∂n0​​+w1​∂m0​∂n1​​=2m0​+0=2×2=4

∂ n ∂ m 1 = w 0 ∂ n 0 ∂ m 1 + w 1 ∂ n 1 ∂ m 1 = 0 + 3 m 1 2 = 3 × 3 2 = 27 \frac{\partial n}{\partial m_1} = w_0 \frac{\partial n_0}{\partial m_1} + w_1 \frac{\partial n_1}{\partial m_1} = 0 + 3 m_1^2 = 3 \times 3^2 = 27 ∂m1​∂n​=w0​∂m1​∂n0​​+w1​∂m1​∂n1​​=0+3m12​=3×32=27

3. 多次自動求導

通過調用

backward

我們可以進行一次自動求導,如果我們再調用一次

backward

,會發現程式報錯,沒有辦法再做一次。這是因為 PyTorch 預設做完一次自動求導之後,計算圖就被丢棄了,是以兩次自動求導需要手動設定一個東西,我們通過下面的小例子來說明。

x = Variable(torch.FloatTensor([3]), requires_grad=True)
y = x * 2 + x ** 2 + 3
print(y)
           
tensor([18.], grad_fn=<AddBackward0>)
           

設定

retain_graph

True

來保留計算圖

y.backward(retain_graph=True) 
print(x.grad)
           
tensor([8.])
           
y.backward() # 再做一次自動求導,這次不保留計算圖
print(x.grad)
           
tensor([16.])
           

注意:這個張量的所有梯度将會自動累加到

.grad

屬性.

y.backward() # 第三次做自動求導,這次不保留計算圖
print(x.grad)# 報錯
           

練習

x = [ x 0   x 1 ] = [ 2   3 ] x = \left[ \begin{matrix} x_0 \ x_1 \end{matrix} \right] = \left[ \begin{matrix} 2 \ 3 \end{matrix} \right] x=[x0​ x1​​]=[2 3​]

k = ( k 0 ,   k 1 ) = ( x 0 2 + 3 x 1 ,   2 x 0 + x 1 2 ) k = (k_0,\ k_1) = (x_0^2 + 3 x_1,\ 2 x_0 + x_1^2) k=(k0​, k1​)=(x02​+3x1​, 2x0​+x12​)

求:

j = [ ∂ k 0 ∂ x 0 ∂ k 0 ∂ x 1   ∂ k 1 ∂ x 0 ∂ k 1 ∂ x 1 ] j = \left[ \begin{matrix} \frac{\partial k_0}{\partial x_0} & \frac{\partial k_0}{\partial x_1} \ \frac{\partial k_1}{\partial x_0} & \frac{\partial k_1}{\partial x_1} \end{matrix} \right] j=[∂x0​∂k0​​​∂x1​∂k0​​ ∂x0​∂k1​​​∂x1​∂k1​​​]

k=Variable(torch.zeros(2))
k[0]=x[0]**2+3*x[1]
k[1]=2*x[0]+x[1]**2
print(k)
           
tensor([13., 13.], grad_fn=<CopySlices>)
           
j = torch.zeros(2, 2)

k.backward(torch.FloatTensor([1, 0]), retain_graph=True)
j[0] = x.grad.data

x.grad.data.zero_() # 歸零之前求得的梯度

k.backward(torch.FloatTensor([0, 1]))
j[1] = x.grad.data
print(j)
           
tensor([[4., 3.],
        [2., 6.]])
           
k.backward(torch.ones_like(k),retain_graph=True)
print(x.grad)
           

參考:自動求導機制