📜  Apache MXNet-NDArray

📅  最后修改于: 2020-12-10 04:49:27             🧑  作者: Mango


在本章中,我们将讨论MXNet的多维数组格式ndarray

使用NDArray处理数据

首先,我们将看到如何使用NDArray处理数据。以下是相同的前提条件-

先决条件

要了解如何使用这种多维数组格式处理数据,我们需要满足以下先决条件:

  • MXNet安装在Python环境中

  • Python 2.7.x或Python 3.x

实施实例

让我们借助下面给出的示例了解基本功能-

首先,我们需要从MXNet导入MXNet和ndarray,如下所示:

import mxnet as mx
from mxnet import nd

导入必要的库后,我们将具有以下基本功能:

具有Python列表的简单一维数组

x = nd.array([1,2,3,4,5,6,7,8,9,10])
print(x)

输出

输出如下所示-

[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]

具有Python列表的二维数组

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)

输出

输出如下所示-

[[ 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

在这里,我们将使用.empty函数创建一个具有3行4列的矩阵。我们还将使用.full函数,它将使用一个额外的运算符来表示要填充数组的值。

x = nd.empty((3, 4))
print(x)
x = nd.full((3,4), 8)
print(x)

输出

输出如下-

[[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]]


[[8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]]

.zeros函数的全零矩阵

x = nd.zeros((3, 8))
print(x)

输出

输出如下-

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

带.ones函数的所有矩阵

x = nd.ones((3, 8))
print(x)

输出

输出在下面提到-

[[1. 1. 1. 1. 1. 1. 1. 1.]
   [1. 1. 1. 1. 1. 1. 1. 1.]
   [1. 1. 1. 1. 1. 1. 1. 1.]]

创建其值随机抽样的数组

y = nd.random_normal(0, 1, shape=(3, 4))
print(y)

输出

输出如下-

[[ 1.2673576 -2.0345826 -0.32537818 -1.4583491 ]
 [-0.11176403 1.3606371 -0.7889914 -0.17639421]
 [-0.2532185 -0.42614475 -0.12548696 1.4022992 ]]

查找每个NDArray的尺寸

y.shape

输出

输出如下-

(3, 4)

查找每个NDArray的大小

y.size

输出

12

查找每个NDArray的数据类型

y.dtype

输出

numpy.float32

NDArray操作

在本节中,我们将向您介绍MXNet的阵列操作。 NDArray支持大量标准数学运算和就地运算。

标准数学运算

以下是NDArray支持的标准数学运算-

逐元素加法

首先,我们需要从MXNet导入MXNet和ndarray,如下所示:

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)

输出

输出如下:

x=
[[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]]

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 ]]

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 ]]

逐元素乘法

x = nd.array([1, 2, 3, 4])
y = nd.array([2, 2, 2, 1])
x * y

输出

您将看到以下输出-

[2. 4. 6. 4.]

求幂

nd.exp(x)

输出

运行代码时,您将看到以下输出:

[ 2.7182817 7.389056 20.085537 54.59815 ]

矩阵转置以计算矩阵矩阵乘积

nd.dot(x, y.T)

输出

下面给出的是代码的输出-

[16.]

就地操作

在上面的示例中,每次运行一个操作时,我们都会分配一个新的内存来承载其结果。

例如,如果我们写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))

输出

执行后,您将收到以下输出-

y=
[2. 2. 2. 1.]

id(y): 2438905634376
after y=y+x, y=
[3. 4. 5. 5.]

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))

输出

输出如下所示-

x=
[1. 2. 3. 4.]

z is zeros_like x, z=
[0. 0. 0. 0.]

id(z): 2438905790760
y=
[3. 4. 5. 5.]

z[:] = x + y, z=
[4. 6. 8. 9.]

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))

输出

在执行上述程序时,您将获得以下结果-

x=
[1. 2. 3. 4.]
 is in id(x): 2438905791152
