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