PyTorch 自动求导机制【附示例代码】

Pytorch 自动求导机制


自动求导是 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=x**3
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)} mn=(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} m0n=w0m0n0+w1m0n1
∂ 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} m1n=w0m1n0+w1m1n1

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 m0n=w0m0n0+w1m0n1=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 m1n=w0m1n0+w1m1n1=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_graphTrue来保留计算图

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=[x0k0x1k0 x0k1x1k1]

x = Variable(torch.Tensor([2,3]),requires_grad=True)
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)

参考:自动求导机制

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值