全部开发者教程

TensorFlow 入门教程

Python / 在 TensorFlow 之中自定义网络层与模型

在 TensorFlow 之中自定义网络层与模型

在上一节之中,我们学习了如何进行微分操作以及梯度的求导。那么这节课我们便开始自定义的下一步 —— 自定义网络层与模型。

这节课主要分为两个大部分进行讲解,它们分别是:

  • 自定义网络层;
  • 自定义模型。

我们会分别对这两个方面进行讲解。而网络层是模型的基础,因此我们会对网络层进行着重的讲解。

1. 如何自定义网络层

在 TensorFlow 之中,我们目前一般采用创建 keras 层的方式来进行网络层的构建,因此,我们的创建步骤大致分为以下几步:

  • 定义网络层的类并继承 tf.keras.layers.Layer 类;
  • 在初始化方法或者 build 方法之中定义网络的参数;
  • 在 call 函数之中编写具体的处理流程。

在第二步之中,我们可以在初始化或者 build 方法之中定义网络的参数,两者的效果在目前的教程之中时一样的,因此为了简单起见,我们统一在初始化函数之中定义我们的网络参数。

以下是一个简单的例子:

import tensorflow as tf

class MyLayer(tf.keras.layers.Layer):
    def __init__(self, hidden_units, input_units):
        super(MyLayer, self).__init__()
        self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
        self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal")

    def call(self, inputs):
        return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b

在这个例子之中,我们定义了一个网络层,该网络层的数学形式为:

y = w*(x**2) + b

我们让 x 进行平方之后乘上一个系数 w ,然后加上一个常数项 b 。

通过代码我们可以发现,我们在初始化函数之中进行了参数的初始化操作,然后再在 call 方式之中进行了具体的计算。

对于添加参数,我们最常用的方式是采用 add_weight 方法来进行的;该方法最常用的参数有两个:

  • shape: 表示该参数的形状,比如 3x3 等;
  • initializer: 初始化器,一般为 “zeros”(初始化为 0),或者 “random_normal”(随机初始化)。

我们可以通过具体的数据进行查看:

x = tf.ones((5, 5))
my_layer = MyLayer(10, 5)
y = my_layer(x)
print(y)

输出为:

tf.Tensor(
[[ 0.5975663   0.46636996 -0.4837241   0.3024946   0.33699942  0.1420103
   0.07284987 -0.5888218   0.13172552  0.01993698]
 [ 0.5975663   0.46636996 -0.4837241   0.3024946   0.33699942  0.1420103
   0.07284987 -0.5888218   0.13172552  0.01993698]
 [ 0.5975663   0.46636996 -0.4837241   0.3024946   0.33699942  0.1420103
   0.07284987 -0.5888218   0.13172552  0.01993698]
 [ 0.5975663   0.46636996 -0.4837241   0.3024946   0.33699942  0.1420103
   0.07284987 -0.5888218   0.13172552  0.01993698]
 [ 0.5975663   0.46636996 -0.4837241   0.3024946   0.33699942  0.1420103
   0.07284987 -0.5888218   0.13172552  0.01993698]], shape=(5, 10), dtype=float32)

因此我们可以发现我们的网络成功的得到了输出。但是因为我们是随机初始化的参数,因此输出一定会是杂乱无章的。

2. 网络层中的固定参数

在上面的例子中,我们学习到了如何对自己的网络层添加参数,但是我们会发现,我们刚刚添加的参数都是可以进行训练的参数,那么如何添加不可训练的参数呢?

我们可以在添加参数的方法之中添加属性 trainable

self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)

通过 trainable 属性,我们可以将该参数设置为不可训练的参数,也就是说,在以后的训练过程之中,该参数不会改变,而是一直保持不变的状态。

我们可以通过以下代码进行实践:

class MyLayer2(tf.keras.layers.Layer):
    def __init__(self, hidden_units, input_units):
        super(MyLayer2, self).__init__()
        self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
        self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)

    def call(self, inputs):
        return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b

my_layer2 = MyLayer2(10, 5)
print(len(my_layer.trainable_weights))
print(len(my_layer2.trainable_weights))

输出为:

2
1

在该程序之中,我们定义了两个模型,一个模型的 b 可训练,另一个模型的 b 不可训练,于是我们可以发现,两个模型的可训练参数不一样,说明我们的属性起到了相应的效果。

3. 决定网络是否进行训练

我们的网络在使用的时候包括两个情景:

  • 训练的情景,需要我们进行参数的调整等;
  • 测试的情景或其他情景,固定参数,只进行输出。

在大多数时间,我们都需要在训练或者测试的过程之中做一些额外的操作。

