Keras深度学习框架第四讲:使用内置方法进行训练和评估(1)

72 篇文章 0 订阅
48 篇文章 0 订阅

一、绪论

在Keras中,使用内置方法进行模型的训练和评估是非常常见的做法。Keras是一个流行的深度学习库,它为用户提供了简洁易用的API来构建和训练深度学习模型。
本问探讨了使用内置API进行模型训练、评估和预测(推断)的过程,这些API包括Model.fit()Model.evaluate()Model.predict()
如果你希望在调用fit()时指定自己的训练步骤函数,可以按照以下内容的要求,了解如何自定义fit()中的行为:

  • 使用TensorFlow编写自定义训练步骤
  • 使用JAX编写自定义训练步骤
  • 使用PyTorch编写自定义训练步骤
    如果你对从头开始编写自己的训练和评估循环,那么你可以按照以下内容的要求进行脚本编写:
  • 使用TensorFlow编写训练循环
  • 使用JAX编写训练循环
  • 使用PyTorch编写训练循环
    总的来说,无论你使用内置循环还是自行编写,模型训练和评估在Keras的各种模型类型中(如Sequential模型、使用Functional API构建的模型以及通过模型子类化从头编写的模型)的工作方式都是完全相同的。

二、API概览:一个端到端的初步示例

当将数据传递给模型的内置训练循环时,应该使用以下方式之一:

  • NumPy数组(如果您的数据较小且能够装入内存)
  • keras.utils.Sequence的子类
  • tf.data.Dataset对象
  • PyTorch 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)

以下是典型的端到端工作流程,包括:

  1. 训练
  2. 在从原始训练数据中分割出来的保留集上进行验证
  3. 在测试数据上进行评估

我们将使用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

通过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)

三、训练和评估方法详解

3.1 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

3.2 内置的优化器、损失函数和评估指标

通常,创建模型时不需要从头开始创建自己的损失函数、评估指标或优化器,因为这些需要已经是Keras API的一部分:
优化器:

  • SGD()(带或不带动量)

  • RMSprop()

  • Adam()

  • 等等
    损失函数:

  • MeanSquaredError()

  • KLDivergence()

  • CosineSimilarity()

  • 等等
    评估指标:

  • AUC()

  • Precision()

  • Recall()

  • 等等
    自定义损失
    如果您需要创建一个自定义损失函数,Keras提供了三种方法来实现。
    第一种方法涉及创建一个函数,该函数接受输入y_truey_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_truey_pred之外还接受参数的损失函数,可以通过继承keras.losses.Loss类并实现以下两个方法来实现:
__init__(self): 接受在调用损失函数时需要传递的参数
call(self, y_true, y_pred): 使用目标值(y_true)和模型预测值(y_pred)来计算模型的损失
假设想使用均方误差,但增加了一个项来减少远离0.5的预测值(我们假设类别目标是独热编码的,取值在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>

3.3 自定义评估指标

如果需要API中没有提供的评估指标,可以通过继承keras.metrics.Metric类来轻松地创建自定义评估指标。可以通过以下四个方法来实现:
__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>

3.4 处理不符合标准签名的损失和指标

绝大多数的损失和指标都可以从y_truey_pred中计算出来,其中y_pred是模型的一个输出——但并非所有情况都如此。例如,正则化损失可能只需要某一层的激活值(在这种情况下没有目标值),而这个激活值可能并不是模型的输出。
在这种情况下,可以在自定义层的call方法内部调用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)

可以在一个具有两个输入(输入数据和目标)的模型中使用它,并在编译时省略损失参数,如下所示:

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>

3.5 使用tf.data数据集进行训练和评估

在前面的几个段落中,已经了解了如何处理损失、指标和优化器,并且看到了当数据作为NumPy数组传递时,如何在fit()方法中使用validation_datavalidation_split参数。
另一种选择是使用迭代器,如tf.data.Dataset、PyTorch的DataLoader或Keras的PyDataset。让我们来看看tf.data.Dataset
tf.data API是TensorFlow 2.0中一组用于以快速且可扩展的方式加载和预处理数据的实用工具。
无论使用的是哪个后端(JAX、PyTorch还是TensorFlow),可以使用tf.data来训练您的Keras模型,可以直接将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}

请注意,在每个epoch结束时,Dataset会被重置,因此可以在下一个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实例作为validation_data参数传递给fit()方法:

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>

请注意,验证数据集在每次使用后都会被重置(。
当从Dataset对象进行训练时,不支持使用validation_split参数(从训练数据中生成保留集),因为此功能需要能够索引数据集的样本,这在一般情况下使用Dataset API是不可行的。
未完待续…

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MUKAMO

你的鼓励是我们创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值