开发者指南 / KerasCV / CutMix, MixUp 和 RandAugment 图像增强与 KerasCV

CutMix, MixUp 和 RandAugment 图像增强与 KerasCV

作者: lukewood
创作日期: 2022/04/08
最后修改日期: 2022/04/08
描述: 使用 KerasCV 通过 CutMix、MixUp、RandAugment 等技术增强图像。

在 Colab 中查看 GitHub 源代码


概述

KerasCV 使得为图像分类和物体检测任务组装最先进的工业级数据增强管道变得简单。 KerasCV 提供了一系列实现常见数据增强技术的预处理层。

也许最有用的三层是 keras_cv.layers.CutMixkeras_cv.layers.MixUpkeras_cv.layers.RandAugment。 这些层几乎在所有最先进的图像分类管道中都会用到。

本指南将向您展示如何将这些层组合成您自己的图像分类任务的数据增强管道。本指南还将指导您定制 KerasCV 数据增强管道的过程。


导入与设置

KerasCV 使用 Keras 3 可与 TensorFlow、PyTorch 或 Jax 一起工作。在下面的指南中,我们将使用 jax 后端。该指南在 TensorFlow 或 PyTorch 后端中不需要任何更改,只需更新下面的 KERAS_BACKEND

!pip install -q --upgrade keras-cv
!pip install -q --upgrade keras  # 升级到 Keras 3。

我们开始导入所有必需的包:

import os

os.environ["KERAS_BACKEND"] = "jax"  # @param ["tensorflow", "jax", "torch"]

import matplotlib.pyplot as plt

# 导入 tensorflow 以使用 [`tf.data`](https://www.tensorflow.org/api_docs/python/tf/data) 及其预处理映射函数
import tensorflow as tf
import tensorflow_datasets as tfds
import keras
import keras_cv

数据加载

本指南使用 102 类别花卉数据集进行演示。

要开始,我们首先加载数据集:

BATCH_SIZE = 32
AUTOTUNE = tf.data.AUTOTUNE
tfds.disable_progress_bar()
data, dataset_info = tfds.load("oxford_flowers102", with_info=True, as_supervised=True)
train_steps_per_epoch = dataset_info.splits["train"].num_examples // BATCH_SIZE
val_steps_per_epoch = dataset_info.splits["test"].num_examples // BATCH_SIZE
 下载并准备数据集 328.90 MiB (下载: 328.90 MiB, 生成: 331.34 MiB, 总计: 660.25 MiB) 到 /usr/local/google/home/rameshsampath/tensorflow_datasets/oxford_flowers102/2.1.1...
 数据集 oxford_flowers102 已下载并准备好至 /usr/local/google/home/rameshsampath/tensorflow_datasets/oxford_flowers102/2.1.1。后续调用将重用此数据。

接下来,我们将图像调整为常量大小 (224, 224),并对标签进行独热编码。 请注意,keras_cv.layers.CutMixkeras_cv.layers.MixUp 期望目标进行独热编码。 这是因为它们以一种不可能用稀疏标签表示的方式修改目标值。

IMAGE_SIZE = (224, 224)
num_classes = dataset_info.features["label"].num_classes


def to_dict(image, label):
    image = tf.image.resize(image, IMAGE_SIZE)
    image = tf.cast(image, tf.float32)
    label = tf.one_hot(label, num_classes)
    return {"images": image, "labels": label}


def prepare_dataset(dataset, split):
    if split == "train":
        return (
            dataset.shuffle(10 * BATCH_SIZE)
            .map(to_dict, num_parallel_calls=AUTOTUNE)
            .batch(BATCH_SIZE)
        )
    if split == "test":
        return dataset.map(to_dict, num_parallel_calls=AUTOTUNE).batch(BATCH_SIZE)


def load_dataset(split="train"):
    dataset = data[split]
    return prepare_dataset(dataset, split)


train_dataset = load_dataset()

让我们检查一下数据集中的一些样本:

