Bootstrap

TensorFlow 篇 | TensorFlow 2.x 基于 Keras 的模型构建

导语」TensorFlow 2.0 版发布以来,Keras 已经被深度集成于 TensorFlow 框架中,Keras API 也成为了构建深度网络模型的第一选择。使用 Keras 进行模型开发与迭代是每一个数据开发人员都需要掌握的一项基本技能,让我们一起走进 Keras 的世界一探究竟。

Keras 介绍

2.1. 对小白用户友好: 具有简单且一致的接口,并对用户产生的错误有明确可行的建议去修正。 之前的版本,由于其代码编写复杂, 接口混乱而且各个版本之间兼容性较差,受到广泛的批评,使用 进行统一化之后,会大大减少开发人员的工作量。

2.2. 模块化且可组合: 模型通过可构建的模块连接在一起,没有任何限制,模型结构清晰,代码容易阅读。

2.3. 便于扩展:当编写新的自定义模块时,可以非常方便的基于已有的接口进行扩展。

Keras 模型构建

在 版本中,可以使用三种方式来构建 模型,分别是 , 以及。下面就分别介绍下这三种构建方式。

Sequential Model

import tensorflow as tf
from tensorflow.keras import layers

model = tf.keras.Sequential()
# Adds a densely-connected layer with 64 units to the model:
model.add(layers.Dense(64, activation='relu', input_shape=(16,)))
# This is identical to the following:
# model.add(layers.Dense(64, activation='relu', input_dim=16))
# model.add(layers.Dense(64, activation='relu', batch_input_shape=(None, 16)))
# Add another:
model.add(layers.Dense(64, activation='relu'))
# Add an output layer with 10 output units:
model.add(layers.Dense(10))
# model.build((None, 16))
print(model.weights)

import tensorflow as tf
from tensorflow.keras import layers

model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=(16, )),
    layers.Dense(64, activation='relu'),
    layers.Dense(10)
])
# model.build((None, 16))
print(model.weights)

函数式 API

from tensorflow import keras
from tensorflow.keras import layers

inputs = keras.Input(shape=(16, ))
dense = layers.Dense(64, activation='relu')
x = dense(inputs)
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name='model')
model.summary()

from tensorflow import keras
from tensorflow.keras import layers

encoder_input = keras.Input(shape=(16, ), name='encoder_input')
x = layers.Dense(32, activation='relu')(encoder_input)
x = layers.Dense(64, activation='relu')(x)
encoder_output = layers.Dense(128, activation='relu')(x)

encoder = keras.Model(encoder_input, encoder_output, name='encoder')
encoder.summary()

x = layers.Dense(64, activation='relu')(encoder_output)
x = layers.Dense(32, activation='relu')(x)
decoder_output = layers.Dense(16, activation='relu')(x)

autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder')
autoencoder.summary()

代码中包含了两个模型,一个编码器 () 和一个自编码器 (),可以看到两个模型共用了 层,当然也包括了 层之前的所有层。

from tensorflow import keras
from tensorflow.keras import layers

encoder_input = keras.Input(shape=(16, ), name='encoder_input')
x = layers.Dense(32, activation='relu')(encoder_input)
x = layers.Dense(64, activation='relu')(x)
encoder_output = layers.Dense(128, activation='relu')(x)

encoder = keras.Model(encoder_input, encoder_output, name='encoder')
encoder.summary()

decoder_input = keras.Input(shape=(128, ), name='decoder_input')
x = layers.Dense(64, activation='relu')(decoder_input)
x = layers.Dense(32, activation='relu')(x)
decoder_output = layers.Dense(16, activation='relu')(x)

decoder = keras.Model(decoder_input, decoder_output, name='decoder')
decoder.summary()

autoencoder_input = keras.Input(shape=(16), name='autoencoder_input')
encoded = encoder(autoencoder_input)
autoencoder_output = decoder(encoded)
autoencoder = keras.Model(
    autoencoder_input,
    autoencoder_output,
    name='autoencoder',
)
autoencoder.summary()

