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

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

Keras 介绍

  1. Keras 是一个用 Python 编写的高级神经网络 API ,它是一个独立的库,可以以 TensorFlowCNTK 或者 Theano 做为后端运行。 TensorFlow1.0 版本开始尝试与 Keras 作集成,到 2.0 版发布后更是深度集成了 Keras ,并紧密依赖 tf.keras 做为其中央高级 API ,官方亦高度推荐使用 keras API 来完成深度模型的构建。
  2. tf.keras 具备三个关键优点:python

    1. 对小白用户友好: Keras 具备简单且一致的接口,并对用户产生的错误有明确可行的建议去修正。 TensorFlow 2.0 以前的版本,因为其代码编写复杂, API 接口混乱并且各个版本之间兼容性较差,受到普遍的批评,使用 Keras 进行统一化以后,会大大减小开发人员的工做量。
    2. 模块化且可组合: Keras 模型经过可构建的模块链接在一块儿,没有任何限制,模型结构清晰,代码容易阅读。
    3. 便于扩展:当编写新的自定义模块时,能够很是方便的基于已有的接口进行扩展。
  3. Keras 使得 TensorFlow 更易于使用,并且不用损失其灵活性和性能。

Keras 模型构建

TensorFlow 2.x 版本中,可使用三种方式来构建 Keras 模型,分别是 Sequential函数式 (Functional) API 以及自定义模型 (Subclassed)。下面就分别介绍下这三种构建方式。后端

Sequential Model

模型结构图

  1. Keras 中,一般是将多个层 (layer) 组装起来造成一个模型 (model),最多见的一种方式就是层的堆叠,可使用 tf.keras.Sequential 来轻松实现。以上图中所示模型为例,其代码实现以下:安全

    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)
  2. 注意对于 Sequential 添加的第一层,能够包含一个 input_shapeinput_dimbatch_input_shape 参数来指定输入数据的维度,详见注释部分。当指定了 input_shape 等参数后,每次 add 新的层,模型都在持续不断地建立过程当中,也就说此时模型中各层的权重矩阵已经被初始化了,能够经过调用 model.weights 来打印模型的权重信息。
  3. 固然,第一层也能够不包含输入数据的维度信息,称之为延迟建立模式,也就是说此时模型还未真正建立,权重矩阵也不存在。能够经过调用 model.build(batch_input_shape) 方法手动建立模型。若是未手动建立,那么只有当调用 fit 或者其余训练和评估方法时,模型才会被建立,权重矩阵才会被初始化,此时模型会根据输入的数据来自动推断其维度信息。
  4. input_shape 中没有指定 batch 的大小而将其设置为 None ,是由于在训练与评估时所采用的 batch 大小可能不一致。若是设为定值,在训练或评估时会产生错误,而这样设置后,能够由模型自动推断 batch 大小并进行计算,鲁棒性更强。
  5. 除了这种顺序性的添加 (add) 外,还能够经过将 layers 以参数的形式传递给 Sequential 来构建模型。示例代码以下所示:网络

    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

  1. Keras函数式 API 是比 Sequential 更为灵活的建立模型的方式。它能够处理具备非线性拓扑结构的模型、具备共享层 (layers) 的模型以及多输入输出的模型。深度学习的模型一般是由层 (layers) 组成的有向无环图,而函数式 API 就是构建这种图的一种有效方式。
  2. Sequential Model 一节中提到的模型为例,使用函数式 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()
  3. 与使用 Sequential 方法构建模型的不一样之处在于,函数式 API 经过 keras.Input 指定了输入 inputs 并经过函数调用的方式生成了输出 outputs ,最后使用 keras.Model 方法构建了整个模型。
  4. 为何叫函数式 API ,从代码中能够看到,能够像函数调用同样来使用各类层 (layers),好比定义好了 dense 层,能够直接将 inputs 做为 dense 的输入而获得一个输出 x ,而后又将 x 做为下一层的输入,最后的函数返回值就是整个模型的输出。
  5. 函数式 API 能够将同一个层 (layers) 做为多个模型的组成部分,示例代码以下所示:dom

    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()

    代码中包含了两个模型,一个编码器 (encoder) 和一个自编码器 (autoencoder),能够看到两个模型共用了 encoder_out 层,固然也包括了 encoder_out 层以前的全部层。ide

  6. 函数式 API 生成的全部模型 (models) 均可以像层 (layers) 同样被调用。还以自编码器 (autoencoder) 为例,如今将它分红编码器 (encoder) 和解码器 (decoder) 两部分,而后用 encoderdecoder 生成 autoencoder ,代码以下:模块化

    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()

    代码中首先生成了两个模型 encoderdecoder ,而后在生成 autoencoder 模型时,使用了模型函数调用的方式,直接将 autoencoder_inputencoded 分别做为 encoderdecoder 两个模型的输入,并最终获得 autoencoder 模型。函数

  7. 函数式 API 能够很容易处理多输入和多输出的模型,这是 Sequential API 没法实现的。好比咱们的模型输入有一部分是类别型特征 ,通常须要通过 Embedding 处理,还有一部分是数值型特征,通常无需特殊处理,显然没法将这两种特征直接合并做为单一输入共同处理,此时就会用到多输入。而有时咱们但愿模型返回多个输出,以供后续的计算使用,此时就会用到多输出模型。多输入与多输出模型的示例代码以下所示:性能

    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()

    代码中有两个输入 categorical_inputnumeric_input ,通过不一样的处理层后,两者经过 Concatenate 结合到一块儿,最后又通过不一样的处理层获得了两个输出 binary_predcategorical_pred 。该模型的结构图以下图所示:

    多输入输出结构图

  8. 函数式 API 另外一个好的用法是模型的层共享,也就是在一个模型中,层被屡次重复使用,它从不一样的输入学习不一样的特征。一种常见的共享层是嵌入层 (Embedding),代码以下:

    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()

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

    共享层结构图

