入门指南 / Keras 常见问题解答

Keras 常见问题解答

一份常见的 Keras 问题列表。

一般问题

训练相关问题

建模相关问题


一般问题

如何在单台机器上使用多个 GPU 训练 Keras 模型?

在多个 GPU 上运行单个模型有两种方式:数据并行性设备并行性。 Keras 涵盖了这两种情况。

对于数据并行性,Keras 支持 JAX、TensorFlow 和 PyTorch 的内置数据并行分发 API。请参阅以下指南:

对于模型并行性,Keras 有自己的分发 API,目前仅支持 JAX 后端。 请参见 LayoutMap API 的文档


如何在 TPU 上训练 Keras 模型?

TPU 是一种快速高效的深度学习硬件加速器,可以在 Google Cloud 上公开访问。 您可以通过 Colab、Kaggle 笔记本和 GCP 深度学习 VM (前提是 VM 上设置了 TPU_NAME 环境变量)使用 TPU。

所有 Keras 后端(JAX、TensorFlow、PyTorch)都支持 TPU,但在这种情况下,我们推荐使用 JAX 或 TensorFlow。

使用 JAX:

连接到 TPU 运行时时,只需在模型构建之前插入以下代码片段:

import jax
distribution = keras.distribution.DataParallel(devices=jax.devices())
keras.distribution.set_distribution(distribution)

使用 TensorFlow:

连接到 TPU 运行时时,使用 TPUClusterResolver 来检测 TPU。 然后,创建 TPUStrategy 并在策略范围内构建您的模型:

try:
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    print("设备:", tpu.master())
    strategy = tf.distribute.TPUStrategy(tpu)
except:
    strategy = tf.distribute.get_strategy()
print("副本数量:", strategy.num_replicas_in_sync)

with strategy.scope():
    # 在这里创建您的模型。
    ...

重要的是,您应该:

  • 确保您能够快速读取数据,以保持 TPU 的高利用率。
  • 考虑在每次图执行中运行多个梯度下降步骤,以保持 TPU 的高利用率。 你可以通过 compile() 函数的 experimental_steps_per_execution 参数来实现。这将给小模型带来显著的速度提升。

Keras 配置文件存储在哪里?

所有 Keras 数据的默认存储目录是:

$HOME/.keras/

例如,对于我在 MacBook Pro 上,它是 /Users/fchollet/.keras/

请注意,Windows 用户应该将 $HOME 替换为 %USERPROFILE%

如果 Keras 无法创建上述目录(例如,由于权限问题),则会使用 /tmp/.keras/ 作为备份。

Keras 配置文件是一个存储在 $HOME/.keras/keras.json 的 JSON 文件。默认的配置文件看起来是这样的:

{
    "image_data_format": "channels_last",
    "epsilon": 1e-07,
    "floatx": "float32",
    "backend": "tensorflow"
}

它包含以下字段:

  • 要作为图像处理层和工具的默认使用的图像数据格式(可以是 channels_lastchannels_first)。
  • 用于防止在某些操作中除以零的 epsilon 数值模糊因子。
  • 默认浮点数据类型。
  • 默认后端。可以是 "jax""tensorflow""torch""numpy" 之一。

同样,缓存的数据集文件(例如,通过 get_file() 下载的那些)默认存储在 $HOME/.keras/datasets/ 中,而 Keras Applications 的缓存模型权重文件默认存储在 $HOME/.keras/models/ 中。


如何使用 Keras 进行超参数调优?

我们建议使用 KerasTuner


如何在开发过程中使用 Keras 获得可重复的结果?

需要考虑四个随机源:

  1. Keras 本身(例如,keras.random 操作或来自 keras.layers 的随机层)。
  2. 当前的 Keras 后端(例如,JAX、TensorFlow 或 PyTorch)。
  3. Python 运行时。
  4. CUDA 运行时。当在 GPU 上运行时,某些操作的输出是非确定性的。这是因为 GPU 以并行方式运行许多操作,因此执行顺序并不总是有保障。由于浮点数精度有限,甚至相加几个数字也可能根据加法顺序的不同而产生略微不同的结果。

