作者: fchollet
创建日期 2019/03/01
最后修改日期 2023/06/25
描述: 使用 fit()
和 evaluate()
进行训练和评估的完整指南。
# We import torch & TF so as to use torch Dataloaders & tf.data.Datasets.
import torch
import tensorflow as tf
import os
import numpy as np
import keras
from keras import layers
from keras import ops
本指南介绍了在使用内置 API 进行训练和验证时(例如 Model.fit()
、Model.evaluate()
和 Model.predict()
)如何训练、评估和预测(推理)模型。
如果您有兴趣在使用 fit()
的同时指定自己的训练步骤函数,请参阅有关自定义 fit()
中发生情况的指南
如果您有兴趣从头开始编写自己的训练和评估循环,请参阅有关编写训练循环的指南
通常,无论您是使用内置循环还是编写自己的循环,模型训练和评估在每种 Keras 模型中的工作方式都完全相同——Sequential 模型、使用函数式 API 构建的模型以及通过模型子类化从头开始编写的模型。
在将数据传递给模型的内置训练循环时,您应该使用以下方法之一
keras.utils.PyDataset
的子类tf.data.Dataset
对象DataLoader
实例在接下来的几个段落中,我们将使用 MNIST 数据集作为 NumPy 数组,以演示如何使用优化器、损失和指标。之后,我们将仔细研究其他每个选项。
让我们考虑以下模型(这里,我们使用函数式 API 构建,但它也可以是 Sequential 模型或子类化模型)
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, activation="softmax", name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
以下是典型的端到端工作流程,包括
我们将在此示例中使用 MNIST 数据。
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
# Preprocess the data (these are NumPy arrays)
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255
y_train = y_train.astype("float32")
y_test = y_test.astype("float32")
# Reserve 10,000 samples for validation
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]
我们指定训练配置(优化器、损失、指标)
model.compile(
optimizer=keras.optimizers.RMSprop(), # Optimizer
# Loss function to minimize
loss=keras.losses.SparseCategoricalCrossentropy(),
# List of metrics to monitor
metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
我们调用 fit()
,它将通过将数据切分成大小为 batch_size
的“批次”,并重复迭代整个数据集给定的 epochs
次数来训练模型。
print("Fit model on training data")
history = model.fit(
x_train,
y_train,
batch_size=64,
epochs=2,
# We pass some validation for
# monitoring validation loss and metrics
# at the end of each epoch
validation_data=(x_val, y_val),
)
Fit model on training data
Epoch 1/2
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 955us/step - loss: 0.5740 - sparse_categorical_accuracy: 0.8368 - val_loss: 0.2040 - val_sparse_categorical_accuracy: 0.9420
Epoch 2/2
782/782 ━━━━━━━━━━━━━━━━━━━━ 0s 390us/step - loss: 0.1745 - sparse_categorical_accuracy: 0.9492 - val_loss: 0.1415 - val_sparse_categorical_accuracy: 0.9581
返回的 history
对象保存了训练期间的损失值和指标值的记录
print(history.history)
{'loss': [0.34448376297950745, 0.16419583559036255], 'sparse_categorical_accuracy': [0.9008600115776062, 0.9509199857711792], 'val_loss': [0.20404714345932007, 0.14145156741142273], 'val_sparse_categorical_accuracy': [0.9419999718666077, 0.9581000208854675]}
我们通过 evaluate()
在测试数据上评估模型
# Evaluate the model on the test data using `evaluate`
print("Evaluate on test data")
results = model.evaluate(x_test, y_test, batch_size=128)
print("test loss, test acc:", results)
# Generate predictions (probabilities -- the output of the last layer)
# on new data using `predict`
print("Generate predictions for 3 samples")
predictions = model.predict(x_test[:3])
print("predictions shape:", predictions.shape)
Evaluate on test data
79/79 ━━━━━━━━━━━━━━━━━━━━ 0s 271us/step - loss: 0.1670 - sparse_categorical_accuracy: 0.9489
test loss, test acc: [0.1484374850988388, 0.9550999999046326]
Generate predictions for 3 samples
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step
predictions shape: (3, 10)
现在,让我们详细回顾一下此工作流程的每个部分。
compile()
方法:指定损失、指标和优化器要使用 fit()
训练模型,您需要指定损失函数、优化器,以及可选地,指定一些要监控的指标。
您将这些作为参数传递给模型的 compile()
方法
model.compile(
optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss=keras.losses.SparseCategoricalCrossentropy(),
metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
metrics
参数应该是一个列表——您的模型可以有任意数量的指标。
如果您的模型有多个输出,您可以为每个输出指定不同的损失和指标,并且可以调节每个输出对模型总损失的贡献。您将在 **将数据传递给多输入、多输出模型** 部分中找到有关此的更多详细信息。
请注意,如果您对默认设置感到满意,在许多情况下,优化器、损失和指标可以通过字符串标识符作为快捷方式进行指定
model.compile(
optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["sparse_categorical_accuracy"],
)
为了以后重用,让我们将模型定义和编译步骤放在函数中;我们将在本指南的不同示例中多次调用它们。
def get_uncompiled_model():
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, activation="softmax", name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
return model
def get_compiled_model():
model = get_uncompiled_model()
model.compile(
optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["sparse_categorical_accuracy"],
)
return model
通常,您不必从头开始创建自己的损失、指标或优化器,因为您需要的内容很可能已经是 Keras API 的一部分
优化器
SGD()
(带或不带动量)RMSprop()
Adam()
损失
MeanSquaredError()
KLDivergence()
CosineSimilarity()
指标
AUC()
Precision()
Recall()
如果您需要创建自定义损失,Keras 提供了三种方法来做到这一点。
第一种方法是创建一个接受输入 y_true
和 y_pred
的函数。以下示例显示了一个计算真实数据和预测值之间均方误差的损失函数
def custom_mean_squared_error(y_true, y_pred):
return ops.mean(ops.square(y_true - y_pred), axis=-1)
model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=custom_mean_squared_error)
# We need to one-hot encode the labels to use MSE
y_train_one_hot = ops.one_hot(y_train, num_classes=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 525us/step - loss: 0.0277
<keras.src.callbacks.history.History at 0x2e5dde350>
如果您需要一个除了 y_true
和 y_pred
之外还接受参数的损失函数,您可以子类化 keras.losses.Loss
类并实现以下两个方法
__init__(self)
:接受在调用损失函数期间传递的参数call(self, y_true, y_pred)
:使用目标 (y_true) 和模型预测 (y_pred) 来计算模型的损失假设您想使用均方误差,但添加一个额外的项,以阻止预测值远离 0.5(我们假设分类目标是 one-hot 编码的,并且取 0 到 1 之间的值)。这会鼓励模型不要过于自信,这可能有助于减少过拟合(在我们尝试之前我们不会知道它是否有效!)。
以下是您如何做到这一点
class CustomMSE(keras.losses.Loss):
def __init__(self, regularization_factor=0.1, name="custom_mse"):
super().__init__(name=name)
self.regularization_factor = regularization_factor
def call(self, y_true, y_pred):
mse = ops.mean(ops.square(y_true - y_pred), axis=-1)
reg = ops.mean(ops.square(0.5 - y_pred), axis=-1)
return mse + reg * self.regularization_factor
model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=CustomMSE())
y_train_one_hot = ops.one_hot(y_train, num_classes=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 532us/step - loss: 0.0492
<keras.src.callbacks.history.History at 0x2e5d0d360>
如果您需要一个不属于 API 的指标,您可以通过子类化 keras.metrics.Metric
类轻松创建自定义指标。您需要实现 4 个方法
__init__(self)
,您将在其中为您的指标创建状态变量。update_state(self, y_true, y_pred, sample_weight=None)
,它使用目标 y_true 和模型预测 y_pred 来更新状态变量。result(self)
,它使用状态变量来计算最终结果。reset_state(self)
,它重新初始化指标的状态。状态更新和结果计算是分开的(分别在 update_state()
和 result()
中),因为在某些情况下,结果计算可能非常昂贵,并且只会定期完成。
这是一个简单的示例,展示了如何实现 CategoricalTruePositives
指标,该指标计算有多少个样本被正确分类为属于给定类别
class CategoricalTruePositives(keras.metrics.Metric):
def __init__(self, name="categorical_true_positives", **kwargs):
super().__init__(name=name, **kwargs)
self.true_positives = self.add_variable(
shape=(), name="ctp", initializer="zeros"
)
def update_state(self, y_true, y_pred, sample_weight=None):
y_pred = ops.reshape(ops.argmax(y_pred, axis=1), (-1, 1))
values = ops.cast(y_true, "int32") == ops.cast(y_pred, "int32")
values = ops.cast(values, "float32")
if sample_weight is not None:
sample_weight = ops.cast(sample_weight, "float32")
values = ops.multiply(values, sample_weight)
self.true_positives.assign_add(ops.sum(values))
def result(self):
return self.true_positives.value
def reset_state(self):
# The state of the metric will be reset at the start of each epoch.
self.true_positives.assign(0.0)
model = get_uncompiled_model()
model.compile(
optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss=keras.losses.SparseCategoricalCrossentropy(),
metrics=[CategoricalTruePositives()],
)
model.fit(x_train, y_train, batch_size=64, epochs=3)
Epoch 1/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 568us/step - categorical_true_positives: 180967.9219 - loss: 0.5876
Epoch 2/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 0s 377us/step - categorical_true_positives: 182141.9375 - loss: 0.1733
Epoch 3/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 0s 377us/step - categorical_true_positives: 182303.5312 - loss: 0.1180
<keras.src.callbacks.history.History at 0x2e5f02d10>
绝大多数损失和指标都可以从 y_true
和 y_pred
计算得出,其中 y_pred
是模型的输出——但并非全部。例如,正则化损失可能只需要一层的激活(在这种情况下没有目标),并且此激活可能不是模型输出。
在这种情况下,您可以从自定义层的调用方法内部调用 self.add_loss(loss_value)
。以这种方式添加的损失会在训练期间添加到“主”损失(传递给 compile()
的损失)。这是一个简单的示例,它添加了活动正则化(请注意,所有 Keras 层都内置了活动正则化——此层只是为了提供一个具体的示例)
class ActivityRegularizationLayer(layers.Layer):
def call(self, inputs):
self.add_loss(ops.sum(inputs) * 0.1)
return inputs # Pass-through layer.
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(
optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)
# The displayed loss will be much higher than before
# due to the regularization component.
model.fit(x_train, y_train, batch_size=64, epochs=1)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 505us/step - loss: 3.4083
<keras.src.callbacks.history.History at 0x2e60226b0>
请注意,当您通过 add_loss()
传递损失时,可以在没有损失函数的情况下调用 compile()
,因为模型已经有一个要最小化的损失。
考虑以下 LogisticEndpoint
层:它将目标和 logits 作为输入,并通过 add_loss()
跟踪交叉熵损失。
class LogisticEndpoint(keras.layers.Layer):
def __init__(self, name=None):
super().__init__(name=name)
self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
def call(self, targets, logits, sample_weights=None):
# Compute the training-time loss value and add it
# to the layer using `self.add_loss()`.
loss = self.loss_fn(targets, logits, sample_weights)
self.add_loss(loss)
# Return the inference-time prediction tensor (for `.predict()`).
return ops.softmax(logits)
您可以在具有两个输入(输入数据和目标)的模型中使用它,该模型在没有 loss
参数的情况下编译,如下所示
inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(targets, logits)
model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam") # No loss argument!
data = {
"inputs": np.random.random((3, 3)),
"targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 89ms/step - loss: 0.6982
<keras.src.callbacks.history.History at 0x2e5cc91e0>
有关训练多输入模型的更多信息,请参阅 **将数据传递给多输入、多输出模型** 部分。
在您看到的第一个端到端示例中,我们使用 validation_data
参数将 NumPy 数组的元组 (x_val, y_val)
传递给模型,以便在每个 epoch 结束时评估验证损失和验证指标。
这是另一个选项:参数 validation_split
允许您自动保留部分训练数据以进行验证。参数值表示要保留用于验证的数据比例,因此应将其设置为大于 0 且小于 1 的数字。例如,validation_split=0.2
表示“使用 20% 的数据进行验证”,而 validation_split=0.6
表示“使用 60% 的数据进行验证”。
验证的计算方式是获取 fit()
调用接收的数组的最后 x% 个样本,在任何打乱之前。
请注意,您只能在使用 NumPy 数据进行训练时使用 validation_split
。
model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1)
625/625 ━━━━━━━━━━━━━━━━━━━━ 1s 563us/step - loss: 0.6161 - sparse_categorical_accuracy: 0.8259 - val_loss: 0.2379 - val_sparse_categorical_accuracy: 0.9302
<keras.src.callbacks.history.History at 0x2e6007610>
tf.data
数据集进行训练和评估在过去的几个段落中,您已经了解了如何处理损失、指标和优化器,并且了解了当您的数据作为 NumPy 数组传递时,如何在 fit()
中使用 validation_data
和 validation_split
参数。
另一种选择是使用类似迭代器的对象,例如 tf.data.Dataset
、PyTorch DataLoader
或 Keras PyDataset
。让我们先看看前者。
tf.data
API 是 TensorFlow 2.0 中的一组实用程序,用于以快速且可扩展的方式加载和预处理数据。有关创建 Datasets
的完整指南,请参阅 tf.data 文档。
您可以使用 tf.data
来训练您的 Keras 模型,无论您使用的是哪个后端——无论是 JAX、PyTorch 还是 TensorFlow。 您可以将 Dataset
实例直接传递给 fit()
、evaluate()
和 predict()
方法。
model = get_compiled_model()
# First, let's create a training Dataset instance.
# For the sake of our example, we'll use the same MNIST data as before.
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
# Now we get a test dataset.
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(64)
# Since the dataset already takes care of batching,
# we don't pass a `batch_size` argument.
model.fit(train_dataset, epochs=3)
# You can also evaluate or predict on a dataset.
print("Evaluate")
result = model.evaluate(test_dataset)
dict(zip(model.metrics_names, result))
Epoch 1/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 688us/step - loss: 0.5631 - sparse_categorical_accuracy: 0.8458
Epoch 2/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 0s 512us/step - loss: 0.1703 - sparse_categorical_accuracy: 0.9484
Epoch 3/3
782/782 ━━━━━━━━━━━━━━━━━━━━ 0s 506us/step - loss: 0.1187 - sparse_categorical_accuracy: 0.9640
Evaluate
157/157 ━━━━━━━━━━━━━━━━━━━━ 0s 622us/step - loss: 0.1380 - sparse_categorical_accuracy: 0.9582
{'loss': 0.11913617700338364, 'compile_metrics': 0.965399980545044}
请注意,Dataset 在每个 epoch 结束时都会重置,因此可以在下一个 epoch 中重复使用。
如果您只想在此 Dataset 中运行特定数量的批次的训练,则可以传递 steps_per_epoch
参数,该参数指定模型在使用此 Dataset 移至下一个 epoch 之前应运行多少个训练步骤。
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
# Only use the 100 batches per epoch (that's 64 * 100 samples)
model.fit(train_dataset, epochs=3, steps_per_epoch=100)
Epoch 1/3
100/100 ━━━━━━━━━━━━━━━━━━━━ 0s 508us/step - loss: 1.2000 - sparse_categorical_accuracy: 0.6822
Epoch 2/3
100/100 ━━━━━━━━━━━━━━━━━━━━ 0s 481us/step - loss: 0.4004 - sparse_categorical_accuracy: 0.8827
Epoch 3/3
100/100 ━━━━━━━━━━━━━━━━━━━━ 0s 471us/step - loss: 0.3546 - sparse_categorical_accuracy: 0.8968
<keras.src.callbacks.history.History at 0x2e64df400>
您还可以将 Dataset
实例作为 fit()
中的 validation_data
参数传递。
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)
model.fit(train_dataset, epochs=1, validation_data=val_dataset)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 837us/step - loss: 0.5569 - sparse_categorical_accuracy: 0.8508 - val_loss: 0.1711 - val_sparse_categorical_accuracy: 0.9527
<keras.src.callbacks.history.History at 0x2e641e920>
在每个 epoch 结束时,模型将迭代验证数据集并计算验证损失和验证指标。
如果您只想从此数据集中运行特定数量的批次的验证,则可以传递 validation_steps
参数,该参数指定模型在使用验证数据集中断验证并移至下一个 epoch 之前应运行多少个验证步骤。
model = get_compiled_model()
# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)
model.fit(
train_dataset,
epochs=1,
# Only run validation using the first 10 batches of the dataset
# using the `validation_steps` argument
validation_data=val_dataset,
validation_steps=10,
)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 771us/step - loss: 0.5562 - sparse_categorical_accuracy: 0.8436 - val_loss: 0.3345 - val_sparse_categorical_accuracy: 0.9062
<keras.src.callbacks.history.History at 0x2f9542e00>
请注意,验证数据集将在每次使用后重置(以便您始终在每个 epoch 中评估相同的样本)。
当从 Dataset
对象训练时,不支持 validation_split
参数(从训练数据生成保留集),因为此功能需要能够索引数据集的样本,这在使用 Dataset
API 时通常是不可能的。
PyDataset
实例进行训练和评估keras.utils.PyDataset
是一个实用程序,您可以对其进行子类化以获得具有两个重要属性的 Python 生成器
fit()
中传递 shuffle=True
时)。PyDataset
必须实现两个方法
__getitem__
__len__
__getitem__
方法应返回一个完整的批次。如果要在 epoch 之间修改数据集,可以实现 on_epoch_end
。
这是一个快速示例
class ExamplePyDataset(keras.utils.PyDataset):
def __init__(self, x, y, batch_size, **kwargs):
super().__init__(**kwargs)
self.x = x
self.y = y
self.batch_size = batch_size
def __len__(self):
return int(np.ceil(len(self.x) / float(self.batch_size)))
def __getitem__(self, idx):
batch_x = self.x[idx * self.batch_size : (idx + 1) * self.batch_size]
batch_y = self.y[idx * self.batch_size : (idx + 1) * self.batch_size]
return batch_x, batch_y
train_py_dataset = ExamplePyDataset(x_train, y_train, batch_size=32)
val_py_dataset = ExamplePyDataset(x_val, y_val, batch_size=32)
要拟合模型,请将数据集作为 x
参数传递(无需 y
参数,因为数据集包含目标),并将验证数据集作为 validation_data
参数传递。并且无需 batch_size
参数,因为数据集已经分批处理了!
model = get_compiled_model()
model.fit(train_py_dataset, batch_size=64, validation_data=val_py_dataset, epochs=1)
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 1s 443us/step - loss: 0.5217 - sparse_categorical_accuracy: 0.8473 - val_loss: 0.1576 - val_sparse_categorical_accuracy: 0.9525
<keras.src.callbacks.history.History at 0x2f9c8d120>
评估模型同样容易
model.evaluate(val_py_dataset)
313/313 ━━━━━━━━━━━━━━━━━━━━ 0s 157us/step - loss: 0.1821 - sparse_categorical_accuracy: 0.9450
[0.15764616429805756, 0.9524999856948853]
重要的是,PyDataset
对象支持三个常见的构造函数参数,用于处理并行处理配置
workers
:在多线程或多处理中使用的 worker 数量。通常,您应将其设置为 CPU 上的核心数。use_multiprocessing
:是否使用 Python 多处理进行并行处理。将其设置为 True
意味着您的数据集将在多个 fork 进程中复制。为了从并行处理中获得计算级别(而不是 I/O 级别)的好处,这是必要的。但是,只有当您的数据集可以安全地进行 pickle 时,才能将其设置为 True
。max_queue_size
:在多线程或多处理设置中迭代数据集时,保持在队列中的最大批次数量。您可以减小此值以减少数据集的 CPU 内存消耗。它默认为 10。默认情况下,多处理被禁用 (use_multiprocessing=False
) 并且仅使用一个线程。您应该确保仅在您的代码在 Python if __name__ == "__main__":
块中运行时才启用 use_multiprocessing
,以避免出现问题。
这是一个 4 线程、非多处理的示例
train_py_dataset = ExamplePyDataset(x_train, y_train, batch_size=32, workers=4)
val_py_dataset = ExamplePyDataset(x_val, y_val, batch_size=32, workers=4)
model = get_compiled_model()
model.fit(train_py_dataset, batch_size=64, validation_data=val_py_dataset, epochs=1)
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 1s 561us/step - loss: 0.5146 - sparse_categorical_accuracy: 0.8516 - val_loss: 0.1623 - val_sparse_categorical_accuracy: 0.9514
<keras.src.callbacks.history.History at 0x2e7fd5ea0>
DataLoader
对象进行训练和评估所有内置的训练和评估 API 也与 torch.utils.data.Dataset
和 torch.utils.data.DataLoader
对象兼容——无论您使用的是 PyTorch 后端、JAX 还是 TensorFlow 后端。让我们看一个简单的例子。
与以批次为中心的 PyDataset
不同,PyTorch Dataset
对象以样本为中心:__len__
方法返回样本数,__getitem__
方法返回特定样本。
class ExampleTorchDataset(torch.utils.data.Dataset):
def __init__(self, x, y):
self.x = x
self.y = y
def __len__(self):
return len(self.x)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
train_torch_dataset = ExampleTorchDataset(x_train, y_train)
val_torch_dataset = ExampleTorchDataset(x_val, y_val)
要使用 PyTorch Dataset,您需要将其包装到 Dataloader
中,后者负责批处理和打乱
train_dataloader = torch.utils.data.DataLoader(
train_torch_dataset, batch_size=32, shuffle=True
)
val_dataloader = torch.utils.data.DataLoader(
val_torch_dataset, batch_size=32, shuffle=True
)
现在您可以像其他任何迭代器一样在 Keras API 中使用它们
model = get_compiled_model()
model.fit(train_dataloader, batch_size=64, validation_data=val_dataloader, epochs=1)
model.evaluate(val_dataloader)
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 1s 575us/step - loss: 0.5051 - sparse_categorical_accuracy: 0.8568 - val_loss: 0.1613 - val_sparse_categorical_accuracy: 0.9528
313/313 ━━━━━━━━━━━━━━━━━━━━ 0s 278us/step - loss: 0.1551 - sparse_categorical_accuracy: 0.9541
[0.16209803521633148, 0.9527999758720398]
在默认设置下,样本的权重由其在数据集中出现的频率决定。有两种方法可以对数据进行加权,而与样本频率无关
这是通过将字典传递给 Model.fit()
的 class_weight
参数来设置的。此字典将类索引映射到应使用于属于该类的样本的权重。
这可以用于在不重新采样的情况下平衡类,或者训练一个模型,该模型更加重视特定的类。
例如,如果类“0”在您的数据中出现的次数是类“1”的一半,则可以使用 Model.fit(..., class_weight={0: 1., 1: 0.5})
。
这是一个 NumPy 示例,我们在其中使用类权重或样本权重来更加重视正确分类类 #5(在 MNIST 数据集中,它是数字“5”)。
class_weight = {
0: 1.0,
1: 1.0,
2: 1.0,
3: 1.0,
4: 1.0,
# Set weight "2" for class "5",
# making this class 2x more important
5: 2.0,
6: 1.0,
7: 1.0,
8: 1.0,
9: 1.0,
}
print("Fit with class weight")
model = get_compiled_model()
model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=1)
Fit with class weight
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 534us/step - loss: 0.6205 - sparse_categorical_accuracy: 0.8375
<keras.src.callbacks.history.History at 0x298d44eb0>
对于细粒度的控制,或者如果您不构建分类器,则可以使用“样本权重”。
sample_weight
参数传递给 Model.fit()
。tf.data
或任何其他类型的迭代器训练时:生成 (input_batch, label_batch, sample_weight_batch)
元组。“样本权重”数组是一个数字数组,用于指定批次中的每个样本在计算总损失时应具有的权重。它通常用于不平衡的分类问题中(其思想是给很少看到的类赋予更大的权重)。
当使用的权重为 1 和 0 时,该数组可以用作损失函数的掩码(完全丢弃某些样本对总损失的贡献)。
sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0
print("Fit with sample weight")
model = get_compiled_model()
model.fit(x_train, y_train, sample_weight=sample_weight, batch_size=64, epochs=1)
Fit with sample weight
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 546us/step - loss: 0.6397 - sparse_categorical_accuracy: 0.8388
<keras.src.callbacks.history.History at 0x298e066e0>
这是一个匹配的 Dataset
示例
sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0
# Create a Dataset that includes sample weights
# (3rd element in the return tuple).
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train, sample_weight))
# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
model = get_compiled_model()
model.fit(train_dataset, epochs=1)
782/782 ━━━━━━━━━━━━━━━━━━━━ 1s 651us/step - loss: 0.5971 - sparse_categorical_accuracy: 0.8445
<keras.src.callbacks.history.History at 0x312854100>
在前面的示例中,我们考虑的是一个具有单个输入(形状为 (764,)
的张量)和单个输出(形状为 (10,)
的预测张量)的模型。但是,具有多个输入或输出的模型呢?
考虑以下模型,该模型具有形状为 (32, 32, 3)
的图像输入(即 (高度, 宽度, 通道)
)和形状为 (None, 10)
的时间序列输入(即 (时间步长, 特征)
)。我们的模型将具有从这些输入的组合计算出的两个输出:一个“分数”(形状为 (1,)
)和一个超过五个类的概率分布(形状为 (5,)
)。
image_input = keras.Input(shape=(32, 32, 3), name="img_input")
timeseries_input = keras.Input(shape=(None, 10), name="ts_input")
x1 = layers.Conv2D(3, 3)(image_input)
x1 = layers.GlobalMaxPooling2D()(x1)
x2 = layers.Conv1D(3, 3)(timeseries_input)
x2 = layers.GlobalMaxPooling1D()(x2)
x = layers.concatenate([x1, x2])
score_output = layers.Dense(1, name="score_output")(x)
class_output = layers.Dense(5, name="class_output")(x)
model = keras.Model(
inputs=[image_input, timeseries_input], outputs=[score_output, class_output]
)
让我们绘制这个模型,以便您可以清楚地看到我们在这里做什么(请注意,图中显示的形状是批次形状,而不是每个样本的形状)。
keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
在编译时,我们可以通过将损失函数作为列表传递来为不同的输出指定不同的损失。
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy(),
],
)
如果我们只向模型传递一个损失函数,则相同的损失函数将应用于每个输出(这在这里是不合适的)。
指标也是如此
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy(),
],
metrics=[
[
keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError(),
],
[keras.metrics.CategoricalAccuracy()],
],
)
由于我们为输出层指定了名称,因此我们还可以通过字典指定每个输出的损失和指标
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={
"score_output": keras.losses.MeanSquaredError(),
"class_output": keras.losses.CategoricalCrossentropy(),
},
metrics={
"score_output": [
keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError(),
],
"class_output": [keras.metrics.CategoricalAccuracy()],
},
)
如果您的输出超过 2 个,我们建议使用显式名称和字典。
可以使用 loss_weights
参数为不同的特定于输出的损失赋予不同的权重(例如,人们可能希望在我们的示例中优先考虑“分数”损失,通过给予 2 倍于类损失的重要性)。
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={
"score_output": keras.losses.MeanSquaredError(),
"class_output": keras.losses.CategoricalCrossentropy(),
},
metrics={
"score_output": [
keras.metrics.MeanAbsolutePercentageError(),
keras.metrics.MeanAbsoluteError(),
],
"class_output": [keras.metrics.CategoricalAccuracy()],
},
loss_weights={"score_output": 2.0, "class_output": 1.0},
)
您还可以选择不计算某些输出的损失,如果这些输出旨在用于预测而不是训练
# List loss version
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[None, keras.losses.CategoricalCrossentropy()],
)
# Or dict loss version
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss={"class_output": keras.losses.CategoricalCrossentropy()},
)
在 fit()
中将数据传递到多输入或多输出模型的工作方式与在编译中指定损失函数的方式类似:您可以传递NumPy 数组列表(与接收损失函数的输出具有 1:1 映射)或将输出名称映射到 NumPy 数组的字典。
model.compile(
optimizer=keras.optimizers.RMSprop(1e-3),
loss=[
keras.losses.MeanSquaredError(),
keras.losses.CategoricalCrossentropy(),
],
)
# Generate dummy NumPy data
img_data = np.random.random_sample(size=(100, 32, 32, 3))
ts_data = np.random.random_sample(size=(100, 20, 10))
score_targets = np.random.random_sample(size=(100, 1))
class_targets = np.random.random_sample(size=(100, 5))
# Fit on lists
model.fit([img_data, ts_data], [score_targets, class_targets], batch_size=32, epochs=1)
# Alternatively, fit on dicts
model.fit(
{"img_input": img_data, "ts_input": ts_data},
{"score_output": score_targets, "class_output": class_targets},
batch_size=32,
epochs=1,
)
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 62ms/step - loss: 18.0146
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 56ms/step - loss: 17.6494
<keras.src.callbacks.history.History at 0x31a6c5810>
这是 Dataset
用例:与我们对 NumPy 数组所做的一样,Dataset
应返回一个字典的元组。
train_dataset = tf.data.Dataset.from_tensor_slices(
(
{"img_input": img_data, "ts_input": ts_data},
{"score_output": score_targets, "class_output": class_targets},
)
)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
model.fit(train_dataset, epochs=1)
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 197ms/step - loss: 17.8578
<keras.src.callbacks.history.History at 0x17c7e5690>
Keras 中的回调是在训练期间的不同点(在 epoch 开始时、在批次结束时、在 epoch 结束时等)调用的对象。它们可用于实现某些行为,例如
回调可以作为列表传递给您的 fit()
调用
model = get_compiled_model()
callbacks = [
keras.callbacks.EarlyStopping(
# Stop training when `val_loss` is no longer improving
monitor="val_loss",
# "no longer improving" being defined as "no better than 1e-2 less"
min_delta=1e-2,
# "no longer improving" being further defined as "for at least 2 epochs"
patience=2,
verbose=1,
)
]
model.fit(
x_train,
y_train,
epochs=20,
batch_size=64,
callbacks=callbacks,
validation_split=0.2,
)
Epoch 1/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 1s 622us/step - loss: 0.6245 - sparse_categorical_accuracy: 0.8275 - val_loss: 0.2231 - val_sparse_categorical_accuracy: 0.9330
Epoch 2/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 404us/step - loss: 0.1809 - sparse_categorical_accuracy: 0.9460 - val_loss: 0.1727 - val_sparse_categorical_accuracy: 0.9476
Epoch 3/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 398us/step - loss: 0.1336 - sparse_categorical_accuracy: 0.9598 - val_loss: 0.1564 - val_sparse_categorical_accuracy: 0.9545
Epoch 4/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 400us/step - loss: 0.1012 - sparse_categorical_accuracy: 0.9699 - val_loss: 0.1502 - val_sparse_categorical_accuracy: 0.9570
Epoch 5/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 403us/step - loss: 0.0835 - sparse_categorical_accuracy: 0.9748 - val_loss: 0.1436 - val_sparse_categorical_accuracy: 0.9589
Epoch 6/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 396us/step - loss: 0.0699 - sparse_categorical_accuracy: 0.9783 - val_loss: 0.1484 - val_sparse_categorical_accuracy: 0.9577
Epoch 7/20
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 402us/step - loss: 0.0603 - sparse_categorical_accuracy: 0.9814 - val_loss: 0.1406 - val_sparse_categorical_accuracy: 0.9629
Epoch 7: early stopping
<keras.src.callbacks.history.History at 0x31ae37c10>
Keras 中已经提供了许多内置回调,例如
ModelCheckpoint
:定期保存模型。EarlyStopping
:当训练不再改善验证指标时停止训练。TensorBoard
:定期写入可以在 TensorBoard 中可视化的模型日志(更多详细信息,请参阅“可视化”部分)。CSVLogger
:将损失和指标数据流式传输到 CSV 文件。有关完整列表,请参阅回调文档。
您可以通过扩展基类 keras.callbacks.Callback
来创建自定义回调。回调可以通过类属性 self.model
访问其关联的模型。
请务必阅读编写自定义回调的完整指南。
这是一个简单的示例,用于在训练期间保存每个批次的损失值列表
class LossHistory(keras.callbacks.Callback):
def on_train_begin(self, logs):
self.per_batch_losses = []
def on_batch_end(self, batch, logs):
self.per_batch_losses.append(logs.get("loss"))
当您在相对较大的数据集上训练模型时,至关重要的是频繁地保存模型的检查点。
实现此目的的最简单方法是使用 ModelCheckpoint
回调
model = get_compiled_model()
callbacks = [
keras.callbacks.ModelCheckpoint(
# Path where to save the model
# The two parameters below mean that we will overwrite
# the current checkpoint if and only if
# the `val_loss` score has improved.
# The saved model name will include the current epoch.
filepath="mymodel_{epoch}.keras",
save_best_only=True, # Only save a model if `val_loss` has improved.
monitor="val_loss",
verbose=1,
)
]
model.fit(
x_train,
y_train,
epochs=2,
batch_size=64,
callbacks=callbacks,
validation_split=0.2,
)
Epoch 1/2
559/625 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 360us/step - loss: 0.6490 - sparse_categorical_accuracy: 0.8209
Epoch 1: val_loss improved from inf to 0.22393, saving model to mymodel_1.keras
625/625 ━━━━━━━━━━━━━━━━━━━━ 1s 577us/step - loss: 0.6194 - sparse_categorical_accuracy: 0.8289 - val_loss: 0.2239 - val_sparse_categorical_accuracy: 0.9340
Epoch 2/2
565/625 ━━━━━━━━━━━━━━━━━━[37m━━ 0s 355us/step - loss: 0.1816 - sparse_categorical_accuracy: 0.9476
Epoch 2: val_loss improved from 0.22393 to 0.16868, saving model to mymodel_2.keras
625/625 ━━━━━━━━━━━━━━━━━━━━ 0s 411us/step - loss: 0.1806 - sparse_categorical_accuracy: 0.9479 - val_loss: 0.1687 - val_sparse_categorical_accuracy: 0.9494
<keras.src.callbacks.history.History at 0x2e5cb7250>
ModelCheckpoint
回调可用于实现容错:在训练随机中断的情况下,能够从模型上次保存的状态重新开始训练。这是一个基本示例
# Prepare a directory to store all the checkpoints.
checkpoint_dir = "./ckpt"
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
def make_or_restore_model():
# Either restore the latest model, or create a fresh one
# if there is no checkpoint available.
checkpoints = [checkpoint_dir + "/" + name for name in os.listdir(checkpoint_dir)]
if checkpoints:
latest_checkpoint = max(checkpoints, key=os.path.getctime)
print("Restoring from", latest_checkpoint)
return keras.models.load_model(latest_checkpoint)
print("Creating a new model")
return get_compiled_model()
model = make_or_restore_model()
callbacks = [
# This callback saves the model every 100 batches.
# We include the training loss in the saved model name.
keras.callbacks.ModelCheckpoint(
filepath=checkpoint_dir + "/model-loss={loss:.2f}.keras", save_freq=100
)
]
model.fit(x_train, y_train, epochs=1, callbacks=callbacks)
Creating a new model
1563/1563 ━━━━━━━━━━━━━━━━━━━━ 1s 390us/step - loss: 0.4910 - sparse_categorical_accuracy: 0.8623
<keras.src.callbacks.history.History at 0x2e5c454e0>
您也可以编写自己的回调来保存和还原模型。
有关序列化和保存的完整指南,请参阅保存和序列化模型的指南。
训练深度学习模型时的一个常见模式是随着训练的进行逐渐降低学习率。这通常称为“学习率衰减”。
学习率衰减计划可以是静态的(预先固定的,作为当前 epoch 或当前批次索引的函数),也可以是动态的(响应模型的当前行为,特别是验证损失)。
您可以通过在优化器中将 schedule 对象作为 learning_rate
参数传递,轻松使用静态学习率衰减计划。
initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True
)
optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)
有几种内置的 schedule 可用:ExponentialDecay
、PiecewiseConstantDecay
、PolynomialDecay
和 InverseTimeDecay
。
动态学习率计划(例如,当验证损失不再改善时降低学习率)无法通过这些 schedule 对象实现,因为优化器无法访问验证指标。
但是,回调可以访问所有指标,包括验证指标!因此,您可以通过使用一个回调来修改优化器上的当前学习率来实现此模式。实际上,这甚至内置在 ReduceLROnPlateau
回调中。
在训练期间密切关注模型的最佳方法是使用 TensorBoard – 一个基于浏览器的应用程序,您可以在本地运行,它为您提供:
Embedding
层学习的嵌入空间的 3D 可视化如果您已通过 pip 安装了 TensorFlow,您应该可以从命令行启动 TensorBoard
tensorboard --logdir=/full_path_to_your_logs
将 TensorBoard 与 Keras 模型和 fit()
方法一起使用的最简单方法是使用 TensorBoard
回调。
在最简单的情况下,只需指定您希望回调写入日志的位置,即可开始使用。
keras.callbacks.TensorBoard(
log_dir="/full_path_to_your_logs",
histogram_freq=0, # How often to log histogram visualizations
embeddings_freq=0, # How often to log embedding visualizations
update_freq="epoch",
) # How often to write logs (default: once per epoch)
<keras.src.callbacks.tensorboard.TensorBoard at 0x31b0188b0>
有关更多信息,请参阅 TensorBoard
回调的文档。