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