而我们的网络是默认进行训练的,那么如何才能将我们的网络调整为测试状态或是训练状态呢?答案就是 call 方法的参数:training。

如以下示例:

class MyLayer2(tf.keras.layers.Layer):
    def __init__(self, hidden_units, input_units):
        super(MyLayer2, self).__init__()
        self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
        self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)

    def call(self, inputs, training=True):
        if training:
          self.b = self.b * 2
          # ...... Other Operations
        return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b

我们在 call 之中定义了 training 参数,从而可以根据是否进行训练进行额外的操作,我们可以通过如下方式来具体使用:

my_layer2 = MyLayer2(10, 5)
y = my_layer2(x, traing=True)
y = my_layer2(x, traing=False)

于是我们在第一调用的时候进行训练,而第二次调用的时候不进行训练。

4. 网络层的嵌套使用

在网络层的使用之中,我们可能会遇到网络层嵌套使用的情况。而且 TensorFlow 也可以支持网络层的嵌套使用

比如以下代码:

class MyLayer(tf.keras.layers.Layer):
    def __init__(self, hidden_units, input_units):
        super(MyLayer, self).__init__()
        self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
        self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal")

    def call(self, inputs):
        return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b

class MyLayer2(tf.keras.layers.Layer):
    def __init__(self, hidden_units, input_units):
        super(MyLayer2, self).__init__()
        self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
        self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)

    def call(self, inputs, training=True):
        return tf.matmul(inputs, self.w) + self.b

class MyLayer3(tf.keras.layers.Layer):
    def __init__(self):
        super(MyLayer3, self).__init__()
        self.l1 = MyLayer(10, 5)
        self.l2 = MyLayer2(5, 10)

    def call(self, inputs, training=True):
        x = self.l1(inputs)
        y = self.l2(x)
        return y

在这个网络层之中,我们在前面重新定义了两个网络层类,并在后面我们嵌套了我们之前的两个网络层,我们通过顺序调用来实现了一个新的网络层的操作。

我们可以通过具体的数据进行测试:

x = tf.ones((5, 5))
my_layer = MyLayer3(10, 5)
y = my_layer(x)
print(y)

我们可以得到输出:

tf.Tensor(
[[ 0.00422265  0.02767846  0.04585129  0.10204907 -0.08051172]
 [ 0.00422265  0.02767846  0.04585129  0.10204907 -0.08051172]
 [ 0.00422265  0.02767846  0.04585129  0.10204907 -0.08051172]
 [ 0.00422265  0.02767846  0.04585129  0.10204907 -0.08051172]
 [ 0.00422265  0.02767846  0.04585129  0.10204907 -0.08051172]], shape=(5, 5), dtype=float32)

可以发现,我们的程序成功地运行了相应的数据,并产生了结果。

5. 自定义模型

既然了解了如何自定义网络层,那么便要知道如何自定义网络模型,其实网络模型是由网络层构成的,因此只要将网络层定义清楚,那么网络模型便可以很轻松得到了。

网络模型的构建大致也分为以下几步:

  • 自定义模型类,并继承自 tf.keras.Model 类
  • 初始化函数之中定义要用到的网络层;
  • 在 call 函数之中定义具体的处理方式

于是,借用上面的网络层,我们可以定义我们的网络模型:


class MyModel(tf.keras.Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.l1 = MyLayer(10, 5)
        self.l2 = MyLayer2(5, 10)

    def call(self, inputs, training=True):
        x = self.l1(inputs)
        y = self.l2(x)
        return y

我们的网络模型使用了 MyLayer 和 MyLayer2 两个网络层,并且在 call 函数之中进行了顺序处理,从而得到最后的结果。

我们可以通过测试:

x = tf.ones((5, 5))
model = MyModel()
y = model(x)
print(y)

得到输出:

tf.Tensor(
[[ 0.07020754 -0.14177878 -0.00841531 -0.0398875   0.14821295]
 [ 0.07020754 -0.14177878 -0.00841531 -0.0398875   0.14821295]
 [ 0.07020754 -0.14177878 -0.00841531 -0.0398875   0.14821295]
 [ 0.07020754 -0.14177878 -0.00841531 -0.0398875   0.14821295]
 [ 0.07020754 -0.14177878 -0.00841531 -0.0398875   0.14821295]], shape=(5, 5), dtype=float32)

我们发现我们的网络模型也是很正确的进行了输出。

6. 小结

在这节课之中,我们学习了如何自定义网络层,如何指定网络层参数是否训练,如何进行运行模式的指定以及如何进行网络层嵌套等,最后我们又学习了如何进行自定义网络的构建。