07-数据增强

1. 数据准备

导入所需要的包:

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_datasets as tfds

from tensorflow.keras import layers

注意这里的tensorflow_datasets是一个开箱即用的数据集集合,包含数十种常用的机器学习数据集。通过简单的几行代码即可将数据以 tf.data.Dataset 的格式载入。关于 tf.data.Dataset 的使用可参考 tf.data

然后,最基础的用法是使用 tfds.load 方法,载入所需的数据集。例如,以下三行代码分别载入了 MNIST、猫狗分类和 tf_flowers 三个图像分类数据集

dataset = tfds.load("mnist", split=tfds.Split.TRAIN, as_supervised=True)
dataset = tfds.load("cats_vs_dogs", split=tfds.Split.TRAIN, as_supervised=True)
dataset = tfds.load("tf_flowers", split=tfds.Split.TRAIN, as_supervised=True)

当第一次载入特定数据集时,TensorFlow Datasets 会自动从云端下载数据集到本地,并显示下载进度。在本例中,我们使用的是tf_flowers数据集。

(train_ds, val_ds, test_ds), metadata = tfds.load(  # 用load下载
    'tf_flowers',
    split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True,  # 返回二元组
    as_supervised=True,
)

as_supervised=True,则根据数据集的特性,将数据集中的每行元素整理为有监督的二元组 (input, label) (即 “数据 + 标签”)形式。下载进度如下所示:
在这里插入图片描述
数据集分割split=['train[:80%]', 'train[80%:90%]', 'train[90%:]']进度如下所示:
在这里插入图片描述
其在服务器中的文件格式为:
在这里插入图片描述
数据集中有5类。

num_classes = metadata.features['label'].num_classes
# print(num_classes)  # 5

我们可以通过take()查看数据集前两个数据样式:

for image, label in train_ds.take(2):
    plt.figure()
    plt.imshow(image)
    plt.title(get_label_name(label))
    plt.show()

在这里插入图片描述
在这里插入图片描述

2. 使用 Keras 预处理层

我们可以使用 Keras 预处理层将图像大小调整为一致的形状(使用 tf.keras.layers.Resizing),并重新缩放像素值(使用 tf.keras.layers.Rescaling)。

IMG_SIZE = 180

resize_and_rescale = tf.keras.Sequential([
  layers.Resizing(IMG_SIZE, IMG_SIZE),  # 调整成一致的形状
  layers.Rescaling(1./255)  # 重新缩放像素值
])

Note: The rescaling layer above standardizes pixel values to the [0,1] range. If instead you wanted it to be [-1, 1], you would write tf.keras.layers.Rescaling(1./127.5, offset=-1).

我们可视化应用这些层于图像的结果。
在这里插入图片描述
验证一下像素值是否在0-1之间:

print("Min and max pixel values:", result.numpy().min(), result.numpy().max())
# Min and max pixel values: 0.0 1.0

我们也可以使用 Keras 预处理层进行数据增强,例如 tf.keras.layers.RandomFliptf.keras.layers.RandomRotation
让我们创建一些预处理层并将它们重复应用于同一图像。

# 数据增强:RandomFlip + RandomRotation
data_augmentation = tf.keras.Sequential([
  layers.RandomFlip("horizontal_and_vertical"),
  layers.RandomRotation(0.2),
])

将第一张图片加入一个batch中查看数据增强的效果:

# Add the image to a batch
image = tf.expand_dims(image, 0)
plt.figure(figsize=(10, 10))
for i in range(9):
    augmented_image = data_augmentation(image)
    ax = plt.subplot(3, 3, i+1)  # 控制9个子图
    plt.imshow(augmented_image[0])
    plt.axis("off")
plt.show()

在这里插入图片描述
有多种预处理层可用于数据增强,包括 tf.keras.layers.RandomContrasttf.keras.layers.RandomCroptf.keras.layers.RandomZoom 等。

使用 Keras 预处理层的两个方法: Make the preprocessing layers part of your model 和 Apply the preprocessing layers to your dataset。

第一种方法: 使预处理层成为模型的一部分。

model = tf.keras.Sequential([
  # Add the preprocessing layers you created earlier.
  resize_and_rescale,
  data_augmentation,
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  # Rest of your model.
])

在这种情况下需要注意两点:
数据增强将在设备上运行,与其他层同步,并受益于GPU加速。
当你使用model.save导出你的模型时,预处理层将与模型的其余部分一起保存。如果您稍后部署这个模型,它将自动标准化映像(根据您的层的配置)。这可以使您不必重新实现逻辑服务器端。

