如何用Keras/TensorFlow进行图像分类的RandAugment

141 阅读10分钟

长期以来,数据增强一直作为一种手段,用转换后的变体取代 "静态 "数据集,加强*卷积神经网络(CNN)*的不变性,并且通常导致对输入的鲁棒性。

注意:不变性可以归结为使模型在做决策时对某些情况视而不见。一只猫的图像,如果你对它进行镜像或旋转,它仍然是一只猫的图像。

虽然我们一直在使用的数据增强的形式确实编码了缺乏关于平移差异的知识,这对物体检测、语义和实例分割等很重要。- 它所提供的不变性往往对分类模型有利,因此,增强被更普遍和更积极地应用于分类模型。

扩增的类型

增强开始时非常简单--小幅旋转、水平和垂直翻转、对比度或亮度波动,等等。近年来,更复杂的方法被设计出来,包括CutOut(在输入图像中随机引入黑色方块的空间剔除)和MixUp(混合图像的一部分并更新标签比例),以及它们的组合--CutMix。较新的增强方法实际上考虑了标签,像CutMix这样的方法改变了标签的比例,使其与被混合的每个类别的部分所占的图像比例相等。

随着可能的增强方法越来越多,一些人开始随机应用这些方法(或者至少是其中的一些子集),他们认为随机的增强方法可以增强模型的稳健性,并以更大的输入图像空间来取代原始的图像集。这就是RandAugment 的作用。

KerasCV和RandAugment

KerasCV是一个独立的包,但仍然是Keras的官方补充,由Keras团队开发。这意味着它获得了与主包同样多的抛光和直观性,但它也与常规的Keras模型及其层无缝集成。你会注意到的唯一区别是调用keras_cv.layers... 而不是keras.layers...

截至目前,KerasCV仍在开发中,已经包括了27个新的预处理层,RandAugmentCutMixMixUp 是其中的一部分。让我们来看看在图像上应用RandAugment ,以及我们如何在有和没有随机增强的情况下训练分类器。

首先,安装keras_cv

$ pip install keras_cv

注意:KerasCV需要TensorFlow 2.9才能工作。如果你还没有它,请先运行$ pip install -U tensorflow

现在,让我们导入TensorFlow、Keras和KerasCV,以及TensorFlow数据集,以方便访问Imagenette。

import tensorflow as tf
from tensorflow import keras
import keras_cv
import tensorflow_datasets as tfds

让我们装入一张图片,并以原始形式显示:

import matplotlib.pyplot as plt
import cv2

cat_img = cv2.cvtColor(cv2.imread('cat.jpg'), cv2.COLOR_BGR2RGB)
cat_img = cv2.resize(cat_img, (224, 224))
plt.imshow(cat_img)

现在,让我们对它应用RandAugment ,几次后看看结果:

fig = plt.figure(figsize=(10,10))
for i in range(16):
    ax = fig.add_subplot(4,4,i+1)
    aug_img = keras_cv.layers.RandAugment(value_range=(0, 255))(cat_img)
    # aug_img is a float-based tensor so we convert it back
    ax.imshow(aug_img.numpy().astype('int'))

该层有一个magnitude 参数,默认为0.5 ,可以改变它来增加或减少增强的效果。

fig = plt.figure(figsize=(10,10))
for i in range(16):
    ax = fig.add_subplot(4,4,i+1)
    aug_img = keras_cv.layers.RandAugment(value_range=(0, 255), magnitude=0.1)(cat_img)
    ax.imshow(aug_img.numpy().astype('int'))

当设置为一个低值时,如0.1 - 你会看到更少的激进增强。

作为一个层--它可以在创建数据集时在模型或tf.data 管道中使用。这使得RandAugment 非常灵活!额外的参数是augmentations_per_imagerate 参数,它们一起工作。

对于0...augmentations_per_image ,该层在管道中添加了一个随机的预处理层,以应用于一个图像。在默认的3 的情况下--三个不同的操作被添加到管道中。然后,为管道中的每一个增量取一个随机数--如果它低于rate (默认为大约0.9 )--就应用增量。

实质上--管道中的每个(随机)增强都有90%的概率被应用到图像上。

这自然意味着不是所有的增强都必须被应用,特别是如果你降低了rate 。你也可以通过RandomAugmentationPipeline 层定制哪些操作是允许的,RandAugment 是它的特殊情况。关于RandomAugmentationPipeline 的单独指南将很快发布。

