Aumento de datos

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar libreta

Descripción general

Este tutorial demuestra el aumento de datos: una técnica para aumentar la diversidad de su conjunto de entrenamiento mediante la aplicación de transformaciones aleatorias (pero realistas), como la rotación de imágenes.

Aprenderá cómo aplicar el aumento de datos de dos maneras:

Configuración

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

from tensorflow.keras import layers

Descargar un conjunto de datos

Este tutorial utiliza el conjunto de datos tf_flowers . Para mayor comodidad, descargue el conjunto de datos mediante TensorFlow Datasets . Si desea conocer otras formas de importar datos, consulte el tutorial de carga de imágenes .

(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,
)

El conjunto de datos de flores tiene cinco clases.

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

Recuperemos una imagen del conjunto de datos y usémosla para demostrar el aumento de datos.

get_label_name = metadata.features['label'].int2str

image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))
2022-01-26 05:09:18.712477: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

png

Usar capas de preprocesamiento de Keras

Redimensionamiento y reescalado

Puede usar las capas de preprocesamiento de Keras para cambiar el tamaño de sus imágenes a una forma consistente (con tf.keras.layers.Resizing ) y para cambiar la escala de los valores de píxeles (con tf.keras.layers.Rescaling ).

IMG_SIZE = 180

resize_and_rescale = tf.keras.Sequential([
  layers.Resizing(IMG_SIZE, IMG_SIZE),
  layers.Rescaling(1./255)
])

Puedes visualizar el resultado de aplicar estas capas a una imagen.

result = resize_and_rescale(image)
_ = plt.imshow(result)

png

Verifique que los píxeles estén en el rango [0, 1] :

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

Aumento de datos

También puede usar las capas de preprocesamiento de Keras para el aumento de datos, como tf.keras.layers.RandomFlip y tf.keras.layers.RandomRotation .

Vamos a crear algunas capas de preprocesamiento y aplicarlas repetidamente a la misma imagen.

data_augmentation = tf.keras.Sequential([
  layers.RandomFlip("horizontal_and_vertical"),
  layers.RandomRotation(0.2),
])
# 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)
  plt.imshow(augmented_image[0])
  plt.axis("off")
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

Hay una variedad de capas de preprocesamiento que puede usar para el aumento de datos, incluidas tf.keras.layers.RandomContrast , tf.keras.layers.RandomCrop , tf.keras.layers.RandomZoom y otras.

Dos opciones para usar las capas de preprocesamiento de Keras

Hay dos formas de utilizar estas capas de preprocesamiento, con importantes compensaciones.

Opción 1: haga que las capas de preprocesamiento formen parte de su modelo

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.
])

Hay dos puntos importantes a tener en cuenta en este caso:

  • El aumento de datos se ejecutará en el dispositivo, de forma sincronizada con el resto de sus capas, y se beneficiará de la aceleración de la GPU.

  • Cuando exporta su modelo usando model.save , las capas de preprocesamiento se guardarán junto con el resto de su modelo. Si luego implementa este modelo, automáticamente estandarizará las imágenes (según la configuración de sus capas). Esto puede ahorrarle el esfuerzo de tener que volver a implementar esa lógica del lado del servidor.

Opción 2: aplique las capas de preprocesamiento a su conjunto de datos

aug_ds = train_ds.map(
  lambda x, y: (resize_and_rescale(x, training=True), y))

Con este enfoque, utiliza Dataset.map para crear un conjunto de datos que produce lotes de imágenes aumentadas. En este caso:

  • El aumento de datos ocurrirá de forma asíncrona en la CPU y no bloqueará. Puede superponer el entrenamiento de su modelo en la GPU con el preprocesamiento de datos, utilizando Dataset.prefetch , que se muestra a continuación.
  • En este caso, las capas de preprocesamiento no se exportarán con el modelo cuando llame a Model.save . Deberá adjuntarlos a su modelo antes de guardarlo o volver a implementarlos en el lado del servidor. Después del entrenamiento, puede adjuntar las capas de preprocesamiento antes de exportar.

Puede encontrar un ejemplo de la primera opción en el tutorial Clasificación de imágenes . Vamos a demostrar la segunda opción aquí.

Aplicar las capas de preprocesamiento a los conjuntos de datos

