欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深度学习框架MXNet(2)--autograd

程序员文章站 2022-03-06 10:00:44
...

          这一节,我们将介绍MXNet框架中的自动求导模块autograd。在深度学习算法中,经常需要计算的就是一个向量的梯度,但求梯度是一个手动编码比较麻烦的事情,并且求向量的梯度并不是算法思想的精髓部分,使用MXNet封装并设计好的autograd模块来计算梯度,不仅可以大大的减少编码的工作量,还能够让我们更好的将注意力集中在算法思想的实现上。

1.什么是梯度?

       梯度是一个数学上的概念,它在机器学习中很常见。在数学中我们对二维空间中的一个点求导,就是求这个点在某一方向上的变化率。如函数y=f(x),求x在函数f上的变化率,就是求导dy/dx。如下图:

       深度学习框架MXNet(2)--autograd

         如上图,在函数曲线上,过A点作与A的法线垂直的直线l,l标识的就是A点在函数曲线上的变化率(也做斜率)。斜率直线越陡,求得的函数曲线在A点的导数dy/dx越大,A点的变化率越大;斜率曲线越平,求得的函数曲线在A点的导数dy/dx越小,A点的变化率也就越小。

         推广到三维空间中,z=f(x,y),求点A的变化率,需要求点A在x,y方向的导数。即微积分中的求偏导数,(dz/dx,dz/dy)。如下图:

深度学习框架MXNet(2)--autograd

       同样,dz/dx越大说明,点A在x方向上的变化率越大,变化的越快,否则相反;dz/dy越大,点A在y方向上的变化率越大,变化的越快,否则相反。

       再次推广到多维空间,z=f(x1,x2,x3,....,xn),在多维空间中,求某一点A(x1,x2,x3,...,xn)的变化率,就是对点A中的每个变量求偏导数,即grad(f)=(dz/dx1,dz/dx2,dz/dx3,...,dz/dxn),grad(f)即点A的梯度方向。梯度方向grad(f)标识的是点A变化最快,变化率最大的方向。在深度学习中,使用优化算法不断减小模型损失的过程中,需要不断的更新模型的参数向量,这时就需要计算参数向量的导数,即梯度。

       以上我们只是从感性上认识梯度,这一节我们主要介绍的是使用MXNet框架中的autograd模块进行梯度的计算。

2.梯度求导

       MXNet中的autograd模块提供了梯度求导的函数,调用其中封装好的函数,我们可以避开数学细节,快速的求得向量的梯度。如下代码:

       

from mxnet import ndarray as nd
import mxnet.autograd as ag # 导入autograd模块

x=nd.array([[1,2],[3,4]])#定义需要求导的变量
x.attach_grad()#申请求导需要的空间,空间用于存放x的导数
with ag.record():
    z=x*x*x*2#定义求导的函数z=2*x*x*x
z.backward()#函数z对x进行求导:dz/dx
print("x.grad:",x.grad)

       在使用autograd模块前,首先需要从MXNet中导入模块。求导的过程一般固定为以下几步:

       ① 首先定义待求导的变量。如上代码,通过NDArray模块,定义了一个2X2的向量矩阵x;

       ②为变量求导申请空间。如上,调用NDArray矩阵对象x的attach_grad()方法后,会为x申请一份用于存放求导结果的空间Buffer。对象x附带着导数空间Buffer,当对x求导成功后,便将求导结果存储在x的空间Buffer中;

       ③定义关于待求导变量的函数f。定义函数f,需要调用autograd模块的record()方法,这个方法返回的结果可以理解为就是我们定义的函数f,需要在with的声明下调用record()方法。如上,z=x*x*x*2,z其实就是我们定义的关于x的函数;

       ④求导。求导只需要函数对象f调用backward()方法,封装好的backward()函数就会自动对变量求导,并将求导结果存储在之前申请的Buffer空间中。如上,z.backward()即是对变量x求导。求导结果:z(x)=2*x^3,z'(x)=6*x^2。求导结果如下:

       深度学习框架MXNet(2)--autograd

       需要注意的一点是,求导结果z'(x)=6*x*x,运算x*x时,是点乘运算,而不是线性代数中矩阵间的叉乘运算。如下:

      深度学习框架MXNet(2)--autograd


3.对控制流求导

       当对自变量求导时,可能会根据自变量x的差异,会有不同的函数f。这个时候我们就可能需要定义一个方法function,function包含控制流for和if,循环迭代的次数和判断语句的执行都取决于输入的自变量x,不同的输入会返回不同的函数f。在正式介绍控制流求导前,我们首先介绍NDArray的相关方法。如下:

from mxnet import ndarray as nd
import mxnet.autograd as ag # 导入autograd模块

x=nd.array([[3,4]])
print(nd.sum(x))#将矩阵中每个元素相加
print(nd.norm(x))#L2范式
y=nd.array([5])
print(y.asscalar())#将只具有1个元素的数组转换为常量

       如上,我们定义了一个NDArray矩阵对象x,sum()方法返回矩阵中的每个元素相加的和;norm()方法返回矩阵的L2范式,即将矩阵中每个元素平方和相加后,开根号返回结果。

调用asscalar()方法的矩阵只有一个元素,asscalar()方法返回就是这个唯一元素的值,是一个常量。以上代码打印结果如下:

       深度学习框架MXNet(2)--autograd深度学习框架MXNet(2)--autograd深度学习框架MXNet(2)--autograd

       对控制流求导与普通的梯度求导过程唯一的区别是,一般需要定义一个方法function。function根据输入自变量x的不同,通过迭代和判断来选取关于x的函数。代码如下:

       

from mxnet import ndarray as nd
import mxnet.autograd as ag # 导入autograd模块
#控制流求导
def function(a):#根据自变量a,来选取函数f并返回
    b=a
    while nd.norm(b).asscalar()<1000:#判断条件为:矩阵b的L2范数<1000
        b=b*2
        if nd.sum(b).asscalar()>0:#判断条件为:矩阵b的L1范数>0
            c=b
        else:
            c=100*b
    return c
a=nd.array([3,4,5])
a.attach_grad()#申请变量a的求导空间
with ag.record():#定义函数f
    c=function(a)
c.backward()#对a求导
print("a.grad:",a.grad)#求导结果

       如上代码所示,自变量a为待求导的变量,自定义函数function(a)通过迭代和判断返回关于变量a的函数f,然后函数f对a进行求导,得出变量a的求导结果。

       本例介绍的重点是,通过控制流的条件判断来获取函数f并求导,至于function(a)是如何迭代和判断的,读者可以自行阅读function(a)中的代码,这里不再详述。代码的打印结果如下:

      深度学习框架MXNet(2)--autograd

            这一节介绍的内容不多,最近更新博客的速度明显没有三月频繁,主要是因为事太多,只能周末写博客,敬请见谅。下一节将介绍线性回归,敬请期待。