Pytorch : 自动求导

Posted Shilong Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch : 自动求导相关的知识,希望对你有一定的参考价值。

在训练神经网络时,最常用的算法是反向传播算法。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。损失函数计算神经网络产生的期望输出和实际输出之间的差值。目标是使损失函数的结果尽可能接近于零。该算法通过网络反向遍历来调整权重和偏差,以重新训练模型。这就是为什么它被称为反向传播。
为了计算这些梯度,PyTorch有一个内置的微分引擎torch.autograd。它支持任何计算图的梯度自动计算。

import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

Tensors, Functions and Computational graph

上面的代码定义的计算图如下:

在这个网络中,w和b是我们需要优化的参数。因此,我们需要能够计算损失函数关于这些变量的的梯度。为了做到这一点,我们设置了这些张量的requires_grad性质。

Note: 你可以在创建一个张量时设置require_grad的值,或者稍后使用x.requires_grad_(True)方法

构造计算图的作用于tensors上的函数实际上是Function类的一个对象。该对象知道如何正向计算函数值,以及如何在反向传播步骤中计算其导数。对反向传播函数的引用存储在tensor的grad _fn属性中
为了优化神经网络中参数的权重,我们需要计算损失函数对参数的导数,在x和y的固定值下,我们需要计算 ∂ l o s s ∂ w , ∂ l o s s ∂ b \\rm \\dfrac\\partial loss\\partial w,\\dfrac\\partial loss\\partial b wloss,bloss。为了计算这些导数,我们调用loss.backward(),然后从w.gradb.grad中获取。

我们只能获得计算图的叶节点的grad属性,这些叶节点需要设置requires_grad属性为true。对于我们图中的其他节点,梯度将不可获得。此外,出于性能原因,我们只能在给定图上使用一次backward()进行梯度计算。如果我们需要在同一图上进行几次反向传播,则需要在调用backward时设置retain_graph = True

Disabling gradient tracking

当我们不需要反向传播计算梯度时,可以使用torch.no_grad()函数停止跟踪计算梯度。

# python代码
z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)
//等效的C++代码
z = torch::matmul(x, w) + b;
std::cout << z.requires_grad() << std::endl;


    torch::NoGradGuard no_grad;
    z = torch::matmul(x, w) + b;

std::cout << z.requires_grad() << std::endl;

对于PyTorch的C++库LibTorch,torch.no_grad在PyTorch的Python API中的等效物是torch::NoGradGuard no_grad。这是一个RAII线程本地守卫,用于在C++中禁用梯度计算。与Python的实现类似,通过实例化torch::NoGradGuard类的对象可以禁用LibTorch中的梯度计算。在此模式下,每个计算的结果都将具有requires_grad=false。

实现相同效果的另一种方法是在张量上使用detach()方法

z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)

从概念上讲,autograd用由Function对象组成的有向无环图(DAG)记录数据(张量)和所有执行的操作(以及由此产生的新张量)。在这个DAG中,叶是输入张量,根是输出张量。通过从根到叶跟踪这个图,您可以使用链式法则自动计算梯度。

使用backward()函数反向传播计算tensor的梯度时,并不计算所有tensor的梯度,而是只计算满足这几个条件的tensor的梯度:

  1. 类型为叶子节点
  2. requires_grad=True
  3. 依赖该tensor的所有tensor的requires_grad=True

所有满足条件的变量梯度会自动保存到对应的grad属性里。

Tensor gradients and Jacobian products

Python API

torch.autograd.grad(outputs, inputs, 
					grad_outputs=None, 
					retain_graph=None, 
					create_graph=False, 
					allow_unused=False, 
					is_grads_batched=False)

C++API

using namespace torch::autograd;
using torch::autograd::variable_list = std::vector<Variable>;
using torch::autograd::Variable = at::Tensor;
//variable_list就是std::vector<at::Tensor>
variable_list torch::autograd::grad
(const variable_list &outputs, 
 const variable_list &inputs, 
 const variable_list &grad_outputs = , 
 c10::optional<bool> retain_graph = c10::nullopt, 
 bool create_graph = false, bool allow_unused = false)