使用和不使用RandAugment训练分类器

为了简化数据准备/加载方面的工作,并专注于RandAugment ,让我们使用tfds 来加载Imagenette的一部分。

(train, valid_set, test_set), info = tfds.load("imagenette", 
                                           split=["train[:70%]", "validation", "train[70%:]"],
                                           as_supervised=True, with_info=True)

class_names = info.features["label"].names
n_classes = info.features["label"].num_classes
print(f'Class names: {class_names}') # Class names: ['n01440764', 'n02102040', 'n02979186', 'n03000684', 'n03028079', 'n03394916', 'n03417042', 'n03425413', 'n03445777', 'n03888257']
print('Num of classes:', n_classes) # Num of classes: 10

print("Train set size:", len(train)) # Train set size: 6628
print("Test set size:", len(test_set)) # Test set size: 2841
print("Valid set size:", len(valid_set)) # Valid set size: 3925

建议:关于加载数据集和使用tfds ,以及它们的分割的更多信息--请阅读我们的 "用Tensorflow数据集分割训练、测试和验证集--tfds"

我们只加载了一部分训练数据,以便在较少的epochs中更容易过度拟合数据集(实际上,使我们的实验运行得更快)。由于Imagenette中的图像大小不同,让我们创建一个preprocess() ,调整它们的大小来映射数据集,以及一个augment() ,在tf.data.Dataset ,增强图像。

def preprocess(images, labels):
  return tf.image.resize(images, (224, 224)), tf.one_hot(labels, 10)
  
def augment(images, labels):
  inputs = {"images": images, "labels": labels}
  outputs = keras_cv.layers.RandAugment(value_range=(0, 255))(inputs)
  return outputs['images'], outputs['labels']

现在--我们对标签进行了单热编码。我们不一定要这样做,但对于像CutMix ,篡改标签和它们的比例的增强功能,你必须这样做。由于你可能想应用这些,以及RandAugment ,以创建强大的分类器 - 让我们留下一热编码。此外,RandAugment ,正是因为如此,才会接收一个带有图像和标签的字典--你可以添加的一些增强功能实际上会改变标签,所以它们是必须的。你可以很容易地从outputs 字典中提取增强的图像和标签,所以这是在增强过程中要采取的一个额外但简单的步骤。

让我们用preprocess() 函数来映射从tfds 返回的现有数据集,对它们进行批处理,只对训练集进行增强。

valid_set = valid_set.map(preprocess).batch(32).prefetch(tf.data.AUTOTUNE)
train_set = train.map(preprocess).batch(32).prefetch(tf.data.AUTOTUNE)
train_set_aug = train.map(preprocess).map(augment_data, 
                                          num_parallel_calls=tf.data.AUTOTUNE).batch(32).prefetch(tf.data.AUTOTUNE)

让我们训练一个网络!keras_cv.models 有一些内置的网络,类似于keras.applications 。虽然这个列表还很短--它将随着时间的推移而扩大,并接管keras.applications 。API非常相似,所以对大多数实践者来说,移植代码将相当容易。

# rescaling to [0..1]
effnet = keras_cv.models.EfficientNetV2B0(include_rescaling=True, include_top=True, classes=10)
          
effnet.compile(
    loss='categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy']
)

history = effnet.fit(train_set, epochs=25, validation_data = valid_set)

另外,你也可以使用目前的keras.applications

effnet = keras.applications.EfficientNetV2B0(weights=None, classes=10)

effnet.compile(
  loss='categorical_crossentropy',
  optimizer='adam',
  metrics=['accuracy']
)

history1 = effnet.fit(train_set, epochs=50, validation_data=valid_set)

这导致了一个模型并没有真正做得超级好:

Epoch 1/50
208/208 [==============================] - 60s 238ms/step - loss: 2.7742 - accuracy: 0.2313 - val_loss: 3.2200 - val_accuracy: 0.3085
...
Epoch 50/50
208/208 [==============================] - 48s 229ms/step - loss: 0.0272 - accuracy: 0.9925 - val_loss: 2.0638 - val_accuracy: 0.6887

现在,让我们在增强的数据集上训练相同的网络设置。每个批次都是单独增强的,所以每当同一批次的图像(在下一个历时中)出现时,它们会有不同的增强。