def visualize_dataset(dataset, title):
    plt.figure(figsize=(6, 6)).suptitle(title, fontsize=18)
    for i, samples in enumerate(iter(dataset.take(9))):
        images = samples["images"]
        plt.subplot(3, 3, i + 1)
        plt.imshow(images[0].numpy().astype("uint8"))
        plt.axis("off")
    plt.show()


visualize_dataset(train_dataset, title="增强前")

png

太好了!现在我们可以进入增强步骤。


RandAugment

RandAugment 已被证明能在众多数据集上提供更好的图像分类结果。 它对图像执行一组标准的增强操作。

要在KerasCV中使用RandAugment,您需要提供一些值:

  • value_range描述您图像中覆盖的值范围
  • magnitude是一个介于0和1之间的值,描述施加的扰动的强度
  • augmentations_per_image是一个整数,告知层对每个单独图像施加多少个增强
  • (可选)magnitude_stddev允许从标准差为magnitude_stddev的分布中随机采样magnitude
  • (可选)rate指示在每一层施加增强的概率。

您可以在RandAugment API文档中查看更多关于这些参数的信息。

让我们使用KerasCV的RandAugment实现。

rand_augment = keras_cv.layers.RandAugment(
    value_range=(0, 255),
    augmentations_per_image=3,
    magnitude=0.3,
    magnitude_stddev=0.2,
    rate=1.0,
)


def apply_rand_augment(inputs):
    inputs["images"] = rand_augment(inputs["images"])
    return inputs


train_dataset = load_dataset().map(apply_rand_augment, num_parallel_calls=AUTOTUNE)

最后,让我们检查一些结果:

visualize_dataset(train_dataset, title="After RandAugment")

png

尝试调整幅度设置,以查看更广泛的结果。


CutMix和MixUp:生成高质量的类间示例

CutMixMixUp允许我们生成类间示例。CutMix随机切割出一幅图像的部分,并将其放置在另一幅图像上,而MixUp对两幅图像之间的像素值进行插值。这两种方法都可以防止模型过拟合训练分布,并提高模型对分布外示例的泛化能力。此外,CutMix还可以防止模型过度依赖任何特定特征来进行分类。您可以在相关论文中了解更多关于这些技术的信息:

在这个示例中,我们将独立使用CutMixMixUp,通过手动创建预处理管道。在大多数最先进的管道中,图像会随机经过CutMixMixUp,或者不经过这两者。下面的函数实现了这两者。

cut_mix = keras_cv.layers.CutMix()
mix_up = keras_cv.layers.MixUp()


def cut_mix_and_mix_up(samples):
    samples = cut_mix(samples, training=True)
    samples = mix_up(samples, training=True)
    return samples


train_dataset = load_dataset().map(cut_mix_and_mix_up, num_parallel_calls=AUTOTUNE)

visualize_dataset(train_dataset, title="After CutMix and MixUp")

png

太好了!看来我们成功地将CutMixMixUp添加到了我们的预处理管道中。


定制您的增强管道

也许您想要从RandAugment中排除某个增强,或者您想要将keras_cv.layers.GridMask作为选项与默认的RandAugment增强一起包含。

KerasCV允许您使用keras_cv.layers.RandomAugmentationPipeline层构建具有生产等级的自定义数据增强管道。这个类的操作方式类似于RandAugment;为每个图像随机选择一个层施加augmentations_per_image次。RandAugment可以被视为RandomAugmentationPipeline的特例。实际上,我们的RandAugment实现从RandomAugmentationPipeline内部继承。

在这个示例中,我们将通过从标准的RandAugment策略中移除RandomRotation层,并将GridMask层替换到它的位置来创建一个自定义的RandomAugmentationPipeline

作为第一步,让我们使用辅助方法RandAugment.get_standard_policy()来创建一个基础管道。

layers = keras_cv.layers.RandAugment.get_standard_policy(
    value_range=(0, 255), magnitude=0.75, magnitude_stddev=0.3
)

