一份常见的 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 深度学习 VM (前提是 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("设备:", tpu.master())
strategy = tf.distribute.TPUStrategy(tpu)
except:
strategy = tf.distribute.get_strategy()
print("副本数量:", strategy.num_replicas_in_sync)
with strategy.scope():
# 在这里创建您的模型。
...
重要的是,您应该:
compile()
函数的 experimental_steps_per_execution
参数来实现。这将给小模型带来显著的速度提升。所有 Keras 数据的默认存储目录是:
$HOME/.keras/
例如,对于我在 MacBook Pro 上,它是 /Users/fchollet/.keras/
。
请注意,Windows 用户应该将 $HOME
替换为 %USERPROFILE%
。
如果 Keras 无法创建上述目录(例如,由于权限问题),则会使用 /tmp/.keras/
作为备份。
Keras 配置文件是一个存储在 $HOME/.keras/keras.json
的 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 # 删除现有模型
# 返回一个已编译的模型
# 与之前的模型相同
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)
示例: """ 假设原始模型看起来是这样的:
model = Sequential() model.add(Dense(2, input_dim=3, name='dense_1')) model.add(Dense(3, name='dense_2')) ... model.save_weights(fname) """
model = Sequential() model.add(Dense(2, input_dim=3, name='dense_1')) # 将被加载 model.add(Dense(10, name='new_dense')) # 将不会被加载
model.load_weights(fname, by_name=True)
另请参阅 如何安装 HDF5 或 h5py 来保存我的模型? 以获取有关如何安装 h5py
的说明。
3) 仅配置保存(序列化)
如果您只需要保存模型的 架构,而不是其权重或训练配置,您可以这样做:
# 保存为 JSON
json_string = model.to_json()
生成的 JSON 文件是可读的,并且可以在必要时手动编辑。
然后,您可以从这些数据构建一个新的模型:
# 从 JSON 重建模型:
from keras.models import model_from_json
model = model_from_json(json_string)
4) 处理保存模型中的自定义层(或其他自定义对象)
如果您要加载的模型包含自定义层或其他自定义类或函数,您可以通过 custom_objects
参数将它们传递给加载机制:
from keras.models import load_model
# 假设您的模型包含“AttentionLayer”类的实例
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.io}},
}
以下是一些常见的定义,了解这些定义是正确使用 Keras fit()
的必要条件:
validation_data
或 validation_split
与 Keras 模型的 fit
方法时,评估将在每个 轮次 结束时运行。
在 Keras 中,有能力添加 回调,专门设计在 轮次 结束时运行。这些的例子包括学习率变化和模型检查点(保存)。Keras 模型有两种模式:训练和测试。正则化机制,例如 Dropout 和 L1/L2 权重正则化,在测试时会关闭。 它们反映在训练时损失中,但不反映在测试时损失中。
此外,Keras 显示的训练损失是每个训练数据批次损失的平均值,在当前轮次内。 由于您的模型随着时间变化,轮次初期的批次损失通常高于最后几个批次的损失。 这可能导致每轮的平均损失下降。 另一方面,轮次的测试损失是使用模型在轮次结束时的状态计算的,从而导致较低的损失。
为了确保在任何时候都能够从中断的训练运行中恢复(容错),
您应该使用 keras.callbacks.BackupAndRestore
回调,定期将您的训练进度(包括纪元号和权重)保存到磁盘,并在下次调用 Model.fit()
时加载它。
import numpy as np
import keras
class InterruptingCallback(keras.callbacks.Callback):
"""一个回调,用于故意引入训练中断。"""
def on_epoch_end(self, epoch, log=None):
if epoch == 15:
raise RuntimeError('中断')
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('***处理中断***')
# 这将在中断时继续训练。
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)) # 创建该层的权重
>>> 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 # 冻结 ResNet50Base。
assert model.layers[0].trainable_weights == [] # ResNet50Base 没有可训练权重。
assert len(model.trainable_weights) == 2 # 只有 Dense 层的偏置和核。
model.compile(...)
model.fit(...) # 训练 Dense 同时排除 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 # 冻结 ResNet50Base。
# 遍历数据集的批次。
for inputs, targets in dataset:
# 打开梯度带。
with tf.GradientTape() as tape:
# 前向传播。
predictions = model(inputs)
# 计算该批次的损失值。
loss_value = loss_fn(targets, predictions)
# 获取关于 *可训练* 权重的损失的梯度。
gradients = tape.gradient(loss_value, model.trainable_weights)
# 更新模型的权重。
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
trainable
和 compile()
之间的交互
在模型上调用 compile()
旨在“冻结”该模型的行为。这意味着,在模型编译时 trainable
属性的值应该在模型的整个生命周期内被保留,直到再次调用 compile
。因此,如果您更改了 trainable
,请确保再次在您的
模型上调用 compile()
以便考虑您的更改。
- 共享层的 trainable
属性值已更改
- 模型 B 已编译
因此模型 A 和 B 为共享层使用不同的 trainable
值。该机制对于大多数现有的 GAN 实现至关重要,这些实现是:
discriminator.compile(...) # 当训练 `discriminator` 时,`discriminator` 的权重应该被更新
discriminator.trainable = False
gan.compile(...) # `discriminator` 是 `gan` 的子模型,当训练 `gan` 时不应该被更新
call()
中的 training
参数与 trainable
属性有什么区别?training
是 call
中的一个布尔参数,它决定了调用是否应在推断模式或训练模式下运行。例如,在训练模式下,Dropout
层应用随机丢弃并调整输出。在推断模式下,相同层将不执行任何操作。示例:
y = Dropout(0.5)(x, training=True) # 在训练和推断时都应用丢弃
trainable
是一个布尔层属性,决定在训练期间是否应更新该层的可训练权重以最小化损失。如果 layer.trainable
被设置为 False
,那么 layer.trainable_weights
将始终是一个空列表。示例:
model = Sequential([
ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
Dense(10),
])
model.layers[0].trainable = False # 冻结 ResNet50Base。
assert model.layers[0].trainable_weights == [] # ResNet50Base 没有可训练权重。
assert len(model.trainable_weights) == 2 # 仅有 Dense 层的偏置和内核。
model.compile(...)
model.fit(...) # 训练 Dense,同时排除 ResNet50Base。
如您所见,“推断模式与训练模式”和“层权重的可训练性”是两个非常不同的概念。
您可以想象以下情况:一个在训练期间通过反向传播学习缩放因子的丢弃层。我们称之为 AutoScaleDropout
。
这个层将同时具有可训练状态,并且在推断和训练中有不同的行为。
因为 trainable
属性和 training
调用参数是独立的,所以您可以这样做:
layer = AutoScaleDropout(0.5)
# 在训练和推断时都应用丢弃
# *并* 在训练期间学习缩放因子
y = layer(x, training=True)
assert len(layer.trainable_weights) == 1
# 在训练和推断时都应用丢弃
# 使用 *冻结* 的缩放因子
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 数组传递时,validation_split
选项才可用(而不是 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()
的行为怎么办?You have two options:
1) 子类化 Model
类并重写 train_step
(和 test_step
)方法
如果您想使用自定义更新规则但仍希望利用 fit()
提供的功能,例如回调、高效的步骤融合等,那么这是更好的选择。
请注意,这种模式并不妨碍您构建使用功能性 API 的模型,在这种情况下,您将使用您创建的类根据 inputs
和 outputs
来实例化模型。对于顺序模型也是如此,在这种情况下,您将子类化 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 和顺序 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 = ... # 创建原始模型
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 和 KerasNLP 中可用的模型。
使 RNN 有状态意味着每个批次样本的状态将被重用于下一个批次样本的初始状态。
使用有状态的 RNN 时,假设:
x1
和 x2
是相邻的样本批次,则对于每个 i
,x2[i]
是 x1[i]
的后续序列。要在 RNN 中使用有状态性,您需要:
- 明确指定您使用的批量大小,通过将 batch_size
参数传递给模型中的第一层。例如,batch_size=32
用于具有 16 个特征每时间步的 10 时间步的 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)) # 这是我们的输入数据,形状为 (32, 21, 16)
# 我们将以长度为 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')
# 我们训练网络以根据前 10 个时间步预测第 11 个时间步:
model.train_on_batch(x[:, :10, :], np.reshape(x[:, 10, :], (32, 16)))
# 网络的状态已经改变。我们可以输入后续序列:
model.train_on_batch(x[:, 10:20, :], np.reshape(x[:, 20, :], (32, 16)))
# 让我们重置 LSTM 层的状态:
model.reset_states()
# 在这种情况下还有另一种方法:
model.layers[0].reset_states()
请注意,predict
、fit
、train_on_batch
等方法都会更新模型中有状态层的状态。这允许您不仅进行有状态训练,还能进行有状态预测。