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 深度学习虚拟机 (如果虚拟机上设置了 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 及更高版本是必要的,以确保某些基于哈希的操作(例如,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})
为了将您的 Keras 模型保存为 HDF5 文件,Keras 使用 h5py Python 包。它是 Keras 的一个依赖项,应该默认安装。在基于 Debian 的发行版上,您还需要额外安装 libhdf5
sudo apt-get install libhdf5-serial-dev
如果您不确定 h5py 是否已安装,可以打开 Python shell 并通过以下方式加载模块
import h5py
如果导入时没有错误,则表示已安装,否则您可以在此处找到详细的安装说明。
如果 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.
您可以使用函数式 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.
如您所见,“推理模式 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()
进行训练时,推荐的监控指标的方法是什么?损失值和指标值通过 fit()
调用显示的默认进度条报告。然而,在控制台盯着不断变化的 ASCII 数字并不是最佳的指标监控体验。我们推荐使用 TensorBoard,它会显示您训练和验证指标的精美图表,在训练期间定期更新,您可以从浏览器访问这些图表。
您可以通过 TensorBoard
回调在 fit()
中使用 TensorBoard。
fit()
的行为怎么办?您有两种选择
1) 子类化 Model
类并重写 train_step
(和 test_step
)方法
如果您想使用自定义更新规则,但仍希望利用 fit()
提供的功能(如回调、高效的步骤融合等),这是一个更好的选择。
请注意,这种模式并不妨碍您使用函数式 API 构建模型,在这种情况下,您将使用创建的类来实例化具有 inputs
和 outputs
的模型。对于顺序模型也是如此,在这种情况下,您将子类化 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.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
等都将全部更新模型中有状态层的状态。这使您不仅可以进行有状态训练,还可以进行有状态预测。