Keras

Keras 是一个用于构建和训练深度学习模型的高级程序接口。它被应用于快速原型设计,高级研究和生产环境,有着三大核心优势:

  • 用户友好
    Keras 为常见用例优化了一套简单一致的接口。它给用户错误提供清晰且可操作的反馈。
  • 模块化和组件化
    Keras 模型将可配置的模块连接在一起,几乎没有任何限制。
  • 易扩展
    编写自定义模块用于研究新想法。创建新的网络层,代价函数和研发先进的模型。

导入 tf.keras

tf.kerasKeras 接口规范{:.external} 的 TensorFlow 实现。这是一个用于构建和训练模型的高级接口,其中包括对 TensorFlow 特定功能的一流支持,比如 Eager executiontf.data 管道和 估计器tf.keras 使得 TensorFlow 在不牺牲灵活性和性能的基础上更加易于使用。

首先,在你的 TensorFlow 程序开始导入 tf.keras

  1. import tensorflow as tf
  2. from tensorflow import keras

tf.keras 能够运行任何 Keras 兼容代码,但是要记住:

  • 在 TensorFlow 最新发布的版本中,tf.keras 的版本可能和 PyPI 中 keras 最新的版本不一样。请检查 tf.keras.__version__
  • 保存模型权重tf.keras 默认检查点格式。将 save_format='h5' 作为参数传入,以使用 HDF5 文件格式。

构建简单模型

序列模型

在 Keras 中, 你可以拼接网络层来构建模型。模型(通常)是包含多个网络层的图。最常见的模型就是由多个网络层堆叠而成的:tf.keras.Sequential 模型。

构建简单的全连接网络(比如多层感知器):

  1. model = keras.Sequential()
  2. # 模型中添加包含 64 个节点的全连接层:
  3. model.add(keras.layers.Dense(64, activation='relu'))
  4. # 添加另外一个:
  5. model.add(keras.layers.Dense(64, activation='relu'))
  6. # 添加包含 10 个输出单元的 softmax 层:
  7. model.add(keras.layers.Dense(10, activation='softmax'))

配置网络层

许多 tf.keras.layers 具有相同的构造参数:

  • activation:设置网络层的激活函数。此参数由内置函数或可调用对象指定。默认情况下,不应用任何激活函数。
  • kernel_initializerbias_initializer:初始化网络层的权重(核和偏差)。该参数是一个名字或者可调用对象。默认是 "Glorot uniform" 初始值。
  • kernel_regularizerbias_regularizer:将正则化方案应用于网络层的权重(核和偏差),比如 L1 和 L2 正则化。默认不使用任何正则化。 下面使用构造函数参数实例化 tf.keras.layers.Dense
  1. # 创建 sigmoid 网络层:
  2. layers.Dense(64, activation='sigmoid')
  3. # 或者:
  4. layers.Dense(64, activation=tf.sigmoid)
  5. # 将 L1 正则化因子为 0.01 的线性层应用于核矩阵:
  6. layers.Dense(64, kernel_regularizer=keras.regularizers.l1(0.01))
  7. # 将 L1 正则化因子为 0.01 的线性层应用于偏差向量:
  8. layers.Dense(64, bias_regularizer=keras.regularizers.l2(0.01))
  9. # 线性层核矩阵初始化为随机正交矩阵:
  10. layers.Dense(64, kernel_initializer='orthogonal')
  11. # 线性层偏置向量初始化为常数值 2.0:
  12. layers.Dense(64, bias_initializer=keras.initializers.constant(2.0))

训练和评估

开始训练

构建模型后,通过调用 compile 方法配置其学习过程:

  1. model.compile(optimizer=tf.train.AdamOptimizer(0.001),
  2. loss='categorical_crossentropy',
  3. metrics=['accuracy'])

tf.keras.Model.compile 包含三个重要参数:

  • optimizer:这个参数指定训练过程。从 tf.train 模块传递优化器实例,比如 Adam 优化器RMSProp 优化器 或者 梯度下降优化器
  • loss:优化期间的目标最小化的函数。常见的有均方误差(mse),categorical_crossentropybinary_crossentropy。损失函数由名称或通过从 tf.keras.losses 模块传递可调用对象来指定。
  • metrics:用于监督训练。可由名称或通过从 tf.keras.metrics 模块传递可调用对象。

