ApacheMXNet-NDArray

この章では、MXNetの多次元配列形式について説明します。 ndarray

NDArrayを使用したデータの処理

まず、NDArrayを使用してデータを処理する方法を見ていきます。以下は、同じの前提条件です-

前提条件

この多次元配列形式でデータを処理する方法を理解するには、次の前提条件を満たしている必要があります。

  • Python環境にインストールされたMXNet

  • Python2.7.xまたはPython3.x

実装例

以下の例を参考にして、基本的な機能を理解しましょう。

まず、MXNetとndarrayをMXNetから次のようにインポートする必要があります-

import mxnet as mx
from mxnet import nd

必要なライブラリをインポートしたら、次の基本機能を使用します。

Pythonリストを含む単純な1次元配列

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

Pythonリストを含む2次元配列

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

初期化なしでNDArrayを作成する

ここでは、を使用して3行4列の行列を作成します。 .empty関数。また使用します.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)>

.zeros関数を使用したすべてのゼロの行列

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

.ones関数を持つすべてのものの行列

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

値がランダムにサンプリングされる配列を作成する

Example

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

Output

出力は以下のとおりです。

[[ 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 3x4 @cpu(0)>

各NDArrayの次元を見つける

Example

y.shape

Output

出力は次のとおりです-

(3, 4)

各NDArrayのサイズを見つける

Example

y.size

Output

12

各NDArrayのデータ型を見つける

Example

y.dtype

Output

numpy.float32

NDArray操作

このセクションでは、MXNetの配列操作を紹介します。NDArrayは、多数の標準的な数学演算とインプレース演算をサポートしています。

標準的な数学演算

以下は、NDArrayでサポートされている標準の数学演算です。

要素ごとの加算

まず、MXNetとndarrayをMXNetから次のようにインポートする必要があります。

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

要素ごとの乗算

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

べき乗

Example

nd.exp(x)

Output

コードを実行すると、次の出力が表示されます。

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

行列転置して行列-行列積を計算します

Example

nd.dot(x, y.T)

Output

以下にコードの出力を示します-

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

インプレースオペレーション

上記の例では、毎回、操作を実行し、その結果をホストするために新しいメモリを割り当てました。

たとえば、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コンテキスト

Apache MXNetでは、各配列にコンテキストがあり、1つのコンテキストが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アレイとNDArray

私たちは皆NumPy配列に精通していますが、ApacheMXNetはNDArrayという名前の独自の配列実装を提供しています。実際、当初はNumPyと同様に設計されていましたが、重要な違いがあります-

主な違いは、NumPyとNDArrayでの計算の実行方法にあります。MXNetでのすべてのNDArray操作は、非同期で非ブロッキングの方法で実行されます。つまり、c = a * bのようなコードを記述すると、関数はにプッシュされます。Execution Engine、計算を開始します。

ここで、aとbは両方ともNDArrayです。これを使用する利点は、関数がすぐに戻り、前の計算がまだ完了していない場合でも、ユーザースレッドが実行を継続できることです。

実行エンジンの動作

実行エンジンの動作について話すと、計算グラフが作成されます。計算グラフは、一部の計算を並べ替えたり組み合わせたりする場合がありますが、常に依存関係の順序を尊重します。

たとえば、プログラミングコードの後半で「X」を使用して他の操作が行われた場合、「X」の結果が利用可能になると、実行エンジンはそれらの操作を開始します。実行エンジンは、後続のコードの実行を開始するためのコールバックの記述など、ユーザーにとって重要な作業を処理します。

Apache MXNetでは、NDArrayを使用して、計算結果を取得するには、結果の変数にアクセスするだけで済みます。計算結果が結果の変数に割り当てられるまで、コードのフローはブロックされます。このようにして、命令型プログラミングモードをサポートしながら、コードのパフォーマンスを向上させます。

NDArrayをNumPy配列に変換する

MXNetでNDArrayをNumPyArrayに変換する方法を学びましょう。

Combining higher-level operator with the help of few lower-level operators

場合によっては、既存の演算子を使用して、より高いレベルの演算子を組み立てることができます。これの最も良い例の1つは、np.full_like()NDArrayAPIにはありません。次のように、既存の演算子の組み合わせに簡単に置き換えることができます。

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

通話のブロックによる影響を最小限に抑える

場合によっては、どちらかを使用する必要があります .asnumpy() または .asscalar()ただし、これにより、結果を取得できるようになるまで、MXNetは実行をブロックします。電話をかけることで、通話のブロックの影響を最小限に抑えることができます.asnumpy() または .asscalar() この値の計算がすでに行われていると思われる現在のメソッド。

実装例

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