要使 Keras 和当前后端框架具有确定性,请使用以下代码:

keras.utils.set_random_seed(1337)

要使 Python 具有确定性,需要在程序开始之前(而不是程序内部)将 PYTHONHASHSEED 环境变量设置为 0。在 Python 3.2.3 及以后的版本中,这对于具有可重复行为的某些哈希基操作(例如,集合或字典中的项目顺序,见 Python 的文档)是必要的。

要使 CUDA 运行时具有确定性:如果使用 TensorFlow 后端,请调用 tf.config.experimental.enable_op_determinism。请注意,这将有性能成本。对于其他后端的解决方法可能会有所不同——请直接查阅您的后端框架的文档。


保存模型的选项是什么?

注意:不建议使用 pickle 或 cPickle 来保存 Keras 模型。

1)完整模型保存(配置 + 权重)

完整模型保存意味着创建一个包含以下内容的文件:

  • 模型的架构,允许您重新创建模型
  • 模型的权重
  • 训练配置(损失、优化器)
  • 优化器的状态,允许您从中断的地方继续训练。

保存完整模型的默认和推荐方法是:model.save(your_file_path.keras)

在以任何格式保存模型后,您可以通过 model = keras.models.load_model(your_file_path.keras) 重新实例化它。

示例:

from keras.saving import load_model

model.save('my_model.keras')
del model  # 删除现有模型

# 返回一个已编译的模型
# 与之前的模型相同
model = load_model('my_model.keras')

2)仅保存权重

如果您需要保存 模型的权重,可以使用以下代码以 HDF5 格式保存,文件扩展名为 .weights.h5

model.save_weights('my_model.weights.h5')

假设您有代码用于实例化模型,可以将保存的权重加载到与同样架构的模型中:

model.load_weights('my_model.weights.h5')

如果您需要将权重加载到不同架构(具有一些共同的层)中,例如用于微调或迁移学习,您可以通过层名称加载:

model.load_weights('my_model.weights.h5', by_name=True)

示例: """ 假设原始模型看起来是这样的:

model = Sequential() model.add(Dense(2, input_dim=3, name='dense_1')) model.add(Dense(3, name='dense_2')) ... model.save_weights(fname) """

新模型

model = Sequential() model.add(Dense(2, input_dim=3, name='dense_1')) # 将被加载 model.add(Dense(10, name='new_dense')) # 将不会被加载

从第一个模型加载权重;只会影响第一层,dense_1。

model.load_weights(fname, by_name=True) 另请参阅 如何安装 HDF5 或 h5py 来保存我的模型? 以获取有关如何安装 h5py 的说明。

3) 仅配置保存(序列化)

如果您只需要保存模型的 架构,而不是其权重或训练配置,您可以这样做:

# 保存为 JSON
json_string = model.to_json()

生成的 JSON 文件是可读的,并且可以在必要时手动编辑。

然后,您可以从这些数据构建一个新的模型:

# 从 JSON 重建模型:
from keras.models import model_from_json
model = model_from_json(json_string)

4) 处理保存模型中的自定义层(或其他自定义对象)

如果您要加载的模型包含自定义层或其他自定义类或函数,您可以通过 custom_objects 参数将它们传递给加载机制:

from keras.models import load_model
# 假设您的模型包含“AttentionLayer”类的实例
model = load_model('my_model.h5', custom_objects={'AttentionLayer': AttentionLayer})

或者,您可以使用 自定义对象范围

from keras.utils import CustomObjectScope

with CustomObjectScope({'AttentionLayer': AttentionLayer}):
    model = load_model('my_model.h5')

自定义对象处理对于 load_modelmodel_from_json 的工作方式相同:

from keras.models import model_from_json
model = model_from_json(json_string, custom_objects={'AttentionLayer': AttentionLayer})

如何安装 HDF5 或 h5py 来保存我的模型?

