深度学习框架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,敬请期待。
上一篇: 听说你用 java new 了一个女朋友
下一篇: Java——有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300}
推荐阅读
-
MXNet设计笔记:深度学习的编程模式比较
-
TensorFlow实战Google深度学习框架-人工智能教程-自学人工智能的第二天-深度学习
-
带你学习AOP框架之Aspect.Core[1]
-
hibernate框架学习笔记1:搭建与测试
-
spring框架学习笔记1:搭建测试
-
深度学习框架Caffe解析:从代码层面定制新功能
-
浪潮在美国发布深度学习计算框架Caffe-MPI
-
TensorFlow与主流深度学习框架对比 TensorFlowTensorFlow实战
-
TensorFlow与主流深度学习框架对比 TensorFlowTensorFlow实战
-
计算机视觉中的深度学习10: 神经网络的训练1