数据增强在测试时处于非活动状态,因此输入图像只会在调用 Model.fit(而不是 Model.evaluateModel.predict)期间被增强。

第二种方法: 将预处理层应用于您的数据集。

aug_ds = train_ds.map(
  lambda x, y: (resize_and_rescale(x, training=True), y))  # 传入2个参数的函数

使用这种方法,您将使用Dataset.map以创建产生成批增强图像的数据集。在这种情况下:

  • 数据扩展将在CPU上异步发生,并且是非阻塞的。您可以使用Dataset.prefetch将GPU上的模型训练与数据预处理重叠。如下所示。
  • 在这种情况下,当您调用model.save时,预处理层将不会随模型一起导出。在保存模型或在服务器端重新实现它们之前,您需要将它们附加到模型上。训练结束后,可以在导出之前附加预处理层。

您可以在图像分类教程中找到第一种方法的示例。在这里演示第二种方案。

3. 将预处理层应用于数据集

使用前面创建的Keras预处理层配置训练、验证和测试数据集。您还将配置数据集以提高性能,使用并行读取和缓冲预取从磁盘生成批处理,而不会造成I/O阻塞。

注意:数据增强应该只应用于训练集。

batch_size = 32
AUTOTUNE = tf.data.AUTOTUNE

def prepare(ds, shuffle=False, augment=False):
  # Resize and rescale all datasets.
  ds = ds.map(lambda x, y: (resize_and_rescale(x), y), 
              num_parallel_calls=AUTOTUNE)

  if shuffle:
    ds = ds.shuffle(1000)

  # Batch all datasets.
  ds = ds.batch(batch_size)

  # Use data augmentation only on the training set.
  if augment:
    ds = ds.map(lambda x, y: (data_augmentation(x, training=True), y), 
                num_parallel_calls=AUTOTUNE)

  # Use buffered prefetching on all datasets.
  return ds.prefetch(buffer_size=AUTOTUNE)
train_ds = prepare(train_ds, shuffle=True, augment=True)  # 数据增强只应用于训练集
val_ds = prepare(val_ds)
test_ds = prepare(test_ds)
3.1 训练模型

完整起见,我们现在将使用刚刚准备的数据集训练模型。
Sequential模型包含三个卷积块(tf.keras.layers.Conv2D),每个卷积块中都有一个最大池化层(tf.keras.layers.MaxPooling2D)。这是一个有128个unit的全连接层(tf.keras.layers.Dense),由ReLU激活功能激活。这个模型还没有调整到精确度(这里的目标是展示机制)。

model = tf.keras.Sequential([
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])

在这里,我们选择 tf.keras.optimizers.Adam 优化方法和tf.keras.losses.SparseCategoricalCrossentropy 损失函数。为了查看每个epoch的训练集和验证集准确性,需将metrics 参数传递给 Model.compile

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

训练几个 epoch:

epochs=5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)

训练过程如下:

Epoch 1/5
92/92 [==============================] - 19s 96ms/step - loss: 1.2814 - accuracy: 0.4632 - val_loss: 1.1246 - val_accuracy: 0.5804
Epoch 2/5
92/92 [==============================] - 3s 26ms/step - loss: 1.0684 - accuracy: 0.5753 - val_loss: 0.9848 - val_accuracy: 0.6213
Epoch 3/5
92/92 [==============================] - 3s 28ms/step - loss: 0.9701 - accuracy: 0.6100 - val_loss: 1.0106 - val_accuracy: 0.6185
Epoch 4/5
92/92 [==============================] - 3s 26ms/step - loss: 0.9046 - accuracy: 0.6502 - val_loss: 0.9455 - val_accuracy: 0.6076
Epoch 5/5
92/92 [==============================] - 3s 27ms/step - loss: 0.8676 - accuracy: 0.6717 - val_loss: 0.9243 - val_accuracy: 0.6512
3.2 评估模型
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)

结果为:

12/12 [==============================] - 1s 14ms/step - loss: 0.8008 - accuracy: 0.6839
Accuracy 0.6839237213134766

4. 自定义数据增强

我们同样可以自定义数据增强层。

本节将展示两种方法:

  • 首先,您将创建一个tf.keras.layers.Lambda层。这是编写简洁代码的好方法。
  • 接下来,您将通过子类化编写一个新层,这将给您更多的控制。

这两个图层都将根据一定的概率随机反转图像中的颜色。

def random_invert_img(x, p=0.5):
  if  tf.random.uniform([]) < p:  # tf.random.uniform函数从均匀分布中输出随机值
    x = (255-x)
  else:
    x
  return x