代码中首先生成了两个模型 和 ,然后在生成 模型时,使用了的方式,直接将 和 分别作为 和 两个模型的输入,并最终得到 模型。

from tensorflow import keras
from tensorflow.keras import layers

categorical_input = keras.Input(shape=(16, ))
numeric_input = keras.Input(shape=(32, ))
categorical_features = layers.Embedding(
    input_dim=100,
    output_dim=64,
    input_length=16,
)(categorical_input)
categorical_features = layers.Reshape([16 * 64])(categorical_features)
numeric_features = layers.Dense(64, activation='relu')(numeric_input)
x = layers.Concatenate(axis=-1)([categorical_features, numeric_features])
x = layers.Dense(128, activation='relu')(x)

binary_pred = layers.Dense(1, activation='sigmoid')(x)
categorical_pred = layers.Dense(3, activation='softmax')(x)

model = keras.Model(
    inputs=[categorical_input, numeric_input],
    outputs=[binary_pred, categorical_pred],
)
model.summary()

代码中有两个输入 和 ,经过不同的处理层后,二者通过 结合到一起,最后又经过不同的处理层得到了两个输出 和 。该模型的结构图如下图所示:

from tensorflow import keras
from tensorflow.keras import layers

categorical_input_one = keras.Input(shape=(16, ))
categorical_input_two = keras.Input(shape=(24, ))

shared_embedding = layers.Embedding(100, 64)

categorical_features_one = shared_embedding(categorical_input_one)
categorical_features_two = shared_embedding(categorical_input_two)

categorical_features_one = layers.Reshape([16 * 64])(categorical_features_one)
categorical_features_two = layers.Reshape([16 * 64])(categorical_features_two)

x = layers.Concatenate(axis=-1)([
    categorical_features_one,
    categorical_features_two,
])
x = layers.Dense(128, activation='relu')(x)
outputs = layers.Dense(1, activation='sigmoid')(x)

model = keras.Model(
    inputs=[categorical_input_one, categorical_input_two],
    outputs=outputs,
)
model.summary()

代码中有两个输入 和 ,它们共享了一个 层 。该模型的结构图如下图所示:

自定义 Keras 层和模型

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super().__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer='random_normal',
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units, ),
            initializer='random_normal',
            trainable=True,
        )

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

    def get_config(self):
        return {'units': self.units}

    @classmethod
    def from_config(cls, config):
        return cls(**config)

inputs = keras.Input((4, ))
layer = CustomDense(10)
outputs = layer(inputs)

model = keras.Model(inputs, outputs)
model.summary()

# layer recreate
config = layer.get_config()
new_layer = CustomDense.from_config(config)
new_outputs = new_layer(inputs)
print(new_layer.weights)
print(new_layer.non_trainable_weights)
print(new_layer.trainable_weights)

# model recreate
config = model.get_config()
new_model = keras.Model.from_config(
    config,
    custom_objects={'CustomDense': CustomDense},
)
new_model.summary()

1.1. 其中 方法用来初始化一些构建该层所需的基本参数, 方法用来创建该层所需的权重矩阵 和偏差矩阵 , 方法则是层构建的真正执行者,它将输入转为输出并返回。其实权重矩阵等的创建也可以在 方法中完成,但是在很多情况下,我们不能提前预知输入数据的维度,需要在实例化层的某个时间点来延迟创建权重矩阵,因此需要在 方法中根据输入数据的维度信息 来动态创建权重矩阵。

1.2. 以上三个方法的调用顺序为 , , ,其中 在实例化层时即被调用,而 和 是在确定了输入后才被调用。其实 类中有一个内置方法 ,在层构建时首先会调用该方法,而在方法内部会调用 和 ,并且只有第一次调用 时才会触发 ,也就是说 中的变量只能被创建一次,而 是可以被调用多次的,比如训练,评估时都会被调用。

