深度学习框架MXNet(1)--NDarray

           从本节开始,我们将开始介绍深度学习框架MXNet,之前断断续续写过一些有关Python的博客,但并没有写完,内容目前更新到了tkinter图形界面编程,有关Python需要学习的东西还有很多,如果有时间我还会持续更新下去,欢迎大家关注我的博客并提出宝贵建议。

      今天开始将更新有关MXNet方面的内容,由于个人能力所限,博客的质量可能有待改善;时间和精力有限,博客更新的速度可能也不是很快,敬请见谅。但我仍然会"全力以赴,做到最好"(出自《鹿鼎记》)。

      本系列博客所介绍的内容主要包含以下几个模块:监督学习,Gluno基础,卷积神经网络(CNN),循环神经网络(RNN),优化算法,Gluno高级,计算机视觉等等。在每学习完一个模块后,会有一个案例帮助我们复习和理解整个模块的内容,每个模块中涉及代码将使用MXNet框架实现。

1.MXNet简介

       维基百科上是这样介绍MXNet框架的, MXNet 是一种现代开源深度学习框架,可以用来训练和部署深层神经网络。它是可扩展的,可以快速的训练出一个模型,并且支持自由的编程模型和多种编程语言,如Python,C++,Julia,Matlab,JavaScript,Go,R,Scala,Perl等编程语言。
       MXNet是可移植的,能够适配在不同机型的GPU上运行。MXNet由AWS和Azure两大全球云提供商支持。亚马逊已经在AWS上选择MXNet作为它的深度学习框架。目前,MXNet是由Intel,百度,微软,拿督,Wolfram Research以及一些研究机构支持,如卡耐基-梅隆大学,麻省理工学院,华盛顿大学和香港科技大学。

2.NDArray

       高效的处理数据是深度学习的开头,主要包含两个方面,(i)读取数据;(ii)处理内存中的数据。而NDArray是MXNet处理数据的核心,它与Numpy十分的类似。但NDArray能够提供更多的功能,如CPU和GPU的异步计算和自动求导功能。本节我们主要介绍下NDArray的以下方面:

       (i)基本矩阵

       (ii)运算符

       (iii)广播(Broadcasting)

       (iv)与Numpy的转换

       (v)替换操作

       (vi)截取(Slicing)

(i)基本矩阵

       NDArray提供了有关矩阵操作的各种方法,首先我们来介绍下如何生成一个矩阵。代码如下:

       

from mxnet import ndarray as nd #导入mxnet的ndarray模块
print(nd.zeros((3,4)))#初始化一个元素全部为0的矩阵
print(nd.ones((3,4)))#初始化一个元素全部为1的矩阵
print(nd.array([[1,2],[3,4]]))#根据Python数组初始化一个矩阵

       在使用NDArray模块前需要先从mxnet中导入。

       ndarray模块中的方法zeros()用于生成一个元素全部为0的矩阵,传入的参数为一个序列(length,width),length用于指定矩阵的行数,width用于指定矩阵的列数。如(3,4)生成的是一个3x4,元素全部为0的矩阵。

       方法ones()与zeros()的不同之处在于,调用ones()方法生成矩阵中每个元素值都为1,仅此而已。

       调用方法array()可以使用Python数组来初始化一个矩阵,其参数是一个二维的序列。如nd.array([[1,2],[3,4]])生成的是一个2x2的矩阵,每一行分别为[1,2],[3,4]。打印结果如下:

       

       

from mxnet import ndarray as nd
x=nd.random_normal(0,1,(3,4))
print(x.shape)#矩阵的形状,返回的是一个元组(行数,列数)
print(x.size)#返回矩阵的元素个数

             ndarray中的方法random_normal()用于生成一个随机矩阵,即矩阵中的每个元素都是随机数,允许指定矩阵的均值,方差。如上,nd.random_normal(0,1,(3,4)),用于生成一个3x4的随机矩阵,矩阵的均值为0,方差为1。

      使用ndarray模块生成的矩阵对象,属性shape表示矩阵形状,属性值是一个元组(行数,列数);属性size表示矩阵对象中含有元素的个数,如一个3x4的矩阵含有12个元素。

      代码打印结果如下:

      

(ii)运算符

        数学中的数字,可以通过运算符实现相互间的运算操作。NDArray中的矩阵对象,也可以如此。ndarray矩阵对象间可实现矩阵相加,点乘,叉乘,指数运算以及矩阵的转置等操作。代码如下:

       

from mxnet import ndarray as nd
x=nd.array([[3,4,5],
            [2,7,4],
            [3,0,1]])