Configure los conjuntos de datos de entrenamiento, validación y prueba con las capas de preprocesamiento de Keras que creó anteriormente. También configurará los conjuntos de datos para el rendimiento, utilizando lecturas paralelas y búsqueda previa almacenada en búfer para generar lotes desde el disco sin que la E/S se convierta en un bloqueo. (Obtenga más información sobre el rendimiento del conjunto de datos en la guía Mejor rendimiento con la API tf.data ).

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)

entrenar a un modelo

Para completar, ahora entrenará un modelo utilizando los conjuntos de datos que acaba de preparar.

El modelo secuencial consta de tres bloques de convolución ( tf.keras.layers.Conv2D ) con una capa de agrupación máxima ( tf.keras.layers.MaxPooling2D ) en cada uno de ellos. Hay una capa totalmente conectada ( tf.keras.layers.Dense ) con 128 unidades encima que se activa mediante una función de activación de ReLU ( 'relu' ). Este modelo no ha sido ajustado para precisión (el objetivo es mostrarle la mecánica).

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)
])

Elija el optimizador tf.keras.optimizers.Adam y la función de pérdida tf.keras.losses.SparseCategoricalCrossentropy . Para ver la precisión del entrenamiento y la validación para cada época de entrenamiento, pase el argumento de metrics a Model.compile .

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

Entrena por algunas épocas:

epochs=5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/5
92/92 [==============================] - 13s 110ms/step - loss: 1.2768 - accuracy: 0.4622 - val_loss: 1.0929 - val_accuracy: 0.5640
Epoch 2/5
92/92 [==============================] - 3s 25ms/step - loss: 1.0579 - accuracy: 0.5749 - val_loss: 0.9711 - val_accuracy: 0.6349
Epoch 3/5
92/92 [==============================] - 3s 26ms/step - loss: 0.9677 - accuracy: 0.6291 - val_loss: 0.9764 - val_accuracy: 0.6431
Epoch 4/5
92/92 [==============================] - 3s 25ms/step - loss: 0.9150 - accuracy: 0.6468 - val_loss: 0.8906 - val_accuracy: 0.6431
Epoch 5/5
92/92 [==============================] - 3s 25ms/step - loss: 0.8636 - accuracy: 0.6604 - val_loss: 0.8233 - val_accuracy: 0.6730
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)
12/12 [==============================] - 5s 14ms/step - loss: 0.7922 - accuracy: 0.6948
Accuracy 0.6948229074478149

Aumento de datos personalizado

También puede crear capas de aumento de datos personalizadas.

Esta sección del tutorial muestra dos formas de hacerlo:

  • Primero, creará una capa tf.keras.layers.Lambda . Esta es una buena manera de escribir código conciso.
  • A continuación, escribirá una nueva capa a través de subclases , lo que le brinda más control.

Ambas capas invertirán aleatoriamente los colores de una imagen, según alguna probabilidad.

def random_invert_img(x, p=0.5):
  if  tf.random.uniform([]) < p:
    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")
2022-01-26 05:09:53.045204: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045264: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045312: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045369: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045418: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045467: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.045511: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module
2022-01-26 05:09:53.047630: W tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc:399] target triple not found in the module

png

A continuación, implemente una capa personalizada mediante subclases :

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.imshow(RandomInvert()(image)[0])

png

Ambas capas se pueden usar como se describe en las opciones 1 y 2 anteriores.

Usando tf.imagen

Las utilidades de preprocesamiento de Keras anteriores son convenientes. Pero, para un control más preciso, puede escribir sus propias canalizaciones o capas de aumento de datos utilizando tf.data y tf.image . (Es posible que también desee consultar Imagen de complementos de TensorFlow: operaciones y E/S de TensorFlow: conversiones de espacio de color ).

Dado que el conjunto de datos de flores se configuró previamente con el aumento de datos, volvamos a importarlo para comenzar de nuevo:

(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,
)

Recuperar una imagen para trabajar con:

image, label = next(iter(train_ds))
_ = plt.imshow(image)
_ = plt.title(get_label_name(label))
2022-01-26 05:09:59.918847: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

png

Usemos la siguiente función para visualizar y comparar las imágenes originales y aumentadas una al lado de la otra:

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)

Aumento de datos

Voltear una imagen

Voltea una imagen ya sea vertical u horizontalmente con tf.image.flip_left_right :

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

png

escala de grises una imagen

Puede escalar una imagen en escala de grises con tf.image.rgb_to_grayscale :

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

png

Saturar una imagen

Sature una imagen con tf.image.adjust_saturation proporcionando un factor de saturación:

