问题提出
计算导数是训练深度学习网络过程中优化参数的关键步骤,现代深度学习框架都提供了自动微分工具(autograd),来避免手动计算过程中可能会出现的问题。
当通过每个连续的函数传递数据时,框架会构建一个计算图来跟踪每个值如何依赖于其他值。为了计算导数,自动微分通过应用链式法则通过该图向后工作。 以这种方式应用链式法则的计算算法称为反向传播(backpropagation)。
理解自动微分的过程是后续学习优化算法优化参数过程的基础,因此记录以下笔记,结合简单的实例和背后的高数原理来理解微分过程。
实例
接下来的微分计算都基于torch
包。
import torch
首先我们定义一个矢量xxx,它包含4个变量,数值分别是0、1、2、3。这里定义y=x2y=x^2y=x2。
x = torch.arange(4.0, requires_grad = True)
y = x * x
此时查看x和y的对应值,得到如下结果:
>>> x
tensor([0., 1., 2., 3.], requires_grad=True)
>>> y
tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
这一步中,我们假设张量x中的4个元素分别为x1、x2、x3、x4,对应的yyy的4个元素分别为y1、y2、y3、y4,那么y的每个元素就可以表示为:
y1=x1⋅x1y2=x2⋅x2y3=x3⋅x3y4=x4⋅x4
到这一步,还不能对y直接进行反向传播操作。y是一个非标量张量,我们只定义了xi→yi的映射关系,现在y并不是一个单个值。只有当y是一个标量时,才能进一步执行梯度计算。
举例说明:不如假设xi的含义为第i个正方形的边长,那么yi就是第i个正方形的面积,y此时是包含了4个正方形面积值的张量。没有x→y的映射定义,我们无法继续计算梯度。
>>> y.backward()
Traceback (most recent call last):
...
...
RuntimeError: grad can be implicitly created only for scalar outputs
下一步,我们以常用的mean
和max
函数为例,解释微分过程。
Mean
调用mean()
方法后,才能获得最终的输出标量值y。以刚才的假设为例,那么这里要预测的y的意义就是求4个正方形的平均面积:
y=(y1+y2+y3+y4)/4=(x12+x22+x32+x42)/4
现在根据链式法则,我们可以对y相对于x中的每一个元素求偏导:
∂x1∂y=∂y1∂y⋅∂x1∂y1=41⋅2x1=21x1∂x2∂y=∂y2∂y⋅∂x2∂y2=41⋅2x2=21x2∂x3∂y=∂y3∂y⋅∂x3∂y3=41⋅2x3=21x3∂x4∂y=∂y4∂y⋅∂x4∂y4=41⋅2x4=21x4
将之前声明的x元素值带入,就能获得对应的x梯度。
>>> y.mean().backward()
>>> x.grad
tensor([0.0000, 0.5000, 1.0000, 1.5000])
Max
同理,调用max()
方法后,我们获得了一个最终输出标量y。还是以刚才的假设为例,这里要预测的y的意义变成了求4个正方形中的面积最大值。而与刚才的mean()
方法不同,并不是所有的变量都参与了最终y的计算。由于我们要计算最大值,需要知道最大值所在的位置,因此在计算最大值的同时也会计算相应的索引。这个索引值会保留在 PyTorch 的计算图中,以便在反向传播时计算梯度。
max()
操作中由于涉及到索引操作,即找到最大值所在的位置。在反向传播时,只有最大值所在位置对应的元素会接收到非零梯度,而其他元素的梯度会为零。这个结果反映了在最大值操作中只有最大值位置的元素对损失函数有影响,而其他元素对损失函数的贡献为零。
因此,计算如下:
y=max(y1, y2, y3, y4)=y4
于是偏导计算如下所示:
∂x1∂y=∂y1∂y⋅∂x1∂y1=0⋅2x1=0∂x2∂y=∂y2∂y⋅∂x2∂y2=0⋅2x2=0∂x3∂y=∂y3∂y⋅∂x3∂y3=0⋅2x3=0∂x4∂y=∂y4∂y⋅∂x4∂y4=1⋅2x4=2x4
最终我们获得x梯度:
>>> y.max().backward()
>>> x.grad
tensor([0., 0., 0., 6.])