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 笔记本和 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.
...
重要的是,您应该
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
回调,它会定期将您的训练进度(包括 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))
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% 的样本。
相同的验证集用于所有 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()
训练时,推荐的监控指标方法是什么?损失值和指标值通过 fit()
调用显示的默认进度条报告。但是,盯着控制台中不断变化的 ascii 数字并不是最佳的指标监控体验。我们建议使用 TensorBoard,它会显示训练和验证指标的漂亮图表,这些图表在训练期间定期更新,您可以从浏览器访问它们。
您可以通过 TensorBoard
回调将 TensorBoard 与 fit()
一起使用。
fit()
的行为怎么办?您有两种选择
1) 子类化 Model
类并重写 train_step
(和 test_step
) 方法
如果您想使用自定义更新规则,但仍然想利用 fit()
提供的功能(例如回调、高效的步骤融合等),这是一个更好的选择。
请注意,此模式不会阻止您使用函数式 API 构建模型,在这种情况下,您将使用您创建的类通过 inputs
和 outputs
实例化模型。对于 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.applications
中提供的模型,或 KerasCV 和 KerasHub 中提供的模型。
使 RNN 有状态意味着每个批次样本的状态将作为下一个批次样本的初始状态重复使用。
因此,当使用有状态 RNN 时,假设
x1
和 x2
是连续的样本批次,则对于每个 i
,x2[i]
是 x1[i]
的后续序列。要在 RNN 中使用有状态性,您需要
batch_size
参数传递给模型中的第一层来显式指定您正在使用的批次大小。例如,对于包含 10 个时间步长的序列的 32 个样本批次,每个时间步长具有 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
等都会全部更新模型中有状态层的状态。这使您不仅可以进行有状态训练,还可以进行有状态预测。