Apache Mxnet 简明教程
Apache MXNet - NDArray
在本章中,我们将讨论 MXNet 名为 ndarray
的多维数组格式。
Handling data with NDArray
首先,我们将了解如何使用 NDArray 处理数据。以下是其先决条件 −
Implementation Example
我们借助以下示例了解基本功能 −
首先需要如下从 MXNet 导入 MXNet 和 ndrray −
import mxnet as mx
from mxnet import nd
导入必要的库后,我们将继续以下基本功能:
A simple 1-D array with a python list
Example
x = nd.array([1,2,3,4,5,6,7,8,9,10])
print(x)
Output
输出如下所述 −
[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]
<NDArray 10 @cpu(0)>
A 2-D array with a python list
Example
y = nd.array([[1,2,3,4,5,6,7,8,9,10], [1,2,3,4,5,6,7,8,9,10], [1,2,3,4,5,6,7,8,9,10]])
print(y)
Output
输出如下所示 −
[[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]
[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]
[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]]
<NDArray 3x10 @cpu(0)>
Creating an NDArray without any initialisation
这里,我们将使用 .empty
函数创建包含 3 行和 4 列的矩阵。我们还将使用 .full
函数,它会将其他运算符用作想要填充到数组中的值。
Example
x = nd.empty((3, 4))
print(x)
x = nd.full((3,4), 8)
print(x)
Output
输出如下 −
[[0.000e+00 0.000e+00 0.000e+00 0.000e+00]
[0.000e+00 0.000e+00 2.887e-42 0.000e+00]
[0.000e+00 0.000e+00 0.000e+00 0.000e+00]]
<NDArray 3x4 @cpu(0)>
[[8. 8. 8. 8.]
[8. 8. 8. 8.]
[8. 8. 8. 8.]]
<NDArray 3x4 @cpu(0)>
Matrix of all zeros with the .zeros function
Example
x = nd.zeros((3, 8))
print(x)
Output
输出如下 −
[[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 3x8 @cpu(0)>
Matrix of all ones with the .ones function
Example
x = nd.ones((3, 8))
print(x)
Output
输出如下:
[[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1. 1. 1. 1.]]
<NDArray 3x8 @cpu(0)>
Standard Mathematical Operations
以下是 NDArray 支持的标准数学运算 −
Element-wise addition
首先需要如下从 MXNet 导入 MXNet 和 ndrray:
import mxnet as mx
from mxnet import nd
x = nd.ones((3, 5))
y = nd.random_normal(0, 1, shape=(3, 5))
print('x=', x)
print('y=', y)
x = x + y
print('x = x + y, x=', x)
Output
输出与此一同给出 −
x=
[[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]]
<NDArray 3x5 @cpu(0)>
y=
[[-1.0554522 -1.3118273 -0.14674698 0.641493 -0.73820823]
[ 2.031364 0.5932667 0.10228804 1.179526 -0.5444829 ]
[-0.34249446 1.1086396 1.2756858 -1.8332436 -0.5289873 ]]
<NDArray 3x5 @cpu(0)>
x = x + y, x=
[[-0.05545223 -0.3118273 0.853253 1.6414931 0.26179177]
[ 3.031364 1.5932667 1.102288 2.1795259 0.4555171 ]
[ 0.6575055 2.1086397 2.2756858 -0.8332436 0.4710127 ]]
<NDArray 3x5 @cpu(0)>
Element-wise multiplication
Example
x = nd.array([1, 2, 3, 4])
y = nd.array([2, 2, 2, 1])
x * y
Output
您将看到以下输出−
[2. 4. 6. 4.]
<NDArray 4 @cpu(0)>
In-place Operations
在上述示例中,每次我们运行一项操作时,我们都会分配一个新内存来承载其结果。
例如,如果我们编写 A = A+B,我们将解除引用 A 用于指向的矩阵,而将其指向新分配的内存。让我们借助 Python 的 id() 函数使用下面给出的示例来理解它−
print('y=', y)
print('id(y):', id(y))
y = y + x
print('after y=y+x, y=', y)
print('id(y):', id(y))
Output
执行后,您将收到以下输出:
y=
[2. 2. 2. 1.]
<NDArray 4 @cpu(0)>
id(y): 2438905634376
after y=y+x, y=
[3. 4. 5. 5.]
<NDArray 4 @cpu(0)>
id(y): 2438905685664
事实上,我们还可以将结果分配给以前分配的数组,如下所示−
print('x=', x)
z = nd.zeros_like(x)
print('z is zeros_like x, z=', z)
print('id(z):', id(z))
print('y=', y)
z[:] = x + y
print('z[:] = x + y, z=', z)
print('id(z) is the same as before:', id(z))
Output
输出如下所示−
x=
[1. 2. 3. 4.]
<NDArray 4 @cpu(0)>
z is zeros_like x, z=
[0. 0. 0. 0.]
<NDArray 4 @cpu(0)>
id(z): 2438905790760
y=
[3. 4. 5. 5.]
<NDArray 4 @cpu(0)>
z[:] = x + y, z=
[4. 6. 8. 9.]
<NDArray 4 @cpu(0)>
id(z) is the same as before: 2438905790760
从上述输出中,我们可以看到 x+y 仍会分配一个临时缓冲区来存储结果,然后再将其复制到 z。所以现在,我们可以就地执行操作,从而更好地利用内存并避免临时缓冲区。为此,我们将按如下方式指定所有运算符支持的 out 关键字参数−
print('x=', x, 'is in id(x):', id(x))
print('y=', y, 'is in id(y):', id(y))
print('z=', z, 'is in id(z):', id(z))
nd.elemwise_add(x, y, out=z)
print('after nd.elemwise_add(x, y, out=z), x=', x, 'is in id(x):', id(x))
print('after nd.elemwise_add(x, y, out=z), y=', y, 'is in id(y):', id(y))
print('after nd.elemwise_add(x, y, out=z), z=', z, 'is in id(z):', id(z))
Output
在执行上述程序后,您将获得以下结果−
x=
[1. 2. 3. 4.]
<NDArray 4 @cpu(0)> is in id(x): 2438905791152
y=
[3. 4. 5. 5.]
<NDArray 4 @cpu(0)> is in id(y): 2438905685664
z=
[4. 6. 8. 9.]
<NDArray 4 @cpu(0)> is in id(z): 2438905790760
after nd.elemwise_add(x, y, out=z), x=
[1. 2. 3. 4.]
<NDArray 4 @cpu(0)> is in id(x): 2438905791152
after nd.elemwise_add(x, y, out=z), y=
[3. 4. 5. 5.]
<NDArray 4 @cpu(0)> is in id(y): 2438905685664
after nd.elemwise_add(x, y, out=z), z=
[4. 6. 8. 9.]
<NDArray 4 @cpu(0)> is in id(z): 2438905790760
NDArray Contexts
在 Apache MXNet 中,每个数组都有一个上下文,一个上下文可能是 CPU,而其他上下文可能是多个 GPU。当我们在多台服务器上部署工作时,情况甚至会变得更糟。这就是我们为什么需要智能地将数组分配给上下文的。它将最大程度地减少在设备间传输数据所花费的时间。
例如,尝试按如下方式初始化一个数组−
from mxnet import nd
z = nd.ones(shape=(3,3), ctx=mx.cpu(0))
print(z)
Output
执行以上代码时,应该看到以下输出 −
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
<NDArray 3x3 @cpu(0)>
我们可以使用 copyto() 方法将给定的 NDArray 从一个上下文复制到另一个上下文,如下所示−
x_gpu = x.copyto(gpu(0))
print(x_gpu)
NumPy array vs. NDArray
我们都熟悉 NumPy 数组,但 Apache MXNet 提供了自己的名为 NDArray 的数组实现。实际上,它最初被设计为类似于 NumPy,但有一个关键区别−
关键区别在于在 NumPy 和 NDArray 中执行计算的方式。在 MXNet 中执行的每个 NDArray 操作都是异步且非阻塞的,这意味着当我们编写 c = a * b 这样的代码时,该函数将被推送到 Execution Engine ,它将启动计算。
这里,a 和 b 都是 NDArrays。使用它的好处是,该函数立即返回,并且用户线程可以继续执行,尽管前面提到的计算可能尚未完成。
Converting NDArray to NumPy Array
让我们了解如何在 MXNet 中将 NDArray 转换为 NumPy 数组。
Combining higher-level operator with the help of few lower-level operators
有时,我们可以通过使用现有运算符来组装高级别的运算符。 np.full_like() 运算符就是其中的一个最佳示例,该运算符在 NDArray API 中不存在。它可以轻松地替换为现有运算符的组合,如下所示:
from mxnet import nd
import numpy as np
np_x = np.full_like(a=np.arange(7, dtype=int), fill_value=15)
nd_x = nd.ones(shape=(7,)) * 15
np.array_equal(np_x, nd_x.asnumpy())
Output
我们将获得如下所示的输出:
True
Finding similar operator with different name and/or signature
在所有运算符中,有些运算符的名称略有不同,但在功能方面它们是相似的。 nd.ravel_index() 带 np.ravel() 函数就是一个示例。同样,某些运算符的名称可能相似,但它们的签名不同。 np.split() 和 nd.split() 是一个示例,它们是相似的。
让我们通过以下编程示例来理解它:
def pad_array123(data, max_length):
data_expanded = data.reshape(1, 1, 1, data.shape[0])
data_padded = nd.pad(data_expanded,
mode='constant',
pad_width=[0, 0, 0, 0, 0, 0, 0, max_length - data.shape[0]],
constant_value=0)
data_reshaped_back = data_padded.reshape(max_length)
return data_reshaped_back
pad_array123(nd.array([1, 2, 3]), max_length=10)
Output
输出如下 −
[1. 2. 3. 0. 0. 0. 0. 0. 0. 0.]
<NDArray 10 @cpu(0)>
Minimising impact of blocking calls
在某些情况下,我们必须使用 .asnumpy() 或 .asscalar() 方法,但这将强制 MXNet 阻塞执行,直到可以检索结果。我们可以通过在某些时刻调用 .asnumpy() 或 .asscalar() 方法来最大程度地减少阻塞调用的影响,我们认为此时已经完成了该值的计算。
Implementation Example
Example
from __future__ import print_function
import mxnet as mx
from mxnet import gluon, nd, autograd
from mxnet.ndarray import NDArray
from mxnet.gluon import HybridBlock
import numpy as np
class LossBuffer(object):
"""
Simple buffer for storing loss value
"""
def __init__(self):
self._loss = None
def new_loss(self, loss):
ret = self._loss
self._loss = loss
return ret
@property
def loss(self):
return self._loss
net = gluon.nn.Dense(10)
ce = gluon.loss.SoftmaxCELoss()
net.initialize()
data = nd.random.uniform(shape=(1024, 100))
label = nd.array(np.random.randint(0, 10, (1024,)), dtype='int32')
train_dataset = gluon.data.ArrayDataset(data, label)
train_data = gluon.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2)
trainer = gluon.Trainer(net.collect_params(), optimizer='sgd')
loss_buffer = LossBuffer()
for data, label in train_data:
with autograd.record():
out = net(data)
# This call saves new loss and returns previous loss
prev_loss = loss_buffer.new_loss(ce(out, label))
loss_buffer.loss.backward()
trainer.step(data.shape[0])
if prev_loss is not None:
print("Loss: {}".format(np.mean(prev_loss.asnumpy())))
Output
输出如下所示:
Loss: 2.3373236656188965
Loss: 2.3656985759735107
Loss: 2.3613128662109375
Loss: 2.3197104930877686
Loss: 2.3054862022399902
Loss: 2.329197406768799
Loss: 2.318927526473999