Datenerweiterung

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Überblick

In diesem Tutorial wird die Datenerweiterung demonstriert: eine Technik, mit der Sie die Vielfalt Ihres Trainingssatzes durch zufällige (aber realistische) Transformationen wie Bilddrehung erhöhen können. Sie erfahren, wie Sie die Datenerweiterung auf zwei Arten anwenden. Zuerst werden Sie Keras Preprocessing Layers verwenden . Als nächstes verwenden Sie tf.image .

Einrichten

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

from tensorflow.keras import layers

Einen Datensatz herunterladen

Dieses Tutorial verwendet das tf_flowers- Dataset. Laden Sie das Dataset der Einfachheit halber mit TensorFlow Datasets herunter. Weitere Informationen zum Importieren von Daten finden Sie im Tutorial zum Laden von Bildern .

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

Der Blumen-Datensatz hat fünf Klassen.

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

Lassen Sie uns ein Bild aus dem Dataset abrufen und es verwenden, um die Datenerweiterung zu demonstrieren.

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

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

png

Keras-Vorverarbeitungsebenen verwenden

Größe ändern und skalieren

Sie können Vorverarbeitung Schichten verwenden , um die Größe Ihrer Bilder auf eine konsistente Form und rescale Pixelwerte.

IMG_SIZE = 180

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

Sie können das Ergebnis der Anwendung dieser Ebenen auf ein Bild sehen.

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

png

Sie können überprüfen, ob sich die Pixel in [0-1] .

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

Datenerweiterung

Sie können Vorverarbeitungsschichten auch für die Datenerweiterung verwenden.

Lassen Sie uns einige Vorverarbeitungsebenen erstellen und diese wiederholt auf dasselbe Bild anwenden.

data_augmentation = tf.keras.Sequential([
  layers.experimental.preprocessing.RandomFlip("horizontal_and_vertical"),
  layers.experimental.preprocessing.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")

png

Es gibt eine Vielzahl von Vorverarbeitungs- Layern, die Sie für die layers.RandomContrast , einschließlich layers.RandomContrast , layers.RandomCrop , layers.RandomZoom und andere.

Zwei Optionen zur Verwendung der Vorverarbeitungsebenen

Es gibt zwei Möglichkeiten, diese Vorverarbeitungsebenen zu verwenden, mit wichtigen Kompromissen.

Option 1: Machen Sie die Vorverarbeitungsebenen zu einem Teil Ihres Modells

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

In diesem Fall sind zwei wichtige Punkte zu beachten:

  • Die Datenerweiterung wird auf dem Gerät synchron mit den restlichen Ebenen ausgeführt und profitiert von der GPU-Beschleunigung.

  • Wenn Sie Ihr Modell mit model.save , werden die Vorverarbeitungsebenen zusammen mit dem Rest Ihres Modells gespeichert. Wenn Sie dieses Modell später bereitstellen, werden Bilder automatisch standardisiert (entsprechend der Konfiguration Ihrer Layer). Dies kann Ihnen den Aufwand ersparen, diese Logik serverseitig neu implementieren zu müssen.

Option 2: Wenden Sie die Vorverarbeitungs-Layer auf Ihr Dataset an

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

Bei diesem Ansatz verwenden Sie Dataset.map , um ein Dataset zu erstellen, das Dataset.map von erweiterten Bildern liefert. In diesem Fall:

  • Die Datenerweiterung erfolgt asynchron auf der CPU und ist nicht blockierend. Sie können das Training Ihres Modells auf der GPU mit der Datenvorverarbeitung überlappen, indem Sie Dataset.prefetch , wie unten gezeigt.
  • In diesem Fall werden die Prepreprocessing-Layer beim Aufruf von model.save nicht mit dem Modell model.save . Sie müssen sie an Ihr Modell anhängen, bevor Sie es speichern oder serverseitig neu implementieren. Nach dem Training können Sie die Vorverarbeitungsebenen vor dem Export anhängen.

Sie können ein Beispiel für die erste Option in dem finden Bildklassifizierungs- Tutorial. Lassen Sie uns hier die zweite Option demonstrieren.

Wenden Sie die Vorverarbeitungs-Layer auf die Datensätze an

Konfigurieren Sie die Trainings-, Validierungs- und Test-Datasets mit den oben erstellten Vorverarbeitungs-Layern. Sie konfigurieren die Datasets auch auf Leistung, indem Sie parallele Lesevorgänge und gepuffertes Prefetching verwenden, um Batches von der Festplatte zu erhalten, ohne dass E/A blockiert wird. Weitere Informationen zur Datensatzleistung finden Sie im Handbuch Bessere Leistung mit dem tf.data API- Leitfaden.

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

Trainiere ein Modell

Der Vollständigkeit halber trainieren Sie nun ein Modell mit diesen Datensätzen. Dieses Modell wurde nicht auf Genauigkeit abgestimmt (das Ziel ist es, Ihnen die Mechanik zu zeigen).

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)
])
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
epochs=5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/5
92/92 [==============================] - 14s 117ms/step - loss: 1.3954 - accuracy: 0.3934 - val_loss: 1.2173 - val_accuracy: 0.5177
Epoch 2/5
92/92 [==============================] - 3s 25ms/step - loss: 1.0951 - accuracy: 0.5555 - val_loss: 1.0964 - val_accuracy: 0.5668
Epoch 3/5
92/92 [==============================] - 3s 25ms/step - loss: 0.9855 - accuracy: 0.6032 - val_loss: 0.9367 - val_accuracy: 0.6349
Epoch 4/5
92/92 [==============================] - 3s 25ms/step - loss: 0.9228 - accuracy: 0.6308 - val_loss: 0.8939 - val_accuracy: 0.6594
Epoch 5/5
92/92 [==============================] - 3s 25ms/step - loss: 0.8850 - accuracy: 0.6536 - val_loss: 0.8712 - val_accuracy: 0.6512
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)
12/12 [==============================] - 4s 12ms/step - loss: 0.8603 - accuracy: 0.6458
Accuracy 0.6457765698432922