以下提供了配置训练模型的几个示例:

  1. # 配置以均方误差作为损失函数的回归模型。
  2. model.compile(optimizer=tf.train.AdamOptimizer(0.01),
  3. loss='mse', # mean squared error
  4. metrics=['mae']) # mean absolute error
  5. # 配置类别分类模型。
  6. model.compile(optimizer=tf.train.RMSPropOptimizer(0.01),
  7. loss=keras.losses.categorical_crossentropy,
  8. metrics=[keras.metrics.categorical_accuracy])

输入 NumPy 数据

对于小型数据集,使用内存型数组 NumPy{:.external} 训练和评估模型。模型使用 fit 方法 “拟合” 训练数据:

  1. import numpy as np
  2. data = np.random.random((1000, 32))
  3. labels = np.random.random((1000, 10))
  4. model.fit(data, labels, epochs=10, batch_size=32)

tf.keras.Model.fit 包含三个重要参数:

  • epochs:训练过程被划分到 epochs。一个 epoch 是对整个输入数据的一次迭代(这是以较小的批次完成的)。
  • batch_size:当传递 NumPy 数据时,模型将数据分成较小的批次,并在训练期间迭代这些批次。此整数指定每个批次的大小。请注意,如果样本总数不能被批次大小整除,则最后一批可能会更小。
  • validation_data:在对模型进行原型设计时,您希望轻松监控其在某些验证数据上的性能。传递包含输入数据和标签的元组,让模型在每个 epoch 的结束后计算并打印损失和度量值。

这里有个例子使用了 validation_data

  1. import numpy as np
  2. data = np.random.random((1000, 32))
  3. labels = np.random.random((1000, 10))
  4. val_data = np.random.random((100, 32))
  5. val_labels = np.random.random((100, 10))
  6. model.fit(data, labels, epochs=10, batch_size=32,
  7. validation_data=(val_data, val_labels))

输入 tf.data datasets

使用数据集接口扩展到大型数据集或者多设备训练。将 tf.data.Dataset 实例传递给 fit 方法:

  1. # 实例化玩具数据集实例。
  2. dataset = tf.data.Dataset.from_tensor_slices((data, labels))
  3. dataset = dataset.batch(32)
  4. dataset = dataset.repeat()
  5. # 在数据集上调用 `fit` 方法不要忘记指定 `steps_per_epoch` 参数值。
  6. model.fit(dataset, epochs=10, steps_per_epoch=30)

这里,fit 方法使用 steps_per_epoch 参数,该参数是模型在移动到下一个 epoch 之前训练步数。由 Dataset 产生批量数据,因此该代码片段不需要 batch_size

数据集同样可以被用来验证:

  1. dataset = tf.data.Dataset.from_tensor_slices((data, labels))
  2. dataset = dataset.batch(32).repeat()
  3. val_dataset = tf.data.Dataset.from_tensor_slices((val_data, val_labels))
  4. val_dataset = val_dataset.batch(32).repeat()
  5. model.fit(dataset, epochs=10, steps_per_epoch=30,
  6. validation_data=val_dataset,
  7. validation_steps=3)

评价和预测

tf.keras.Model.evaluatetf.keras.Model.predict 方法可以传递 NumPy 数据和 tf.data.Dataset 对象。

评估在提供的数据集上的代价损失和指标:

  1. model.evaluate(x, y, batch_size=32)
  2. model.evaluate(dataset, steps=30)

并且预测提供的 NumPy 数据在最后一层输出结果:

  1. model.predict(x, batch_size=32)
  2. model.predict(dataset, steps=30)

构建高级模型

函数式接口

tf.keras.Sequential 模型是一个简单的网络层拼接,不能代表任意模型。使用Keras 函数式接口{:.external}构建复杂的模型拓扑,比如:

  • 多输入模型,
  • 多输出模型,
  • 包含共享层模型(相同层被调用多次),
  • 不包含序列化数据流的模型 (比如残余神经网络模型)。