def random_invert(factor=0.5):
  return layers.Lambda(lambda x: random_invert_img(x, factor))

random_invert = random_invert()
plt.figure(figsize=(10, 10))
for i in range(9):
  augmented_image = random_invert(image)
  ax = plt.subplot(3, 3, i + 1)
  plt.imshow(augmented_image[0].numpy().astype("uint8"))
  plt.axis("off")

在这里插入图片描述
接下来,通过子类化实现自定义层:

class RandomInvert(layers.Layer):
  def __init__(self, factor=0.5, **kwargs):
    super().__init__(**kwargs)
    self.factor = factor

  def call(self, x):
    return random_invert_img(x)

展示经过自定义数据增强层处理后的图片:

plt.figure()
_ = plt.imshow(RandomInvert()(image)[0])
plt.show()

在这里插入图片描述
这两个层都可以按照上面方法 1 和 2 中的说明使用。

5. 使用tf.image

上面的Keras预处理实用程序是方便的。但是,为了更好地控制,您可以使用 tf.datatf.image编写自己的数据增强管道或层。
由于花卉的数据集之前配置了数据增强,让我们导入它以重新开始:

(train_ds, val_ds, test_ds), metadata = tfds.load(
    'tf_flowers',
    split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True,
    as_supervised=True,
)

检索要使用的图像:

image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))

在这里插入图片描述
让我们使用以下函数来并排可视化比较原始图像和增强图像:

def visualize(original, augmented):
  fig = plt.figure()
  plt.subplot(1,2,1)
  plt.title('Original image')
  plt.imshow(original)

  plt.subplot(1,2,2)
  plt.title('Augmented image')
  plt.imshow(augmented)
5.1 翻转图像

使用 tf.image.flip_left_right 垂直或水平翻转图像:

flipped = tf.image.flip_left_right(image)
visualize(image, flipped)

在这里插入图片描述

5.2 灰度图像

我们可以使用 tf.image.rgb_to_grayscale 对图像进行灰度化:

grayscaled = tf.image.rgb_to_grayscale(image)
visualize(image, tf.squeeze(grayscaled))
_ = plt.colorbar()

在这里插入图片描述

5.3 图像饱和
saturated = tf.image.adjust_saturation(image, 3)
visualize(image, saturated)

在这里插入图片描述

5.4 改变图像亮度

tf.image.adjust_brightness 通过提供一个亮度因子来改变图像的亮度:

bright = tf.image.adjust_brightness(image, 0.4)
visualize(image, bright)

在这里插入图片描述

5.5 居中裁剪图像

使用 tf.image.central_crop 将图像从中心裁剪到您想要的图像部分:

cropped = tf.image.central_crop(image, central_fraction=0.5)
visualize(image, cropped)

在这里插入图片描述

5.6 图像旋转

使用 tf.image.rot90 将图像旋转 90 度:

rotated = tf.image.rot90(image)
visualize(image, rotated)

在这里插入图片描述
对图像应用随机变换可以进一步帮助泛化和扩展数据集。 当前的 tf.image API 提供了八种这样的随机图像操作(ops):

  1. tf.image.stateless_random_brightness
  2. tf.image.stateless_random_contrast
  3. tf.image.stateless_random_crop
  4. tf.image.stateless_random_flip_left_right
  5. tf.image.stateless_random_flip_up_down
  6. tf.image.stateless_random_hue
  7. tf.image.stateless_random_jpeg_quality
  8. tf.image.stateless_random_saturation

这些随机图像操作是纯功能性的:输出只依赖于输入。这使得它们在高性能、确定性的输入管道中很容易使用。它们需要每一步输入一个seed值。对于相同的种子,它们将返回与调用次数无关的相同结果。(seed 是一个形状为 (2,) 的张量,其值为任意整数。)

6. 应用示例

在以下部分中, 我们将演示使用随机图像操作来转换图像的示例。 演示如何将随机变换应用于训练数据集。

6.1 随机改变图像亮度

使用 tf.image.stateless_random_brightness 通过其提供的亮度因子和种子随机改变图像的亮度。亮度因子在[-max_delta, max_delta]范围内随机选择,并与给定的种子相关联。

for i in range(3):
  seed = (i, 0)  # tuple of size (2,)
  stateless_random_brightness = tf.image.stateless_random_brightness(
      image, max_delta=0.95, seed=seed)
  visualize(image, stateless_random_brightness)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2 随机改变图像对比度