计算并返回输出对输入的梯度。

  • outputs: 可微函数的输出
  • inputs: 输入,计算的输出相对于输入的梯度(不会积累到at::Tensor::grad).
  • grad_outputs: Jacobian-vector乘积中的向量。通常是相对于每个输出的预先计算的梯度。标量Tensors 或者不需要计算梯度的张量可以设置为torch::Tensor().
  • retain_graph: 如果为false,则用于计算grad的图形将被释放。请注意,在几乎所有情况下,将此选项设置为true是不必要的,而且通常可以以更高效的方式解决。默认值为create_graph的值。
  • create_graph: 如果为真,则将构建导数图,从而允许计算更高阶导数乘积。默认值为:false。
  • allow_unused: 如果设置为false,则在计算输出与输入无关(因此它们的梯度始终为零),则报错。默认为false。
  • is_grads_batched: 目前为python特有参数。如果为True, grad_output的第一个维度将被解释为批处理维度。我们不再计算单个的向量-雅可比矩阵积,而是为批中的每个向量计算一批向量-雅可比矩阵积。我们使用vmap原型特性作为后端来向量化对autograd引擎的调用,这样计算就可以在单个调用中执行。与手动循环和多次向后执行相比,这应该会导致性能改进。

函数行为

输入 m m m 个输入向量 x i x_i xi , n n n 个输出向量 y j y_j yj以及 以及 n n n 个权重向量 v j v_j vj(形状与对应 y j y_j yj一致),函数会输出 m m m 个向量的元组 ( g 1 , g 2 , ⋯   , g m ) (g_1,g_2,\\cdots,g_m) (g1,g2,,gm),细节为 :
输入: x ⃗ i , y ⃗ j , v ⃗ j ; i = 1 , ⋯   , m , j = 1 , ⋯   , n ; y ⃗ j , v ⃗ j \\vecx_i,\\vecy_j,\\vecv_j;i=1,\\cdots,m,j=1,\\cdots,n;\\vecy_j,\\vecv_j x i,y j,v j;i=1,,m,j=1,,n;y j,v j形状一致
y = ∑ j = 1 n v ⃗ j ⋅ y ⃗ j y=\\sum_j=1^n\\vecv_j\\cdot\\vecy_j y=j=1nv jy j
其中 ( ⋅ ) (\\cdot) () 为向量内积运算
对任意 i ∈ 1 , 2 , … , m i\\in\\1,2,\\ldots,m\\ i1,2,,m,记 x ⃗ i = ( x i 1 , x i 2 , … , x i k i ) \\vecx_i=(x_i_1,x_i_2,\\ldots,x_i_k_i) x i=(xi1,xi2,,xiki),则 g ⃗ i = ( ∂ y ∂ x i 1 , … , ∂ y ∂ x i k i ) \\vecg_i=\\left(\\dfrac\\partial y\\partial x_i_1,\\ldots,\\dfrac\\partial y\\partial x_i_k_i\\right) g i=(xi1y,,xikiy)
输出元组 g = ( g ⃗ 1 , g ⃗ 2 , … , g ⃗ m ) \\mathbfg=(\\vecg_1,\\vecg_2,\\ldots,\\vecg_m) g=(

Pytorch Note6 自动求导Autograd

全部笔记的汇总贴:Pytorch Note 快乐星球

自动求导

用Tensor训练网络很方便,但有时候反向传播过程需要手动实现。这对于像线性回归等较为简单的模型来说,还可以应付,但实际使用中经常出现非常复杂的网络结构,此时如果手动实现反向传播,不仅费时费力,而且容易出错,难以检查。torch.autograd就是为方便用户使用,而专门开发的一套自动求导引擎,它能够根据输入和前向传播过程自动构建计算图,并执行反向传播。

计算图(Computation Graph)是现代深度学习框架如PyTorch和TensorFlow等的核心,其为高效自动求导算法——反向传播(Back Propogation)提供了理论支持,了解计算图在实际写程序过程中会有极大的帮助。

import torch
from torch.autograd import Variable

简单情况的自动求导

下面我们显示一些简单情况的自动求导,"简单"体现在计算的结果都是标量,也就是一个数,我们对这个标量进行自动求导。

x = Variable(torch.Tensor([2]), requires_grad=True)
y = x + 2
z = y ** 2 + 3
print(z)

tensor([19.], grad_fn=)

通过上面的一些列操作,我们从 x 得到了最后的结果out,我们可以将其表示为数学公式

z = ( x + 2 ) 2 + 3 z = (x + 2)^2 + 3 z=(x+2)2+3

那么我们从 z 对 x 求导的结果就是

∂ z ∂ x = 2 ( x + 2 ) = 2 ( 2 + 2 ) = 8 \\frac{\\partial z}{\\partial x} = 2 (x + 2) = 2 (2 + 2) = 8 xz=2(x+2)=2(2+2)=8
如果你对求导不熟悉,可以查看以下网址进行复习

# 使用自动求导
z.backward()
print(x.grad)

tensor([8.])

对于上面这样一个简单的例子,我们验证了自动求导,同时可以发现发现使用自动求导非常方便。如果是一个更加复杂的例子,那么手动求导就会显得非常的麻烦,所以自动求导的机制能够帮助我们省去麻烦的数学计算,下面我们可以看一个更加复杂的例子。

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

out = torch.mean(y - torch.matmul(x, w)) # torch.matmul 是做矩阵乘法
out.backward()

如果你对矩阵乘法不熟悉,可以查看下面的网址进行复习

# 得到 x 的梯度
print(x.grad)
# 得到 y 的的梯度
print(y.grad)
# 得到 w 的梯度
print(w.grad)

上面数学公式就更加复杂,矩阵乘法之后对两个矩阵对应元素相乘,然后所有元素求平均,有兴趣的同学可以手动去计算一下梯度,使用 PyTorch 的自动求导,我们能够非常容易得到 x, y 和 w 的导数,因为深度学习中充满大量的矩阵运算,所以我们没有办法手动去求这些导数,有了自动求导能够非常方便地解决网络更新的问题。

复杂情况的自动求导

上面我们展示了简单情况下的自动求导,都是对标量进行自动求导,可能你会有一个疑问,如何对一个向量或者矩阵自动求导了呢?感兴趣的同学可以自己先去尝试一下,下面我们会介绍对多维数组的自动求导机制。

m = Variable(torch.FloatTensor([[2, 3]]), requires_grad=True) # 构建一个 1 x 2 的矩阵
n = Variable(torch.zeros(1, 2)) # 构建一个相同大小的 0 矩阵
print(m)
print(n)

Variable containing:
2 3
[torch.FloatTensor of size 1x2]

Variable containing:
0 0
[torch.FloatTensor of size 1x2]

# 通过 m 中的值计算新的 n 中的值
n[0, 0] = m[0, 0] ** 2
n[0, 1] = m[0, 1] ** 3
print(n)

Variable containing:
4 27
[torch.FloatTensor of size 1x2]

将上面的式子写成数学公式,可以得到
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 对 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)

Variable containing:
4 27
[torch.FloatTensor of size 1x2]

通过自动求导我们得到了梯度是 4 和 27,我们可以验算一下
∂ 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 m1以上是关于Pytorch : 自动求导的主要内容,如果未能解决你的问题,请参考以下文章

深度学习-pytorch自动求导实现

(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记前言(介绍各种机器学习问题)以及数据操作预备知识Ⅱ(线性代数微分自动求导)

深度学习之30分钟快速入门PyTorch(附学习资源推荐)

深度学习——Pytorch基础

深度学习——Pytorch基础

PyTorch学习之自动求导机制-Volatile标志