Benutzerdefinierte Datenerweiterung

Sie können auch benutzerdefinierte Datenerweiterungs-Layer erstellen. Dieses Tutorial zeigt zwei Möglichkeiten, dies zu tun. Zuerst erstellen Sie eine layers.Lambda Schicht. Dies ist eine gute Möglichkeit, prägnanten Code zu schreiben. Als Nächstes schreiben Sie über Unterklassen eine neue Ebene, die Ihnen mehr Kontrolle gibt. Beide Ebenen invertieren die Farben in einem Bild mit einer gewissen Wahrscheinlichkeit zufällig.

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

png

Implementieren Sie als Nächstes eine benutzerdefinierte Ebene, indem Sie eine Unterklasse von .

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

Beide Ebenen können wie in den Optionen 1 und 2 oben beschrieben verwendet werden.

tf.image verwenden

Die oben layers.preprocessing Dienstprogramme für die layers.preprocessing sind praktisch. Für eine genauere Kontrolle können Sie mit tf.data und tf.image eigene Pipelines oder Layer zur tf.data tf.image . Sehen Sie sich auch TensorFlow Addons Image: Operations and TensorFlow I/O: Color Space Conversions an

Da das Blumen-Dataset zuvor mit Datenerweiterung konfiguriert wurde, importieren wir es erneut, um neu zu beginnen.

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

Rufen Sie ein Bild ab, mit dem Sie arbeiten möchten.

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

png

Lassen Sie uns die folgende Funktion verwenden, um die Original- und Augmented-Bilder nebeneinander zu visualisieren und zu vergleichen.

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)

Datenerweiterung

Spiegeln des Bildes

Spiegeln Sie das Bild entweder vertikal oder horizontal.

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

png

Graustufen des Bildes

Graustufen eines Bildes.

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

png

Sättigen Sie das Bild

Sättigen Sie ein Bild, indem Sie einen Sättigungsfaktor angeben.

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

png

Bildhelligkeit ändern

Ändern Sie die Helligkeit des Bildes, indem Sie einen Helligkeitsfaktor angeben.

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

png

Bild mittig zuschneiden

Beschneiden Sie das Bild von der Mitte bis zum gewünschten Bildteil.

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

png

Bild drehen

Drehen Sie ein Bild um 90 Grad.

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

png

Zufällige Transformationen

Durch das Anwenden zufälliger Transformationen auf die Bilder kann das Dataset weiter verallgemeinert und erweitert werden. Die aktuelle tf.image API bietet 8 solcher zufälligen Bildoperationen (ops):