saturated = tf.image.adjust_saturation(image, 3)
visualize(image, saturated)

png

Cambiar el brillo de la imagen

Cambie el brillo de la imagen con tf.image.adjust_brightness proporcionando un factor de brillo:

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

png

Recortar al centro una imagen

Recorta la imagen desde el centro hasta la parte de la imagen que desees usando tf.image.central_crop :

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

png

Girar una imagen

Gire una imagen 90 grados con tf.image.rot90 :

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

png

Transformaciones aleatorias

Aplicar transformaciones aleatorias a las imágenes puede ayudar aún más a generalizar y expandir el conjunto de datos. La API tf.image actual proporciona ocho operaciones de imagen aleatorias (ops):

Estas operaciones de imágenes aleatorias son puramente funcionales: la salida solo depende de la entrada. Esto los hace fáciles de usar en canalizaciones de entrada deterministas de alto rendimiento. Requieren que se ingrese un valor seed en cada paso. Dada la misma seed , devuelven los mismos resultados independientemente de cuántas veces se llamen.

En las siguientes secciones, usted:

  1. Repase ejemplos del uso de operaciones aleatorias de imágenes para transformar una imagen.
  2. Demostrar cómo aplicar transformaciones aleatorias a un conjunto de datos de entrenamiento.

Cambiar aleatoriamente el brillo de la imagen

Cambie aleatoriamente el brillo de la image usando tf.image.stateless_random_brightness al proporcionar un factor de brillo y una seed . El factor de brillo se elige aleatoriamente en el rango [-max_delta, max_delta) y está asociado con la seed dada.

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)

png

png

png

Cambiar aleatoriamente el contraste de la imagen

Cambie aleatoriamente el contraste de la image usando tf.image.stateless_random_contrast proporcionando un rango de contraste y seed . El rango de contraste se elige aleatoriamente en el intervalo [lower, upper] y se asocia con la seed dada.

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)

png

png

png

Recortar aleatoriamente una imagen

Recorte aleatoriamente la image usando tf.image.stateless_random_crop al proporcionar el size objetivo y seed . La parte que se recorta de la image se encuentra en un desplazamiento elegido al azar y se asocia con la seed dada.

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)

png

png

png

Aplicar aumento a un conjunto de datos

Primero descarguemos el conjunto de datos de imágenes nuevamente en caso de que se hayan modificado en las secciones anteriores.

(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,
)

A continuación, defina una función de utilidad para cambiar el tamaño y la escala de las imágenes. Esta función se utilizará para unificar el tamaño y la escala de las imágenes en el conjunto de datos:

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

Definamos también la función de augment que puede aplicar las transformaciones aleatorias a las imágenes. Esta función se usará en el conjunto de datos en el siguiente paso.

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

Opción 1: usar tf.data.experimental.Counter

Cree un objeto tf.data.experimental.Counter (llamémoslo counter ) y Dataset.zip el conjunto de datos con (counter, counter) . Esto asegurará que cada imagen en el conjunto de datos se asocie con un valor único (de forma (2,) ) basado en el counter que luego se puede pasar a la función de augment como valor seed para transformaciones aleatorias.

# 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 la función de aumento al conjunto de datos de entrenamiento:

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

Opción 2: Usar tf.random.Generator

  • Cree un objeto tf.random.Generator con un valor seed inicial. Llamar a la función make_seeds en el mismo objeto generador siempre devuelve un nuevo valor seed único.
  • Defina una función contenedora que: 1) llame a la función make_seeds ; y 2) pasa el valor seed recién generado a la función de augment para transformaciones aleatorias.
# 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

Asigne la función contenedora f al conjunto de datos de entrenamiento y la función resize_and_rescale a los conjuntos de validación y prueba:

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

Estos conjuntos de datos ahora se pueden usar para entrenar un modelo como se mostró anteriormente.

Próximos pasos

Este tutorial demostró el aumento de datos utilizando capas de preprocesamiento de Keras y tf.image .

  • Para aprender a incluir capas de preprocesamiento dentro de su modelo, consulte el tutorial de clasificación de imágenes .
  • También puede estar interesado en aprender cómo las capas de preprocesamiento pueden ayudarlo a clasificar el texto, como se muestra en el tutorial de clasificación de texto básico .
  • Puede obtener más información sobre tf.data en esta guía , y puede aprender cómo configurar sus canalizaciones de entrada para el rendimiento aquí .