使用函数式接口构建模型的工作方式如下:

  1. 网络层实例可调用并返回张量。
  2. 输入张量和输出张量用于定义 tf.keras.Model 实例。
  3. 模型像 Sequential 序列模型一样训练。

以下示例使用函数式接口构建一个简单,完全连接的网络:

  1. inputs = keras.Input(shape=(32,)) # 返回占位张量
  2. # 网络层可调用张量并且返回张量。
  3. x = keras.layers.Dense(64, activation='relu')(inputs)
  4. x = keras.layers.Dense(64, activation='relu')(x)
  5. predictions = keras.layers.Dense(10, activation='softmax')(x)
  6. # 给定输入和输出实例化模型。
  7. model = keras.Model(inputs=inputs, outputs=predictions)
  8. # 编译步骤指定训练的配置。
  9. model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
  10. loss='categorical_crossentropy',
  11. metrics=['accuracy'])
  12. # 训练 5 个 epoch
  13. model.fit(data, labels, batch_size=32, epochs=5)

模型子类化

通过继承 tf.keras.Model 并定义自己的前向传播来构建一个完全自定义的模型。在 __init__ 方法中创建图层并将它们设置为类实例的属性。在 call 方法中定义前向传播。

Eager execution 被启用时,对模型子类化特别有用,因为可以强制写入前向传播。

关键点:工作中使用正确的接口。虽然模型子类化提供了灵活性,但其代价是更高的复杂性和更多的用户错误机会。如果可能,请选择函数式接口。

下面的示例使用自定义前向传播展示了一个子类化 tf.keras.Model

  1. class MyModel(keras.Model):
  2. def __init__(self, num_classes=10):
  3. super(MyModel, self).__init__(name='my_model')
  4. self.num_classes = num_classes
  5. # Define your layers here.
  6. self.dense_1 = keras.layers.Dense(32, activation='relu')
  7. self.dense_2 = keras.layers.Dense(num_classes, activation='sigmoid')
  8. def call(self, inputs):
  9. # Define your forward pass here,
  10. # using layers you previously defined (in `__init__`).
  11. x = self.dense_1(inputs)
  12. return self.dense_2(x)
  13. def compute_output_shape(self, input_shape):
  14. # You need to override this function if you want to use the subclassed model
  15. # as part of a functional-style model.
  16. # Otherwise, this method is optional.
  17. shape = tf.TensorShape(input_shape).as_list()
  18. shape[-1] = self.num_classes
  19. return tf.TensorShape(shape)
  20. # 实例化子类化模型。
  21. model = MyModel(num_classes=10)
  22. # 编译步骤指定训练配置。
  23. model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
  24. loss='categorical_crossentropy',
  25. metrics=['accuracy'])
  26. # 训练 5 epochs。
  27. model.fit(data, labels, batch_size=32, epochs=5)

自定义网络层

通过继承 tf.keras.layers.Layer 创建自定义网络层并且实现以下方法:

  • build:创建网络层的权重。使用 add_weight 方法添加权重。
  • call:定义前向传播。
  • compute_output_shape:指定在给定输入大小的情况下如何计算网络层的输出大小。
  • 另外,可以通过实现 get_configfrom_config 类方法实现序列化。