y=nd.array([[1,3,7],
            [5,1,3],
            [0,5,2]])
print("x+y:")
print(x+y)#矩阵加法
print("x*y:")
print(x*y)#矩阵点乘
print("y的指数运算:")
print(nd.exp(y))#矩阵的指数运算
print("xXy:")
print(nd.dot(x,y))#矩阵的叉乘运算
print("y的转置:")
print(y.T)

       如上,x和y两个矩阵通过运算符进行了矩阵的加法,点乘,指数运算,叉乘以及矩阵的转置操作。具体实现过程如下:

      x + y :

      

     x * y :

                    

     nd.exp(y) :

                        

     nd.dot(x,y) :

  

     y.T :

     

      代码打印结果如下:

      

      

 (iii)广播(Broadcasting)

        广播即是通过复制来扩展矩阵的维数,如两个NDArray矩阵进行运算操作时,矩阵的维数的差异导致两个矩阵并不能实现运算,这时NDArray会尝试扩展两个矩阵的维数,来使得两者能够进行运算操作。如下案例:

        

from mxnet import ndarray as nd
a = nd.arange(3).reshape((3,1))
b = nd.arange(2).reshape((1,2))
print('a:', a)
print('b:', b)
print('a+b:', a+b)

        ndarray模块的方法arange()用于生成一个序列,如nd.arange(num)生成一个[0,1,2,...,num-1]的序列;reshape((length,width))用于修改一个ndarray矩阵的形状,如reshape((3,1))为将矩阵改为3x1的矩阵。矩阵形状修改成功的前提是,修改前矩阵中元素的数量与length*width的数值是一致的。

      如上代码,nd.arange(3)生成一个[0,1,2]的序列,相当于1x3的矩阵。nd.arange(3).reshape((3,1)),就是将1x3的矩阵修改为3x1矩阵。

      如上,a为3x1的矩阵,b为1x2的矩阵,若要实现两个矩阵的相加。就需要通过广播扩维实现两个矩阵维数相同,这就是广播。

       原有基础上的矩阵a和b,因为形状的不同,并不能相加。这时,需要通过复制扩维来使得a和b具有相同维数。

       对矩阵a扩维后,结果如下:

       

       对矩阵b扩维后,结果如下:

       

       通过广播扩维后的矩阵a和b,都是形状3x2的矩阵,a+b操作可以执行。打印结果如下:

       

(iv)与numpy的转换

      尽管NDArray与Numpy很类似,但有时我们在处理NDArray矩阵时,可能需要调用Numpy中有而NDArray中没有的方法,或者使用Numpy更加方便的时候,这种情况下,就需要先把NDArray矩阵转换成Numpy矩阵,再使用Numpy中的方法处理转化后的矩阵。之后,可能还需要把Numpy矩阵转换成NDArray矩阵。这样,就用到了Numpy矩阵和NDArray矩阵的相互转换。代码如下:

       

from mxnet import ndarray as nd
import numpy as np
x=np.ones((3,4))# x为Numpy矩阵
print("x:",x)
y=nd.array(x)# Numpy矩阵-->NDArray矩阵
print("y:",y)
z=y.asnumpy()# NDArray矩阵-->Numpy矩阵
print("z:",z)

      如上代码,np.ones((3,4))生成一个形状为3x4,元素全部为1的Numpy矩阵x;

      nd.array(x)将Numpy矩阵转换为NDArray矩阵y;

      z=y.asnumpy()将NDArray矩阵y转换成Numpy矩阵z。

      打印结果如下:

       


(v)替换操作

        通过了解NDArray中的替换操作,可以让我们明白NDArray中内存中存储和搬运数据的机制,可以最大化的节省内存。下面我们通过几个例子来层层递进的了解这种机制。

        代码一:

         

from mxnet import ndarray as nd
x=nd.ones((3,4))
y=nd.ones((3,4))
print("before:")
copy_y=y
print("y:",id(y))
print("copy_y:",id(copy_y))
y=x+y
print("after:")
print("y:",id(y))
print("copy_y:",id(copy_y))

       如上代码,首先我们创建了两个NDArray矩阵对象x和y,copy_y为矩阵y的一个备份。打印结果如下:

        

        由代码打印结果可知,copy_y=y后通过打印两者的id,发现id(copy_y)==id(y),这说明copy_y只是指向y对应的内存区域的一个索引,执行copy_y=y后并没有开辟出新的内存空间来存储copy_y的数据。

        执行y=x+y后。

        打印id(y)和id(copy_y)后,发现两者并不一致,copy_y仍然指向y原来的内存空间,并无变化.而y=x+y之后,id(y)发生了变化,这说明内存开辟出了新的空间存储新的数据y。

         代码二:

         

