Apache MXNet - กลูออน
แพ็คเกจ MXNet Python ที่สำคัญที่สุดอีกอย่างคือ Gluon ในบทนี้เราจะพูดถึงแพ็คเกจนี้ Gluon มี API ที่ชัดเจนกระชับและเรียบง่ายสำหรับโครงการ DL ช่วยให้ Apache MXNet สร้างต้นแบบสร้างและฝึกโมเดล DL โดยไม่เสียความเร็วในการฝึกอบรม
บล็อก
บล็อกเป็นพื้นฐานของการออกแบบเครือข่ายที่ซับซ้อนมากขึ้น ในเครือข่ายประสาทเทียมเมื่อความซับซ้อนของเครือข่ายประสาทเพิ่มขึ้นเราจำเป็นต้องเปลี่ยนจากการออกแบบเซลล์ประสาทชั้นเดียวไปสู่ทั้งชั้น ตัวอย่างเช่นการออกแบบ NN เช่น ResNet-152 มีระดับความสม่ำเสมอที่ยุติธรรมมากโดยประกอบด้วยblocks ของเลเยอร์ซ้ำ
ตัวอย่าง
ในตัวอย่างด้านล่างนี้เราจะเขียนโค้ดเป็นบล็อกง่ายๆคือบล็อกสำหรับเพอร์เซปตรอนหลายชั้น
from mxnet import nd
from mxnet.gluon import nn
x = nd.random.uniform(shape=(2, 20))
N_net = nn.Sequential()
N_net.add(nn.Dense(256, activation='relu'))
N_net.add(nn.Dense(10))
N_net.initialize()
N_net(x)
Output
สิ่งนี้สร้างผลลัพธ์ต่อไปนี้:
[[ 0.09543004 0.04614332 -0.00286655 -0.07790346 -0.05130241 0.02942038
0.08696645 -0.0190793 -0.04122177 0.05088576]
[ 0.0769287 0.03099706 0.00856576 -0.044672 -0.06926838 0.09132431
0.06786592 -0.06187843 -0.03436674 0.04234696]]
<NDArray 2x10 @cpu(0)>
ขั้นตอนที่จำเป็นในการเริ่มต้นตั้งแต่การกำหนดเลเยอร์ไปจนถึงการกำหนดบล็อคของเลเยอร์อย่างน้อยหนึ่งเลเยอร์ -
Step 1 - บล็อกรับข้อมูลเป็นอินพุต
Step 2- ตอนนี้บล็อกจะเก็บสถานะในรูปแบบของพารามิเตอร์ ตัวอย่างเช่นในตัวอย่างการเข้ารหัสด้านบนบล็อกมีเลเยอร์ที่ซ่อนอยู่สองชั้นและเราต้องการสถานที่สำหรับจัดเก็บพารามิเตอร์
Step 3- บล็อกถัดไปจะเรียกใช้ฟังก์ชันไปข้างหน้าเพื่อดำเนินการเผยแพร่ไปข้างหน้า เรียกอีกอย่างว่าการคำนวณล่วงหน้า ในฐานะที่เป็นส่วนหนึ่งของการโอนสายครั้งแรกบล็อกเริ่มต้นพารามิเตอร์ในลักษณะขี้เกียจ
Step 4- ในที่สุดบล็อกจะเรียกใช้ฟังก์ชันย้อนกลับและคำนวณการไล่ระดับสีโดยอ้างอิงกับข้อมูลที่ป้อน โดยปกติขั้นตอนนี้จะดำเนินการโดยอัตโนมัติ
บล็อกตามลำดับ
บล็อกตามลำดับคือบล็อกชนิดพิเศษที่ข้อมูลไหลผ่านลำดับของบล็อก ในสิ่งนี้แต่ละบล็อกจะนำไปใช้กับเอาต์พุตของหนึ่งก่อนโดยบล็อกแรกจะถูกนำไปใช้กับข้อมูลอินพุตเอง
ให้เราดูว่า sequential ผลงานในชั้นเรียน -
from mxnet import nd
from mxnet.gluon import nn
class MySequential(nn.Block):
def __init__(self, **kwargs):
super(MySequential, self).__init__(**kwargs)
def add(self, block):
self._children[block.name] = block
def forward(self, x):
for block in self._children.values():
x = block(x)
return x
x = nd.random.uniform(shape=(2, 20))
N_net = MySequential()
N_net.add(nn.Dense(256, activation
='relu'))
N_net.add(nn.Dense(10))
N_net.initialize()
N_net(x)
Output
ผลลัพธ์จะได้รับในที่นี้ -
[[ 0.09543004 0.04614332 -0.00286655 -0.07790346 -0.05130241 0.02942038
0.08696645 -0.0190793 -0.04122177 0.05088576]
[ 0.0769287 0.03099706 0.00856576 -0.044672 -0.06926838 0.09132431
0.06786592 -0.06187843 -0.03436674 0.04234696]]
<NDArray 2x10 @cpu(0)>
บล็อกที่กำหนดเอง
เราสามารถไปไกลกว่าการเชื่อมต่อกับบล็อกลำดับตามที่กำหนดไว้ข้างต้นได้อย่างง่ายดาย แต่ถ้าเราต้องการปรับแต่งไฟล์Blockคลาสยังมีฟังก์ชันการทำงานที่จำเป็น คลาสบล็อกมีตัวสร้างโมเดลที่จัดเตรียมไว้ในโมดูล nn เราสามารถสืบทอดตัวสร้างโมเดลนั้นเพื่อกำหนดโมเดลที่เราต้องการ
ในตัวอย่างต่อไปนี้ไฟล์ MLP class ลบล้างไฟล์ __init__ และส่งต่อฟังก์ชันของคลาส Block
ให้เราดูว่ามันทำงานอย่างไร
class MLP(nn.Block):
def __init__(self, **kwargs):
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu') # Hidden layer
self.output = nn.Dense(10) # Output layer
def forward(self, x):
hidden_out = self.hidden(x)
return self.output(hidden_out)
x = nd.random.uniform(shape=(2, 20))
N_net = MLP()
N_net.initialize()
N_net(x)
Output
เมื่อคุณเรียกใช้รหัสคุณจะเห็นผลลัพธ์ต่อไปนี้:
[[ 0.07787763 0.00216403 0.01682201 0.03059879 -0.00702019 0.01668715
0.04822846 0.0039432 -0.09300035 -0.04494302]
[ 0.08891078 -0.00625484 -0.01619131 0.0380718 -0.01451489 0.02006172
0.0303478 0.02463485 -0.07605448 -0.04389168]]
<NDArray 2x10 @cpu(0)>
เลเยอร์ที่กำหนดเอง
Gluon API ของ Apache MXNet มาพร้อมกับเลเยอร์ที่กำหนดไว้ล่วงหน้าจำนวนพอประมาณ แต่ในบางจุดเราอาจพบว่าจำเป็นต้องมีเลเยอร์ใหม่ เราสามารถเพิ่มเลเยอร์ใหม่ใน Gluon API ได้อย่างง่ายดาย ในส่วนนี้เราจะดูว่าเราสามารถสร้างเลเยอร์ใหม่ตั้งแต่เริ่มต้นได้อย่างไร
Custom Layer ที่ง่ายที่สุด
ในการสร้างเลเยอร์ใหม่ใน Gluon API เราต้องสร้างคลาสที่สืบทอดมาจากคลาส Block ซึ่งมีฟังก์ชันพื้นฐานที่สุด เราสามารถสืบทอดเลเยอร์ที่กำหนดไว้ล่วงหน้าทั้งหมดจากมันโดยตรงหรือผ่านคลาสย่อยอื่น ๆ
สำหรับการสร้างเลเยอร์ใหม่วิธีการอินสแตนซ์เดียวที่จำเป็นในการดำเนินการคือ forward (self, x). วิธีนี้กำหนดว่าเลเยอร์ของเรากำลังจะทำอะไรระหว่างการเผยแพร่ไปข้างหน้า ดังที่ได้กล่าวไว้ก่อนหน้านี้เช่นกันการส่งผ่านกลับสำหรับบล็อกจะทำโดย Apache MXNet เองโดยอัตโนมัติ
ตัวอย่าง
ในตัวอย่างด้านล่างเราจะกำหนดเลเยอร์ใหม่ นอกจากนี้เรายังจะดำเนินการforward() วิธีการปรับข้อมูลอินพุตให้เป็นปกติโดยปรับให้พอดีกับช่วง [0, 1]
from __future__ import print_function
import mxnet as mx
from mxnet import nd, gluon, autograd
from mxnet.gluon.nn import Dense
mx.random.seed(1)
class NormalizationLayer(gluon.Block):
def __init__(self):
super(NormalizationLayer, self).__init__()
def forward(self, x):
return (x - nd.min(x)) / (nd.max(x) - nd.min(x))
x = nd.random.uniform(shape=(2, 20))
N_net = NormalizationLayer()
N_net.initialize()
N_net(x)
Output
ในการรันโปรแกรมข้างต้นคุณจะได้รับผลลัพธ์ดังต่อไปนี้ -
[[0.5216355 0.03835821 0.02284337 0.5945146 0.17334817 0.69329053
0.7782702 1. 0.5508242 0. 0.07058554 0.3677264
0.4366546 0.44362497 0.7192635 0.37616986 0.6728799 0.7032008
0.46907538 0.63514024]
[0.9157533 0.7667402 0.08980197 0.03593295 0.16176797 0.27679572
0.07331014 0.3905285 0.6513384 0.02713427 0.05523694 0.12147208
0.45582628 0.8139887 0.91629887 0.36665893 0.07873632 0.78268915
0.63404864 0.46638715]]
<NDArray 2x20 @cpu(0)>
การผสมพันธุ์
อาจถูกกำหนดให้เป็นกระบวนการที่ Apache MXNet ใช้ในการสร้างกราฟสัญลักษณ์ของการคำนวณล่วงหน้า Hybridisation ช่วยให้ MXNet เพิ่มประสิทธิภาพการคำนวณโดยการปรับกราฟสัญลักษณ์เชิงคำนวณให้เหมาะสม แทนที่จะสืบทอดโดยตรงจากBlockในความเป็นจริงเราอาจพบว่าในขณะที่ใช้เลเยอร์ที่มีอยู่บล็อกจะสืบทอดมาจากไฟล์ HybridBlock.
เหตุผลดังต่อไปนี้ -
Allows us to write custom layers: HybridBlock ช่วยให้เราสามารถเขียนเลเยอร์ที่กำหนดเองซึ่งสามารถใช้เพิ่มเติมในการเขียนโปรแกรมที่จำเป็นและเชิงสัญลักษณ์ได้
Increase computation performance- HybridBlock ปรับกราฟสัญลักษณ์การคำนวณให้เหมาะสมซึ่งช่วยให้ MXNet เพิ่มประสิทธิภาพการคำนวณ
ตัวอย่าง
ในตัวอย่างนี้เราจะเขียนเลเยอร์ตัวอย่างของเราขึ้นมาใหม่โดยใช้ HybridBlock:
class NormalizationHybridLayer(gluon.HybridBlock):
def __init__(self):
super(NormalizationHybridLayer, self).__init__()
def hybrid_forward(self, F, x):
return F.broadcast_div(F.broadcast_sub(x, F.min(x)), (F.broadcast_sub(F.max(x), F.min(x))))
layer_hybd = NormalizationHybridLayer()
layer_hybd(nd.array([1, 2, 3, 4, 5, 6], ctx=mx.cpu()))
Output
ผลลัพธ์ระบุไว้ด้านล่าง:
[0. 0.2 0.4 0.6 0.8 1. ]
<NDArray 6 @cpu(0)>
Hybridisation ไม่มีส่วนเกี่ยวข้องกับการคำนวณบน GPU และสามารถฝึกเครือข่ายแบบไฮบริดได้เช่นเดียวกับเครือข่ายที่ไม่ใช่ไฮบริดสลีทั้งบน CPU และ GPU
ความแตกต่างระหว่าง Block และ HybridBlock
ถ้าเราจะเปรียบเทียบ Block ชั้นเรียนและ HybridBlockเราจะเห็นว่า HybridBlock มีไฟล์ forward() ใช้วิธีการ HybridBlock กำหนด hybrid_forward()วิธีการที่ต้องดำเนินการในขณะที่สร้างเลเยอร์ F อาร์กิวเมนต์สร้างความแตกต่างหลักระหว่างforward() และ hybrid_forward(). ในชุมชน MXNet อาร์กิวเมนต์ F เรียกว่าแบ็กเอนด์ F สามารถอ้างถึงmxnet.ndarray API (ใช้สำหรับการเขียนโปรแกรมที่จำเป็น) หรือ mxnet.symbol API (ใช้สำหรับการเขียนโปรแกรม Symbolic)
จะเพิ่มเลเยอร์ที่กำหนดเองในเครือข่ายได้อย่างไร?
แทนที่จะใช้เลเยอร์ที่กำหนดเองแยกต่างหากเลเยอร์เหล่านี้จะใช้กับเลเยอร์ที่กำหนดไว้ล่วงหน้า เราสามารถใช้อย่างใดอย่างหนึ่งSequential หรือ HybridSequentialคอนเทนเนอร์ไปยังจากโครงข่ายประสาทแบบต่อเนื่อง ตามที่กล่าวไว้ก่อนหน้านี้ยังSequential คอนเทนเนอร์สืบทอดจากบล็อกและ HybridSequential สืบทอดจาก HybridBlock ตามลำดับ
ตัวอย่าง
ในตัวอย่างด้านล่างเราจะสร้างโครงข่ายประสาทเทียมแบบง่ายๆด้วยเลเยอร์ที่กำหนดเอง ผลลัพธ์จากDense (5) เลเยอร์จะเป็นอินพุตของ NormalizationHybridLayer. ผลลัพธ์ของNormalizationHybridLayer จะกลายเป็นอินพุตของ Dense (1) ชั้น.
net = gluon.nn.HybridSequential()
with net.name_scope():
net.add(Dense(5))
net.add(NormalizationHybridLayer())
net.add(Dense(1))
net.initialize(mx.init.Xavier(magnitude=2.24))
net.hybridize()
input = nd.random_uniform(low=-10, high=10, shape=(10, 2))
net(input)
Output
คุณจะเห็นผลลัพธ์ต่อไปนี้ -
[[-1.1272651]
[-1.2299833]
[-1.0662932]
[-1.1805027]
[-1.3382034]
[-1.2081106]
[-1.1263978]
[-1.2524893]
[-1.1044774]
[-1.316593 ]]
<NDArray 10x1 @cpu(0)>
พารามิเตอร์เลเยอร์ที่กำหนดเอง
ในโครงข่ายประสาทชั้นหนึ่งมีชุดพารามิเตอร์ที่เกี่ยวข้อง บางครั้งเราเรียกมันว่าน้ำหนักซึ่งเป็นสถานะภายในของเลเยอร์ พารามิเตอร์เหล่านี้มีบทบาทแตกต่างกัน -
บางครั้งสิ่งเหล่านี้คือสิ่งที่เราต้องการเรียนรู้ระหว่างขั้นตอนการย้อนกลับ
บางครั้งค่าเหล่านี้เป็นเพียงค่าคงที่ที่เราต้องการใช้ระหว่างส่งต่อ
หากเราพูดถึงแนวคิดการเขียนโปรแกรมพารามิเตอร์เหล่านี้ (น้ำหนัก) ของบล็อกจะถูกจัดเก็บและเข้าถึงผ่านทาง ParameterDict คลาสที่ช่วยในการเริ่มต้นการอัพเดตการบันทึกและการโหลด
ตัวอย่าง
ในตัวอย่างด้านล่างเราจะกำหนดพารามิเตอร์สองชุดต่อไปนี้ -
Parameter weights- สิ่งนี้สามารถฝึกได้และไม่ทราบรูปร่างระหว่างขั้นตอนการก่อสร้าง จะสรุปได้จากการแพร่กระจายไปข้างหน้าครั้งแรก
Parameter scale- นี่คือค่าคงที่ซึ่งค่าไม่เปลี่ยนแปลง ตรงข้ามกับน้ำหนักพารามิเตอร์รูปร่างของมันถูกกำหนดระหว่างการก่อสร้าง
class NormalizationHybridLayer(gluon.HybridBlock):
def __init__(self, hidden_units, scales):
super(NormalizationHybridLayer, self).__init__()
with self.name_scope():
self.weights = self.params.get('weights',
shape=(hidden_units, 0),
allow_deferred_init=True)
self.scales = self.params.get('scales',
shape=scales.shape,
init=mx.init.Constant(scales.asnumpy()),
differentiable=False)
def hybrid_forward(self, F, x, weights, scales):
normalized_data = F.broadcast_div(F.broadcast_sub(x, F.min(x)),
(F.broadcast_sub(F.max(x), F.min(x))))
weighted_data = F.FullyConnected(normalized_data, weights, num_hidden=self.weights.shape[0], no_bias=True)
scaled_data = F.broadcast_mul(scales, weighted_data)
return scaled_data