Pytorch 是如何进行梯度计算的?

2022-10-0819:08:58云计算与物联网Comments1,151 views字数 2798阅读模式

Pytorch 是如何进行梯度计算的?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

Pytorch 作为最受欢迎的深度学习框架之一,其对于梯度计算的支持自然是不言而喻的。最近对 Pytorch 进行了更进一步的学习,发现了它为了服务深度学习,做了很多针对性的设计。搭积木并不是 Pytorch 的本质,梯度才是。而 Pytorch 对于梯度的运算方式,则是这篇文章讨论的重点。我相信如果要将一个工具用好,深入了解原理是必不可少的一步。我做了一些可以拿来分享的笔记,于是有了这篇介绍基础但好像又不那么基础的文章。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

Tensor 与梯度计算相关的性质

在进行基本运算的时候,Pytorch 的 Tensor 确实可以当作 Numpy 的 ndarray 处理,但到了神经网络这里,Tensor 的优势才真正显现出来。为了支持神经网络的反向梯度传播,Tensor 里提供的额外性质大抵包括这么几个:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

  • 是否为参数 requires_grad
  • 梯度数值 grad
  • 梯度函数 grad_fn

requires_grad

一般来说,当我们创建一个 Tensor 时,我们可以令 requires_grad=False 来定义它为常量,或者 requires_grad=False 定义其为参数。常量不存储任何梯度信息,为参数的梯度计算而服务。requires_grad 默认是 False文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

import torch
# 声明为常量
c1 = torch.randn(5, 3)
c2 = torch.Tensor([1, 2, 3])
# 声明为参数
param = torch.ones(3, requires_grad=True)
print(c1.requires_grad, c2.requires_grad, param.requires_grad) # False, False, True
复制代码

而对是否为参量的修改也可以在定义完成后,通过 requires_grad_() 来修改:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

# 常量 -> 参数
c2.requires_grad_(True)
# 参数 -> 常量
param.requires_grad_(False)
print(c2.requires_grad, param.requires_grad) # True, False
复制代码

grad & grad_fn

这两个东西按照字面意思就可以理解,grad 是这个参数的梯度值,是一个 Tensor,而 grad_fn 是这个参数的梯度函数,存储了这个参数经历的上一步运算,这和后面要讲到的反向传播树形图有关。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

Pytorch 梯度计算工具包:autograd

梯度反向传播其实就是链式法则在固定点处的计算友好版。不需要算表达式,只要数值就行,具体原理不细说了。在 Pytorch 中,这一过程是由 autograd 包自动进行的。这里拿 Pytorch 官方教程 来说明一下反向传播过程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

import torch

x = torch.ones(5)
y = torch.zeros(3)
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)
复制代码

这里,x 是输入,z 是输出,y 是标签,待优化的参数则是 W 和 b。最终使用 CE (Cross Entropy 交叉熵)来计算 loss。其实就是一个简单的,不包含激活函数的线性层:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

loss=CE(y,z);z=Wx+bloss = CE(y, z); \quad z = Wx+b

在这里例子中,w.requires_gradb.requires_grad 被设为 True,其余变量的相关属性都是 False。在最开始,w.gradb.grad 被初始化为 None。那么现在,我想计算 loss 相对于这两个参数的梯度 ∂loss∂w\frac{\partial loss}{\partial w}∂loss∂b\frac{\partial loss}{\partial b}. 这个过程在实现起来很简单,只需要文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

loss.backward()
复制代码

就直接将算好的梯度写入 .grad 里面了。后面就可以用文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

print(W.grad)
print(b.grad)
复制代码

把计算好的梯度打印出来了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

但问题是, backward() 究竟做了什么呢?为了回答这个问题,我们需要从前向过程开始讲起。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

前向过程:构建计算图

实际上,在计算梯度之前的从变量计算 loss 过程中, Pytorch 会事先将变量之间的关系存储为图的形式,即计算图(Computational Graph)。上面例子中的计算图如下文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

Pytorch 是如何进行梯度计算的?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

这个图是一个有向无环图,所有的出发点被称为叶节点(入度为0,图中 x, W, b, y),而终点被称为根节点(出度为0,图中 loss)。没错,这是一个树形结构文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

反向过程:梯度反向传播

与前向计算类似,反向传播的过程中同样存在一个图结构。这个图大体上和计算图类似,不同的是里面仅包含了根节点到声明为参数的叶节点之间的路径:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

Pytorch 是如何进行梯度计算的?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

这个图不难理解,按照链式法则,我们在计算梯度时会列出下面的式子(注意这里只是简单表示梯度的传播关系,实际的计算式比这个多了转置,调了顺序,但还是线性的):文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

∂loss∂W=∂loss∂z⋅∂z∂Wx⋅∂Wx∂W;∂loss∂b=∂loss∂z⋅∂z∂b\frac{\partial loss}{\partial W} = \frac{\partial loss}{\partial z}\cdot \frac{\partial z}{\partial Wx}\cdot \frac{\partial Wx}{\partial W};\quad \frac{\partial loss}{\partial b} = \frac{\partial loss}{\partial z}\cdot \frac{\partial z}{\partial b}

这个式子里每一个梯度都可以和上图中的反向传播函数对应上。而反向传播函数正如前面所说,是存储在 grad_fn 里面的。我们可以将它们打印出来:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

print(f"Gradient function for z = {z.grad_fn}") # BinaryCrossEntropyLossWithLogitsBackward0
print(f"Gradient function for loss = {loss.grad_fn}") # AddBackward0
复制代码

要注意的是,Wx 对应的节点是隐藏的,如果用一个变量去代替它,也可以将其反向传播函数打印出来,即乘法运算对应的 MvBackward0文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

有了这样一张图,梯度就可以从最末端的 loss 逐步传递到每个参数,用于后续的优化工作。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

作者:零度蛋花粥
来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/yunda/28217.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/yunda/28217.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定