y=
[3. 4. 5. 5.]
 is in id(y): 2438905685664
z=
[4. 6. 8. 9.]
 is in id(z): 2438905790760
after nd.elemwise_add(x, y, out=z), x=
[1. 2. 3. 4.]
 is in id(x): 2438905791152
after nd.elemwise_add(x, y, out=z), y=
[3. 4. 5. 5.]
 is in id(y): 2438905685664
after nd.elemwise_add(x, y, out=z), z=
[4. 6. 8. 9.]
 is in id(z): 2438905790760

NDArray上下文

在Apache MXNet中,每个阵列都有一个上下文,一个上下文可以是CPU,而其他上下文可以是多个GPU。当我们跨多台服务器部署工作时,情况可能变得更糟。因此,我们需要智能地将数组分配给上下文。它将最大限度地减少在设备之间传输数据所花费的时间。

例如,尝试如下初始化数组-

from mxnet import nd
z = nd.ones(shape=(3,3), ctx=mx.cpu(0))
print(z)

输出

当执行上述代码时,您应该看到以下输出-

[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]

我们可以使用copyto()方法将给定的NDArray从一个上下文复制到另一个上下文,如下所示:

x_gpu = x.copyto(gpu(0))
print(x_gpu)

NumPy数组与NDArray

我们都很熟悉NumPy数组,但是Apache MXNet提供了自己的名为NDArray的数组实现。实际上,它最初的设计类似于NumPy,但有一个关键区别-

关键区别在于在NumPy和NDArray中执行计算的方式。 MXNet中的每个NDArray操作都是以异步和非阻塞方式完成的,这意味着,当我们编写类似c = a * b的代码时,该函数将被推送到Execution Engine ,它将开始计算。

这里,a和b都是NDArrays。使用它的好处是,该函数立即返回,并且尽管先前的计算可能尚未完成,但用户线程可以继续执行。

执行引擎的工作

如果我们谈论执行引擎的工作原理,它将构建计算图。计算图可能会重新排序或合并一些计算,但始终遵循依赖关系顺序。

例如,如果稍后在编程代码中使用“ X”进行其他操作,则一旦“ X”的结果可用,执行引擎将开始执行操作。执行引擎将为用户处理一些重要的工作,例如编写回调以开始执行后续代码。

在Apache MXNet中,借助NDArray,要获取计算结果,我们只需要访问结果变量。代码流将被阻塞,直到将计算结果分配给结果变量为止。这样,它在提高代码性能的同时仍支持命令式编程模式。

将NDArray转换为NumPy数组

让我们学习如何在MXNet中将NDArray转换为NumPy Array。

在少数几个较低级别的运算符的帮助下将较高级别的运算符符合并

有时,我们可以使用现有的运算符来组合一个更高级别的运算符符。最好的例子之一是nArray.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())

输出

我们将获得类似以下的输出-

True

查找具有不同名称和/或签名的相似运算符

在所有运营商中,其中一些运算符的名称略有不同,但在功能方面相似。这样的一个例子是具有np.ravel()函数nd.ravel_index()。同样,某些运算符可能具有相似的名称,但是它们具有不同的签名。这样的一个例子是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)

输出

输出说明如下-

[1. 2. 3. 0. 0. 0. 0. 0. 0. 0.]

最大限度地减少阻止呼叫的影响

在某些情况下,我们必须使用.asnumpy().asscalar()方法,但这将强制MXNet阻止执行,直到可以检索结果为止。当我们认为此值的计算已完成时,可以通过立即调用.asnumpy().asscalar()方法来最大程度地减少阻塞调用的影响。

实施实例

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())))

输出

输出引用如下:

Loss: 2.3373236656188965
Loss: 2.3656985759735107
Loss: 2.3613128662109375
Loss: 2.3197104930877686
Loss: 2.3054862022399902
Loss: 2.329197406768799
Loss: 2.318927526473999