入门 / Keras 常见问题解答

Keras 常见问题解答

Keras 常见问题列表。

通用问题

训练相关问题

模型相关问题


通用问题

如何(在单机上)在多个 GPU 上训练 Keras 模型?

在多个 GPU 上运行单个模型有两种方式:数据并行设备并行。Keras 同时支持这两种方式。

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

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


如何在 TPU 上训练 Keras 模型?

TPU 是一种用于深度学习的快速高效的硬件加速器,可在 Google Cloud 上公开使用。您可以通过 Colab、Kaggle notebooks 和 GCP 深度学习虚拟机 (如果虚拟机上设置了 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("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 及更高版本是必要的,以确保某些基于哈希的操作(例如,set 或 dict 中的项目顺序)具有可重现的行为(参见 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_model & model_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 对您的研究有所帮助,请在您的出版物中引用它。以下是一个 BibTeX 条目示例:

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


训练相关问题

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

以下是正确使用 Keras fit() 所需了解和理解的一些常见定义

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

为什么我的训练损失比测试损失高得多?

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

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


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

为了确保能够随时从中断的训练运行中恢复(容错),您应该使用 keras.callbacks.BackupAndRestore 回调,该回调会定期将您的训练进度(包括周期数和权重)保存到磁盘,并在下次调用 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.

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

您可以想象以下情况:一个 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 设置为 False 也会使其 training 调用参数默认设置为 False,这意味着该层在训练期间不会更新其状态。

此行为仅适用于 BatchNormalization。对于所有其他层,权重可训练性和“推理 vs 训练模式”保持独立。


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

如果您在 model.fit 中设置 validation_split 参数,例如 0.1,那么使用的验证数据将是数据的最后 10%。如果您设置为 0.25,它将是数据的最后 25%,依此类推。请注意,在提取验证分割之前数据不会被打乱,因此验证集实际上只是您传入的输入中最后 x% 的样本。

所有周期(在同一次 fit 调用内)都使用相同的验证集。

请注意,validation_split 选项仅在您的数据作为 Numpy 数组(而不是不可索引的 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() 调用显示的默认进度条报告。然而,在控制台盯着不断变化的 ASCII 数字并不是最佳的指标监控体验。我们推荐使用 TensorBoard,它会显示您训练和验证指标的精美图表,在训练期间定期更新,您可以从浏览器访问这些图表。

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


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

您有两种选择

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

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

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

请参阅以下指南:

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

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

请参阅以下指南:


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

我们用 Python 深度学习,第二版 中的一段摘录来回答

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)

当然,对于重写 call 方法的 Model 子类化模型,这是不可能的。

这是另一个示例:实例化一个返回特定命名层输出的 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 参数传递给模型中的第一层,显式指定您正在使用的批次大小。例如,对于包含 32 个样本、每个序列 10 个时间步、每个时间步 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 等都将全部更新模型中有状态层的状态。这使您不仅可以进行有状态训练,还可以进行有状态预测。