首先,让我们过滤掉RandomRotation层。

layers = [
    layer for layer in layers if not isinstance(layer, keras_cv.layers.RandomRotation)
]

接下来,让我们将keras_cv.layers.GridMask添加到我们的层中:

layers = layers + [keras_cv.layers.GridMask()]

最后,我们可以组合我们的管道。

pipeline = keras_cv.layers.RandomAugmentationPipeline(
    layers=layers, augmentations_per_image=3
)


def apply_pipeline(inputs):
    inputs["images"] = pipeline(inputs["images"])
    return inputs

让我们看看结果吧!

train_dataset = load_dataset().map(apply_pipeline, num_parallel_calls=AUTOTUNE)
visualize_dataset(train_dataset, title="After custom pipeline")

png

太棒了!正如你所看到的,没有图像被随机旋转。你可以按照自己的喜好自定义管道:

pipeline = keras_cv.layers.RandomAugmentationPipeline(
    layers=[keras_cv.layers.GridMask(), keras_cv.layers.Grayscale(output_channels=3)],
    augmentations_per_image=1,
)

这个管道将会应用 GrayScale 或 GridMask:

train_dataset = load_dataset().map(apply_pipeline, num_parallel_calls=AUTOTUNE)
visualize_dataset(train_dataset, title="自定义管道后的效果")

png

看起来不错!你可以随心所欲地使用 RandomAugmentationPipeline


训练 CNN

作为最后的练习,让我们试用这些层。在这一部分,我们将使用 CutMixMixUpRandAugment 来训练一个最先进的 ResNet50 图像分类器,在牛津花卉数据集上。

def preprocess_for_model(inputs):
    images, labels = inputs["images"], inputs["labels"]
    images = tf.cast(images, tf.float32)
    return images, labels


train_dataset = (
    load_dataset()
    .map(apply_rand_augment, num_parallel_calls=AUTOTUNE)
    .map(cut_mix_and_mix_up, num_parallel_calls=AUTOTUNE)
)

visualize_dataset(train_dataset, "CutMix、MixUp 和 RandAugment")

train_dataset = train_dataset.map(preprocess_for_model, num_parallel_calls=AUTOTUNE)

test_dataset = load_dataset(split="test")
test_dataset = test_dataset.map(preprocess_for_model, num_parallel_calls=AUTOTUNE)

train_dataset = train_dataset.prefetch(AUTOTUNE)
test_dataset = test_dataset.prefetch(AUTOTUNE)

png

接下来我们应该创建模型本身。请注意我们在损失函数中使用了 label_smoothing=0.1。在使用 MixUp 时,强烈建议使用标签平滑。

input_shape = IMAGE_SIZE + (3,)


def get_model():
    model = keras_cv.models.ImageClassifier.from_preset(
        "efficientnetv2_s", num_classes=num_classes
    )
    model.compile(
        loss=keras.losses.CategoricalCrossentropy(label_smoothing=0.1),
        optimizer=keras.optimizers.SGD(momentum=0.9),
        metrics=["accuracy"],
    )
    return model

最后我们训练模型:

model = get_model()
model.fit(
    train_dataset,
    epochs=1,
    validation_data=test_dataset,
)
 32/32 ━━━━━━━━━━━━━━━━━━━━ 103s 2s/step - accuracy: 0.0059 - loss: 4.6941 - val_accuracy: 0.0114 - val_loss: 10.4028

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

结论与下一步

这就是使用 KerasCV 组装最先进的图像增强管道所需的全部内容!

作为读者的附加练习,你可以:

  • 在 RandAugment 参数上进行超参数搜索,以提高分类器的准确性
  • 用你自己的数据集替换牛津花卉数据集
  • 尝试定制 RandomAugmentationPipeline 对象。

目前,在 Keras core 和 KerasCV 之间有 28 个图像增强层! 每个层都可以独立使用,也可以在管道中使用。查看它们,如果你发现缺少的增强技术,请在 KerasCV 上提交 GitHub 问题