目录
绪论
在深度学习中,我们通常会频繁地对数据进行操作。作为深度学习的基础,本节将介绍如何对CPU内存中的数据的基本操作。
MXNet中,NDArray
是一个类,也是存储和变换数据的主要工具。NDArray
和NumPy的多维数组非常类似。然而,NDArray
提供GPU计算和自动求梯度等更多功能,这些使NDArray
更加适合深度学习。
前言
这里作者是使用juputer notebook来学习,对比于pycharm和它的控制台来说,对于较大型的项目用pycharm可以实现分文件管理,pycharm的控制台适合运行单行代码,jupyter notebook因为对于单个模块运行之后都会有一个输出直观看到结果;
2.2.1. 创建NDArray
首先从MXNet导入ndarray
模块。这里的nd
是ndarray
的缩写形式。
from mxnet import nd
然后我们用arange
函数创建一个行向量。
x = nd.arange(12)
#这里x的作用相当于print(x)
x
Out[2]: [ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.] <NDArray 12 @cpu(0)> 可以看到nd.arange创建了一个从0开始的12个连续整数,类型 NDArray ,长度 12,创建在cpu内存上
shape
属性来获取NDArray
实例的形状。
x.shape
Out[3]: (12,)
size
属性得到NDArray
实例中元素(element)的总数。
x.size
Out[4]:
12
reshape
函数把行向量x
的形状改为(3, 4),也就是一个3行4列的矩阵,并记作X
。(注意前面是小写x,后面是大写X)
X = x.reshape((3, 4)) #这里X的作用相当于print(X) X
Out[5]:
[[ 0. 1. 2. 3.] [ 4. 5. 6. 7.] [ 8. 9. 10. 11.]]
小技巧:上面x.reshape((3, 4))
也可写成x.reshape((-1, 4))
或x.reshape((3, -1))
。由于x
的元素个数是已知的12,这里的-1
是能够通过元素个数和其他维度的大小推断出来的。
接下来,创建一个各元素为0,形状为(2, 3, 4)的张量。之前创建的向量和矩阵都是特殊的张量。
nd.zeros((2, 3, 4))
Out[6]:
[[[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]]]
可以理解成2层,每一层都是3*4的矩阵。
类似地,我们可以创建各元素为1的张量。
nd.ones((3, 4))
Out[7]:
[[1. 1. 1. 1.] [1. 1. 1. 1.] [1. 1. 1. 1.]]
我们也可以通过Python的列表(list)指定NDArray
中每个元素的值。
Y = nd.array([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
Y
Out[8]:
[[2. 1. 4. 3.] [1. 2. 3. 4.] [4. 3. 2. 1.]]
有些情况下,我们需要随机生成NDArray
中每个元素的值。下面我们创建一个形状为(3, 4)的NDArray
。它的每个元素都随机采样于均值为0、标准差为1的正态分布。
nd.random.normal(0, 1, shape=(3, 4))
Out[9]:
[[ 2.2122064 0.7740038 1.0434405 1.1839255 ] [ 1.8917114 -1.2347414 -1.771029 -0.45138445] [ 0.57938355 -1.856082 -1.9768796 -0.20801921]]
2.2.2. 运算
NDArray
支持大量的运算符(operator)。例如,我们可以对之前创建的X和Y形状为(3, 4)NDArray
做按元素运算。
X:
[[ 0. 1. 2. 3.] [ 4. 5. 6. 7.] [ 8. 9. 10. 11.]]
Y:
[[2. 1. 4. 3.] [1. 2. 3. 4.] [4. 3. 2. 1.]]
按元素加法:
X + Y
Out[10]:
[[ 2. 2. 6. 6.] [ 5. 7. 9. 11.] [12. 12. 12. 12.]]
按元素乘法(也叫阿达玛(Hadamard)积)如下:
X * Y
Out[11]:
[[ 0. 1. 8. 9.] [ 4. 10. 18. 28.] [32. 27. 20. 11.]]
按元素除法如下:
X / Y
Out[12]:
[[ 0. 1. 0.5 1. ] [ 4. 2.5 2. 1.75] [ 2. 3. 5. 11. ]]
按元素做指数运算如下:
Y.exp()
Out[13]:
[[ 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
函数做矩阵乘法:
nd.dot(X, Y.T)
Out[14]:
[[ 18. 20. 10.] [ 58. 60. 50.] [ 98. 100. 90.]]
将多个NDArray连接
(concatenate)。
#维度dim = 0或者1,在行上连接即上下连接或者在列上左右连接 nd.concat(X, Y, dim=0), nd.concat(X, Y, dim=1)
Out[15]:
( [[ 0. 1. 2. 3.] [ 4. 5. 6. 7.] [ 8. 9. 10. 11.] [ 2. 1. 4. 3.] [ 1. 2. 3. 4.] [ 4. 3. 2. 1.]], [[ 0. 1. 2. 3. 2. 1. 4. 3.] [ 4. 5. 6. 7. 1. 2. 3. 4.] [ 8. 9. 10. 11. 4. 3. 2. 1.]]
使用条件判断式,如果X
和Y
在相同位置的条件判断为真(值相等),那么新的NDArray
在相同位置的值为1;反之为0。
X == Y
Out[16]:
[[0. 1. 0. 1.] [0. 0. 0. 0.] [0. 0. 0. 0.]]
对NDArray
中的所有元素求和得到只有一个元素的NDArray
。
X.sum()
Out[17]:
[66.]
通过asscalar
函数将结果变换为Python中的标量。
X.norm().asscalar()
Out[18]:
22.494442
norm():矩阵X的Frobenius范数,为该矩阵元素平方和的平方根。
2.2.3. broadcasting机制
当对两个形状不同的NDArray
按元素运算时,可能会触发broadcasting机制。
A = nd.arange(3).reshape((3, 1))
B = nd.arange(2).reshape((1, 2))
A,B
Out[19]:
( [[0.] [1.] [2.]], [[0. 1.]]
计算A+B
,那么A
中第一列的3个元素复制到第二列,而B
中第一行的2个元素复制到第二行和第三行。如此,就可以对2个3行2列的矩阵按元素相加。
A + B
Out[20]:
[[0. 1.] [1. 2.] [2. 3.]]
2.2.4. 索引
在NDArray
中,索引(index)代表了元素的位置。NDArray
的索引从0开始逐一递增。例如,一个3行4列的矩阵的行索引分别为0、1和2,列索引分别为0丶1丶2和3。
X: [[ 0. 1. 2. 3.] [ 4. 5. 6. 7.] [ 8. 9. 10. 11.]]
在下面的例子中,我们指定了NDArray
的行索引截取范围[1:3]
。依据左闭右开指定范围的惯例,它截取了矩阵X
中行索引为1和2的两行。
X[1, 2] = 9 X
Out[21]:
[[ 4. 5. 6. 7.] [ 8. 9. 10. 11.]]
指定NDArray
中需要访问的单个元素的位置
X[1, 2] = 9
X
Out[22]:
[[ 0. 1. 2. 3.] [ 4. 5. 9. 7.] [ 8. 9. 10. 11.]]
截取一部分元素,并为它们重新赋值。
X[1:2, :] = 12
X
Out[23]: [[ 0. 1. 2. 3.] [12. 12. 12. 12.] [ 8. 9. 10. 11.]]
2.2.5. 运算的内存开销
在前面的例子里我们对每个操作新开内存来存储运算结果。举个例子,即使像Y = X + Y
这样的运算,我们也会新开内存,然后将Y
指向新内存。我们可以使用Python自带的id
函数:如果两个实例的ID一致,那么它们所对应的内存地址相同;反之则不同。
before = id(Y) Y = Y + X id(Y) == before
Out[24]:
False
这里学过指针的同学应该就比较清楚。
如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。
Z = Y.zeros_like()
before = id(Z)
Z[:] = X + Y
id(Z) == before
Out[25]:
True
实际上,我们还是为X+Y
开了临时内存来存储计算结果,再复制到Z
对应的内存。如果想避免这个临时内存开销,我们可以使用运算符全名函数中的out
参数。
nd.elemwise_add(X, Y, out=Z)
id(Z) == before
Out[26]: True
如果X
的值在之后的程序中不会复用,我们也可以用 X[:] = X + Y
或者 X += Y
来减少运算的内存开销。
before = id(X) X += Y id(X) == before
Out[27]: True
2.2.6. NDArray
和NumPy相互变换
我们可以通过array
函数和asnumpy
函数令数据在NDArray
和NumPy格式之间相互变换。
import numpy as np P = np.ones((2, 3)) D = nd.array(P) D
Out[28]: [[1. 1. 1.] [1. 1. 1.]] <NDArray 2x3 @cpu(0)>
再将NDArray
实例变换成NumPy实例。
D.asnumpy()
Out[29]:
array([[1., 1., 1.], [1., 1., 1.]], dtype=float32)
2.2.7. 小结
NDArray
是MXNet中存储和变换数据的主要工具。- 可以轻松地对
NDArray
创建、运算、指定索引,并与NumPy之间相互变换。
2.2.8. 练习
- 运行本节中的代码。将本节中条件判别式
X == Y
改为X < Y
或X > Y
,看看能够得到什么样的NDArray
。 - 将broadcasting中按元素运算的两个
NDArray
替换成其他形状,结果是否和预期一样?
2.2.8. 答案
将本节中条件判别式X == Y
改为X < Y
或X > Y。
X < Y
[[1. 0. 1. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]]
X > Y
[[0. 0. 0. 0.] [1. 1. 1. 1.] [1. 1. 1. 1.]]
将broadcasting中按元素运算的两个NDArray
替换成其他形状。
A = nd.arange(8).reshape((4, 2))
B = nd.arange(4).reshape((4, 1))
A, B
( [[0. 1.] [2. 3.] [4. 5.] [6. 7.]], [[0.] [1.] [2.] [3.]] <NDArray 4x1 @cpu(0)>)
A + B
[[ 0. 1.] [ 3. 4.] [ 6. 7.] [ 9. 10.]]
触发broadcasting的条件是,如果是列复制,那么就应该只有一列。同理,如果是行复制的话,原数组应该只有一行,不然会报错。