Diese zufälligen Bildoperationen sind rein funktional: Die Ausgabe hängt nur von der Eingabe ab. Dies macht sie einfach in leistungsstarken, deterministischen Eingabepipelines zu verwenden. Sie benötigen einen seed Wert eingegeben werden , jeder Schritt. Bei gleichem seed sie die gleichen Ergebnisse zurück, unabhängig davon, wie oft sie aufgerufen werden.

In den folgenden Abschnitten werden wir:

  1. Gehen Sie Beispiele für die Verwendung zufälliger Bildoperationen zum Transformieren eines Bildes durch, und
  2. Demonstrieren Sie, wie Sie zufällige Transformationen auf ein Trainings-Dataset anwenden.

Bildhelligkeit zufällig ändern

Ändern Sie die Helligkeit des image nach dem Zufallsprinzip, indem Sie einen Helligkeitsfaktor und einen seed bereitstellen. Der Helligkeitsfaktor wird zufällig im Bereich [-max_delta, max_delta) und dem gegebenen seed .

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

Bildkontrast zufällig ändern

Ändern Sie den image nach dem Zufallsprinzip, indem Sie einen Kontrastbereich und einen seed bereitstellen. Der Kontrastbereich wird im Intervall [lower, upper] zufällig gewählt und dem gegebenen seed .

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

Ein Bild zufällig zuschneiden

Ernte Randomly image durch genauere size und seed . Der Teil, der aus dem image befindet sich an einem zufällig ausgewählten Versatz und ist mit dem gegebenen seed .

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

Anwenden der Erweiterung auf ein Dataset

Laden wir zunächst den Bilddatensatz erneut herunter, falls er in den vorherigen Abschnitten geändert wurde.

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

Lassen Sie uns eine Dienstprogrammfunktion zum Ändern der Größe und Skalierung der Bilder definieren. Diese Funktion wird verwendet, um die Größe und den Maßstab der Bilder im Datensatz zu vereinheitlichen:

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

Lassen Sie uns auch eine augment definieren, die die zufälligen Transformationen auf die Bilder anwenden kann. Diese Funktion wird im nächsten Schritt für den Datensatz verwendet.

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

Option 1: Verwenden von tf.data.experimental.Counter()

Erstellen Sie ein tf.data.experimental.Counter() Objekt (nennen wir es counter ) und zip den Datensatz mit (counter, counter) . Dadurch wird sichergestellt , dass jedes Bild in dem Datensatz mit einem eindeutigen Wert zugeordnet wird (der Form (2,) ) , basierend auf counter , die in die vergangen später bekommen augment Funktion als seed Wert für Zufall Transformationen.

# Create counter and zip together with train dataset
counter = tf.data.experimental.Counter()
train_ds = tf.data.Dataset.zip((train_datasets, (counter, counter)))

Ordnen Sie die augment dem Trainings-Dataset zu.

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

Option 2: Verwenden von tf.random.Generator

Erstellen Sie ein tf.random.Generator Objekt mit einem anfänglichen seed Wert. Der Aufruf der Funktion make_seeds für dasselbe Generatorobjekt gibt immer einen neuen, eindeutigen seed Wert zurück. Definieren Sie eine Wrapper-Funktion, die 1) die Funktion make_seeds und 2) den neu generierten seed Wert für zufällige Transformationen an die augment Funktion übergibt.

# Create a generator
rng = tf.random.Generator.from_seed(123, alg='philox')
# 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

Ordnen Sie die Wrapper-Funktion f dem Trainings-Dataset zu.

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

Diese Datensätze können nun verwendet werden, um ein Modell wie zuvor gezeigt zu trainieren.

Nächste Schritte

In diesem Tutorial wurde die tf.image mit Keras Preprocessing Layers und tf.image . Um zu erfahren , wie man eine Vorverarbeitung Schichten in Ihrem Modell enthält, finden Sie in der Bildklassifizierung Tutorial. Sie könnten auch daran interessiert sein zu erfahren, wie die Vorverarbeitung von Layern Ihnen beim Klassifizieren von Text helfen kann, wie im Tutorial zu den Grundlagen der Textklassifizierung gezeigt. Sie können mehr darüber erfahren , tf.data in dieser Anleitung , und Sie können lernen , wie Sie Ihre Eingangsleitungen für die Leistung konfigurieren hier .