自定义 Keras 层和模型

  1. tf.keras 模块下包含了许多内置的层 (layers),好比上面咱们用到的 DenseEmbeddingReshape 等。有时咱们会发现这些内置的层并不能知足咱们的需求,此时能够很方便建立自定义的层来进行扩展。自定义的层经过继承 tf.keras.Layer 类来实现,且该子类要实现父类的 buildcall 方法。对于内置的 Dense 层,使用自定义层来实现的话,其代码以下所示:

    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. 其中 __init__ 方法用来初始化一些构建该层所需的基本参数, build 方法用来建立该层所需的权重矩阵 w 和误差矩阵 bcall 方法则是层构建的真正执行者,它将输入转为输出并返回。其实权重矩阵等的建立也能够在 __init__ 方法中完成,可是在不少状况下,咱们不能提早预知输入数据的维度,须要在实例化层的某个时间点来延迟建立权重矩阵,所以须要在 build 方法中根据输入数据的维度信息 input_shape 来动态建立权重矩阵。
    2. 以上三个方法的调用顺序为 __init__buildcall ,其中 __init__ 在实例化层时即被调用,而 buildcall 是在肯定了输入后才被调用。其实 Layer 类中有一个内置方法 __call__ ,在层构建时首先会调用该方法,而在方法内部会调用 buildcall ,而且只有第一次调用 __call__ 时才会触发 build ,也就是说 build 中的变量只能被建立一次,而 call 是能够被调用屡次的,好比训练,评估时都会被调用。
    3. 若是须要对该层提供序列化的支持,则须要实现一个 get_config 方法来以字典的形式返回该层实例的构造函数参数。在给定 config 的字典后,能够经过调用该层的类方法 (classmethod) from_config 来从新建立该层, from_config 的默认实现如代码所示,层的从新建立见 layer recreate 代码部分,固然也能够重写 from_config 类方法来提供新的建立方式。而从新建立新模型 (model) 的代码与 layer 重建的代码有所不一样,它须要借助于 keras.Model.from_config 方法来完成构建,详见 model recreate 代码部分。
  2. 自定义的层是能够递归组合的,也就是说一个层能够做为另外一个层的属性。通常推荐在 __init__ 方法中建立子层,由于子层本身的 build 方法会在外层 build 调用时被触发而去执行权重矩阵的构建任务,无需在父层中显示建立。还以 Sequential Model 一节提到的模型为例做为说明,代码以下:

    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))

    从代码中能够看到,咱们将三个 Dense 层做为 MLP 的子层,而后利用它们来完成 MLP 的构建,能够达到与 Sequential Model 中同样的效果,并且全部子层的权重矩阵都会做为新层的权重矩阵而存在。

  3. 层 (layers) 在构建的过程当中,会去递归地收集在此建立过程当中生成的损失 (losses)。在重写 call 方法时,可经过调用 add_loss 方法来增长自定义的损失。层的全部损失中也包括其子层的损失,并且它们均可以经过 layer.losses 属性来进行获取,该属性是一个列表 (list),须要注意的是正则项的损失会自动包含在内。示例代码以下所示:

    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)
  4. 层或模型的 call 方法预置有一个 training 参数,它是一个 bool 类型的变量,表示是否处于训练状态,它会根据调用的方法来设置值,训练时为 True , 评估时为 False 。由于有一些层像 BatchNormalizationDropout 通常只会用在训练过程当中,而在评估和预测的过程当中通常是不会使用的,因此能够经过该参数来控制模型在不一样状态下所执行的不一样计算过程。
  5. 自定义模型与自定义层的实现方式比较类似,不过模型须要继承自 tf.keras.ModelModel 类的有些 API 是与 Layer 类相同的,好比自定义模型也要实现 __init__buildcall 方法。不过二者也有不一样之处,首先 Model 具备训练,评估以及预测接口,其次它能够经过 model.layers 查看全部内置层的信息,另外 Model 类还提供了模型保存和序列化的接口。以 AutoEncoder 为例,一个完整的自定义模型的示例代码以下所示:

    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)

    上述代码实现了一个 AutoEncoder Model 类,它由两层组成,分别为 EncoderDecoder ,而这两层也是自定义的。经过调用 model.weights 能够查看该模型全部的权重信息,固然这里包含子层中的全部权重信息。

  6. 对于自定义的层或模型,在调用其 summary, weights, variables, trainable_weightslosses 等方法或属性时,要先确保层或模型已经被建立,否则可能报错或返回为空,在模型调试时要注意这一点。

