Apache MXNet - NDArray

Trong chương này, chúng ta sẽ thảo luận về định dạng mảng đa chiều của MXNet được gọi là ndarray.

Xử lý dữ liệu với NDArray

Đầu tiên, chúng ta sẽ xem cách chúng ta có thể xử lý dữ liệu với NDArray. Sau đây là các điều kiện tiên quyết cho cùng một -

Điều kiện tiên quyết

Để hiểu cách chúng tôi có thể xử lý dữ liệu với định dạng mảng đa chiều này, chúng tôi cần đáp ứng các điều kiện tiên quyết sau:

  • MXNet được cài đặt trong môi trường Python

  • Python 2.7.x hoặc Python 3.x

Ví dụ triển khai

Hãy để chúng tôi hiểu chức năng cơ bản với sự trợ giúp của một ví dụ dưới đây:

Đầu tiên, chúng ta cần nhập MXNet và ndarray từ MXNet như sau:

import mxnet as mx
from mxnet import nd

Khi chúng tôi nhập các thư viện cần thiết, chúng tôi sẽ thực hiện các chức năng cơ bản sau:

Mảng 1-D đơn giản với danh sách python

Example

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

Output

Đầu ra như được đề cập bên dưới -

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

Mảng 2-D với danh sách python

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

Đầu ra như được nêu dưới đâ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 3x10 @cpu(0)>

Tạo NDArray mà không cần bất kỳ khởi tạo nào

Ở đây, chúng ta sẽ tạo một ma trận với 3 hàng và 4 cột bằng cách sử dụng .emptychức năng. Chúng tôi cũng sẽ sử dụng.full hàm này sẽ nhận thêm một toán tử cho giá trị bạn muốn điền vào mảng.

Example

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

Output

Đầu ra được đưa ra dưới đây -

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

Ma trận của tất cả các số không với hàm .zeros

Example

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

Output

Kết quả như sau:

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

Ma trận của tất cả những cái có chức năng .ones

Example

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

Output

Đầu ra được đề cập bên dưới -

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

Tạo mảng có các giá trị được lấy mẫu ngẫu nhiên

Example

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

Output

Đầu ra được đưa ra dưới đâ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 3x4 @cpu(0)>

Tìm thứ nguyên của mỗi NDArray

Example

y.shape

Output

Kết quả như sau:

(3, 4)

Tìm kích thước của từng NDArray

Example

y.size

Output

12

Tìm kiểu dữ liệu của mỗi NDArray

Example

y.dtype

Output

numpy.float32

Hoạt động NDArray

Trong phần này, chúng tôi sẽ giới thiệu cho bạn các thao tác với mảng của MXNet. NDArray hỗ trợ số lượng lớn các phép toán tiêu chuẩn cũng như tại chỗ.

Các phép toán chuẩn

Sau đây là các phép toán tiêu chuẩn được hỗ trợ bởi NDArray:

Bổ sung nguyên tố khôn ngoan

Đầu tiên, chúng ta cần nhập MXNet và ndarray từ MXNet như sau:

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

Đầu ra được cung cấp kèm theo:

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

Phép nhân khôn ngoan nguyên tố

Example

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

Output

Bạn sẽ thấy kết quả sau

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

Luỹ thừa

Example

nd.exp(x)

Output

Khi bạn chạy mã, bạn sẽ thấy kết quả sau:

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

Chuyển vị ma trận để tính tích ma trận-ma trận

Example

nd.dot(x, y.T)

Output

Dưới đây là đầu ra của mã:

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

Hoạt động tại chỗ

Mỗi lần, trong ví dụ trên, chúng tôi chạy một hoạt động, chúng tôi cấp phát một bộ nhớ mới để lưu trữ kết quả của nó.

Ví dụ, nếu chúng ta viết A = A + B, chúng ta sẽ tham khảo ma trận mà A đã dùng để trỏ tới và thay vào đó trỏ nó vào bộ nhớ mới được cấp phát. Hãy để chúng tôi hiểu nó với ví dụ được đưa ra bên dưới, sử dụng hàm id () của Python -

print('y=', y)
print('id(y):', id(y))
y = y + x
print('after y=y+x, y=', y)
print('id(y):', id(y))

Output

Khi thực hiện, bạn sẽ nhận được kết quả sau:

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

Trên thực tế, chúng ta cũng có thể gán kết quả cho một mảng đã được phân bổ trước đó như sau:

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

Đầu ra được hiển thị bên dưới -

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

Từ kết quả trên, chúng ta có thể thấy rằng x + y vẫn sẽ cấp phát một bộ đệm tạm thời để lưu trữ kết quả trước khi sao chép nó vào z. Vì vậy, bây giờ, chúng ta có thể thực hiện các hoạt động tại chỗ để sử dụng bộ nhớ tốt hơn và tránh bộ đệm tạm thời. Để làm điều này, chúng tôi sẽ chỉ định đối số từ khóa out mà mọi toán tử hỗ trợ như sau:

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

