日付を保存! Google I / Oが5月18日から20日に戻ってきます今すぐ登録
このページは Cloud Translation API によって翻訳されました。
Switch to English

データの増強

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロード

概要概要

このチュートリアルでは、データの拡張について説明します。これは、画像の回転などのランダムな(ただし現実的な)変換を適用することにより、トレーニングセットの多様性を高める手法です。 2つの方法でデータ拡張を適用する方法を学習します。まず、 Keras前処理レイヤーを使用します。次に、 tf.imageを使用しtf.image

セットアップ

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

from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist

データセットをダウンロードする

このチュートリアルでは、 tf_flowersデータセットを使用します。便宜上、 TensorFlowデータセットを使用してデータセットをダウンロードします。データをインポートする他の方法について知りたい場合は、画像読み込みチュートリアルを参照してください。

(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,
)
Downloading and preparing dataset 218.21 MiB (download: 218.21 MiB, generated: 221.83 MiB, total: 440.05 MiB) to /home/kbuilder/tensorflow_datasets/tf_flowers/3.0.1...
Dataset tf_flowers downloaded and prepared to /home/kbuilder/tensorflow_datasets/tf_flowers/3.0.1. Subsequent calls will reuse this data.

花のデータセットには5つのクラスがあります。

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

データセットから画像を取得し、それを使用してデータの拡張を示しましょう。

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

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

png

Keras前処理レイヤーを使用する

サイズ変更と再スケーリング

前処理レイヤーを使用して、画像のサイズを一貫した形状に変更したり、ピクセル値を再スケーリングたりできます。

IMG_SIZE = 180

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

これらのレイヤーを画像に適用した結果を確認できます。

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

png

ピクセルが[0-1]ことを確認できます。

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

データの増強

データ拡張のために前処理レイヤーを使用することもできます。

いくつかの前処理レイヤーを作成し、それらを同じ画像に繰り返し適用してみましょう。

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

layers.RandomContrastlayers.RandomCroplayers.RandomZoomなど、データ拡張に使用できるさまざまな前処理レイヤーがあります。

前処理レイヤーを使用する2つのオプション

これらの前処理レイヤーを使用するには2つの方法がありますが、重要なトレードオフがあります。

オプション1:前処理レイヤーをモデルの一部にする

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

この場合、注意すべき2つの重要なポイントがあります。

  • データ拡張は、デバイス上で残りのレイヤーと同期して実行され、GPUアクセラレーションの恩恵を受けます。

  • model.saveを使用してモデルをエクスポートすると、前処理レイヤーがモデルの残りの部分と一緒に保存されます。後でこのモデルをデプロイすると、(レイヤーの構成に従って)イメージが自動的に標準化されます。これにより、そのロジックをサーバー側で再実装する手間を省くことができます。

オプション2:前処理レイヤーをデータセットに適用する

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

このアプローチでは、 Dataset.mapを使用して、拡張画像のバッチを生成するデータセットを作成します。この場合:

  • データの拡張はCPUで非同期に行われ、非ブロッキングです。以下に示すDataset.prefetchを使用して、GPUでのモデルのトレーニングをデータ前処理とオーバーラップさせることができます。
  • この場合、prepreprocessing層が呼び出すモデルにエクスポートされませんmodel.save 。モデルを保存する前、またはサーバー側で再実装する前に、モデルにアタッチする必要があります。トレーニング後、エクスポートする前に前処理レイヤーをアタッチできます。

最初のオプションの例は、画像分類チュートリアルにあります。ここで2番目のオプションを示しましょう。

前処理レイヤーをデータセットに適用します

上で作成した前処理レイヤーを使用して、トレーニング、検証、およびテストのデータセットを構成します。また、並列読み取りとバッファー付きプリフェッチを使用してデータセットのパフォーマンスを構成し、I / Oがブロックされることなくディスクからバッチを生成します。 tf.data APIガイドを使用すると、パフォーマンスの向上でデータセットのパフォーマンスの詳細を学ぶことができます

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)

モデルをトレーニングする

完全を期すために、これらのデータセットを使用してモデルをトレーニングします。このモデルは精度が調整されていません(目標はメカニズムを示すことです)。

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 [==============================] - 17s 47ms/step - loss: 1.6206 - accuracy: 0.2998 - val_loss: 1.1271 - val_accuracy: 0.5613
Epoch 2/5
92/92 [==============================] - 3s 28ms/step - loss: 1.0935 - accuracy: 0.5384 - val_loss: 1.0343 - val_accuracy: 0.5858
Epoch 3/5
92/92 [==============================] - 3s 27ms/step - loss: 0.9785 - accuracy: 0.6022 - val_loss: 0.8810 - val_accuracy: 0.6540
Epoch 4/5
92/92 [==============================] - 3s 27ms/step - loss: 0.9167 - accuracy: 0.6399 - val_loss: 0.8247 - val_accuracy: 0.6594
Epoch 5/5
92/92 [==============================] - 3s 27ms/step - loss: 0.8545 - accuracy: 0.6615 - val_loss: 0.8591 - val_accuracy: 0.6458
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)
12/12 [==============================] - 0s 13ms/step - loss: 0.9010 - accuracy: 0.6131
Accuracy 0.6130790114402771

