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