深度学习框架MXNet(2)--autograd
这一节,我们将介绍MXNet框架中的自动求导模块autograd。在深度学习算法中,经常需要计算的就是一个向量的梯度,但求梯度是一个手动编码比较麻烦的事情,并且求向量的梯度并不是算法思想的精髓部分,使用MXNet封装并设计好的autograd模块来计算梯度,不仅可以大大的减少编码的工作量,还能够让我们更好的将注意力集中在算法思想的实现上。
1.什么是梯度?
梯度是一个数学上的概念,它在机器学习中很常见。在数学中我们对二维空间中的一个点求导,就是求这个点在某一方向上的变化率。如函数y=f(x),求x在函数f上的变化率,就是求导dy/dx。如下图:
如上图,在函数曲线上,过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)。如下图:
同样,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。求导结果如下:
需要注意的一点是,求导结果z'(x)=6*x*x,运算x*x时,是点乘运算,而不是线性代数中矩阵间的叉乘运算。如下:
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()方法返回就是这个唯一元素的值,是一个常量。以上代码打印结果如下:
对控制流求导与普通的梯度求导过程唯一的区别是,一般需要定义一个方法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)中的代码,这里不再详述。代码的打印结果如下:
这一节介绍的内容不多,最近更新博客的速度明显没有三月频繁,主要是因为事太多,只能周末写博客,敬请见谅。下一节将介绍线性回归,敬请期待。
推荐阅读
-
[机器学习与深度学习] - No.2 遗传算法原理及简单实现
-
Spark与深度学习框架——H2O、deeplearning4j、SparkNet 框架算法脚本scalajava
-
Python的Flask框架中的Jinja2模板引擎学习教程
-
Symfony2框架学习笔记之HTTP Cache用法详解
-
Symfony2框架学习笔记之表单用法详解
-
Python的Flask框架中的Jinja2模板引擎学习教程
-
Java框架学习Struts2复选框实例代码
-
MXNet设计笔记:深度学习的编程模式比较
-
英特尔公布Nervana NNP-T深度学习训练加速器 16nm工艺、32GB HBM2
-
TensorFlow实战Google深度学习框架-人工智能教程-自学人工智能的第二天-深度学习