1.3. 如果需要对该层提供序列化的支持,则需要实现一个 方法来以字典的形式返回该层实例的构造函数参数。在给定 的字典后,可以通过调用该层的 来重新创建该层, 的默认实现如代码所示,层的重新创建见 代码部分,当然也可以重写 类方法来提供新的创建方式。而重新创建新模型 () 的代码与 重建的代码有所不同,它需要借助于 方法来完成构建,详见 代码部分。

from tensorflow import keras
from tensorflow.keras import layers

class MLP(layers.Layer):
    def __init__(self):
        super().__init__()
        self.dense_1 = layers.Dense(64, activation='relu')
        self.dense_2 = layers.Dense(64, activation='relu')
        self.dense_3 = layers.Dense(10)

    def call(self, inputs):
        x = self.dense_1(inputs)
        x = self.dense_2(x)
        x = self.dense_3(x)
        return x

inputs = keras.Input((16, ))
mlp = MLP()

y = mlp(inputs)
print('weights:', len(mlp.weights))
print('trainable weights:', len(mlp.trainable_weights))

从代码中可以看到,我们将三个 层作为 的子层,然后利用它们来完成 的构建,可以达到与 中一样的效果,而且所有子层的权重矩阵都会作为新层的权重矩阵而存在。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

class CustomLayer(layers.Layer):
    def __init__(self, rate=1e-2, l2_rate=1e-3):
        super().__init__()
        self.rate = rate
        self.l2_rate = l2_rate
        self.dense = layers.Dense(
            units=32,
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )

    def call(self, inputs):
        self.add_loss(self.rate * tf.reduce_sum(inputs))
        return self.dense(inputs)

inputs = keras.Input((16, ))
layer = CustomLayer()
x = layer(inputs)
print(layer.losses)

from tensorflow import keras
from tensorflow.keras import layers

class Encoder(layers.Layer):
    def __init__(self, l2_rate=1e-3):
        super().__init__()
        self.l2_rate = l2_rate

    def build(self, input_shape):
        self.dense1 = layers.Dense(
            units=32,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )
        self.dense2 = layers.Dense(
            units=64,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )
        self.dense3 = layers.Dense(
            units=128,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )

    def call(self, inputs):
        x = self.dense1(inputs)
        x = self.dense2(x)
        x = self.dense3(x)
        return x

class Decoder(layers.Layer):
    def __init__(self, l2_rate=1e-3):
        super().__init__()
        self.l2_rate = l2_rate

    def build(self, input_shape):
        self.dense1 = layers.Dense(
            units=64,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )
        self.dense2 = layers.Dense(
            units=32,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )
        self.dense3 = layers.Dense(
            units=16,
            activation='relu',
            kernel_regularizer=keras.regularizers.l2(self.l2_rate),
        )

    def call(self, inputs):
        x = self.dense1(inputs)
        x = self.dense2(x)
        x = self.dense3(x)
        return x

class AutoEncoder(keras.Model):
    def __init__(self):
        super().__init__()
        self.encoder = Encoder()
        self.decoder = Decoder()

    def call(self, inputs):
        x = self.encoder(inputs)
        x = self.decoder(x)
        return x

model = AutoEncoder()
model.build((None, 16))
model.summary()
print(model.layers)
print(model.weights)

上述代码实现了一个 类,它由两层组成,分别为 和 ,而这两层也是自定义的。通过调用 可以查看该模型所有的权重信息,当然这里包含子层中的所有权重信息。

配置层 (layer)

在 模块下面有很多预定义的层,这些层大多都具有相同的构造函数参数。下面介绍一些常用的参数,对于每个层的独特参数以及参数的含义,可以在使用时查询官方文档即可,文档的解释一般会很详细。

模型创建方式对比

Keras 模型创建技巧

参考资料