本記事では、深層学習において重要なテクニックの一つであるデータオーグメンテーション(データ拡張)について解説します。PythonのディープラーニングフレームワークであるTensorFlowを用いた簡単な実装方法についても紹介します。
データ拡張とは
深層学習では非常に多くのデータが必要とされますが、データが少ないときもあります。そんなときにデータを増やすための手段の一つがデータ拡張で、画像データにおいて用いられます。どのようにデータを増やすのかですが、すでに存在する実際のデータに対して少しだけ変化を加えたものをたくさん作ることで、データ数を”水増し”します。しかしただ闇雲に増やせばよいというわけではなく、テストしたときによりよい精度を発揮するためにはどのような変化を加えるかも考慮する必要があります。
今回はデータ拡張でデータに加える変化には具体的にどのようなものがあるのかを解説しつつ、その実装も紹介します。
TensorFlowを用いた実装
まず、データ拡張の実装方法にはあらかじめデータの数自体を増やす「オフライン」の方法と、学習時にミニバッチ毎に変換を加えることで疑似的にデータ数を増やす「オンラインの方法」とがあります。オンラインの方法には実際の画像枚数が増えない分メモリを食わないという利点があります。TensorFlowではオンラインのデータ拡張も行うことができますが、今回は単純に一枚の画像のみに対して処理を行うだけとします。画像は以下のコードを実行して表示されるものを用います。
デフォルトのコード
import matplotlib.pyplot as plt
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#読み込んだ画像を表示
plt.imshow(img_rgb)

TensorFlowでデータ拡張を行う方法は主に3つあります。
一つ目はTensorFlowの高レベルAPIであるkerasに含まれるImageDataGeneratorを使用する方法です。二つ目は、こちらもkerasを使いますがkerasの前処理レイヤーを使用する方法です。これらのkerasを用いる手法は使いやすいですが、独自のデータ拡張を定義できないというデメリットもあります。三つ目はkerasを用いず、tensorflowのimageクラスを使用する方法です。この方法ではより細かい処理を定義することができます。
それぞれ、以下の実装の具体例を通して身につけましょう。
Random Flip
Random Flip は、ランダムな確率で画像を反転させる処理です。HorizontalFlipとVerticalFlipの二種類があり、HorizontalFlipは画像を水平方向に反転させ、VerticalFlipは画像を垂直方向に反転させます。
では、Random Flipの処理を実装してみましょう。まずはImageDataGeneratorを用いる方法を試してみます。data_generator = ImageDataGenerator(vertical_flip=True)のようにImageDataGeneratorクラスのオブジェクトを作成し、そのflowメソッドを実行することで処理を行うという形になります。ImageDataGeneratorクラスの引数にvertical_flip=Trueのように変換を定義します。今回はVerticalFlip処理を行うように定義しています。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#データのサイズを変更
img_rgb_t = img_rgb[np.newaxis,...]
#ImageDataGeneratorクラスのオブジェクトを生成(引数で変換を定義)
data_generator = ImageDataGenerator(vertical_flip=True)
data= data_generator.flow(img_rgb_t, batch_size=1)
#処理されたものはミニバッチ(イテレータ形式)となるため、next()で取り出す
batches = next(data)
batches = batches.astype(np.uint8)
#画像描画用の関数
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)
visualize(img_rgb, batches[0])

変換を行う確率を設定できないため、何度か実行しないと処理が行われないかもしれません。Augmented imageが元の画像と変わらないようでしたら、何度か実行してみてください。上下逆さまの画像が表示されるはずです。
なお、画像のデータサイズに気を付ける必要があります。もとの画像サイズは(310, 454, 3):縦、横、チャンネル数の3次元でしたがImageDataGeneratorで処理するためにはミニバッチサイズの次元も必要なため、最初に(1, 310, 454, 3)とデータサイズを変更しています。また、処理後はイテレータ形式で出力されるため、next関数でデータを取り出してあげる必要があります。
次にkerasの前処理レイヤーを用いる方法で実装してみます。この方法では、Sequential APIでモデルを構築するときのようにtf.keras.Sequentialでkerasの前処理レイヤーをまとめたSequentialモデルを作成し、そこに画像データを通すことで処理を行います。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#行う変換をSequentialモデルで定義
process = tf.keras.Sequential([
keras.layers.RandomFlip("horizontal_and_vertical")
])
#処理
result = process(img_rgb)
#画像描画用の関数
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)
visualize(img_rgb, result)