カスタムデータの拡張

カスタムデータ拡張レイヤーを作成することもできます。このチュートリアルでは、そのための2つの方法を示します。まず、 layers.Lambdaレイヤーを作成します。これは簡潔なコードを書くための良い方法です。次に、サブクラス化を介して新しいレイヤーを作成します。これにより、より詳細に制御できます。両方のレイヤーは、ある程度の確率に従って、画像の色をランダムに反転します。

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

次に、をサブクラス化してカスタムレイヤーを実装します。

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

これらのレイヤーは両方とも、上記のオプション1と2で説明したように使用できます。

tf.imageを使用する

上記のlayers.preprocessingユーティリティは便利です。より細かく制御するために、 tf.datatf.imageを使用して独自のデータ拡張パイプラインまたはレイヤーをtf.data tf.imageます。 TensorFlowアドオン画像:操作TensorFlow I / O:色空間変換も確認することをお勧めします

花のデータセットは以前にデータ拡張で構成されていたので、再インポートして最初からやり直してみましょう。

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

png

次の関数を使用して、元の画像と拡張された画像を並べて視覚化して比較してみましょう。

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)

データの増強

画像を反転する

画像を垂直または水平に反転します。

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

png

画像をグレースケール

画像をグレースケールします。

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

png

画像を飽和させる

飽和係数を指定して画像を飽和させます。

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

png

画像の明るさを変更する

明るさ係数を指定して、画像の明るさを変更します。

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

png

画像を中央でトリミング

画像を中央から目的の画像部分まで切り抜きます。

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

png

画像を回転させる

画像を90度回転します。

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

png

ランダム変換

画像にランダムな変換を適用すると、データセットの一般化と拡張にさらに役立ちます。現在のtf.imageは、次の8つのランダムイメージ操作(ops)を提供します。

これらのランダムイメージ操作は純粋に機能的です。出力は入力にのみ依存します。これにより、高性能で決定論的な入力パイプラインで簡単に使用できるようになります。各ステップでseed値を入力する必要があります。同じseed与えられると、呼び出された回数に関係なく、同じ結果が返されます。

次のセクションでは、次のことを行います。

  1. ランダムな画像操作を使用して画像を変換する例を確認し、
  2. ランダム変換をトレーニングデータセットに適用する方法を示します。

画像の明るさをランダムに変更する

明るさ係数とseedを指定して、 imageの明るさをランダムに変更します。輝度係数は[-max_delta, max_delta)範囲でランダムに選択され、指定された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

画像のコントラストをランダムに変更する

コントラスト範囲とseedを指定して、 imageのコントラストをランダムに変更します。コントラスト範囲は[lower, upper]間隔でランダムに選択され、指定された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

画像をランダムに切り抜く

ターゲットsizeseedimageランダムにトリミングしimageimageから切り取られる部分は、ランダムに選択されたオフェットにあり、指定された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

データセットに拡張を適用する

前のセクションで変更された場合に備えて、最初に画像データセットを再度ダウンロードしましょう。

(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

また、画像にランダム変換を適用できるaugment関数を定義しましょう。この関数は、次のステップでデータセットで使用されます。

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

オプション1: tf.data.experimental.Counter()を使用する

作成tf.data.experimental.Counter()オブジェクトを(せのはそれを呼び出すcounter )とzipでデータセットを(counter, counter) 。これにより、データセット内の各画像が、 counter基づいて(2,)形状(2,) )一意の値に関連付けられ、後でランダム変換のseed値としてaugment関数に渡されるようになります。

# Create counter and zip together with train dataset
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)
)

オプション2: tf.random.Generatorを使用tf.random.Generator

初期seed値を使用してtf.random.Generatorオブジェクトを作成します。同じジェネレータオブジェクトでmake_seeds関数を呼び出すと、常に新しい一意のseed値が返されます。 1) make_seeds関数を呼び出し、2)新しく生成されたseed値をランダム変換のaugment関数に渡すラッパー関数を定義します。

# 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

ラッパー関数fをトレーニングデータセットにマップします。

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

これらのデータセットを使用して、前に示したようにモデルをトレーニングできるようになりました。

次のステップ

このチュートリアルでは、 tf.image前処理レイヤーtf.imageを使用したデータ拡張tf.image 。モデル内に前処理レイヤーを含める方法については、画像分類チュートリアルを参照してください。基本的なテキスト分類チュートリアルに示されているように、前処理レイヤーがテキストの分類にどのように役立つかを学ぶことにも興味があるかもしれません。このガイドtf.data詳細を学ぶことができ、パフォーマンスのために入力パイプラインを構成する方法をここで学ぶことができます。