通过提供对比度范围和种子,使用 tf.image.stateless_random_contrast 随机更改图像的对比度。对比度范围是在区间 [lower, upper] 中随机选择的,并与给定的种子相关联。

for i in range(3):
  seed = (i, 0)  # tuple of size (2,)
  stateless_random_contrast = tf.image.stateless_random_contrast(
      image, lower=0.1, upper=0.9, seed=seed)
  visualize(image, stateless_random_contrast)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3 随机裁剪图像

通过提供目标大小和种子,使用 tf.image.stateless_random_crop 随机裁剪图像。从图像中裁剪出来的部分位于随机选择的偏移处,并与给定的种子相关联。

for i in range(3):
  seed = (i, 0)  # tuple of size (2,)
  stateless_random_crop = tf.image.stateless_random_crop(
      image, size=[210, 300, 3], seed=seed)
  visualize(image, stateless_random_crop)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.4 将数据增强应用到数据集

首先,让我们再次下载图像数据集,以防它们在前面的部分中被修改。

(train_datasets, val_ds, test_ds), metadata = tfds.load(
    'tf_flowers',
    split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
    with_info=True,
    as_supervised=True,
)

接下来,定义一个用于调整图像大小和重新缩放图像的实用函数。此函数将用于统一数据集中图像的大小和比例:

def resize_and_rescale(image, label):
  image = tf.cast(image, tf.float32)
  image = tf.image.resize(image, [IMG_SIZE, IMG_SIZE])
  image = (image / 255.0)
  return image, label

我们还定义可以将随机变换应用于图像的增强函数。下一步将在数据集上使用此函数。

def augment(image_label, seed):
  image, label = image_label
  image, label = resize_and_rescale(image, label)
  image = tf.image.resize_with_crop_or_pad(image, IMG_SIZE + 6, IMG_SIZE + 6)
  # Make a new seed.
  new_seed = tf.random.experimental.stateless_split(seed, num=1)[0, :]
  # Random crop back to the original size.
  image = tf.image.stateless_random_crop(
      image, size=[IMG_SIZE, IMG_SIZE, 3], seed=seed)
  # Random brightness.
  image = tf.image.stateless_random_brightness(
      image, max_delta=0.5, seed=new_seed)
  image = tf.clip_by_value(image, 0, 1)
  return image, label

方案一:使用tf.data.experimental.Counter
创建一个tf.data.experimental.Counter对象(我们叫它counter)和Dataset .zip (counter, counter)。这将确保数据集中的每个图像都与一个唯一的值(形状(2,))相关联,这是基于counter,稍后可以作为随机转换的seed值传递到augment函数。

# Create a `Counter` object and `Dataset.zip` it together with the training set.
counter = tf.data.experimental.Counter()
train_ds = tf.data.Dataset.zip((train_datasets, (counter, counter)))

augment函数应用于数据集:

train_ds = (
    train_ds
    .shuffle(1000)
    .map(augment, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)
val_ds = (
    val_ds
    .map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)
test_ds = (
    test_ds
    .map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)

方案二:使用tf.random.Generator

  • 创建一个带有初始种子值的tf.random.Generator对象。在同一个生成器对象上调用make_seeds函数总是会返回一个新的、唯一的种子值。
  • 定义一个包装函数:1)调用make_seeds函数;2)将新生成的种子值传递给增广函数进行随机转换。

(注意:tf.random.Generator 对象将 RNG 状态存储在 tf.Variable 中,这意味着它可以保存为检查点或 SavedModel)

# Create a generator.
rng = tf.random.Generator.from_seed(123, alg='philox')
# Create a wrapper function for updating seeds.
def f(x, y):
  seed = rng.make_seeds(2)[0]
  image, label = augment((x, y), seed)
  return image, label

将包装函数 f 映射到训练数据集,并将 resize_and_rescale 函数映射到验证集和测试集:

train_ds = (
    train_datasets
    .shuffle(1000)
    .map(f, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)
val_ds = (
    val_ds
    .map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)
test_ds = (
    test_ds
    .map(resize_and_rescale, num_parallel_calls=AUTOTUNE)
    .batch(batch_size)
    .prefetch(AUTOTUNE)
)

这些数据集现在可用于训练模型,如上所示。

下一步:

本教程演示了使用Keras预处理层和tf.image进行数据增强。要了解如何在模型中包含预处理层,请参阅图像分类教程。您可能还对学习预处理层如何帮助您对文本进行分类感兴趣,如基本文本分类教程所示。你可以学到更多关于tf的知识。您可以在这里学习如何配置您的输入管道以提高性能。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值