データ拡張
ディープラーニングの世界では大量のデータが必要であることが前提となるため、欲しいデータが潤沢にあれば良いのですが、実現場ではなかなか求めているデータ数を集めることは思ったようにいかないケースが多いです。
そういった場合に、学習データの画像に対して移動、回転、拡大・縮小など人工的な操作を加えることでデータ数を水増しするテクニックがあります。水増しされることで同じ画像が学習されることが少なくなるので汎化性能が向上されることが期待されます。
本章では、水増しテクニックである データ拡張 (Data Augmentation) の代表的な処理を確認したうえで、適用前後で精度がどのように変化するかを確認します。
TensorFlow では、tensorflow.keras.preprocess.image.ImageDataGenerator に様々な水増しのメソッドが用意されています。
通常の学習では、データセットから指定した枚数だけ画像を選択し、ミニバッチを作成します。 一方、 ImageDataGenerator を使用すると、画像を選択したあと、各画像にデータ拡張を行い、ミニバッチを作成します。どのような処理をおこなうかはインスタンス生成時の引数で指定することができ、変換はリアルタイムでおこなられるため、保存するわけではないのでディスク容量を圧迫する心配はありません。
代表的な処理として、以下があげられます。
- 回転
- 水平移動
- 拡大
- せん断
- 水平反転
- 垂直反転
本章の流れ
- ベースモデルの作成
- 各処理の確認
- 各処理適用後の画像を保存
- データ拡張による精度の確認
ベースモデルの作成
前章と同じように、まずはベースモデルを作成しましょう。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
      データセットの準備
本章でも CIFAR10 と呼ばれる 10 クラス分類を行います。tf.keras.datasets にデータセットが用意されています。
(x_train, t_train), (x_test, t_test) = tf.keras.datasets.cifar10.load_data()
      それでは、今回扱うデータを 25 枚ランダムに抜粋して表示します。
| 正解ラベル | 種別 | 
|---|---|
| 0 | airplane | 
| 1 | automobile | 
| 2 | bird | 
| 3 | cat | 
| 4 | deer | 
| 5 | dog | 
| 6 | frog | 
| 7 | horse | 
| 8 | ship | 
| 9 | truck | 
10 クラス分類となっており、上記の表の種別を分類することが目標です。 と低解像度なところも、CIFAR10 がよく画像の練習問題として扱われる理由のひとつです。
# データをプロット
plt.figure(figsize=(12,12))
for i in range(25):
    plt.subplot(5, 5, i+1)
    plt.imshow(x_train[i])
      # 正規化
x_train = x_train / 255.0
x_test = x_test / 255.0
      x_train.shape, x_test.shape, t_train.shape, t_test.shape
      モデルの定義と学習
import os
import random
def reset_seed(seed=0):
    os.environ['PYTHONHASHSEED'] = '0'
    random.seed(seed) # random関数のシードを固定
    np.random.seed(seed) #numpyのシードを固定
    tf.random.set_seed(seed) #tensorflowのシードを固定
      from tensorflow.keras import models, layers
      # シードの固定
reset_seed(0)
# モデル構築
model = models.Sequential([
    layers.Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(32, 32, 3)),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dense(10, activation='softmax')
]) 
  
# optimizerの設定
optimizer = tf.keras.optimizers.Adam(lr=1e-3)
# モデルのコンパイル
model.compile(loss='sparse_categorical_crossentropy', 
              optimizer=optimizer,
              metrics=['accuracy'])
model.summary()
      # 学習の詳細設定
batch_size = 1024
epochs = 100
# 学習の実行
history = model.fit(x_train, t_train, 
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1,
                    validation_data=(x_test, t_test))
      results = pd.DataFrame(history.history)
results.tail(1)
      | loss | accuracy | val_loss | val_accuracy | |
|---|---|---|---|---|
| 99 | 0.005615 | 0.99998 | 2.147902 | 0.721 | 
results[['loss', 'val_loss']].plot()
      | Train | Val | |
|---|---|---|
| Base Accuracy | 0.999 | 0.721 | 
| Base Loss | 0.006 | 2.148 | 
過学習を起こしてしまっていることがわかります。上記のスコアをベースラインとして、データ拡張を適用することで汎化性能が向上するか確認しましょう。
各処理の確認
具体的に実装する前に、以下の代表的な水増し処理を一つ一つ見てきましょう。
- 回転
- 水平移動
- 拡大
- せん断
- 水平反転
- 垂直反転
まずは、CIFAR10 の画像を 1 枚取り出し、サンプルイメージとします。
# サンプルイメージ
img = x_train[4]
      変換をかけて、変換前と後の画像を表示する関数を作成します。何度も使う処理は先に関数化しておくと使い回せるため便利です。
# データ拡張のためのモジュール
from tensorflow.keras.preprocessing.image import ImageDataGenerator
      # 処理後を可視化する関数
def show(img, datagen):
    # (batch_size, height, width, channel) に reshape する
    img_batch = img.reshape(1, 32, 32, 3)
    # datagen.flow() でデータセットから Augmentation 処理をかけながらミニバッチを読み込む
    # 今回は、1 サンプルのデータセットを batch_size=1 で読み込む。
    for img_augmented in datagen.flow(img_batch, batch_size=1):
        # batch_size の次元を削除
        out = img_augmented.reshape(32, 32, 3)
        break
    plt.figure(figsize=(10, 10))
    plt.subplot(1, 2, 1)
    plt.title('before')
    plt.imshow(img)
  
    plt.subplot(1, 2, 2)
    plt.title('after')
    plt.imshow(out)
      回転
