- Apache MXNet 教程
- Apache MXNet - 主页
- Apache MXNet - 简介
- Apache MXNet - 安装 MXNet
- Apache MXNet - 工具包和生态系统
- Apache MXNet - 系统架构
- Apache MXNet - 系统组件
- Apache MXNet - 统一操作员 API
- Apache MXNet - 分布式训练
- Apache MXNet - Python 包
- Apache MXNet - NDArray
- Apache MXNet - Gluon
- Apache MXNet - KVStore 和可视化
- Apache MXNet - Python API ndarray
- Apache MXNet - Python API 胶子
- Apache MXNet - Python API 自动分级和初始化程序
- Apache MXNet - Python API 符号
- Apache MXNet - Python API 模块
- Apache MXNet 有用资源
- Apache MXNet - 快速指南
- Apache MXNet - 有用资源
- Apache MXNet - 讨论
Apache MXNet - Gluon
另一个最重要的 MXNet Python 包是 Gluon。在本章中,我们将讨论这个包。Gluon 为 DL 项目提供了清晰、简洁且简单的 API。它使 Apache MXNet 能够在不损失训练速度的情况下进行原型设计、构建和训练 DL 模型。
积木
块构成了更复杂网络设计的基础。在神经网络中,随着神经网络复杂性的增加,我们需要从设计单层神经元转向设计整层神经元。例如,像 ResNet-152 这样的神经网络设计由重复层块组成,具有非常公平的规律性。
例子
在下面给出的示例中,我们将编写一个简单块的代码,即多层感知器的块。
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)>
定制块
我们可以轻松地超越上面定义的与顺序块的串联。但是,如果我们想要进行自定义,那么Block类也为我们提供了所需的功能。Block 类在 nn 模块中提供了一个模型构造函数。我们可以继承该模型构造函数来定义我们想要的模型。
在以下示例中,MLP 类重写了Block 类的__init__和forward 函数。
让我们看看它是如何工作的。
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 中创建新层,我们必须创建一个继承自 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)
输出
执行上述程序,您将得到以下结果 -
[[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 通过优化计算符号图来提高计算性能。事实上,我们可能会发现,在实现现有层时,块继承自HybridBlock,而不是直接继承自Block。
以下是原因 -
允许我们编写自定义层: HybridBlock 允许我们编写自定义层,这些层可以进一步用于命令式和符号式编程。
提高计算性能- 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()))
输出
输出如下:
[0. 0.2 0.4 0.6 0.8 1. ] <NDArray 6 @cpu(0)>
混合与 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(用于符号式编程)。
如何向网络添加自定义层?
这些层不是单独使用自定义层,而是与预定义层一起使用。我们可以使用Sequential或HybridSequential容器来构建顺序神经网络。正如前面所讨论的,Sequential容器继承自 Block,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)
输出
您将看到以下输出 -
[[-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类存储和访问的,该类有助于初始化、更新、保存和加载它们。
例子
在下面的示例中,我们将定义以下两组参数 -
参数权重- 这是可训练的,并且其形状在构建阶段是未知的。它将在第一次运行前向传播时推断出来。
参数比例- 这是一个常数,其值不会改变。与参数权重相反,它的形状是在构造过程中定义的。
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