Apache MXNet - NDArray

ในบทนี้เราจะพูดถึงรูปแบบอาร์เรย์หลายมิติของ MXNet ที่เรียกว่า ndarray.

การจัดการข้อมูลด้วย NDArray

ขั้นแรกเราจะมาดูกันว่าเราจะจัดการข้อมูลด้วย NDArray ได้อย่างไร ต่อไปนี้เป็นข้อกำหนดเบื้องต้นสำหรับสิ่งเดียวกัน -

ข้อกำหนดเบื้องต้น

เพื่อให้เข้าใจว่าเราสามารถจัดการข้อมูลด้วยรูปแบบอาร์เรย์หลายมิตินี้ได้อย่างไรเราจำเป็นต้องปฏิบัติตามข้อกำหนดเบื้องต้นต่อไปนี้:

  • MXNet ติดตั้งในสภาพแวดล้อม Python

  • Python 2.7.x หรือ Python 3.x

ตัวอย่างการใช้งาน

ให้เราเข้าใจฟังก์ชันพื้นฐานด้วยความช่วยเหลือของตัวอย่างด้านล่าง -

ขั้นแรกเราต้องนำเข้า MXNet และ ndarray จาก MXNet ดังนี้ -

import mxnet as mx
from mxnet import nd

เมื่อเรานำเข้าไลบรารีที่จำเป็นเราจะไปกับฟังก์ชันพื้นฐานดังต่อไปนี้:

อาร์เรย์ 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)>

อาร์เรย์ 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)>

เมทริกซ์ของศูนย์ทั้งหมดที่มีฟังก์ชัน. seros

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 รองรับการดำเนินการทางคณิตศาสตร์มาตรฐานและ In-place จำนวนมาก

การดำเนินการทางคณิตศาสตร์มาตรฐาน

ต่อไปนี้คือการดำเนินการทางคณิตศาสตร์มาตรฐานที่รองรับโดย 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 ใช้ชี้ไปและชี้ไปที่หน่วยความจำที่จัดสรรใหม่แทน ให้เราทำความเข้าใจกับตัวอย่างด้านล่างโดยใช้ฟังก์ชัน id () ของ 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

เมื่อดำเนินการคุณจะได้รับผลลัพธ์ต่อไปนี้ -

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 ตอนนี้เราสามารถดำเนินการแทนเพื่อใช้หน่วยความจำได้ดีขึ้นและเพื่อหลีกเลี่ยงบัฟเฟอร์ชั่วคราว ในการดำเนินการนี้เราจะระบุอาร์กิวเมนต์คำหลักทุกตัวรองรับดังนี้ -

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 แต่ละอาร์เรย์มีบริบทและบริบทหนึ่งอาจเป็น 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)>

เราสามารถคัดลอก NDArray ที่กำหนดจากบริบทหนึ่งไปยังอีกบริบทหนึ่งโดยใช้เมธอด copyto () ดังนี้ -

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

อาร์เรย์ NumPy เทียบกับ NDArray

เราทุกคนคุ้นเคยกับอาร์เรย์ NumPy แต่ Apache MXNet มีการใช้งานอาร์เรย์ของตัวเองชื่อ NDArray จริงๆแล้วในตอนแรกถูกออกแบบมาให้คล้ายกับ NumPy แต่มีข้อแตกต่างที่สำคัญ -

ความแตกต่างที่สำคัญคือวิธีดำเนินการคำนวณใน NumPy และ NDArray ทุกการจัดการ NDArray ใน MXNet จะทำในแบบอะซิงโครนัสและไม่ปิดกั้นซึ่งหมายความว่าเมื่อเราเขียนโค้ดเช่น c = a * b ฟังก์ชันจะถูกผลักไปที่Execution Engineซึ่งจะเริ่มการคำนวณ

ที่นี่ a และ b ทั้งสองคือ NDArrays ประโยชน์ของการใช้ฟังก์ชันนี้คือฟังก์ชันจะส่งกลับทันทีและเธรดผู้ใช้สามารถดำเนินการต่อได้แม้ว่าการคำนวณก่อนหน้านี้อาจยังไม่เสร็จสมบูรณ์

การทำงานของ Execution Engine

ถ้าเราพูดถึงการทำงานของกลไกการสั่งการมันจะสร้างกราฟการคำนวณ กราฟการคำนวณอาจเรียงลำดับใหม่หรือรวมการคำนวณบางอย่างเข้าด้วยกัน แต่จะให้เกียรติลำดับการอ้างอิงเสมอ

ตัวอย่างเช่นหากมีการจัดการอื่น ๆ ด้วย 'X' ที่ทำในภายหลังในโค้ดการเขียนโปรแกรม Execution Engine จะเริ่มดำเนินการเมื่อผลลัพธ์ของ 'X' พร้อมใช้งาน Execution Engine จะจัดการงานที่สำคัญบางอย่างสำหรับผู้ใช้เช่นการเขียน callback เพื่อเริ่มการเรียกใช้โค้ดที่ตามมา

ใน Apache MXNet ด้วยความช่วยเหลือของ NDArray เพื่อให้ได้ผลลัพธ์ของการคำนวณเราจำเป็นต้องเข้าถึงตัวแปรที่เป็นผลลัพธ์เท่านั้น การไหลของรหัสจะถูกบล็อกจนกว่าผลการคำนวณจะถูกกำหนดให้กับตัวแปรที่เป็นผลลัพธ์ ด้วยวิธีนี้จะเพิ่มประสิทธิภาพของโค้ดในขณะที่ยังคงรองรับโหมดการเขียนโปรแกรมที่จำเป็น

การแปลง NDArray เป็น NumPy Array

ให้เราเรียนรู้ว่าเราจะแปลง NDArray เป็น NumPy Array ใน MXNet ได้อย่างไร

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

ลดผลกระทบของการบล็อกการโทร

ในบางกรณีเราต้องใช้อย่างใดอย่างหนึ่ง .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