Keras 常见问题解答。
call() 中的 training 参数与 trainable 属性之间有什么区别?fit() 中,验证集是如何计算的?fit() 中,数据在训练时是否被打乱?fit() 进行训练时,监控指标的推荐方法是什么?fit() 的功能,该怎么办?Model 方法 predict() 和 __call__() 之间有什么区别?有两种方法可以在多 GPU 上运行单个模型:**数据并行** 和 **模型并行**。Keras 支持这两种方法。
对于数据并行,Keras 支持 JAX、TensorFlow 和 PyTorch 内置的数据并行分发 API。请参阅以下指南:
对于模型并行,Keras 有自己的分发 API,目前仅支持 JAX 后端。请参阅 LayoutMap API 文档。
TPU 是深度学习的快速且高效的硬件加速器,可在 Google Cloud 上公开使用。您可以通过 Colab、Kaggle Notebooks 和 GCP 深度学习虚拟机(前提是 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("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.
...
重要的是,您应该:
compile() 中的 experimental_steps_per_execution 参数来实现。对于小型模型,这将显著加快速度。所有 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_last 或 channels_first)。epsilon 数值模糊因子。"jax"、"tensorflow"、"torch" 或 "numpy" 之一。同样,缓存的数据集文件(例如通过 get_file() 下载的文件)默认存储在 $HOME/.keras/datasets/ 中,Keras Applications 缓存的模型权重文件默认存储在 $HOME/.keras/models/ 中。
我们推荐使用 KerasTuner。
有四种随机性来源需要考虑:
keras.random 操作或 keras.layers 中的随机层)。要使 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_model 和 model_from_json 的工作方式相同。
from keras.models import model_from_json
model = model_from_json(json_string, custom_objects={'AttentionLayer': AttentionLayer})
为了将 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() 至关重要:
fit 方法中使用 validation_data 或 validation_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.
您可以使用 Functional 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))
trainable 与 compile() 之间的交互
在模型上调用 compile() 是为了“冻结”该模型的行为。这意味着模型编译时 trainable 属性的值应在模型整个生命周期内保持不变,直到再次调用 compile。因此,如果您更改 trainable,请确保再次调用模型的 compile(),以便您的更改被采纳。
例如,如果两个模型 A 和 B 共享某些层,并且:
trainable 属性值被更改。那么模型 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 属性之间有什么区别?training 是 call 中的一个布尔参数,用于确定调用是应在推理模式还是训练模式下运行。例如,在训练模式下,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% 的样本。
相同的验证集用于所有轮次(在同一次 fit 调用中)。
请注意,只有当您的数据以 Numpy 数组形式传入时(而不是 tf.data.Datasets,它们不可索引),validation_split 选项才可用。
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 回调将 TensorBoard 与 fit() 一起使用。
fit() 的功能,该怎么办?您有两个选择:
1) 子类化 Model 类并重写 train_step(和 test_step)方法。
如果您想使用自定义更新规则,但仍想利用 fit() 提供的功能(如回调、高效的步融合等),这是更好的选择。
请注意,此模式并不会阻止您构建使用 Functional API 的模型,在这种情况下,您将使用您创建的类来实例化带有 inputs 和 outputs 的模型。对于 Sequential 模型也是如此,在这种情况下,您将子类化 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()调用可以扩展到非常大的数组。同时,predict()是不可微分的:如果您在GradientTape作用域中调用它,则无法检索其梯度。当您需要检索模型调用的梯度时,应使用
model(x);如果您只需要输出值,则应使用predict()。换句话说,除非您正在编写低级梯度下降循环(就像我们现在这样),否则始终使用predict()。
在 Functional 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)
当然,这对于重写 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.applications 中提供的 模型,或者 KerasCV 和 KerasHub 中提供的模型。
使 RNN 具有状态化意味着每个批次样本的状态将被用作下一个批次样本的初始状态。
使用状态化 RNN 时,因此假定:
x1 和 x2 是连续的样本批次,那么对于每个 i,x2[i] 是 x1[i] 的后续序列。要使用 RNN 的状态化,您需要:
batch_size 参数。例如,对于 32 个样本的序列批次(每个时间步 10 个时间步,每个时间步 16 个特征),使用 batch_size=32。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()
请注意,predict、fit、train_on_batch 等方法**都会**更新模型中状态化层的状态。这使您不仅可以进行状态化训练,还可以进行状态化预测。