MXNet官方文档教程(5):CPU&GPU多维数组

NDArray教程

MXNet中一个主要的对象就是mxnet.ndarray(缩写mxnet.nd)中的多维数组。如果你对python的科学计算包Numpy熟悉的话,你会发现mxnet.ndarray与numpy.ndarray在诸多方面十分相似。源网址:CPU/GPU Array Manipulation

 

基础

多维数组是一组同类型数据的集合,例如一个3D空间中的点的坐标值[1, 2, 3]就是一个长度为3的一维数组。而一个二维数组如下所示,其第一纬度的长度为2,第二纬度的长度为3:

[ [0, 1, 2]

  [ 3, 4, 5] ]

数组类的名称为NDArray。NDArray对象常用的属性有:

l ndarray.shape:数组的纬度。是一个表明每个纬度的长度的整数元组。对于一个n行m列的矩阵,其shape值为(n, m)。

l ndarry.dtype:一个用于描述元素类型的numpy对象。

l ndarry.size:数组中的元素总个数,与shape中元素的乘积相等。

l ndarray.context:用来表明数组储存的设备。设备可以是CPU或第i个GPU。

 

建立数组

可以通过多种途径建立数组。例如,我们可以通过array函数由一般的Python列表或者元组建立数组:

import mxnet as mx

# create a1-dimensional array with a python list

a = mx.nd.array([1,2,3])

# create a2-dimensional array with a nested python list

b = mx.nd.array([[1,2,3], [2,3,4]])

{'a.shape':a.shape, 'b.shape':b.shape}

输出:

{'a.shape': (3L,), 'b.shape': (2L, 3L)}

或者由一个numpy.ndarray对象建立:

import numpy as np
import math
c= np.arange(15).reshape(3,5)
# create a 2-dimensional array from a numpy.ndarray object
a= mx.nd.array(c)
{'a.shape':a.shape}

输出:

{'a.shape': (3L, 5L)}

我们可以通过dtype选项来指定元素的类型(支持numpy类型),元素类型默认为float32。

# float32 is used in deafult
a= mx.nd.array([1,2,3])
# create an int32 array
b= mx.nd.array([1,2,3], dtype=np.int32)
# create a 16-bit float array
c= mx.nd.array([1.2,2.3], dtype=np.float16)
(a.dtype, b.dtype, c.dtype)

输出:

(numpy.float32, numpy.int32, numpy.float16)

如果我们仅仅知道数组的大小而还不确定元素的具体值。MXNet还提供了多种通过初始化占位符来创建数组的函数。

# create a 2-dimensional array full of zeros with shape (2,3) 
a= mx.nd.zeros((2,3))
# create a same shape array full of ones
b= mx.nd.ones((2,3))
# create a same shape array with all elements set to 7
c= mx.nd.full((2,3),7)
# create a same shape whose initial content is random and 
# depends on the state of the memory
d= mx.nd.empty((2,3))

 

打印数组

为了打印数组,我们通常首先用asnumpy函数将NDArray转换为numpy.ndarray。Numpy使用如下布局:

l 最后一个坐标轴从左至右打印;

l 第二至最后一个坐标轴从上至下打印;

l 其余的也从上至下打印,每一个切片和下一个用空行隔开

a= mx.nd.ones((2,3))
a.asnumpy()
b= mx.nd.ones((3,3,3))
b.asnumpy()
 

输出

array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]], dtype=float32)
 
array([[[ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.]],
 
       [[ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.]],
 
       [[ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.]]], dtype=float32)

 

基础操作

数组的算术运算符作用于两个数组依次对应的元素。结果被写入到一个新创建的数组。

a = mx.nd.ones((2,3))

b = mx.nd.ones((2,3))

# elementwise plus

c = a + b

# elementwise minus

d =- c

# elementwise powand sin, and then transpose

e = mx.nd.sin(c**2).T

# elementwise max

f = mx.nd.maximum(a,c) 

f.asnumpy()

输出

array([[ 2.,  2., 2.],

       [ 2., 2.,  2.]], dtype=float32)

与Numpy相似,*用于矩阵对应元素乘法,而矩阵乘法则用dot。

a = mx.nd.ones((2,2))

b = a * a

c = mx.nd.dot(a,a)

{'b':b.asnumpy(), 'c':c.asnumpy()}

输出

{'b': array([[1.,  1.],

        [ 1., 1.]], dtype=float32), 'c': array([[ 2., 2.],

        [ 2., 2.]], dtype=float32)}

赋值运算符,例如+=和*=,用于将结果写入一个已存在数组而不是新建一个数组。

a = mx.nd.ones((2,2))

b = mx.nd.ones(a.shape)

b += a

b.asnumpy()

输出

array([[ 2.,  2.],

       [ 2., 2.]], dtype=float32)

 

索引与切片

切片操作符 [ ] 从0维开始

a = mx.nd.array(np.arange(6).reshape(3,2))

a[1:2] =1

a[:].asnumpy()

输出