为了将 Keras 模型保存为 HDF5 文件,Keras 使用 h5py Python 包。它是 Keras 的依赖项,应该默认安装。在基于 Debian 的发行版中,您还需要额外安装 libhdf5

sudo apt-get install libhdf5-serial-dev

如果您不确定 h5py 是否已安装,可以打开 Python shell 并通过

import h5py

加载模块。

如果没有错误地导入,则表示已安装,否则您可以在此处找到 详细的安装说明


我应该如何引用 Keras?

如果 Keras 对您的研究有所帮助,请在您的出版物中引用 Keras。以下是一个 BibTeX 条目的示例:

@misc{chollet2015keras,
  title={Keras},
  author={Chollet, Fran\c{c}ois and others},
  year={2015},
  howpublished={\url{https://keras.io}},
}


与训练相关的问题

“样本”、“批次”和“轮次”是什么意思?

以下是一些常见的定义,了解这些定义是正确使用 Keras fit() 的必要条件:

  • 样本:数据集的一个元素。例如,一个图像是卷积网络中的一个 样本。一个音频片段是语音识别模型的一个 样本
  • 批次:一组 N 样本。批次中的样本是独立处理的,并行处理。如果训练,一个批次将仅导致模型的一个更新。批次通常比单个输入更好地近似输入数据的分布。批次越大,近似效果越好;但是,同样也会导致批次处理时间更长,并且仍然只导致一个更新。对于推理(评估/预测),建议选择一个尽可能大的批次大小,而不超出内存限制(因为较大的批次通常会导致更快的评估/预测)。
  • 轮次:一个任意的截止,通常定义为“对整个数据集的一次通过”,用于将训练分为不同的阶段,这对于记录和定期评估很有用。 使用 validation_datavalidation_split 与 Keras 模型的 fit 方法时,评估将在每个 轮次 结束时运行。 在 Keras 中,有能力添加 回调,专门设计在 轮次 结束时运行。这些的例子包括学习率变化和模型检查点(保存)。

为什么我的训练损失远高于我的测试损失?

Keras 模型有两种模式:训练和测试。正则化机制,例如 Dropout 和 L1/L2 权重正则化,在测试时会关闭。 它们反映在训练时损失中,但不反映在测试时损失中。

此外,Keras 显示的训练损失是每个训练数据批次损失的平均值,在当前轮次内。 由于您的模型随着时间变化,轮次初期的批次损失通常高于最后几个批次的损失。 这可能导致每轮的平均损失下降。 另一方面,轮次的测试损失是使用模型在轮次结束时的状态计算的,从而导致较低的损失。


如何确保我的训练运行能够从程序中断中恢复?

为了确保在任何时候都能够从中断的训练运行中恢复(容错), 您应该使用 keras.callbacks.BackupAndRestore 回调,定期将您的训练进度(包括纪元号和权重)保存到磁盘,并在下次调用 Model.fit() 时加载它。

import numpy as np
import keras

class InterruptingCallback(keras.callbacks.Callback):
  """一个回调,用于故意引入训练中断。"""
  def on_epoch_end(self, epoch, log=None):
    if epoch == 15:
      raise RuntimeError('中断')

model = keras.Sequential([keras.layers.Dense(10)])
optimizer = keras.optimizers.SGD()
model.compile(optimizer, loss="mse")

x = np.random.random((24, 10))
y = np.random.random((24,))

backup_callback = keras.callbacks.experimental.BackupAndRestore(
    backup_dir='/tmp/backup')
try:
  model.fit(x, y, epochs=20, steps_per_epoch=5,
            callbacks=[backup_callback, InterruptingCallback()])
except RuntimeError:
  print('***处理中断***')
  # 这将在中断时继续训练。
  model.fit(x, y, epochs=20, steps_per_epoch=5,
            callbacks=[backup_callback])

回调文档 中了解更多信息。


如何在验证损失不再下降时中断训练?

您可以使用 EarlyStopping 回调:

from keras.callbacks import EarlyStopping

early_stopping = EarlyStopping(monitor='val_loss', patience=2)
model.fit(x, y, validation_split=0.2, callbacks=[early_stopping])

回调文档 中了解更多信息。


如何冻结层并进行微调?

设置 trainable 属性

所有层和模型都有一个 layer.trainable 布尔属性:

>>> layer = Dense(3)
>>> layer.trainable
True

在所有层和模型上,trainable 属性可以设置(为 True 或 False)。 当设置为 False 时,layer.trainable_weights 属性为空:

>>> layer = Dense(3)
>>> layer.build(input_shape=(None, 3)) # 创建该层的权重
>>> layer.trainable
True
>>> layer.trainable_weights
[<KerasVariable shape=(3, 3), dtype=float32, path=dense/kernel>, <KerasVariable shape=(3,), dtype=float32, path=dense/bias>]
>>> layer.trainable = False
>>> layer.trainable_weights
[]

在层上设置 trainable 属性会递归地在所有子层上设置它(self.layers 的内容)。

1) 使用 fit() 进行训练时:

要使用 fit() 进行微调,您需要:

  • 实例化一个基础模型并加载预训练权重
  • 冻结该基础模型
  • 在顶部添加可训练层
  • 调用 compile()fit()

如下所示:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # 冻结 ResNet50Base。

assert model.layers[0].trainable_weights == []  # ResNet50Base 没有可训练权重。
assert len(model.trainable_weights) == 2  # 只有 Dense 层的偏置和核。

model.compile(...)
model.fit(...)  # 训练 Dense 同时排除 ResNet50Base。

您可以在功能 API 或模型子类化 API 中遵循类似的工作流。 确保在更改 trainable 值之后调用 compile(),以便您的更改能够被考虑在内。调用 compile() 将冻结模型的训练步骤状态。

2) 使用自定义训练循环时:

