【TF2】ImageDataGeneratorを継承・拡張して、自作Augmentation機能を追加する

TIPS ディープラーニング
スポンサーリンク

画像読み込みとAugmentationが可能で、非常に便利なImageDataGeneratorですが、ノイズ付与やカラー変化などの機能は実装されておりません。
そんな時に自前で機能追加する方法です。

はじめに

ImageDataGenerator とは、
Model.fit_generator()に渡せるジェネレータであり、
データセットや、フォルダ内の画像を読み込んで、
バッチ毎にオーグメンテーションができるクラスです。

学習時に画像を読み込みつつ
カンタンに「毎回違う画像変化」をつけることで、

画像分類での正解率向上にバツグンの効果を発揮します。

ただし、標準のAugmentation機能には、
RandomNoiseとかmixupなどの最新の論文にあるような機能は実装されておらず、実現するには自作する必要があります。
今回はその第一歩のサンプルとして記事にしました。

 

なお、ImageDataGenerator の持つAugmentation機能の説明は、
Keras公式ドキュメントのほか、
以下のサイトが非常にわかりやすかったです。

Kerasでデータ拡張(Data Augmentation)後の画像を表示する - Qiita
2019/07/24更新内容 KerasのImageDataGeneratorの出力を簡単に確認できるGUIツールを作りました。

 

ソースコード

こちらにありますので、実際に動作させたい方はこちらを。
以下の説明は、このソースコードの前提で書いています。

MaxiParadise/Extended_ImageDataGenerator_Sample
Contribute to MaxiParadise/Extended_ImageDataGenerator_Sample development by creating an account on GitHub.

 

ImageDataGeneratorの拡張

ImageDataGeneratorはクラスの一個なので、
クラス継承して機能拡張が可能です。
これにノイズ付与機能を加えてみます。

class ExtImageDataGenerator(ImageDataGenerator):
    def __init__(self,
                 noise_pb=0.25,
                 *args, **kwargs):
        super().__init__(*args, **kwargs)

        # noise parameter
        assert noise_pb >= 0.0
        self.noise_pb = noise_pb

拡張クラス ExtImageDataGenerator では、
ノイズ付与する対象の確率 “noise_pb” を追加し、

例えば 0.25 なら、4つに1つがノイズ付与される仕様にします。

そして、親クラスの引数も使えるように、
super().__init__(*args, **kwargs) をCallしています(重要)。

 

ノイズ付与メソッドの追加

ノイズ付与メソッドは、
一から作ってもいいですが、手っ取り早く
scikit-imageのutil.random_noiseで実装します。
var=0.005くらいあれば見た目に分かりやすいのですが、
影響も過多なのでとりあえず0.001で。

from skimage import util
def add_noise(self, rgb):
    return util.random_noise(rgb, var=0.001)

 

flow() もしくはflow_from_directory()のオーバーライド

そして、バッチ実行されるflow() をオーバーライドします。

このメソッドは学習時に随時呼び出されるメソッドなので、
この中から先程のadd_noise()を実行することで、
自作Augmentationが実施される仕組み
です。

# Override flow()
def flow(self, *args, **kwargs):
    batches = super().flow(*args, **kwargs)

    while True:
        batch_x, batch_y = next(batches)
        # My Augumentation
        for i,img in enumerate(batch_x):
            if np.random.random() < self.noise_pb:
                batch_x[i] = self.add_noise(img)

        yield (batch_x, batch_y)

flow() 仕様に基づき、
while~yieldによって、学習時にデータを随時参照させます。

他の機能も追加したい場合、
このテンプレートの # My Augmentation以下に同じようにメソッド追加していけばいいです。

generator定義

拡張した ExtImageDataGenerator でオブジェクト作成。
これをもとに、学習用のジェネレータを作成します。

train_datagen = ExtImageDataGenerator(horizontal_flip = True, channel_shift_range = 0.3, noise_pb=0.5)
train_generator = train_datagen.flow(
   x_train, y_train,
   batch_size=BATCH_SIZE,
   shuffle=True,
   seed = 12345
)

ここで、
horizontal_flip, channel_shift_range は、元々ある親クラスの引数で、
noise_pb が継承クラスの引数です。

同時に使えることの確認として実施しています。

validationデータも同様に作成します。
こちらは、validation用なので特にAugmentation必要無いと思って実施しません。
(やったほうが良いのかどうかよく分かっていません・・・)

valid_datagen = ExtImageDataGenerator()
valid_generator = valid_datagen.flow(
   x_valid, y_valid,
   batch_size=BATCH_SIZE,
   shuffle=True,
   seed = 12345
)

 

train実行

あとは”fit_generator” に渡して完了です。

# モデル訓練実行
history = model.fit_generator(
    train_generator,
    steps_per_epoch=x_train.shape[0]//BATCH_SIZE,
    epochs=MAX_EPOCH,
    verbose=1,
    validation_data=valid_generator,
    validation_steps=x_test.shape[0]//BATCH_SIZE,
    callbacks=cb_funcs
)

 

通常Augmentationとの精度比較

CIFAR10のような小さな画像なので、
今回は非常に単純な4層CNNを用いて実施しました。

def Conv_L4(input_shape):
    model = Sequential()
    model.add(Conv2D(64, (3, 3), padding='same', input_shape=input_shape, kernel_initializer='random_uniform', activation='relu'))
    model.add(MaxPooling2D((2,2), strides=(2, 2)))
    model.add(Conv2D(128, (3, 3), padding='same', kernel_initializer='random_uniform', activation='relu'))
    model.add(MaxPooling2D((2,2), strides=(2, 2)))
    model.add(Conv2D(128, (3, 3), padding='same', kernel_initializer='random_uniform', activation='relu'))
    model.add(MaxPooling2D((2,2), strides=(2, 2)))
    model.add(Conv2D(256, (3, 3), padding='same', kernel_initializer='random_uniform', activation='relu'))
    model.add(MaxPooling2D((2,2), strides=(2, 2)))
    return model

 

結果

  • ExtImageDataGeneratorによる HFlip + ChannelShift + AddNoise実施 学習結果  loss:0.772628215932846
    acc :0.7592999935150146
  • ImageDataGeneratorによる HFlip + ChannelShift実施 学習結果
    loss:0.7938857310771942
    acc :0.7570000290870667
  • ImageDataGeneratorでAugmentなし学習結果
    loss:1.076167423915863
    acc :0.7027000188827515

若干ですが、ノイズ付与を追加したことで、
一番精度が良い結果
が得られました。

今回のCIFAR10のような小さな画像だとあまり効かなったですが、
ランダムノイズ付与は様々なシーンで有効な結果が出ています。

他にも有効なAugmentation術が日々開発されています。
(「
Data Augmentation」でググると色々見つかります RandomErasing, mixup など…)

これらを試したい時も、
ImageDataGeneratorを拡張して実装すると便利
だと思います。

 

 

最後まで読んでいただき、ありがとうございます!

ブックマーク登録、
ツイッターフォロー、
よろしくお願いいたします!🙇‍♂️🙇‍♂️
↓↓↓

👇 フォローボタン 👇
スポンサーリンク
マキシ's ディープパラダイス