这是一个自定义网络层的示例,它实现了带有内核矩阵输入的 matmul

  1. class MyLayer(keras.layers.Layer):
  2. def __init__(self, output_dim, **kwargs):
  3. self.output_dim = output_dim
  4. super(MyLayer, self).__init__(**kwargs)
  5. def build(self, input_shape):
  6. shape = tf.TensorShape((input_shape[1], self.output_dim))
  7. # Create a trainable weight variable for this layer.
  8. self.kernel = self.add_weight(name='kernel',
  9. shape=shape,
  10. initializer='uniform',
  11. trainable=True)
  12. # Be sure to call this at the end
  13. super(MyLayer, self).build(input_shape)
  14. def call(self, inputs):
  15. return tf.matmul(inputs, self.kernel)
  16. def compute_output_shape(self, input_shape):
  17. shape = tf.TensorShape(input_shape).as_list()
  18. shape[-1] = self.output_dim
  19. return tf.TensorShape(shape)
  20. def get_config(self):
  21. base_config = super(MyLayer, self).get_config()
  22. base_config['output_dim'] = self.output_dim
  23. return base_config
  24. @classmethod
  25. def from_config(cls, config):
  26. return cls(**config)
  27. # 使用自定义网络层构建模型
  28. model = keras.Sequential([MyLayer(10),
  29. keras.layers.Activation('softmax')])
  30. # 编译步骤指定训练模型配置
  31. model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
  32. loss='categorical_crossentropy',
  33. metrics=['accuracy'])
  34. # 训练 5 个 epoch。
  35. model.fit(data, targets, batch_size=32, epochs=5)

回调

回调是传递给模型的对象,用于在训练期间自定义和扩展其行为。您可以编写自己的自定义回调,或使用内置的 tf.keras.callbacks,其中包括:

  • tf.keras.callbacks.ModelCheckpoint:定期保存模型的检查点。
  • tf.keras.callbacks.LearningRateScheduler:动态调整学习率。
  • tf.keras.callbacks.EarlyStopping:当验证性能停止增长时,中断训练。
  • tf.keras.callbacks.TensorBoard:使用 TensorBoard 监控模型行为。

要使用 tf.keras.callbacks.Callback,将其传递给模型的 fit 方法:

  1. callbacks = [
  2. # Interrupt training if `val_loss` stops improving for over 2 epochs
  3. keras.callbacks.EarlyStopping(patience=2, monitor='val_loss'),
  4. # Write TensorBoard logs to `./logs` directory
  5. keras.callbacks.TensorBoard(log_dir='./logs')
  6. ]
  7. model.fit(data, labels, batch_size=32, epochs=5, callbacks=callbacks,
  8. validation_data=(val_data, val_targets))

保存与恢复

仅权重

使用 tf.keras.Model.save_weights 保存和加载模型的权重:

  1. # 保存权重到 TensorFlow 检查点文件
  2. model.save_weights('./my_model')
  3. # 恢复模型的状态,
  4. # 这需要具有相同架构的模型。
  5. model.load_weights('my_model')

默认情况下,这会将模型的权重保存在 TensorFlow 检查点 文件格式中。权重也可以保存为 Keras HDF5 格式(Keras 的多后端实现的默认值):

  1. # 保存权重到 HDF5 文件
  2. model.save_weights('my_model.h5', save_format='h5')
  3. # 恢复模型状态
  4. model.load_weights('my_model.h5')

仅配置

可以保存模型的配置 — 这可以在没有任何权重的情况下序列化模型体系结构。即使没有定义原始模型的代码,保存的配置也可以重新创建和初始化相同的模型。Keras 支持 JSON 和 YAML 序列化格式。

  1. # 使用 JSON 格式序列化模型
  2. json_string = model.to_json()
  3. # 重新创建模型(首次初始化)
  4. fresh_model = keras.models.model_from_json(json_string)
  5. # 使用 YAML 格式序列化模型
  6. yaml_string = model.to_yaml()
  7. # 重建模型
  8. fresh_model = keras.models.model_from_yaml(yaml_string)

注意:子类化模型不可序列化,因为它们的体系结构是由 call 方法中的 Python 代码定义的。

全模型

整个模型都可以保存到文件,包含权重值,模型配置甚至优化器配置。这允许您检查模型并稍后从完全相同的状态恢复训练 — 无需访问原始代码。

  1. # 创建一个简单的模型
  2. model = keras.Sequential([
  3. keras.layers.Dense(10, activation='softmax', input_shape=(32,)),
  4. keras.layers.Dense(10, activation='softmax')
  5. ])
  6. model.compile(optimizer='rmsprop',
  7. loss='categorical_crossentropy',
  8. metrics=['accuracy'])
  9. model.fit(data, targets, batch_size=32, epochs=5)
  10. # 保存整个模型到 HDF5 文件中
  11. model.save('my_model.h5')
  12. # 重建完全一样的模型,包含权重和优化器。
  13. model = keras.models.load_model('my_model.h5')