在编写训练循环时,确保仅更新 属于 model.trainable_weights 的权重(而不是所有 model.weights)。 以下是一个简单的 TensorFlow 示例:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # 冻结 ResNet50Base。

# 遍历数据集的批次。
for inputs, targets in dataset:
    # 打开梯度带。
    with tf.GradientTape() as tape:
        # 前向传播。
        predictions = model(inputs)
        # 计算该批次的损失值。
        loss_value = loss_fn(targets, predictions)

    # 获取关于 *可训练* 权重的损失的梯度。
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # 更新模型的权重。
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

trainablecompile() 之间的交互

在模型上调用 compile() 旨在“冻结”该模型的行为。这意味着,在模型编译时 trainable 属性的值应该在模型的整个生命周期内被保留,直到再次调用 compile。因此,如果您更改了 trainable,请确保再次在您的 模型上调用 compile() 以便考虑您的更改。 - 共享层的 trainable 属性值已更改 - 模型 B 已编译

因此模型 A 和 B 为共享层使用不同的 trainable 值。该机制对于大多数现有的 GAN 实现至关重要,这些实现是:

discriminator.compile(...)  # 当训练 `discriminator` 时,`discriminator` 的权重应该被更新
discriminator.trainable = False
gan.compile(...)  # `discriminator` 是 `gan` 的子模型,当训练 `gan` 时不应该被更新

call() 中的 training 参数与 trainable 属性有什么区别?

trainingcall 中的一个布尔参数,它决定了调用是否应在推断模式或训练模式下运行。例如,在训练模式下,Dropout 层应用随机丢弃并调整输出。在推断模式下,相同层将不执行任何操作。示例:

y = Dropout(0.5)(x, training=True)  # 在训练和推断时都应用丢弃

trainable 是一个布尔层属性,决定在训练期间是否应更新该层的可训练权重以最小化损失。如果 layer.trainable 被设置为 False,那么 layer.trainable_weights 将始终是一个空列表。示例:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # 冻结 ResNet50Base。

assert model.layers[0].trainable_weights == []  # ResNet50Base 没有可训练权重。
assert len(model.trainable_weights) == 2  # 仅有 Dense 层的偏置和内核。