array([[ 0.,  1.],

       [ 1., 1.],

       [ 4., 5.]], dtype=float32)

我们也可以通过slice_axis方法来获得特定维的切片。

d = mx.nd.slice_axis(a,axis=1, begin=1, end=2)

d.asnumpy()

输出

array([[ 1.],

       [ 1.],

       [ 5.]], dtype=float32)

 

形状修改

数组的形状可以改变,但要保证数组的大小保持不变。

a = mx.nd.array(np.arange(24))

b = a.reshape((2,3,4))

b.asnumpy()

输出

array([[[  0.,  1.,   2.,   3.],

        [ 4.,   5.,   6.,  7.],

        [ 8.,   9.,  10., 11.]],

 

       [[ 12., 13.,  14.,  15.],

        [ 16., 17.,  18.,  19.],

        [ 20., 21.,  22.,  23.]]], dtype=float32)

concatenate方法将多个数组堆叠在第一维(数组的形状必须一致)。

a = mx.nd.ones((2,3))

b = mx.nd.ones((2,3))*2

c = mx.nd.concatenate([a,b])

c.asnumpy()

输出

array([[ 1.,  1., 1.],

       [ 1., 1.,  1.],

       [ 2., 2.,  2.],

       [ 2., 2.,  2.]], dtype=float32)

 

分解

我们可以将一个数组分解为标量。

a = mx.nd.ones((2,3))

b = mx.nd.sum(a)

b.asnumpy()

输出

array([ 6.],dtype=float32)

或者对某一维分解

c = mx.nd.sum_axis(a, axis=1)

c.asnumpy()

输出:

array([ 3.,  3.], dtype=float32)

 

传播

我们也可以通过赋值传播数组。以下代码在第1维传播数组:

a = mx.nd.array(np.arange(6).reshape(6,1))

b = a.broadcast_to((6,2))  #

b.asnumpy()

输出

array([[ 0.,  0.],

       [ 1., 1.],

       [ 2., 2.],

       [ 3., 3.],

       [ 4., 4.],

       [ 5., 5.]], dtype=float32)

或者沿第1和2维传播:

c = a.reshape((2,1,1,3))

d = c.broadcast_to((2,2,2,3))

d.asnumpy()

输出

array([[[[ 0.,  1., 2.],

         [ 0., 1.,  2.]],

 

        [[ 0., 1.,  2.],

         [ 0., 1.,  2.]]],

 

 

       [[[ 3., 4.,  5.],

         [ 3., 4.,  5.]],

 

        [[ 3., 4.,  5.],

         [ 3., 4.,  5.]]]], dtype=float32)

传播应用于操作符,例如*和+。

a = mx.nd.ones((3,2))

b = mx.nd.ones((1,2))

c = a + b

c.asnumpy()

输出

array([[ 2.,  2.],

       [ 2., 2.],

       [ 2., 2.]], dtype=float32)

 

赋值

在一般赋值中,数据没有被复制。

a = mx.nd.ones((2,2))

b =

b is a

输出

True

函数的参数传递也是一样

deff(x): 

    return x

a is f(a)

输出

True

copy方法完成数组及其数据的深度拷贝

b = a.copy()

b is a

输出

False

上述代码申请了一个新的 NDArray并将其赋给b。我们可以使用copyto方法或者切片操作符 [] 来避免额外的存储空间的申请。

b = mx.nd.ones(a.shape)

c = b

c[:] = a

d = b

a.copyto(d)

(c is b, d is b)

输出

(True, True)

 

 

进阶

mxnet.ndarray还有一些高级的特性,让mxnet与其他库有所区别。

GPU支持

默认情况下,操作符在CPU上执行。MXNet可以很方便地切换到另一个计算资源,例如GPU(如果可行的话)。资源设备信息存储在ndarray.context。当MXNet以标志位USE_CUDA = 1编译且存在至少一个英伟达GPU显卡时,我们可以通过使用上下文mx.gpu(0)或者仅仅使用mx.gpu()来将所有的的计算运行于GPU 0。如果有两个以上GPU,第二个GPU由mx.gpu(1)表示。

deff():

    a = mx.nd.ones((100,100))

    b = mx.nd.ones((100,100))

    c = a + b

    print(c)

# in defaultmx.cpu() is used

f() 

# change thedefault context to the first GPU

with mx.Context(mx.gpu()): 

    f()

输出

我们也可以在创建数组时明确地指定上下文:

a = mx.nd.ones((100, 100), mx.gpu(0))

a

输出

通常MXNet的计算需要两个数组位于同一个设备。而有多种方法可以在不同设备之间拷贝数据:

a = mx.nd.ones((100,100), mx.cpu())

b = mx.nd.ones((100,100), mx.gpu())

c = mx.nd.ones((100,100), mx.gpu())

a.copyto(c)  # copy from CPU to GPU

d = b + c

e = b.as_in_context(c.context) + # same to above

{'d':d, 'e':e}

输出

{'d': , 'e': }

 

