《动手学深度学习》
Table Of Contents
《动手学深度学习》
Table Of Contents

自定义层

深度学习的一个魅力在于神经网络中各式各样的层,例如全连接层和后面章节中将要介绍的卷积层、池化层与循环层。虽然 Gluon 提供了大量常用的层,但有时候我们依然希望自定义层。本节将介绍如何使用 NDArray 来自定义一个 Gluon 的层,从而可以被重复调用。

不含模型参数的自定义层

我们先介绍如何定义一个不含模型参数的自定义层。事实上,这和“模型构造”一节中介绍的使用 Block 类构造模型类似。以下的CenteredLayer类通过继承 Block 类自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了forward函数里。这个层里不含模型参数。

In [1]:
from mxnet import gluon, nd
from mxnet.gluon import nn

class CenteredLayer(nn.Block):
    def __init__(self, **kwargs):
        super(CenteredLayer, self).__init__(**kwargs)

    def forward(self, x):
        return x - x.mean()

我们可以实例化这个层,然后做前向计算。

In [2]:
layer = CenteredLayer()
layer(nd.array([1, 2, 3, 4, 5]))
Out[2]:

[-2. -1.  0.  1.  2.]
<NDArray 5 @cpu(0)>

我们也可以用它来构造更复杂的模型。

In [3]:
net = nn.Sequential()
net.add(nn.Dense(128),
        CenteredLayer())

下面打印自定义层各个输出的均值。由于均值是浮点数,它的值是个很接近 0 的数。

In [4]:
net.initialize()
y = net(nd.random.uniform(shape=(4, 8)))
y.mean().asscalar()
Out[4]:
-7.212293e-10

含模型参数的自定义层

我们还可以自定义含模型参数的自定义层。其中的模型参数可以通过训练学出。

“模型参数的访问、初始化和共享”一节里分别介绍了Parameter类和ParameterDict类。在自定义含模型参数的层时,我们可以利用 Block 类自带的ParameterDict类型的成员变量params。它是一个由字符串类型的参数名字映射到 Parameter 类型的模型参数的字典。我们可以通过get函数从ParameterDict创建Parameter实例。

In [5]:
params = gluon.ParameterDict()
params.get('param2', shape=(2, 3))
params
Out[5]:
(
  Parameter param2 (shape=(2, 3), dtype=<class 'numpy.float32'>)
)

现在我们尝试实现一个含权重参数和偏差参数的全连接层。它使用 ReLU 作为激活函数。其中in_unitsunits分别是输入个数和输出个数。

In [6]:
class MyDense(nn.Block):
    # units:该层的输出个数;in_units:该层的输入个数。
    def __init__(self, units, in_units, **kwargs):
        super(MyDense, self).__init__(**kwargs)
        self.weight = self.params.get('weight', shape=(in_units, units))
        self.bias = self.params.get('bias', shape=(units,))

    def forward(self, x):
        linear = nd.dot(x, self.weight.data()) + self.bias.data()
        return nd.relu(linear)

下面,我们实例化MyDense类并访问它的模型参数。

In [7]:
dense = MyDense(units=3, in_units=5)
dense.params
Out[7]:
mydense0_ (
  Parameter mydense0_weight (shape=(5, 3), dtype=<class 'numpy.float32'>)
  Parameter mydense0_bias (shape=(3,), dtype=<class 'numpy.float32'>)
)

我们可以直接使用自定义层做前向计算。

In [8]:
dense.initialize()
dense(nd.random.uniform(shape=(2, 5)))
Out[8]:

[[0.06917784 0.01627153 0.01029644]
 [0.02602214 0.0453731  0.        ]]
<NDArray 2x3 @cpu(0)>

我们也可以使用自定义层构造模型。它和 Gluon 的其他层在使用上很类似。

In [9]:
net = nn.Sequential()
net.add(MyDense(8, in_units=64),
        MyDense(1, in_units=8))
net.initialize()
net(nd.random.uniform(shape=(2, 64)))
Out[9]:

[[0.03820474]
 [0.04035058]]
<NDArray 2x1 @cpu(0)>

小结

  • 我们可以通过 Block 类自定义神经网络中的层,从而可以被重复调用。

练习

  • 自定义一个层,使用它做一次前向计算。

扫码直达讨论区

image0