from mxnet import ndarray as nd
x=nd.ones((3,4))
y=nd.ones((3,4))
result=nd.zeros_like(x)
print("result_id:",id(result))
result[:]=x+y
print("after_x+y_result_id:",id(result))

        如上代码,我们通过NDArray的zeros_like()方法,创建了一个形状如矩阵x,元素全部为0的矩阵result,使用开辟好内存空间的数组result[:]来接收x+y的结果,代码打印结果如下:

         

         通过打印result的id可以发现执行result=x+y前后,id(result)是一致的;但x+y执行完成后,实际上NDArray还是将结果存在临时的内存空间中,然后再把结果从临时空间复制到result指向的内存空间中。那么如何避免这个临时空间的开销呢?有两种方法:

        方法一:

        

from mxnet import ndarray as nd
x=nd.ones((3,4))
y=nd.ones((3,4))
result=nd.zeros_like(x)
print("before_add:",id(result))
nd.elemwise_add(x,y,out=result)
print("after_add:",id(result))

        调用NDArray模块的方法elemwise_add(),指定参数out,意为将相加的结果直接复制到out对应参数的中,中间避免了临时空间的开销。如上,nd.elemwise_add(x,y,out=result)将x+y的值直接复制到result指向的空间中,没有中间的临时空间。打印结果如下:

        

        方法二:

        如果现有的数组不会复用,我们也可以使用 x[:] = x + y ,或者 x += y 达到这个目的:

from mxnet import ndarray as nd
x=nd.ones((3,4))
y=nd.ones((3,4))
print("before_add:",id(x))
x+=y
print("after_add:",id(x))

         打印结果如下:

         

       这种方式是将x+y的结果直接覆盖x原来的存储空间中的数据,这样做会导致x中原来的数据不可复用,一般不建议使用,除非程序中不再访问x中的数据。

(vi)截取(Slicing)

       截取就是取一个矩阵中某行或者某列,甚至某一个数据元素。下面我们通过例子来了解截取。如下代码:

from mxnet import ndarray as nd
x=nd.array([[1,2],[3,4],[5,6],[7,8]])
print("x:",x)
print("x[:,0]",x[:,0])
print("x[1:2]",x[1:2])
print("x[1,0]",x[1,0])
 
print("x[2:3,1:2]",x[2:3,1:2])

 
 

       如上,我们创建了一个4x2的矩阵x。通过中括号[]来访问x中的数据。[]中指定矩阵的行和列,被截取的行和列用","隔开。一般的形式为x[colStart:colEnd,rowStart:rowEnd],截取后行的取值范围为:colStart<=col<colEnd;列的取值范围为:rowStart<=row<rowEnd。

       如下打印结果:

       x:

       

       x[:,0]:在没有指定行范围的情况下,默认是截取矩阵的所有行;列只有起始值,没有终止值,那么会截取起始值在矩阵中对应的列。如x[:,0]截取的是矩阵x的所有行中的第1列。对应的打印结果如下:

       

       x[1:2]:指定截取行的范围为:1<=col<2;没有指定列的取值范围,默认会截取所有列。最终会截取第2行的所有列,打印结果如下:

      

      x[1,0]:不指定终止行和终止列,默认col=起始行,row=起始列。x[1,0]为截取第2行的第1列。打印结果如下:

       

       x[2:3,1:2]:行的取值范围为,2<=col<3;列的取值范围为:1<=row<2。即截取第3行的第2列。打印结果如下:

      

       还可以使用的ndarray模块的take(matrix,cols)来截取矩阵的某些行,参数matrix为待截取的矩阵,cols为一个整数序列矩阵,指定matrix的哪些行被截取,如果整数超过矩阵matrix的下标,将会默认截取下标最大的行。代码如下:

       

from mxnet import ndarray as nd
x=nd.array([[1,2],[3,4],[5,6],[7,8]])
i=nd.array([[1,7]])
print(nd.take(x,i))

       如上,x为待截取矩阵,矩阵i指定截取x中行的下标。本例中i=[[1,7]],为截取x的第2行和第8行,但x中并没有第8行,在超出x行下标的情况下,会截取x的最后一行,即最终会截取x的第2行和第4行。打印结果如下:

       

       这节写的挺多的,写的也比较匆忙的,写的不好,敬请见谅。下一节将会介绍MXNet中的自动求导autograd,敬请期待。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值