代码示例 / 快速Keras食谱 / 评估和导出 scikit-learn 指标的 Keras 回调

评估和导出 scikit-learn 指标的 Keras 回调

作者: lukewood
创建日期: 10/07/2021
最后修改: 11/17/2023
描述: 此示例展示了如何使用 Keras 回调来评估和导出非 TensorFlow 基于的指标。

在 Colab 中查看 GitHub 源代码


介绍

Keras 回调 允许在 Keras 训练过程的各个阶段执行任意代码。虽然 Keras 提供了对指标评估的优先支持,但 Keras 指标 可能仅依赖于 TensorFlow 代码。

虽然网上有许多指标的 TensorFlow 实现,但某些指标是使用 NumPy 或其他基于 Python 的数值计算库实现的。通过在 Keras 回调中进行指标评估,我们可以利用任何现有的指标,并最终将结果导出到 TensorBoard。


Jaccard 分数指标

此示例使用了一个 sklearn 指标 sklearn.metrics.jaccard_score(),并通过 tf.summary API 将结果写入 TensorBoard。

此模板可以稍作修改以使其适用于任何现有的 sklearn 指标。

import os

os.environ["KERAS_BACKEND"] = "tensorflow"

import tensorflow as tf
import keras as keras
from keras import layers
from sklearn.metrics import jaccard_score
import numpy as np
import os


class JaccardScoreCallback(keras.callbacks.Callback):
    """计算 Jaccard 分数并将结果记录到 TensorBoard。"""

    def __init__(self, name, x_test, y_test, log_dir):
        self.x_test = x_test
        self.y_test = y_test
        self.keras_metric = keras.metrics.Mean("jaccard_score")
        self.epoch = 0
        self.summary_writer = tf.summary.create_file_writer(os.path.join(log_dir, name))

    def on_epoch_end(self, batch, logs=None):
        self.epoch += 1
        self.keras_metric.reset_state()
        predictions = self.model.predict(self.x_test)
        jaccard_value = jaccard_score(
            np.argmax(predictions, axis=-1), self.y_test, average=None
        )
        self.keras_metric.update_state(jaccard_value)
        self._write_metric(
            self.keras_metric.name, self.keras_metric.result().numpy().astype(float)
        )

    def _write_metric(self, name, value):
        with self.summary_writer.as_default():
            tf.summary.scalar(
                name,
                value,
                step=self.epoch,
            )
            self.summary_writer.flush()

示例用法

让我们用 Keras 模型测试我们的 JaccardScoreCallback 类。

# 模型 / 数据参数
num_classes = 10
input_shape = (28, 28, 1)

# 数据集,分为训练集和测试集
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# 将图像缩放到 [0, 1] 范围
x_train = x_train.astype("float32") / 255
x_test = x_test.astype("float32") / 255
# 确保图像形状为 (28, 28, 1)
x_train = np.expand_dims(x_train, -1)
x_test = np.expand_dims(x_test, -1)
print("x_train shape:", x_train.shape)
print(x_train.shape[0], "训练样本")
print(x_test.shape[0], "测试样本")


# 将类向量转换为二进制类矩阵。
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = keras.Sequential(
    [
        keras.Input(shape=input_shape),
        layers.Conv2D(32, kernel_size=(3, 3), activation="relu"),
        layers.MaxPooling2D(pool_size=(2, 2)),
        layers.Conv2D(64, kernel_size=(3, 3), activation="relu"),
        layers.MaxPooling2D(pool_size=(2, 2)),
        layers.Flatten(),
        layers.Dropout(0.5),
        layers.Dense(num_classes, activation="softmax"),
    ]
)

model.summary()

batch_size = 128
epochs = 15

model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
callbacks = [
    JaccardScoreCallback(model.name, x_test, np.argmax(y_test, axis=-1), "logs")
]
model.fit(
    x_train,
    y_train,
    batch_size=batch_size,
    epochs=epochs,
    validation_split=0.1,
    callbacks=callbacks,
)
x_train shape: (60000, 28, 28, 1)
60000 训练样本
10000 测试样本
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层 (类型)                       输出形状                    参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ conv2d (卷积层)                │ (None, 26, 26, 32)        │        320 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d (最大池化层)    │ (None, 13, 13, 32)        │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ conv2d_1 (卷积层)              │ (None, 11, 11, 64)        │     18,496 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ max_pooling2d_1 (最大池化层)  │ (None, 5, 5, 64)          │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ flatten (展平层)              │ (None, 1600)              │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dropout (丢弃层)              │ (None, 1600)              │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense (密集层)                │ (None, 10)                │     16,010 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 Total params: 34,826 (136.04 KB)
 Trainable params: 34,826 (136.04 KB)
 Non-trainable params: 0 (0.00 B)
Epoch 1/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 16ms/step - 准确率: 0.7706 - 损失: 0.7534 - 验证准确率: 0.9768 - 验证损失: 0.0842
Epoch 2/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 16ms/step - 准确率: 0.9627 - 损失: 0.1228 - 验证准确率: 0.9862 - 验证损失: 0.0533
Epoch 3/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 16ms/step - 准确率: 0.9739 - 损失: 0.0854 - 验证准确率: 0.9870 - 验证损失: 0.0466
Epoch 4/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9787 - 损失: 0.0676 - 验证准确率: 0.9892 - 验证损失: 0.0416
Epoch 5/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9818 - 损失: 0.0590 - 验证准确率: 0.9892 - 验证损失: 0.0396
Epoch 6/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9834 - 损失: 0.0534 - 验证准确率: 0.9920 - 验证损失: 0.0341
Epoch 7/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9837 - 损失: 0.0528 - 验证准确率: 0.9907 - 验证损失: 0.0358
Epoch 8/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 8s 18ms/step - 准确率: 0.9847 - 损失: 0.0466 - 验证准确率: 0.9908 - 验证损失: 0.0327
Epoch 9/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 18ms/step - 准确率: 0.9873 - 损失: 0.0397 - 验证准确率: 0.9912 - 验证损失: 0.0346
Epoch 10/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 8s 18ms/step - 准确率: 0.9862 - 损失: 0.0419 - 验证准确率: 0.9913 - 验证损失: 0.0315
Epoch 11/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9880 - 损失: 0.0370 - 验证准确率: 0.9915 - 验证损失: 0.0309
Epoch 12/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9880 - 损失: 0.0377 - 验证准确率: 0.9912 - 验证损失: 0.0318
Epoch 13/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 17ms/step - 准确率: 0.9889 - 损失: 0.0347 - 验证准确率: 0.9930 - 验证损失: 0.0293
Epoch 14/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 7s 16ms/step - 准确率: 0.9896 - 损失: 0.0333 - 验证准确率: 0.9913 - 验证损失: 0.0326
Epoch 15/15
 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
 422/422 ━━━━━━━━━━━━━━━━━━━━ 8s 18ms/step - 准确率: 0.9908 - 损失: 0.0282 - 验证准确率: 0.9925 - 验证损失: 0.0303

<keras.src.callbacks.history.History at 0x17f0655a0>

如果您现在使用 tensorboard --logdir=logs 启动一个 TensorBoard 实例,您将看到 jaccard_score 指标以及其他任何导出指标!

TensorBoard Jaccard Score


结论

许多机器学习从业者和研究人员依赖于尚未在 TensorFlow 中实现的指标。 Keras 用户仍然可以通过使用 Keras 回调来利用其他框架中现有的各种指标实现。这些指标可以像其他任何指标一样在 TensorBoard 中导出、查看和分析。