入门指南 / 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 深度学习虚拟机使用 TPU(前提是虚拟机上设置了 TPU_NAME 环境变量)。

所有 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("Device:", tpu.master())
    strategy = tf.distribute.TPUStrategy(tpu)
except:
    strategy = tf.distribute.get_strategy()
print("Number of replicas:", strategy.num_replicas_in_sync)

with strategy.scope():
    # Create your model here.
    ...

重要的是,您应该

  • 确保您能够足够快地读取数据,以保持 TPU 的利用率。
  • 考虑在每次图形执行中运行多个梯度下降步骤,以保持 TPU 的利用率。您可以通过 compile()experimental_steps_per_execution 参数来实现。它会为小型模型带来显着的加速。

Keras 配置文件存储在哪里?

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

$HOME/.keras/

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

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

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

Keras 配置文件是一个 JSON 文件,存储在 $HOME/.keras/keras.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  # deletes the existing model

# returns a compiled model
# identical to the previous one
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)

示例

"""
Assuming the original model looks like this:

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

# new model
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))  # will be loaded
model.add(Dense(10, name='new_dense'))  # will not be loaded

# load weights from the first model; will only affect the first layer, dense_1.
model.load_weights(fname, by_name=True)

另请参阅 如何安装 HDF5 或 h5py 来保存我的模型?,以获取有关如何安装 h5py 的说明。

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

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

# save as JSON
json_string = model.to_json()

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

然后,您可以从此数据构建一个全新的模型

# model reconstruction from JSON:
from keras.models import model_from_json
model = model_from_json(json_string)

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

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

from keras.models import load_model
# Assuming your model includes instance of an "AttentionLayer" class
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.org.cn}},
}


训练相关问题

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

以下是一些常用的定义,需要了解和理解才能正确使用 Keras fit()

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

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

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

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


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

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

import numpy as np
import keras

class InterruptingCallback(keras.callbacks.Callback):
  """A callback to intentionally introduce interruption to training."""
  def on_epoch_end(self, epoch, log=None):
    if epoch == 15:
      raise RuntimeError('Interruption')

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('***Handling interruption***')
  # This continues at the epoch where it left off.
  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)) # Create the weights of the layer
>>> 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  # Freeze ResNet50Base.

assert model.layers[0].trainable_weights == []  # ResNet50Base has no trainable weights.
assert len(model.trainable_weights) == 2  # Just the bias & kernel of the Dense layer.

model.compile(...)
model.fit(...)  # Train Dense while excluding 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  # Freeze ResNet50Base.

# Iterate over the batches of a dataset.
for inputs, targets in dataset:
    # Open a GradientTape.
    with tf.GradientTape() as tape:
        # Forward pass.
        predictions = model(inputs)
        # Compute the loss value for this batch.
        loss_value = loss_fn(targets, predictions)

    # Get gradients of loss wrt the *trainable* weights.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # Update the weights of the model.
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

trainablecompile() 之间的交互

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

例如,如果两个模型 A 和 B 共享一些层,并且

  • 模型 A 被编译
  • 共享层上的 trainable 属性值被更改
  • 模型 B 被编译

那么模型 A 和 B 对于共享层使用不同的 trainable 值。这种机制对于大多数现有的 GAN 实现至关重要,这些实现会

discriminator.compile(...)  # the weights of `discriminator` should be updated when `discriminator` is trained
discriminator.trainable = False
gan.compile(...)  # `discriminator` is a submodel of `gan`, which should not be updated when `gan` is trained

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

trainingcall 中的一个布尔参数,它确定调用应在推理模式还是训练模式下运行。例如,在训练模式下,Dropout 层应用随机 dropout 并重新缩放输出。在推理模式下,同一层不执行任何操作。示例

y = Dropout(0.5)(x, training=True)  # Applies dropout at training time *and* inference time

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

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

assert model.layers[0].trainable_weights == []  # ResNet50Base has no trainable weights.
assert len(model.trainable_weights) == 2  # Just the bias & kernel of the Dense layer.

model.compile(...)
model.fit(...)  # Train Dense while excluding ResNet50Base.

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

您可以想象以下情况:一个 dropout 层,其缩放因子在训练期间通过反向传播学习。我们将其命名为 AutoScaleDropout。该层将同时具有可训练状态,并且在推理和训练中具有不同的行为。由于 trainable 属性和 training 调用参数是独立的,您可以执行以下操作

layer = AutoScaleDropout(0.5)

# Applies dropout at training time *and* inference time  
# *and* learns the scaling factor during training
y = layer(x, training=True)

assert len(layer.trainable_weights) == 1
# Applies dropout at training time *and* inference time  
# with a *frozen* scaling factor

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% 的样本。

相同的验证集用于所有 epoch(在同一次 fit 调用中)。

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


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

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

如果您将数据作为 tf.data.Dataset 对象传递,并且 model.fit() 中的 shuffle 参数设置为 True,则数据集将进行本地洗牌(缓冲洗牌)。

当使用 tf.data.Dataset 对象时,最好预先洗牌数据(例如,通过调用 dataset = dataset.shuffle(buffer_size)),以便控制缓冲区大小。

验证数据永远不会被洗牌。


损失值和指标值通过 fit() 调用显示的默认进度条报告。但是,盯着控制台中不断变化的 ascii 数字并不是最佳的指标监控体验。我们建议使用 TensorBoard,它会显示训练和验证指标的漂亮图表,这些图表在训练期间定期更新,您可以从浏览器访问它们。

您可以通过 TensorBoard 回调将 TensorBoard 与 fit() 一起使用。


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

您有两种选择

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

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

请注意,此模式不会阻止您使用函数式 API 构建模型,在这种情况下,您将使用您创建的类通过 inputsoutputs 实例化模型。对于 Sequential 模型也是如此,在这种情况下,您将子类化 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 和 Sequential 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)

当然,对于子类化 Model 并覆盖 call 的模型,这是不可能的。

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

model = ...  # create the original 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 中提供的模型,或 KerasCVKerasHub 中提供的模型。


如何使用有状态的 RNN?

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

因此,当使用有状态 RNN 时,假设

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

要在 RNN 中使用有状态性,您需要

  • 通过将 batch_size 参数传递给模型中的第一层来显式指定您正在使用的批次大小。例如,对于包含 10 个时间步长的序列的 32 个样本批次,每个时间步长具有 16 个特征,则 batch_size=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))  # this is our input data, of shape (32, 21, 16)
# we will feed it to our model in sequences of length 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')

# we train the network to predict the 11th timestep given the first 10:
model.train_on_batch(x[:, :10, :], np.reshape(x[:, 10, :], (32, 16)))

# the state of the network has changed. We can feed the follow-up sequences:
model.train_on_batch(x[:, 10:20, :], np.reshape(x[:, 20, :], (32, 16)))

# let's reset the states of the LSTM layer:
model.reset_states()

# another way to do it in this case:
model.layers[0].reset_states()

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