effnet = keras.applications.EfficientNetV2B0(weights=None, classes=10)
effnet.compile(
  loss='categorical_crossentropy',
  optimizer='adam',
  metrics=['accuracy']
)

history2 = effnet.fit(train_set_aug, epochs=50, validation_data = valid_set)
Epoch 1/50
208/208 [==============================] - 141s 630ms/step - loss: 2.9966 - accuracy: 0.1314 - val_loss: 2.7398 - val_accuracy: 0.2395
...
Epoch 50/50
208/208 [==============================] - 125s 603ms/step - loss: 0.7313 - accuracy: 0.7583 - val_loss: 0.6101 - val_accuracy: 0.8143

好多了!虽然你仍然想应用其他的增强,如CutMixMixUp ,与其他训练技术一起,以最大限度地提高网络的准确性 - 只是RandAugment 显著帮助,可以与更长的增强管道相媲美。

如果你比较训练曲线,包括训练验证曲线--才会发现RandAugment 的帮助有多大。

在非增强管道中,网络过度拟合(训练精度达到上限),验证精度保持在较低水平。在增强的管道中,训练精度从头到尾都低于验证精度

有了更高的训练损失,网络就更能意识到它仍在犯的错误,可以进行更多的更新以使它对变换不产生影响。前者认为不需要更新,而后者则认为需要,并提高了潜力的上限。

总结

KerasCV是一个独立的软件包,但仍然是Keras的官方补充,由Keras团队开发,旨在为你的Keras项目带来行业实力的CV。截至发稿时,KerasCV仍在开发中,已经包括了27个新的预处理层,RandAugmentCutMixMixUp 是其中的一部分。

在这个简短的指南中,我们已经看了一下你如何使用RandAugment ,从一个给定的应用变换列表中应用一些随机变换,以及它是如何轻松地包含在任何Keras训练管道中的。

更进一步--计算机视觉的实用深度学习

你好奇的天性让你想更进一步?我们建议查看我们的 课程: "用Python进行计算机视觉的实用深度学习".

另一个计算机视觉课程?

我们不会对MNIST的数字或MNIST的时尚进行分类。它们在很久以前就已经发挥了作用。太多的学习资源都集中在基本数据集和基本架构上,然后再让先进的黑盒架构来承担性能的负担。

我们希望把重点放在解密实用理解直觉真实项目上。想了解你如何能有所作为吗?我们将带你从我们的大脑处理图像的方式到为乳腺癌写一个研究级的深度学习分类器,再到 "产生幻觉 "的深度学习网络,通过实际工作教你原理和理论,让你掌握诀窍和工具,成为应用深度学习解决计算机视觉的专家。

里面有什么?

  • 视觉的首要原则以及如何教会计算机 "看"。
  • 计算机视觉的不同任务和应用
  • 使你的工作更容易的行业工具
  • 寻找、创建和利用计算机视觉的数据集
  • 卷积神经网络的理论和应用
  • 处理数据集中的领域转移、共同发生和其他偏见
  • 转移学习和利用他人的训练时间和计算资源为你服务
  • 构建和训练一个最先进的乳腺癌分类器
  • 如何对主流观点采取健康的怀疑态度并理解广泛采用的技术的含义
  • 使用t-SNE和PCA可视化ConvNet的 "概念空间"。
  • 公司如何使用计算机视觉技术以取得更好的结果的案例研究
  • 正确的模型评估、潜空间可视化和识别模型的注意力
  • 进行领域研究,处理自己的数据集和建立模型测试
  • 尖端的架构,想法的进展,它们的独特之处以及如何实现它们
  • KerasCV--一个用于创建最先进的管道和模型的WIP库
  • 如何解析和阅读论文并自己实现它们
  • 根据你的应用选择模型
  • 创建一个端到端的机器学习管道
  • 使用Faster R-CNNs、RetinaNets、SSDs和YOLO进行物体检测的景观和直觉
  • 实例和语义分割
  • 用YOLOv5进行实时物体识别
  • 训练YOLOv5物体检测器
  • 使用KerasNLP(业界强大的WIP库)与变形器一起工作
  • 将变形器与ConvNets整合,生成图像的标题
  • 深度梦想
  • 用于计算机视觉的深度学习模型优化