Apache MXNet - Gluon
Một gói MXNet Python quan trọng nhất khác là Gluon. Trong chương này, chúng ta sẽ thảo luận về gói này. Gluon cung cấp một API rõ ràng, ngắn gọn và đơn giản cho các dự án DL. Nó cho phép Apache MXNet tạo mẫu, xây dựng và đào tạo các mô hình DL mà không làm giảm tốc độ đào tạo.
Khối
Các khối tạo thành cơ sở cho các thiết kế mạng phức tạp hơn. Trong mạng nơ-ron, khi độ phức tạp của mạng nơ-ron tăng lên, chúng ta cần chuyển từ thiết kế từng lớp nơ-ron đơn lẻ sang toàn bộ lớp. Ví dụ, thiết kế NN như ResNet-152 có mức độ đều đặn rất công bằng bằng cách bao gồmblocks của các lớp lặp lại.
Thí dụ
Trong ví dụ dưới đây, chúng ta sẽ viết mã một khối đơn giản, cụ thể là khối cho một perceptron nhiều lớp.
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
Điều này tạo ra kết quả sau:
[[ 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)>
Các bước cần thiết để đi từ xác định các lớp đến xác định các khối của một hoặc nhiều lớp -
Step 1 - Khối lấy dữ liệu làm đầu vào.
Step 2- Bây giờ, các khối sẽ lưu trữ trạng thái dưới dạng tham số. Ví dụ, trong ví dụ mã hóa ở trên, khối chứa hai lớp ẩn và chúng ta cần một nơi để lưu trữ các tham số cho nó.
Step 3- Khối tiếp theo sẽ gọi hàm forward để thực hiện quá trình truyền tiến. Nó còn được gọi là tính toán chuyển tiếp. Là một phần của lệnh gọi chuyển tiếp đầu tiên, các khối khởi tạo các tham số theo kiểu lười biếng.
Step 4- Cuối cùng, các khối sẽ gọi hàm lùi và tính toán độ dốc với tham chiếu đến đầu vào của chúng. Thông thường, bước này được thực hiện tự động.
Khối tuần tự
Khối tuần tự là một loại khối đặc biệt, trong đó dữ liệu chảy qua một chuỗi khối. Trong cách này, mỗi khối được áp dụng cho đầu ra của một khối trước đó với khối đầu tiên được áp dụng cho chính dữ liệu đầu vào.
Hãy để chúng tôi xem làm thế nào sequential tác phẩm của lớp -
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
Đầu ra được cung cấp kèm theo:
[[ 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)>
Khối tùy chỉnh
Chúng ta có thể dễ dàng vượt ra ngoài phép nối với khối tuần tự như đã định nghĩa ở trên. Nhưng, nếu chúng tôi muốn thực hiện các tùy chỉnh thìBlocklớp cũng cung cấp cho chúng tôi các chức năng cần thiết. Lớp khối có một phương thức xây dựng mô hình được cung cấp trong mô-đun nn. Chúng ta có thể kế thừa hàm tạo mô hình đó để định nghĩa mô hình mà chúng ta muốn.
Trong ví dụ sau, MLP class ghi đè lên __init__ và các chức năng chuyển tiếp của lớp Block.
Hãy để chúng tôi xem nó hoạt động như thế nào.
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
Khi bạn chạy mã, bạn sẽ thấy kết quả sau:
[[ 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)>
Lớp tùy chỉnh
API Gluon của Apache MXNet đi kèm với một số lượng khiêm tốn các lớp được xác định trước. Nhưng vẫn còn ở một số điểm, chúng tôi có thể thấy rằng một lớp mới là cần thiết. Chúng ta có thể dễ dàng thêm một lớp mới trong API Gluon. Trong phần này, chúng ta sẽ xem cách chúng ta có thể tạo một lớp mới từ đầu.
Lớp tùy chỉnh đơn giản nhất
Để tạo một lớp mới trong API Gluon, chúng ta phải tạo một lớp kế thừa từ lớp Block cung cấp chức năng cơ bản nhất. Chúng ta có thể kế thừa tất cả các lớp được xác định trước từ nó trực tiếp hoặc thông qua các lớp con khác.
Để tạo lớp mới, phương thức phiên bản duy nhất cần được triển khai là forward (self, x). Phương thức này xác định, chính xác lớp của chúng ta sẽ làm gì trong quá trình truyền chuyển tiếp. Như đã thảo luận trước đó, quá trình truyền ngược cho các khối sẽ được chính Apache MXNet thực hiện tự động.
Thí dụ
Trong ví dụ dưới đây, chúng tôi sẽ xác định một lớp mới. Chúng tôi cũng sẽ thực hiệnforward() phương pháp chuẩn hóa dữ liệu đầu vào bằng cách phù hợp với phạm vi [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
Khi thực hiện chương trình trên, bạn sẽ nhận được kết quả sau:
[[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)>
Lai hóa
Nó có thể được định nghĩa là một quy trình được sử dụng bởi Apache MXNet để tạo một biểu đồ tượng trưng của một phép tính chuyển tiếp. Hybridisation cho phép MXNet tăng cường hiệu suất tính toán bằng cách tối ưu hóa biểu đồ tính toán tượng trưng. Thay vì kế thừa trực tiếp từBlock, trên thực tế, chúng ta có thể thấy rằng trong khi triển khai các lớp hiện có, một khối kế thừa từ HybridBlock.
Sau đây là những lý do cho điều này -
Allows us to write custom layers: HybridBlock cho phép chúng tôi viết các lớp tùy chỉnh có thể được sử dụng thêm trong cả lập trình mệnh lệnh và biểu tượng.
Increase computation performance- HybridBlock tối ưu hóa đồ thị biểu tượng tính toán cho phép MXNet tăng hiệu suất tính toán.
Thí dụ
Trong ví dụ này, chúng tôi sẽ viết lại lớp mẫu của chúng tôi, được tạo ở trên, bằng cách sử dụng 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
Đầu ra được nêu dưới đây:
[0. 0.2 0.4 0.6 0.8 1. ]
<NDArray 6 @cpu(0)>
Lai hóa không liên quan gì đến tính toán trên GPU và người ta có thể đào tạo các mạng lai cũng như không lai trên cả CPU và GPU.
Sự khác biệt giữa Block và HybridBlock
Nếu chúng ta sẽ so sánh Block Lớp và HybridBlock, chúng ta sẽ thấy rằng HybridBlock đã có nó forward() phương pháp thực hiện. HybridBlock xác định một hybrid_forward()phương pháp cần được thực hiện trong khi tạo các lớp. Đối số F tạo ra sự khác biệt chính giữaforward() và hybrid_forward(). Trong cộng đồng MXNet, đối số F được gọi là phụ trợ. F có thể tham khảomxnet.ndarray API (được sử dụng cho lập trình mệnh lệnh) hoặc mxnet.symbol API (dùng cho lập trình Symbolic).
Làm cách nào để thêm lớp tùy chỉnh vào mạng?
Thay vì sử dụng các lớp tùy chỉnh riêng biệt, các lớp này được sử dụng với các lớp được xác định trước. Chúng ta có thể sử dụngSequential hoặc là HybridSequentialvùng chứa từ một mạng nơ-ron tuần tự. Như đã thảo luận trước đó,Sequential vùng chứa kế thừa từ Block và HybridSequential Kế thừa từ HybridBlock tương ứng.
Thí dụ
Trong ví dụ dưới đây, chúng ta sẽ tạo một mạng nơ-ron đơn giản với một lớp tùy chỉnh. Đầu ra từDense (5) lớp sẽ là đầu vào của NormalizationHybridLayer. Đầu ra củaNormalizationHybridLayer sẽ trở thành đầu vào của Dense (1) lớp.
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
Bạn sẽ thấy kết quả sau:
[[-1.1272651]
[-1.2299833]
[-1.0662932]
[-1.1805027]
[-1.3382034]
[-1.2081106]
[-1.1263978]
[-1.2524893]
[-1.1044774]
[-1.316593 ]]
<NDArray 10x1 @cpu(0)>
Thông số lớp tùy chỉnh
Trong mạng nơron, một lớp có một tập hợp các tham số được liên kết với nó. Đôi khi chúng ta gọi chúng là trọng số, là trạng thái bên trong của một lớp. Các tham số này đóng các vai trò khác nhau -
Đôi khi đây là những thứ mà chúng ta muốn học trong bước nhân giống ngược.
Đôi khi đây chỉ là những hằng số mà chúng ta muốn sử dụng trong quá trình chuyển tiếp.
Nếu chúng ta nói về khái niệm lập trình, các tham số này (trọng số) của một khối được lưu trữ và truy cập thông qua ParameterDict lớp giúp khởi tạo, cập nhật, lưu và tải chúng.
Thí dụ
Trong ví dụ dưới đây, chúng tôi sẽ xác định hai bộ tham số sau:
Parameter weights- Đây là loại cây có thể huấn luyện được, và hình dạng của nó không được biết trong giai đoạn xây dựng. Nó sẽ được suy ra trong lần truyền đầu tiên.
Parameter scale- Đây là một hằng số có giá trị không thay đổi. Ngược lại với trọng số tham số, hình dạng của nó được xác định trong quá trình xây dựng.
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