MXNet中的NDArray

  想要跑代码可以参考这里搭建环境,同时再查一下关于jupyter notebook的基本操作即可。本文是笔记以及一些自己的补充。


  在MXNet中,NDArray是一个类,也是存储和变换数据的主要工具,和NumPy的多维数组类似,提供GPU计算和自动求梯度等功能。


创建NDArray

  首先从MXNet导入ndarray模块,nd是ndarray的缩写形式,两种导入方式均可,但要注意之后使用的时候前缀要对应导入的形式。

from mxnet import ndarray #之后要使用ndarray.function()
from mxnet import nd #之后要使用nd.function(),后面以nd为例

  arange函数创建一个行向量。(不是arrange)

x = nd.arange(12)
x

[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11.] #从0开始的12个连续整数
<NDArray 12 @cpu(0)> #被创建在CPU使用的内存上

  通过shape属性来获取NDArray实例的形状
  通过size属性得到NDArray实例中元素(element)的总数。

x.shape

(12,) #x是长度为12的一维数组,并不是1行12列的矩阵

x.size

12

  reshape函数把行向量x的形状改为(3, 4),也就是一个3行4列的矩阵,并记作X。除了形状改变之外,X中的元素保持不变。

X = x.reshape((3, 4))
X

[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
<NDArray 3x4 @cpu(0)>
X = X.reshape((1,12))
X

[[ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11.]] #此时的X是矩阵,比刚开始的x外面又多了一层[ ]
<NDArray 1x12 @cpu(0)>

X.shape
(1, 12) #且shape表示为1行12列

  注意X属性中的形状发生了变化。上面x.reshape((3, 4))也可写成x.reshape((-1, 4))或x.reshape((3, -1))。由于x的元素个数是已知的,这里的-1是能够通过元素个数和其他维度的大小推断出来的。


  创建一个各元素为0,形状为(2, 3, 4)的张量。实际上,之前创建的向量和矩阵都是特殊的张量。同样我们也可以创建各元素为1的张量。然鹅并不支持twos以及后面的数字了。

nd.zeros((2, 3, 4))

[[[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]

 [[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]]
<NDArray 2x3x4 @cpu(0)>

nd.ones((3, 4))

[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]
<NDArray 3x4 @cpu(0)>

  通过Python的列表(list)指定需要创建的NDArray中每个元素的值。要注意[ ]。矩阵的表示为最外面一层[ ],中间每一对[ ]表示一行

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

[[2. 1. 4. 3.]
 [1. 2. 3. 4.]
 [4. 3. 2. 1.]]
<NDArray 3x4 @cpu(0)>

  有些情况下,我们需要随机生成NDArray中每个元素的值。下面我们创建一个形状为(3, 4)的NDArray。它的每个元素都随机采样于均值为0、标准差为1的正态分布。

nd.random.normal(0, 1, shape=(3, 4))

[[-0.5916499   0.85860497 -0.22794183  0.20131476]
 [ 0.3500547   0.5360521   1.5194443   1.9040879 ]
 [-1.5734432  -0.14007866  0.29670075  1.3111951 ]]
<NDArray 3x4 @cpu(0)>

运算

  对矩阵内的元素进行运算,首先要保证两个矩阵形状一致,所得结果形状不变。

X + Y #加

[[ 2.  2.  6.  6.]
 [ 5.  7.  9. 11.]
 [12. 12. 12. 12.]]
<NDArray 3x4 @cpu(0)>

Y = Y.reshape(4,3) #此时做X+Y会报错
X + Y.T # T将Y矩阵进行转置(可以看看这个reshape和转置是怎么转的)

[[ 2.  4.  5.  6.]
 [ 5.  6. 10.  9.]
 [12. 11. 14. 12.]]
<NDArray 3x4 @cpu(0)>

X * Y #乘

[[ 0.  1.  8.  9.]
 [ 4. 10. 18. 28.]
 [32. 27. 20. 11.]]
<NDArray 3x4 @cpu(0)>

X / Y #除

[[ 0.    1.    0.5   1.  ]
 [ 4.    2.5   2.    1.75]
 [ 2.    3.    5.   11.  ]]
<NDArray 3x4 @cpu(0)>

Y.exp() #指数运算(e的x次方,x为矩阵对应元素)

[[ 7.389056   2.7182817 54.59815   20.085537 ]
 [ 2.7182817  7.389056  20.085537  54.59815  ]
 [54.59815   20.085537   7.389056   2.7182817]]
<NDArray 3x4 @cpu(0)>


  除了按元素计算外,我们还可以使用dot函数做矩阵乘法。下面将X与Y的转置做矩阵乘法。由于X是3行4列的矩阵,Y转置为4行3列的矩阵,因此两个矩阵相乘得到3行3列的矩阵。

nd.dot(X, Y.T) #此处Y为最开始的Y


[[ 18.  20.  10.]
 [ 58.  60.  50.]
 [ 98. 100.  90.]]
<NDArray 3x3 @cpu(0)>

  我们也可以将多个NDArray连结(concatenate)。简单理解,当dim为0时将两矩阵上下拼起来,当dim为1将两矩阵左右拼起来。注意结果行数和列数的变化。

nd.concat(X, Y, dim=0), nd.concat(X, Y, dim=1)

(
 [[ 0.  1.  2.  3.]
  [ 4.  5.  6.  7.]
  [ 8.  9. 10. 11.]
  [ 2.  1.  4.  3.]
  [ 1.  2.  3.  4.]
  [ 4.  3.  2.  1.]]
 <NDArray 6x4 @cpu(0)>,
 
 [[ 0.  1.  2.  3.  2.  1.  4.  3.]
  [ 4.  5.  6.  7.  1.  2.  3.  4.]
  [ 8.  9. 10. 11.  4.  3.  2.  1.]]
 <NDArray 3x8 @cpu(0)>)

  使用条件判断式可以得到元素为0或1的新的NDArray。以X == Y为例,如果X和Y在相同位置的条件判断为真(值相等),那么新的NDArray在相同位置的值为1;反之为0。X > Y,X < Y都能作为条件判断式。

X == Y

[[0. 1. 0. 1.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
<NDArray 3x4 @cpu(0)>

X > Y

[[0. 0. 0. 0.]
 [1. 1. 1. 0.]
 [1. 1. 1. 0.]]
<NDArray 3x4 @cpu(0)>

  对NDArray中的所有元素求和得到只有一个元素的NDArray。

X.sum()

[66.]
<NDArray 1 @cpu(0)>

  我们可以通过asscalar函数将结果变换为Python中的标量。(用通俗的说法,标量是只有大小,没有方向的量。矢量是一种既有大小又有方向的量,又称为向量。)
  下面例子中X的 𝐿2 范数结果同上例一样是单元素NDArray,但最后结果变换成了Python中的标量。(这里的norm()函数其实就是把矩阵中所有元素的平方加起来再开方)

X.norm()


[22.494442]
<NDArray 1 @cpu(0)>

X.norm().asscalar()

22.494442

  我们也可以把Y.exp()、X.sum()、X.norm()等分别改写为nd.exp(Y)、nd.sum(X)、nd.norm(X)等。

广播机制

  当对两个形状不同的NDArray按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个NDArray形状相同后再按元素运算。

A = nd.arange(3).reshape((3, 1))
B = nd.arange(2).reshape((1, 2))
A, B

(
 [[0.]
  [1.]
  [2.]]
 <NDArray 3x1 @cpu(0)>,
 
 [[0. 1.]]
 <NDArray 1x2 @cpu(0)>)

  由于A和B分别是3行1列和1行2列的矩阵,如果要计算A + B,那么A中第一列的3个元素被广播(复制)到了第二列,而B中第一行的2个元素被广播(复制)到了第二行和第三行。如此,就可以对2个3行2列的矩阵按元素相加。

A + B


[[0. 1.]
 [1. 2.]
 [2. 3.]]
<NDArray 3x2 @cpu(0)>

索引

  在NDArray中,索引(index)代表了元素的位置。NDArray的索引从0开始逐一递增。例如,一个3行2列的矩阵的行索引分别为0、1和2,列索引分别为0和1。

  在下面的例子中,我们指定了NDArray的行索引截取范围[1:3]。依据左闭右开指定范围的惯例,它截取了矩阵X中行索引为1和2的两行。

X[1:3]

[[ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
<NDArray 2x4 @cpu(0)>

  我们可以指定NDArray中需要访问的单个元素的位置,如矩阵中行和列的索引,并为该元素重新赋值。

X[1, 2] = 9 #将第二行第三列元素设为9
X

[[ 0.  1.  2.  3.]
 [ 4.  5.  9.  7.]
 [ 8.  9. 10. 11.]]
<NDArray 3x4 @cpu(0)>

  当然,我们也可以截取一部分元素,并为它们重新赋值。在下面的例子中,我们为行索引为1的每一列元素重新赋值。

X[1:2, :] = 12 #将第二行(行索引为1)的元素设为12
X

[[ 0.  1.  2.  3.]
 [12. 12. 12. 12.]
 [ 8.  9. 10. 11.]]
<NDArray 3x4 @cpu(0)>

  针对列索引,可以用’,'隔开行和列索引,可以如下表示:

X[:,2:4]

[[ 2.  3.]
 [12. 12.]
 [10. 11.]]
<NDArray 3x2 @cpu(0)>

X[1:3,3:4] = 0 #将第二、三行的第四列设为0
X

[[ 0.  1.  2.  3.]
 [12. 12. 12.  0.]
 [ 8.  9. 10.  0.]]
<NDArray 3x4 @cpu(0)>

运算的内存开销

  在前面的例子里我们对每个操作新开内存来存储运算结果。举个例子,即使像Y = X + Y这样的运算,我们也会新开内存,然后将Y指向新内存。为了演示这一点,我们可以使用Python自带的id函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。

from mxnet import nd
X = nd.ones((3,4))
Y = nd.ones((3,4))
before = id(Y)
Y = Y + X
id(Y) == before

False #经过加操作后,Y的存储地址发生了变化

  如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。在下面的例子中,我们先通过zeros_like创建和Y形状相同且元素为0的NDArray,记为Z。接下来,我们把X + Y的结果通过[:]写进Z对应的内存中。

Z = Y.zeros_like() #Z和Y形状相同且全部元素为0
before = id(Z)
Z[:] = X + Y # 相当于把X+Y的元素赋给对应位置的Z
id(Z) == before

True

  实际上,上例中我们还是为X + Y开了临时内存来存储计算结果,再复制到Z对应的内存。如果想避免这个临时内存开销,我们可以使用运算符全名函数中的out参数。

nd.elemwise_add(X, Y, out=Z)
id(Z) == before

True

  如果X的值在之后的程序中不会复用,我们也可以用 X[:] = X + Y 或者*X += Y 来减少运算的内存开销。

from mxnet import nd
X = nd.ones((3,4))
Y = nd.ones((3,4))
before = id(Y)
Y += X
id(Y) == before

True #用+=运算符不会有内存地址变化

NDArray和NumPy相互变换

  我们可以通过array函数和asnumpy函数令数据在NDArray和NumPy格式之间相互变换。下面将NumPy实例变换成NDArray实例。

import numpy as np

P = np.ones((2, 3))
D = nd.array(P)
D

[[1. 1. 1.]
 [1. 1. 1.]]
<NDArray 2x3 @cpu(0)>

  再将NDArray实例通过asnumpy()函数变换成NumPy实例。

D.asnumpy()

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

参考文献:动手学深度学习第一版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值