model.compile(...)
model.fit(...)  # 训练 Dense,同时排除 ResNet50Base。

如您所见,“推断模式与训练模式”和“层权重的可训练性”是两个非常不同的概念。

您可以想象以下情况:一个在训练期间通过反向传播学习缩放因子的丢弃层。我们称之为 AutoScaleDropout。 这个层将同时具有可训练状态,并且在推断和训练中有不同的行为。 因为 trainable 属性和 training 调用参数是独立的,所以您可以这样做:

layer = AutoScaleDropout(0.5)

# 在训练和推断时都应用丢弃  
# *并* 在训练期间学习缩放因子
y = layer(x, training=True)

assert len(layer.trainable_weights) == 1
# 在训练和推断时都应用丢弃  
# 使用 *冻结* 的缩放因子

layer = AutoScaleDropout(0.5)
layer.trainable = False
y = layer(x, training=True)

BatchNormalization 层的特殊情况

对于 BatchNormalization 层,设置 bn.trainable = False 将使其 training 调用参数默认为 False,意味着该层在训练过程中将不更新其状态。

这种行为仅适用于 BatchNormalization。对于其他每一层,权重的可训练性与“推断与训练模式”仍然是独立的。


fit() 中,验证分割如何计算?

如果在 model.fit 中将 validation_split 参数设置为例如 0.1,则使用的验证数据将是数据的 最后 10%。如果将其设置为 0.25,则将是数据的最后 25%,等等。请注意,在提取验证分割之前数据未被打乱,因此验证实际上仅是您传入的输入中最后的 x% 的样本。

相同的验证集在所有纪元中均被使用(在同一次 fit 调用中)。

请注意,仅当您的数据作为 Numpy 数组传递时,validation_split 选项才可用(而不是 tf.data.Datasets,因为它们不可索引)。


fit() 中,数据在训练期间是否被打乱?

如果您将数据作为 NumPy 数组传递,并且在 model.fit() 中将 shuffle 参数设置为 True(这是默认值),则每个纪元的训练数据将被全局随机打乱。

如果您将数据作为 tf.data.Dataset 对象传递,并且在 model.fit() 中将 shuffle 参数设置为 True,则数据集将进行局部打乱(缓冲打乱)。

使用 tf.data.Dataset 对象时,建议您预先打乱数据(例如,通过调用 dataset = dataset.shuffle(buffer_size)),以便控制缓冲区大小。

验证数据不会被打乱。


在使用 fit() 训练时,监视我的指标的推荐方法是什么?

损失值和指标值通过调用 fit() 显示的默认进度条报告。 然而,盯着控制台中变化的 ASCII 数字并不是一种最佳的指标监测体验。 我们推荐使用 TensorBoard,它将显示您训练和验证指标的美观图表,在训练期间定期更新,您可以从浏览器访问。

您可以通过 TensorBoard 回调 在使用 fit() 时使用 TensorBoard。


如果我需要自定义 fit() 的行为怎么办?

You have two options:

1) 子类化 Model 类并重写 train_step(和 test_step)方法

如果您想使用自定义更新规则但仍希望利用 fit() 提供的功能,例如回调、高效的步骤融合等,那么这是更好的选择。

请注意,这种模式并不妨碍您构建使用功能性 API 的模型,在这种情况下,您将使用您创建的类根据 inputsoutputs 来实例化模型。对于顺序模型也是如此,在这种情况下,您将子类化 keras.Sequential 并重写其 train_step,而不是 keras.Model

请参阅以下指南:

2) 编写低级自定义训练循环

如果您想控制每一个细节,这是一个不错的选择——尽管它可能有些冗长。

请参阅以下指南:


Model 方法 predict()__call__() 之间有什么区别?

让我们用摘自 Deep Learning with Python, Second Edition 的内容来回答:

y = model.predict(x)y = model(x)(其中 x 是输入数据的数组)都意味着“在 x 上运行模型并检索输出 y。” 然而,它们并不完全相同。