配置层 (layer)

tf.keras.layers 模块下面有不少预约义的层,这些层大多都具备相同的构造函数参数。下面介绍一些经常使用的参数,对于每一个层的独特参数以及参数的含义,能够在使用时查询官方文档便可,文档的解释通常会很详细。

  1. activation 指激活函数,能够设置为字符串如 reluactivations 对象 tf.keras.activations.relu() ,默认状况下为 None ,即表示线性关系。
  2. kernel_initializerbias_initializer ,表示层中权重矩阵和误差矩阵的初始化方式,能够设置为字符串如 Glorotuniform
    或者 initializers 对象 tf.keras.initializers.GlorotUniform() ,默认状况下即为 Glorotuniform 初始化方式。
  3. kernel_regularizerbias_regularizer ,表示权重矩阵和误差矩阵的正则化方式,上面介绍过,能够是 L1L2 正则化,如 tf.keras.regularizers.l2(1e-3) ,默认状况下是没有正则项的。

模型建立方式对比

  1. 当构建比较简单的模型,使用 Sequential 方式固然是最方便快捷的,能够利用现有的 Layer 完成快速构建、验证的过程。
  2. 若是模型比较复杂,则最好使用函数式 API 或自定义模型。一般函数式 API 是更高级、更容易以及更安全的实现方式,它还具备一些自定义模型所不具有的特性。可是,当构建不容易表示为有向无环图的模型时,自定义模型提供了更大的灵活性。
  3. 函数式 API 能够提早作模型校验,由于它经过 Input 方法提早指定了模型的输入维度,因此当输入不合规范会更早的发现,有助于咱们调试,而自定义模型开始是没有指定输入数据的维度的,它是在运行过程当中根据输入数据来自行推断的。
  4. 使用函数式 API 编写代码模块化不强,阅读起来有些吃力,而经过自定义模型,能够很是清楚的了解该模型的总体结构,易于理解。
  5. 在实际使用中,能够将函数式 API 和自定义模型结合使用,来知足咱们各式各样的模型构建需求。

Keras 模型建立技巧

  1. 在编写模型代码时,能够多参考借鉴别人的模型构建方式,有时会有不小的收获。
  2. 在查找所需的 tensorflow 方法时,若是 keras 模块下有提供实现则优先使用该方法,若是没有则找 tf 模块下的方法便可,这样可以使得代码的兼容性以及鲁棒性更强。
  3. 在模型建立过程当中,多使用模型和层的内置方法和属性,如 summaryweights 等,这样能够从全局角度来审视模型的结构,有助于发现一些潜在的问题。
  4. 由于 TensorFlow 2.x 模型默认使用 Eager Execution 动态图机制来运行代码,因此能够在代码的任意位置直接打印 Tensor 来查看其数值以及维度等信息,在模型调试时十分有帮助。

参考资料

  1. Keras Sequential 模型
  2. Keras 函数式 API
  3. Keras 编写自定义层和模型
相关文章
相关标签/搜索