Eager execution

Eager execution 是一个命令式编程环境,可立即评估操作。这不是 Keras 所必需的,但是 tf.keras 支持,对于检查程序和调试很有用。

所有 tf.keras 模型构建接口都与 eager execution 兼容。虽然可以使用 Sequential 和函数式接口,但是 eager execution 对子类化模型和构建自定义层有特别友好 — 仅需要您编写前向传播的接口代码(而不是使用现有的创建模型的接口)。

有关使用自定义训练和 tf.GradientTape 的 Keras 模型示例,请参阅 eager execution 指引

分布式

估计器

估计器接口可以用于分布式环境的训练模型。应用对象主要是工业界,例如可以导出模型进行生产的大型数据集的分布式训练。

通过 tf.keras.estimator.model_to_estimatortf.keras.Model 转化为 tf.estimator.Estimator 对象,使用 tf.estimator 接口训练。详见从 Keras 模型中创建估计器

  1. model = keras.Sequential([layers.Dense(10,activation='softmax'),
  2. layers.Dense(10,activation='softmax')])
  3. model.compile(optimizer=tf.train.RMSPropOptimizer(0.001),
  4. loss='categorical_crossentropy',
  5. metrics=['accuracy'])
  6. estimator = keras.estimator.model_to_estimator(model)

注意:开启 eager execution 可以调试估计器输入函数和观察数据。

GPU 集群

可以使用 tf.contrib.distribute.DistributionStrategy 在多个 GPU 上运行。 此接口在多个 GPU 上提供分布式训练,几乎不需要对现有代码进行任何更改。

目前,tf.contrib.distribute.MirroredStrategy 是唯一受支持的分发策略。MirroredStrategy 使用 all-reduce 在一台机器上进行图模型内部的复制与同步。要使用 Keras 的 DistributionStrategy,将 tf.keras.Model 转换为tf.estimator.Estimatortf.keras.estimator.model_to_estimator,然后训练估算器。

以下示例在单个计算机上的多个 GPU 之间分发 tf.keras.Model

首先,定义一个简单的模型:

  1. model = keras.Sequential()
  2. model.add(keras.layers.Dense(16, activation='relu', input_shape=(10,)))
  3. model.add(keras.layers.Dense(1, activation='sigmoid'))
  4. optimizer = tf.train.GradientDescentOptimizer(0.2)
  5. model.compile(loss='binary_crossentropy', optimizer=optimizer)
  6. model.summary()

定义输入管道input_fn 返回一个 tf.data.Dataset 对象,用于在多个设备之间分配数据 — 每个设备处理一个批处理输入分片。

  1. def input_fn():
  2. x = np.random.random((1024, 10))
  3. y = np.random.randint(2, size=(1024, 1))
  4. x = tf.cast(x, tf.float32)
  5. dataset = tf.data.Dataset.from_tensor_slices((x, y))
  6. dataset = dataset.repeat(10)
  7. dataset = dataset.batch(32)
  8. return dataset

接下来,创建一个 tf.estimator.RunConfig 并将 train_distribute 参数设置为 tf.contrib.distribute.MirroredStrategy 实例。创建 MirroredStrategy 时,可以指定设备列表或设置 num_gpus 参数。默认使用所有可用的 GPU,如下所示:

  1. strategy = tf.contrib.distribute.MirroredStrategy()
  2. config = tf.estimator.RunConfig(train_distribute=strategy)

Keras 模型转化为 tf.estimator.Estimator 实例:

  1. keras_estimator = keras.estimator.model_to_estimator(
  2. keras_model=model,
  3. config=config,
  4. model_dir='/tmp/model_dir')

最后,提供 input_fnsteps 参数训练 估计器 实例:

  1. keras_estimator.train(input_fn=input_fn, steps=10)