Apache MXNet - Gluon



另一个最重要的 MXNet Python 包是 Gluon。在本章中,我们将讨论这个包。Gluon 为 DL 项目提供了清晰、简洁和简单的 API。它使 Apache MXNet 能够在不牺牲训练速度的情况下对 DL 模型进行原型设计、构建和训练。

块构成了更复杂网络设计的基础。在神经网络中,随着神经网络复杂性的增加,我们需要从设计单个神经元转向设计整个神经元层。例如,像 ResNet-152 这样的 NN 设计通过由组成的重复层具有相当程度的规律性。

示例

在下面给出的示例中,我们将编写一个简单块的代码,即多层感知机的块。

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)

输出

这将产生以下输出

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

从定义层到定义一个或多个层的块所需步骤 -

步骤 1 - 块将数据作为输入。

步骤 2 - 现在,块将以参数的形式存储状态。例如,在上面的编码示例中,块包含两个隐藏层,我们需要一个地方来存储它的参数。

步骤 3 - 接下来,块将调用前向函数来执行前向传播。它也称为前向计算。作为第一次前向调用的部分,块以延迟的方式初始化参数。

步骤 4 - 最后,块将调用反向函数并计算相对于其输入的梯度。通常,此步骤会自动执行。

顺序块

顺序块是一种特殊的块,其中数据流经一系列块。在这里,每个块都应用于前一个块的输出,第一个块应用于输入数据本身。

让我们看看顺序类是如何工作的 -

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)

输出

输出如下所示 -

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

自定义块

我们可以轻松地超越上面定义的顺序块的串联。但是,如果我们想进行自定义,那么类也为我们提供了所需的功能。块类有一个在 nn 模块中提供的模型构造函数。我们可以继承该模型构造函数来定义我们想要的模型。

在下面的示例中,MLP 类覆盖了块类的__init__和前向函数。

让我们看看它是如何工作的。

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)

输出

运行代码后,您将看到以下输出

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

自定义层

Apache MXNet 的 Gluon API 带有少量预定义层。但即使如此,在某些时候,我们也可能会发现需要一个新层。我们可以轻松地在 Gluon API 中添加一个新层。在本节中,我们将了解如何从头开始创建新层。

最简单的自定义层

要在 Gluon API 中创建新层,我们必须创建一个继承自块类的类,该类提供最基本的功能。我们可以直接或通过其他子类继承所有预定义层。

为了创建新层,唯一需要实现的实例方法是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)

输出

执行上述程序后,您将获得以下结果 -

[[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 用于创建前向计算的符号图的过程。混合允许 MXNet 通过优化计算符号图来提高计算性能。实际上,我们可能会发现,在实现现有层时,块继承自混合块,而不是直接继承自

以下是这样做的原因 -

  • 允许我们编写自定义层:混合块允许我们编写自定义层,这些层可以进一步用于命令式编程和符号式编程。

  • 提高计算性能- 混合块优化计算符号图,这使得 MXNet 能够提高计算性能。

示例

在这个例子中,我们将使用混合块重写上面创建的示例层

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

输出

输出如下所示

[0. 0.2 0.4 0.6 0.8 1. ]
<NDArray 6 @cpu(0)>

混合与 GPU 上的计算无关,可以在 CPU 和 GPU 上训练混合网络和非混合网络。

块和混合块的区别

如果我们比较类和混合块,我们会发现混合块已经实现了它的forward()方法。混合块定义了一个需要在创建层时实现的hybrid_forward()方法。F 参数是forward()hybrid_forward()之间主要的区别。在 MXNet 社区中,F 参数被称为后端。F 可以指mxnet.ndarray API(用于命令式编程)或mxnet.symbol API(用于符号式编程)。

如何将自定义层添加到网络?

这些层不是单独使用,而是与预定义层一起使用。我们可以使用顺序混合顺序容器来形成一个顺序神经网络。如前所述,顺序容器继承自块,而混合顺序分别继承自混合块

示例

在下面的示例中,我们将创建一个具有自定义层的基本神经网络。来自密集 (5)层的输出将成为标准化混合层的输入。标准化混合层的输出将成为密集 (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)

输出

您将看到以下输出 -

[[-1.1272651]
 [-1.2299833]
 [-1.0662932]
 [-1.1805027]
 [-1.3382034]
 [-1.2081106]
 [-1.1263978]
 [-1.2524893]
 
 [-1.1044774]

 [-1.316593 ]]
<NDArray 10x1 @cpu(0)>

自定义层参数

在神经网络中,一个层有一组与其关联的参数。我们有时将它们称为权重,它是层的内部状态。这些参数扮演不同的角色 -

  • 有时这些是我们希望在反向传播步骤中学习的参数。

  • 有时这些只是我们在前向传递过程中想要使用的常量。

如果我们谈论编程概念,块的这些参数(权重)通过参数字典类存储和访问,这有助于它们的初始化、更新、保存和加载。

示例

在下面的示例中,我们将定义以下两组参数 -

  • 参数权重- 这是可训练的,并且其形状在构造阶段是未知的。它将在第一次运行前向传播时推断出来。

  • 参数比例- 这是一个其值不会改变的常数。与参数权重相反,它的形状在构造期间定义。

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
广告