こちらも処理が行われる確率はランダムのため、元の画像と変わっていないようでしたら何度か実行してみてください。なお、この方法は入力画像データが3次元でも機能します。
最後に、tf.imageを使用する方法で実装してみます。tf.imageクラスのメソッドに画像とシード値を渡すだけと、シンプルです。Random Flipはtf.image.stateless_random_flip_left_rightメソッドで実装できます。渡す画像は3次元でも機能します。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#tf.imageのstateless_random_flip_left_right機能を使用
flipped = tf.image.stateless_random_flip_left_right(img_rgb, seed = (2, 3))
#画像描画用の関数
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)
visualize(img_rgb, flipped)

Random Crop
Random Cropは画像のランダムな一部を切り抜く処理を行います。三つの方法の内、ImageDataGeneratorを用いる方法以外でRandom Crop処理を行うことができます。
<keras前処理レイヤーを用いる方法>
tf.keras.layers.RandomCropレイヤーを使用します。引数には切り取り後のサイズを指定します。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#行う変換をSequentialモデルで定義
process = tf.keras.Sequential([
keras.layers.RandomCrop(180, 120)
])
#処理
result = process(img_rgb)
#画像描画用の関数
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)
visualize(img_rgb, result)

<tf.imageの機能を用いる方法>
tf.image.stateless_random_cropを使用します。引数には切り取り後のサイズ、シード値も指定する必要があります。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#tf.imageのstateless_random_crop機能を使用
flipped = tf.image.stateless_random_crop(img_rgb, size = (180, 180, 3), seed = (2, 3))
#画像描画用の関数
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)
visualize(img_rgb, flipped)

縦軸、横軸の値を見ると切り取っていることがよくわかります。
Random Contrast, Random Brightness
Random Contrastは画像のコントラストをランダムに変更する処理で、Random Brightnessもその名の通り画像の明るさをランダムに変更する処理です。ImageDataGeneratorを用いる方法ではRandom Brightnessを実装でき、tf.imageの機能を用いる方法ではRandom ContrastとRandom Brightnessのどちらも実装することができます。
<ImageDataGeneratorを用いる方法>
brightness_range(明るさの範囲)を引数に指定することでRandom Brightness処理を適用することができます。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#データのサイズを変更
img_rgb_t = img_rgb[np.newaxis,...]
#ImageDataGeneratorクラスのオブジェクトを生成(引数で変換を定義)
data_generator = ImageDataGenerator(brightness_range=[0.5,1.5])
data= data_generator.flow(img_rgb_t, batch_size=1)
#処理されたものはミニバッチ(イテレータ形式)となるため、next()で取り出す
batches = next(data)
batches = batches.astype(np.uint8)
#画像描画用の関数
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)
visualize(img_rgb, batches[0])

<tf.imageの機能を用いる方法>
stateless_random_contrastでRandom Contrast処理、stateless_random_brightnessでRandom Brightness処理を行うことができます。引数に調整の範囲、シード値の設定が必要です。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#tf.imageの機能を使用
aug1 = tf.image.stateless_random_contrast(img_rgb, 0.2, 0.5, seed=(1, 2))
aug2 = tf.image.stateless_random_brightness(aug1, 0.2, seed=(1, 2))
#画像描画用の関数
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)
visualize(img_rgb, aug2)

Random Rotate
Random Rotateは画像をランダムな角度だけ回転させる処理です。TensorFlowでは三つの手法のうち、tf.imageを用いる手法以外の二つの手法にRandom Rotateの機能が備わっています。
<ImageDataGeneratorを用いる方法>
これまで同様、ImageDataGeneratorの引数に今度は角度の範囲(rotation_range)を指定します。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#データのサイズを変更
img_rgb_t = img_rgb[np.newaxis,...]
#ImageDataGeneratorクラスのオブジェクトを生成(引数で変換を定義)
data_generator = ImageDataGenerator(rotation_range=50)
data= data_generator.flow(img_rgb_t, batch_size=1)
#処理されたものはミニバッチ(イテレータ形式)となるため、next()で取り出す
batches = next(data)
batches = batches.astype(np.uint8)
#画像描画用の関数
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)
visualize(img_rgb, batches[0])

<keras前処理レイヤーを用いる方法>
tf.keras.layers.RandomRotationレイヤーを用います。引数には回転の範囲を指定します。
デフォルトのコード
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import cv2
# OpenCVを使って画像を読み込む
img = cv2.imread('/mnt/lib/ztodataset/hk_nami.jpg')
#BGRをRGBに変換
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#行う変換をSequentialモデルで定義
process = tf.keras.Sequential([
keras.layers.RandomRotation(factor=0.2)
])
#処理
result = process(img_rgb)
#画像描画用の関数
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)
visualize(img_rgb, result)

まとめ
今回はデータ拡張で頻繁に用いられる手法のいくつかを簡単に解説・実装しました。データ拡張の手段は今回紹介したもの以外にも多数存在するので、興味がある方は調べてみてもよいでしょう。