predict() 按批次循环处理数据 (实际上,您可以通过 predict(x, batch_size=64) 指定批次大小), 并提取输出的 NumPy 值。它在示意上等价于:

def predict(x):
    y_batches = []
    for x_batch in get_batches(x):
        y_batch = model(x_batch).numpy()  # 模型的输出
        y_batches.append(y_batch)
    return np.concatenate(y_batches)

这意味着 predict() 调用可以扩展到非常大的数组。与此同时, model(x) 在内存中发生,并且不具有扩展性。 另一方面,predict() 是不可微分的:如果在 GradientTape 范围内调用,您无法检索它的梯度。

当您需要检索模型调用的梯度时,应使用 model(x), 如果您只需要输出值,则应使用 predict()。换句话说, 除非您正在编写低级梯度下降循环(就像我们现在这样),否则始终使用 predict()


与建模相关的问题

如何获取中间层的输出(特征提取)?

在功能 API 和顺序 API 中,如果某一层被调用刚好一次,您可以通过 layer.output 检索其输出,并通过 layer.input 检索其输入。 这使您能够快速实例化特征提取模型,例如这个:

import keras
from keras import layers

model = Sequential([
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(32, 3, activation='relu'),
    layers.MaxPooling2D(2),
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(32, 3, activation='relu'),
    layers.GlobalMaxPooling2D(),
    layers.Dense(10),
])
extractor = keras.Model(inputs=model.inputs,
                        outputs=[layer.output for layer in model.layers])
features = extractor(data)

当然,这对于重新定义了 callModel 子类来说是不可能的。

这是另一个示例:实例化一个返回特定命名层输出的 Model

model = ...  # 创建原始模型

layer_name = 'my_layer'
intermediate_layer_model = keras.Model(inputs=model.input,
                                       outputs=model.get_layer(layer_name).output)
intermediate_output = intermediate_layer_model(data)

如何在 Keras 中使用预训练模型?

您可以利用 keras.applications 中可用的模型, 或 KerasCVKerasNLP 中可用的模型。


如何使用有状态的 RNN?

使 RNN 有状态意味着每个批次样本的状态将被重用于下一个批次样本的初始状态。

使用有状态的 RNN 时,假设:

  • 所有批次具有相同数量的样本
  • 如果 x1x2 是相邻的样本批次,则对于每个 ix2[i]x1[i] 的后续序列。

要在 RNN 中使用有状态性,您需要: - 明确指定您使用的批量大小,通过将 batch_size 参数传递给模型中的第一层。例如,batch_size=32 用于具有 16 个特征每时间步的 10 时间步的 32 个样本批次序列。 - 在您的 RNN 层中设置 stateful=True。 - 在调用 fit() 时指定 shuffle=False

要重置累积的状态:

  • 使用 model.reset_states() 重置模型中所有层的状态
  • 使用 layer.reset_states() 重置特定有状态 RNN 层的状态

示例:

import keras
from keras import layers
import numpy as np

x = np.random.random((32, 21, 16))  # 这是我们的输入数据,形状为 (32, 21, 16)
# 我们将以长度为 10 的序列将其输入到我们的模型中

model = keras.Sequential()
model.add(layers.LSTM(32, input_shape=(10, 16), batch_size=32, stateful=True))
model.add(layers.Dense(16, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

# 我们训练网络以根据前 10 个时间步预测第 11 个时间步:
model.train_on_batch(x[:, :10, :], np.reshape(x[:, 10, :], (32, 16)))

# 网络的状态已经改变。我们可以输入后续序列:
model.train_on_batch(x[:, 10:20, :], np.reshape(x[:, 20, :], (32, 16)))

# 让我们重置 LSTM 层的状态:
model.reset_states()

# 在这种情况下还有另一种方法:
model.layers[0].reset_states()

请注意,predictfittrain_on_batch 等方法都会更新模型中有状态层的状态。这允许您不仅进行有状态训练,还能进行有状态预测。