序列化从/至(分布式)文件系统

有两种简便的方法可以保存数据到(从读取数据)磁盘。第一种方法使用piickle.NDArray,与pickle模块兼容。

importpickleaspkl

a = mx.nd.ones((2, 3))

# pack and thendump into disk

data = pkl.dumps(a)

pkl.dump(data, open('tmp.pickle', 'wb'))

# load from diskand then unpack

data = pkl.load(open('tmp.pickle', 'rb'))

b = pkl.loads(data)

b.asnumpy()

输出

array([[ 1.,  1., 1.],

       [ 1., 1.,  1.]], dtype=float32)

第二种方法是通过save和load方法直接以二进制的格式存入磁盘。除了NDArray,我们也可以load/save列表(list):

a = mx.nd.ones((2,3))

b = mx.nd.ones((5,6))              

mx.nd.save("temp.ndarray", [a,b])

c = mx.nd.load("temp.ndarray")

c

输出

[, ]

或者词典(dict):

d = {'a':a, 'b':b}

mx.nd.save("temp.ndarray", d)

c = mx.nd.load("temp.ndarray")

c

输出

{'a': , 'b': }

load/save在两方面优于pickle模块:

1.使用Python接口保存的数据可以被用于其他的语言联编(Lanuage Binding)。例如我们可以在Python中保存数据:

a = mx.nd.ones((2, 3))

mx.save("temp.ndarray", [a,])

接着我们可以在R语言中读取它:

a <- mx.nd.load("temp.ndarray")

as.array(a[[1]])

##      [,1] [,2] [,3]

## [1,]    1   1    1

## [2,]    1   1    1

2.如果建立了分布式文件系统例如Amazon S3或Hadoop HDFS,我们可以直接在其load和save。

mx.nd.save('s3://mybucket/mydata.ndarray', [a,])  # if compiled with USE_S3=1

mx.nd.save('hdfs///users/myname/mydata.bin', [a,])  # if compiled with USE_HDFS=1

 

 

惰性计算与自动并行化

MXNet使用惰性计算以达到更好的性能。当我们在Python中运行a=b+1时,Python线程仅仅就是将操作符送入后端引擎然后返回。这种优化有两点好处:

1.一旦前一个操作符已经送出,python主线程可以继续执行其他计算。这对于有着巨大开销的前端语言十分有用。

2.使得后端引擎更加容易地探索深度优化,例如我们马上要讨论的自动并行化。

 

后端引擎能分解数据的相关性并保持计算的正确性。这些都对前端使用者透明。我们可以明确地使用结果数组的wait_to_read方法来等待计算完成。从数组复制数据到其他包(例如Numpy)的操作会隐式调用wait_to_read。

# @@@AUTOTEST_OUTPUT_IGNORED_CELL

importtime

 

defdo(x, n):

    """pushcomputation into the backend engine"""

    return [mx.nd.dot(x,x) for i inrange(n)]

defwait(x):

    """waituntil 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 allcomputations are pushed into the backend engine:\n %f sec'% (time.time() - tic))

wait(b)

print('time for allcomputations are finished:\n %f sec'% (time.time() - tic))

输出

time for allcomputations are pushed into the backend engine:

 0.001089 sec

time for allcomputations are finished:

 5.398588 sec

除了分析数据读写的相关性,后端引擎能将非相关的计算并行。例如如下代码:

a = mx.nd.ones((2,3))

b = a +1

c = a +2

d = b * c

第二和第三条语句可以并行执行。以下示例首先运行于CPU然后运行与GPU:

# @@@AUTOTEST_OUTPUT_IGNORED_CELL

n =10

a = mx.nd.ones((1000,1000))

b = mx.nd.ones((6000,6000), mx.gpu())

tic = time.time()

c = do(a, n)

wait(c)

print('Time to finishthe CPU workload: %f sec'% (time.time() - tic))

d = do(b, n)

wait(d)

print('Time to finishboth CPU/CPU workloads: %f sec'% (time.time() - tic))

输出:

Time to finish theCPU workload: 1.089354 sec

Time to finish bothCPU/CPU workloads: 2.663608 sec

现在我们同时下放所有工作量。后端引擎会尽量并行CPU和GPU计算。

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL

tic = time.time()

c = do(a, n)

d = do(b, n)

wait(c)

wait(d)

print('Both asfinished in: %f sec'% (time.time() - tic))

输出

Both as finished in: 1.543902 sec

 

 

目前情况

我们尽量保持NDArray API与numpy一致。但是还未完全与numpy兼容。在此,我们总结了一些我们期望在近期修改的主要区别。我们同样也欢迎任何贡献。

l 切片与索引

Ø  NDArray每次只能在一个维度上切片,也就是说我们不能使用x[:,1]来切片两个维度。

Ø  只支持连续的索引值,我们不能执行x[1:2:3]操作。

Ø  不支持布尔索引,例如x[y==1]。

l 缺少分解函数例如max,min…

你可能感兴趣的:(mxnet)