【PyTorch入门】一篇弄懂Autograd(自动微分)【全网最详细】
【PyTorch入门】一篇弄懂Autograd(自动微分)【全网最详细】
自动微分(Autograd)是PyTorch进行神经网络优化的核心技术。它能够自动计算出复杂的计算图中每个变量的梯度,从而帮助我们优化模型参数。本文将从基本概念出发,通过具体的数学示例和代码演示,深入浅出地讲解自动微分的工作原理、前向传播和反向传播的过程,以及在非标量输出情况下的处理方法。
1. 前言
在前一篇《PyTorch入门》系列中,我们讲解了Tensor(张量)这个对象。Tensor在PyTorch中就像Array在numPy中,两者都是最核心最底层的部分。但是Tensor不同于Array之处就在于Tensor可以使用GPU加速计算(通过x.cuda()
),同时Tensor能够实现自动微分(也就是我们这篇马上要讲解的Autograd)。
2. Autograd
Autograd中文叫作自动微分,是PyTorch进行神经网络优化的核心。自动微分,顾名思义就是让PyTorch自动为我们计算微分。
2.1 微分示例
我们先来看一个直观的例子。假如有一个向量:
将它作为输入。接着,将输人乘以4得到向量z,最后求出长度并输出一个标量y,值为5.6569。从向量输入x到标量输出y的完整计算过程如下图所示:
在深度学习中,我们想要根据想要的y值去修正对应的x值。因此就需要让y对x求出偏导数,根据这个偏导数去调整x的值,从而调整y的值。
下面从数学角度推导y关于x的微分。由下图不难得到y关于x的表达式:
其中,
、
,所以y关于x的微分为:
这个结果能用来对x值进行调整,从而让y的值更接近0(在深度学习中,y通常都是loss,我们想要让损失无限接近0)。如果一个输人需要经过比上面例子更多的计算步骤,那么靠人工去计算微分就变得力不从心。幸运的是,PyTorch的Autograd技术可以帮助我们自动求出这些微分值。
2.2 基本原理
我们可以将上面微分示例的计算过程抽象为图像,如下图所示,x、z和y被当作节点,运行过程被抽象为信息流。复杂的计算也可以被抽象成一张图(graph)。如下图所示,一张复杂的计算图可以分成4个部分:叶子节点、中间节点、输出节点和信息流。叶子节点是图的末端,没有信息流经过,在神经网络模型中就是输人值和神经网络的参数。
Tensor在自动微分方面有3个重要属性:requires_grad、grad和grad_fn。
- requires_grad属性是一个布尔值,默认为False。当requires_grad为True时,表示该Tensor需要自动微分。
- grad属性用于存储Tensor的微分值。
- grad_fn属性用于存储Tensor的微分函数。
当叶子节点的requires_grad为True时,信息流经过该节点时,所有中间节点的requires_grad属性都会变成True,只要在输出节点调用反向传播函数backward(),PyTorch就会自动求出叶子节点的微分值并更新存储在叶子节点的grad属性中。需要注意的是,只有叶子节点的grad属性能被更新。
怎么理解grad的值?
- grad反映的是这一个参数对y输出节点整体的影响大小和方向。
- grad结果能够为对应参数值的调整起指导作用。具体看梯度下降算法。
- 只有叶子节点的属性能被更新
2.3 前向传播
Autograd技术可以帮助我们从叶子节点开始追踪信息流,记下整个过程使用的函数,直到输出节点,这个过程被称为前向传播。本节先初始化叶子节点x:
默认情况下,Tensor的requires_grad属性为False。因为我们要让PyTorch自动帮我们计算x的微分值,所以将x的requires_grad属性设为True:
设置完成后,打印结果会显示requires_grad=True。此时x的 grad 属性和 grad_fn 属性均为空值:
接下来,我们让x乘以4得到z。可以看到,z的grad_fn为
grad_fn是微分函数,这里是乘法的反向函数。最后我们用norm()函数求z的长度得到y:
grad_fn:指示该张量是如何计算得来的。它记录了生成当前张量的操作,以及如何通过这些操作进行反向传播。例如上面是乘法得到的张量,因此在反向传播时依靠乘法法则。
可以发现,y的grad_fn是norm()的反向函数。
常见的反向传播函数grad_fn:
操作 | 对应的 grad_fn | 用途 |
---|---|---|
加法 | AddBackward | 计算加法操作的梯度 |
乘法 | MulBackward | 计算乘法操作的梯度 |
矩阵乘法 | MatmulBackward | 计算矩阵乘法操作的梯度 |
Sigmoid 激活 | SigmoidBackward | 计算 Sigmoid 激活函数的梯度 |
Tanh 激活 | TanhBackward | 计算 Tanh 激活函数的梯度 |
ReLU 激活 | ReLUBackward | 计算 ReLU 激活函数的梯度 |
批量归一化 | BatchNormBackward | 计算批量归一化操作的梯度 |
Softmax | SoftmaxBackward | 计算 Softmax 操作的梯度 |
view 操作 | ViewBackward | 计算 view 操作的梯度 |
reshape 操作 | ReshapeBackward | 计算 reshape 操作的梯度 |
拼接操作 | CatBackward | 计算拼接操作的梯度 |
扩展操作 | ExpandBackward | 计算 expand 操作的梯度 |
求和操作 | SumBackward | 计算求和操作的梯度 |
均值操作 | MeanBackward | 计算均值操作的梯度 |
对数操作 | LogBackward | 计算对数操作的梯度 |
幂运算 | PowBackward | 计算幂运算的梯度 |
范数计算 | NormBackward | 计算范数操作的梯度 |
最大值操作 | AmaxBackward | 计算最大值操作的梯度 |
2.4 反向传播
接下来,调用输出节点的backward()函数对整个图进行反向传播,求出微分值:
同时,z.grad和y.grad结果没有发生变化,因为他们都不是叶子节点。
- 反向传播(Backpropagation)
反向传播是计算神经网络中每个参数(如权重和偏置)的梯度的一种方法。它利用链式法则,通过计算损失函数相对于每个参数的导数,逐层传播误差信号来调整参数。
- grad(梯度)
梯度(gradient)是损失函数对于模型参数(如权重和偏置)的导数。在反向传播过程中,计算梯度的过程就是通过链式法则来获得每个参数的梯度。
2.5 非标量输出
在以上讨论的例子中,输出节点是一个标量。当输出节点为非标量时,使用backward()函数就需要增加一个参数gradient。gradient的形状应该与输出节点的形状保持一致且元素值均为1。尽管在深度神经网络中很少碰到这种情况,我们还是利用下面的例子简单了解一下其操作方式。初始化矩阵X和向量z,并将矩阵与向量相乘,得到形状为2x1的向量:
特别地:
此时,调用y的backward()函数时,需要传入一个形状与y的形状相同且元素全为1的向量:
这时我们看到,x有grad结果但是z没有存储grad结果。于是我就想利用z.requires_grad=True去调整,然后重新backward求导。结果如下:
原因如下:
如果你在同一个计算图上再次调用 backward(),PyTorch 会报错是因为它无法再访问已经被释放的中间张量。如果计算图已经被释放,再次调用 backward()就相当于试图访问已经消失的数据。而且,PyTorch 默认不会重新计算前向传播,因为:
- 计算图和中间张量已经被释放,重新计算会涉及到重新创建和计算这些中间值,这会增加计算和内存的开销。
- 如果需要多次反向传播或者想要访问中间值,通常我们会手动保留计算图(通过 retain_graph=True),否则就假定反向传播只会执行一次,且计算图不再需要。
结果发现,z.grad仍然没有结果:
仔细思考后发现,z本质上并没有参与运算,不在计算图中。计算图中只有x和y,z是x、y直接的信息流,类似于下图中的 * 4:
3. 总结
本文详细介绍了PyTorch中自动微分(Autograd)的工作原理和使用方法。通过具体的数学示例和代码演示,帮助读者理解自动微分在神经网络优化中的重要作用。希望本文能够帮助读者更好地掌握PyTorch的自动微分机制,为深度学习实践打下坚实的基础。