NDArray.ipynbhtml
机器学习处理的对象是数据,数据通常是由外部传感器(sensors)采集,通过数字化后存储在计算机中,多是文本、声音,图片、视频等不一样形式。
这些数字化的数据最终会加载到内存进行各类清洗,运算操做。
几乎全部的机器学习算法都涉及到对数据的各类数学运算,好比:加减、点乘、矩阵乘等。因此咱们须要一个易用的、高效的、功能强大的工具来处理这些数据并组支持各类复杂的数学运算。前端
在C/C++中已经开发出来了不少高效的针对于向量、矩阵的运算库,好比:OpenBLAS,Altlas,MKL等。python
对于Python来讲Numpy无疑是一个强大针对数据科学的工具包,它提供了一个强大的高维数据的数组表示,以及支持Broadcasting的运算,并提供了线性代数、傅立叶变换、随机数等功能强大的函数。git
MXNet的NDArray与Numpy中的ndarray极为类似,NDAarray为MXNet中的各类数学计算提供了核心的数据结构,NDArray表示一个多维的、固定大小的数组,而且支持异构计算。那为何不直接使用Numpy呢?MXNet的NDArray提供额外提供了两个好处:github
每一个NDarray都具备如下重要的属性,咱们能够经过相应的api来访问:算法
ndarray.shape
:数组的维度。它返回了一个整数的元组,元组的长度等于数组的维数,元组的每一个元素对应了数组在该维度上的长度。好比对于一个n行m列的矩阵,那么它的形状就是(n,m)。ndarray.dtype
:数组中全部元素的类型,它返回的是一个numpy.dtype的类型,它能够是int32/float32/float64
等,默认是'float32'的。ndarray.size
:数组中元素的个数,它等于ndarray.shape
的全部元素的乘积。ndarray.context
:数组的存储设备,好比:cpu()
或gpu(1)
import mxnet as mx import mxnet.ndarray as nd a = nd.ones(shape=(2,3),dtype='int32',ctx=mx.gpu(1)) print(a.shape, a.dtype, a.size, a.context)
通常来常见有2种方法来建立NDarray数组:apache
ndarray.array
直接将一个list或numpy.ndarray转换为一个NDArrayzeros
,ones
以及一些随机数模块ndarray.random
建立NDArray,并预填充了一些数据。import numpy as np l = [[1,2],[3,4]] print(nd.array(l)) # 从List转到NDArray print(nd.array(np.array(l))) # 从np.array转到NDArray # 直接利用函数建立指定大小的NDArray print (nd.zeros((3,4), dtype='float32')) print (nd.ones((3,4), ctx=mx.gpu())) # 从一个正态分布的随机数引擎生成了一个指定大小的NDArray,咱们还能够指定分布的参数,好比均值,标准差等 print (nd.random.normal(shape=(3,4))) print (nd.arange(18).reshape(3,2,3))
通常状况下,咱们能够经过直接使用print来查看NDArray中的内容,咱们也可使用nd.asnumpy()
函数,将一个NDArray转换为一个numpy.ndarray来查看。后端
a = nd.random.normal(0, 2, shape=(3,3)) print(a) print(a.asnumpy())
NDArray之间能够进行加减乘除等一系列的数学运算,其中大部分的运算都是逐元素进行的。api
shape=(3,4) x = nd.ones(shape) y = nd.random_normal(0, 1, shape=shape) x + y # 逐元素相加 x * y # 逐元素相乘 nd.exp(y) # 每一个元素取指数 nd.sin(y**2).T # 对y逐元素求平方,而后求sin,最后对整个NDArray转置 nd.maximum(x,y) # x与y逐元素求最大值
这里须要注意的是*
运算是两个NDArray之间逐元素的乘法,要进行矩阵乘法,必须使用ndarray.dot
函数进行矩阵乘数组
nd.dot(x, y.T)
MXNet NDArray提供了各类截取的方法,其用法与Python中list的截取操做以及Numpy.ndarray中的截取操做基本一致。
x = nd.arange(0, 9).reshape((3,3)) x[1:3] # 截取x的axis=0的第1和第2行 x[1:2,1:3] # 截取x的axis=0的第1行,axis=1的第一行和第二行
在对NDArray进行算法运算时,每一个操做都会开辟新的内存来存储运算的结果。例如:若是咱们写y = x + y
,咱们会把y
从如今指向的实例转到新建立的实例上去。咱们能够把上面的运算当作两步:z = x + y; y = z
。
咱们可使用python的内置函数id()
来验证。id()
返回一个对象的标识符,当这个对象存在时,这个标识符必定是唯一的,在CPython中这个标识符实际上就是对象的地址。
x = nd.ones((3,4)) y = nd.ones((3,4)) before = id(y) y = x + y print(before, id(y))
在不少状况下,咱们但愿可以在原地对数组进行运算,那么咱们可使用下面的一些语句:
y += x print(id(y)) nd.elemwise_add(x, y, out=y) print(id(y)) y[:] = x + y print(id(y))
在NDArray中通常的赋值语句像y = x
,y实际上只是x的一个别名而已,x和y是共享一份数据存储空间的
x = nd.ones((2,2)) y = x print(id(x)) print(id(y))
若是咱们想获得一份x的真实拷贝,咱们可使用copy函数
y = x.copy() print(id(y))
广播是一种强有力的机制,可让不一样大小的NDArray在一块儿进行数学计算。咱们经常会有一个小的矩阵和一个大的矩阵,而后咱们会须要用小的矩阵对大的矩阵作一些计算。
举个例子,若是咱们想要把一个向量加到矩阵的每一行,咱们能够这样作
# 将v加到x的每一行中,并将结果存储在y中 x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) y = nd.zeros_like(x) # Create an empty matrix with the same shape as x for i in range(4): y[i, :] = x[i, :] + v print (y)
这样是行得通的,可是当x矩阵很是大,利用循环来计算就会变得很慢很慢。咱们能够换一种思路:
x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) vv = nd.tile(v, (4, 1)) # Stack 4 copies of v on top of each other y = x + vv # Add x and vv elementwise print (y) # 也能够经过broadcast_to来实现 vv = v.broadcast_to((4,3)) print(vv)
NDArray的广播机制使得咱们不用像上面那样先建立vv,能够直接进行运算
x = nd.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]]) v = nd.array([1, 0, 1]) y = x + v print(y)
对两个数组使用广播机制要遵照下列规则:
NDArray支持数组在GPU设备上运算,这是MXNet NDArray和Numpy的ndarray最大的不一样。默认状况下NDArray的全部操做都是在CPU上执行的,咱们能够经过ndarray.context来查询数组所在设备。在有GPU支持的环境上,咱们能够指定NDArray在gpu设备上。
gpu_device = mx.gpu(0) def f(): a = mx.nd.ones((100,100)) b = mx.nd.ones((100,100), ctx=mx.cpu()) c = a + b.as_in_context(a.context) print(c) f() # 在CPU上运算 # 在GPU上运算 with mx.Context(gpu_device): f()
上面语句中使用了with来构造了一个gpu环境的上下文,在上下文中的全部语句,若是没有显式的指定context,则会使用wtih语句指定的context。
当前版本的NDArray要求进行相互运算的数组的context必须一致。咱们可使用as_in_context
来进行NDArray context的切换。
有两种方法能够对NDArray对象进行序列化后保存在磁盘,第一种方法是使用pickle
,就像咱们序列化其余python对象同样。
import pickle a = nd.ones((2,3)) data = pickle.dumps(a) # 将NDArray直接序列化为内存中的bytes b = pickle.loads(data) # 从内存中的bytes反序列化为NDArray pickle.dump(a, open('tmp.pickle', 'wb')) # 将NDArray直接序列化为文件 b = pickle.load(open('tmp.pickle', 'rb')) # 从文件反序列化为NDArray
在NDArray模块中,提供了更优秀的接口用于数组与磁盘文件(分布式存储系统)之间进行数据转换
a = mx.nd.ones((2,3)) b = mx.nd.ones((5,6)) nd.save("temp.ndarray", [a, b]) # 写入与读取的路径支持Amzzon S3以及Hadoop HDFS等。 c = nd.load("temp.ndarray")
MXNet使用了惰性求值来追求最佳的性能。当咱们在Python中运行a = b + 1
时,Python线程只是将运算Push到了后端的执行引擎,而后就返回了。这样作有下面两个好处:
后端引擎必需要解决的问题就是数据依赖和合理的调度。但这些操做对于前端的用户来讲是彻底透明的。咱们可使用wait_to_read
来等侍后端对于NDArray操做的完成。在NDArray模块一类将数据拷贝到其余模块的操做,内部已经使用了wait_to_read,好比asnumpy()
。
import time def do(x, n): """push computation into the backend engine""" return [mx.nd.dot(x,x) for i in range(n)] def wait(x): """wait until all results are available""" for y in x: y.wait_to_read() tic = time.time() a = mx.nd.ones((1000,1000)) b = do(a, 50) print('time for all computations are pushed into the backend engine:\n %f sec' % (time.time() - tic)) wait(b) print('time for all computations are finished:\n %f sec' % (time.time() - tic))
除了分析数据的读写依赖外,后端的引擎还可以将没有彼此依赖的操做语句进行并行化调度。好比下面的代码第二行和第三行能够被并行的执行。
a = mx.nd.ones((2,3)) b = a + 1 c = a + 2 d = b * c
下面的代码演示了在不一样设备上并行调度
n = 10 a = mx.nd.ones((1000,1000)) b = mx.nd.ones((6000,6000), gpu_device) tic = time.time() c = do(a, n) wait(c) print('Time to finish the CPU workload: %f sec' % (time.time() - tic)) d = do(b, n) wait(d) print('Time to finish both CPU/GPU workloads: %f sec' % (time.time() - tic))
tic = time.time() c = do(a, n) d = do(b, n) #上面两条语句能够同时执行,一条在CPU上运算,一条在GPU上运算 wait(c) wait(d) print('Both as finished in: %f sec' % (time.time() - tic))