Khi thực hiện chương trình trên, bạn sẽ nhận được kết quả sau:

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

Trong Apache MXNet, mỗi mảng có một ngữ cảnh và một ngữ cảnh có thể là CPU, trong khi các ngữ cảnh khác có thể là một số GPU. Mọi thứ thậm chí có thể trở nên tồi tệ nhất, khi chúng tôi triển khai công việc trên nhiều máy chủ. Đó là lý do tại sao, chúng ta cần gán các mảng cho các ngữ cảnh một cách thông minh. Nó sẽ giảm thiểu thời gian truyền dữ liệu giữa các thiết bị.

Ví dụ: hãy thử khởi tạo một mảng như sau:

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

Output

Khi bạn thực thi đoạn mã trên, bạn sẽ thấy kết quả sau:

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

Chúng ta có thể sao chép NDArray đã cho từ ngữ cảnh này sang ngữ cảnh khác bằng cách sử dụng phương thức copyto () như sau:

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

Mảng NumPy so với NDArray

Tất cả chúng ta đều quen thuộc với mảng NumPy nhưng Apache MXNet cung cấp triển khai mảng riêng có tên là NDArray. Trên thực tế, ban đầu nó được thiết kế tương tự như NumPy nhưng có một điểm khác biệt chính:

Sự khác biệt chính là cách tính toán được thực hiện trong NumPy và NDArray. Mọi thao tác NDArray trong MXNet đều được thực hiện theo cách không đồng bộ và không chặn, có nghĩa là khi chúng ta viết mã như c = a * b, hàm được đẩy đếnExecution Engine, sẽ bắt đầu tính toán.

Ở đây, a và b đều là NDArrays. Lợi ích của việc sử dụng nó là, hàm ngay lập tức trả về trở lại và luồng người dùng có thể tiếp tục thực thi mặc dù thực tế là phép tính trước đó có thể chưa được hoàn thành.

Hoạt động của công cụ thực thi

Nếu chúng ta nói về hoạt động của công cụ thực thi, nó sẽ xây dựng đồ thị tính toán. Biểu đồ tính toán có thể sắp xếp lại hoặc kết hợp một số phép tính, nhưng nó luôn tuân theo thứ tự phụ thuộc.

Ví dụ: nếu có các thao tác khác với 'X' được thực hiện sau này trong mã lập trình, thì Công cụ thực thi sẽ bắt đầu thực hiện chúng khi có kết quả là 'X'. Công cụ thực thi sẽ xử lý một số công việc quan trọng đối với người dùng, chẳng hạn như viết các lệnh gọi lại để bắt đầu thực thi mã tiếp theo.

Trong Apache MXNet, với sự trợ giúp của NDArray, để có được kết quả tính toán, chúng ta chỉ cần truy cập vào biến kết quả. Dòng mã sẽ bị chặn cho đến khi kết quả tính toán được gán cho biến kết quả. Bằng cách này, nó làm tăng hiệu suất mã trong khi vẫn hỗ trợ chế độ lập trình mệnh lệnh.

Chuyển đổi NDArray thành NumPy Array

Hãy để chúng tôi tìm hiểu cách chuyển NDArray thành NumPy Array trong MXNet.

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

Đôi khi, chúng ta có thể tập hợp toán tử cấp cao hơn bằng cách sử dụng các toán tử hiện có. Một trong những ví dụ tốt nhất về điều này là,np.full_like()toán tử này không có trong API NDArray. Nó có thể dễ dàng được thay thế bằng sự kết hợp của các toán tử hiện có như sau:

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

Chúng ta sẽ nhận được kết quả tương tự như sau:

True

Finding similar operator with different name and/or signature

Trong số tất cả các toán tử, một số trong số chúng có tên hơi khác nhau, nhưng chúng giống nhau về chức năng. Một ví dụ về điều này lànd.ravel_index() với np.ravel()chức năng. Theo cách tương tự, một số toán tử có thể có tên tương tự, nhưng chúng có chữ ký khác nhau. Một ví dụ về điều này lànp.split()nd.split() tương tự nhau.

Hãy hiểu nó với ví dụ lập trình sau:

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

Đầu ra được nêu dưới đây -

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

Giảm thiểu tác động của việc chặn cuộc gọi

Trong một số trường hợp, chúng ta phải sử dụng .asnumpy() hoặc là .asscalar()nhưng điều này sẽ buộc MXNet phải chặn việc thực thi, cho đến khi kết quả có thể được truy xuất. Chúng tôi có thể giảm thiểu tác động của việc chặn cuộc gọi bằng cách gọi.asnumpy() hoặc là .asscalar() tại thời điểm này, khi chúng tôi nghĩ rằng việc tính toán giá trị này đã được thực hiện.

Ví dụ triển khai

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

Kết quả được trích dẫn dưới đây:

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