rotation_range 引数で指定します。
datagen = ImageDataGenerator(rotation_range=60)
show(img, datagen)
      水平移動
width_shift_range, height_shift_range で横方向あるいは縦方向への水平移動を指定します。
datagen = ImageDataGenerator(width_shift_range=0.5,
                             height_shift_range=0.5)
show(img, datagen)
      せん断
せん断は、四角形の画像を平行四辺形に変形する処理です。shear_range 引数で指定します。
datagen = ImageDataGenerator(shear_range=30)
show(img, datagen)
      拡大
zoom_range 引数で指定します。 例えば 0.5 を与えると、-0.5 ~ 0.5 の範囲でランダムに元のサイズに掛け算をして拡大縮小が行われます。
datagen = ImageDataGenerator(zoom_range=0.5)
show(img, datagen)
      水平反転
horizontal_flip 引数で指定します。
datagen = ImageDataGenerator(horizontal_flip=True)
show(img, datagen)
      垂直反転
vertical_flip 引数で指定します。
datagen = ImageDataGenerator(vertical_flip=True)
show(img, datagen)
      option : fill_mode
変形により画像に空白箇所ができてしまう際、その画素をどのようにして埋めるかどうかを fill_mode 引数で指定できます。以下の 4 つの値を受け取ることができます。初期値は nearest です。
- constant
- nearest
- reflect
- wrap
datagen = ImageDataGenerator(width_shift_range=0.5, fill_mode='constant')
show(img, datagen)
      datagen = ImageDataGenerator(width_shift_range=0.5, fill_mode='reflect')
show(img, datagen)
      datagen = ImageDataGenerator(width_shift_range=0.5, fill_mode='wrap')
show(img, datagen)
      各処理適用後の画像の保存
変換をかけた画像の保存は以下のように行います。
# 画像保存する関数
from tensorflow.keras.preprocessing import image
      tensorflow.keras.preprocessing.image.save_img() で保存でき、引数は大きく 2 つ準備します。
- path:保存先ディレクトリ
- x:保存する画像
公式ドキュメントはこちらを参考にしてください。上記 2 つを設定し、保存しましょう。 今回は画像を 1 枚保存する方法を紹介します。
# サンプル画像の用意
img = x_train[4]
# 水増し処理を定義
datagen = ImageDataGenerator(vertical_flip=True)
# (batch_size, height, width, channel) に reshape する
img_batch = img.reshape(1, 32, 32, 3)
# 今回は 1 枚だけ保存します
max_img_num = 1
counts = 1
for img_augmented in datagen.flow(img_batch, batch_size=1):
    # batch_size の次元を削除
    img_augmented = img_augmented.reshape(32, 32, 3)
    # 画像を保存
    image.save_img('augmented_output.png', img_augmented)
    # max_img_num の枚数を保存したら終了
    if (counts % max_img_num) == 0:
        print('Finish!!')
        break
    counts += 1
      これで保存完了です。複数枚保存したい場合でも、max_img_num の数を変更したら良いので使いまわしてください。保存先ディレクトリは任意の場所を指定して管理しやすい形にしましょう。
データ拡張による精度の変化
今回は水平反転と垂直反転をランダムに入れて、汎化性能が向上するか確認しましょう。
# 適用したいデータ拡張の種類を定義
datagen = ImageDataGenerator(
    horizontal_flip=True,
    vertical_flip=True)
      # シードの固定
reset_seed(0)
# モデルの構築
model = models.Sequential([
    layers.Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(32, 32, 3)),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Conv2D(128, (3, 3), padding='same', activation='relu'),
    layers.MaxPooling2D((2, 2)),
    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dense(10, activation='softmax')
]) 
  
# optimizerの設定
optimizer = tf.keras.optimizers.Adam(lr=1e-3)
 
# モデルのコンパイル
model.compile(loss='sparse_categorical_crossentropy', 
              optimizer=optimizer,
              metrics=['accuracy'])
model.summary()
      batch_size = 1024
epochs = 100
history = model.fit_generator(datagen.flow(x_train, t_train, batch_size=batch_size),
                    steps_per_epoch = len(x_train) / batch_size, 
                    epochs = epochs,
                    validation_data = (x_test, t_test))
      results = pd.DataFrame(history.history)
results.tail(1)
      | loss | accuracy | val_loss | val_accuracy | |
|---|---|---|---|---|
| 99 | 0.502889 | 0.82516 | 0.795237 | 0.7314 | 
results[['loss', 'val_loss']].plot()
      | Train | Val | |
|---|---|---|
| Base Accuracy | 0.999 | 0.721 | 
| Base Loss | 0.006 | 2.148 | 
| Augmentation Accuracy | 0.825 | 0.731 | 
| Augmentation Loss | 0.503 | 0.795 | 
検証データの正解率が向上し、学習データの正解率との乖離が小さくなりました。汎化性能が向上したことが確認できました。
データ拡張は、簡単な処理でありながら手軽に精度向上に貢献してくれる重要な手法です。最新の手法では、適用していないモデルは無いと言ってよいほどスタンダードになっていますので、ぜひ適用してください。

