人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

Kerasによるデータ拡張

今回は、画像認識の精度向上に有効な データ拡張(Data Augmentation) を実験してみた。データ拡張は、訓練データの画像に対して移動、回転、拡大・縮小など人工的な操作を加えることでデータ数を水増しするテクニック。画像の移動、回転、拡大・縮小に対してロバストになるため認識精度が向上するようだ。

音声認識でも訓練音声に人工的なノイズを上乗せしてデータを拡張するテクニックがあるらしいのでそれの画像版みたいなものだろう。

ソースコード

ImageDataGenerator

Kerasには画像データの拡張を簡単に行うImageDataGeneratorというクラスが用意されている。今回は、この使い方をまとめておきたい。ドキュメントを調べるとこのクラスにはパラメータが大量にあって目が回る。一気に理解するのは難しいので一つずつ検証しよう。

keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,
    samplewise_center=False,
    featurewise_std_normalization=False,
    samplewise_std_normalization=False,
    zca_whitening=False,
    rotation_range=0.,
    width_shift_range=0.,
    height_shift_range=0.,
    shear_range=0.,
    zoom_range=0.,
    channel_shift_range=0.,
    fill_mode='nearest',
    cval=0.,
    horizontal_flip=False,
    vertical_flip=False,
    rescale=None,
    dim_ordering=K.image_dim_ordering())

まずは、適当な画像を1枚だけ入力し、各パラメータを1つだけ指定してどのような画像が生成されるか確認してみた。

if __name__ == '__main__':
    # 画像をロード(PIL形式画像)
    img = load_img(IMAGE_FILE)

    # numpy arrayに変換(row, col, channel)
    x = img_to_array(img)
    # print(x.shape)

    # 4次元テンソルに変換(sample, row, col, channel)
    x = np.expand_dims(x, axis=0)
    # print(x.shape)

    # パラメータを一つだけ指定して残りはデフォルト
    datagen = ImageDataGenerator(rotation_range=90)

    # 生成した画像をファイルに保存
    draw_images(datagen, x, "result_rotation.jpg")

draw_images()は、ジェネレータオブジェクト、画像の4次元テンソル、出力ファイル名を指定すると生成した画像をファイルに描画する自作関数。

def draw_images(datagen, x, result_images):
    # 出力先ディレクトリを作成
    temp_dir = "temp"
    os.mkdir(temp_dir)

    # generatorから9個の画像を生成
    # xは1サンプルのみなのでbatch_sizeは1で固定
    g = datagen.flow(x, batch_size=1, save_to_dir=temp_dir, save_prefix='img', save_format='jpg')
    for i in range(9):
        batch = g.next()

    # 生成した画像を3x3で描画
    images = glob.glob(os.path.join(temp_dir, "*.jpg"))
    fig = plt.figure()
    gs = gridspec.GridSpec(3, 3)
    gs.update(wspace=0.1, hspace=0.1)
    for i in range(9):
        img = load_img(images[i])
        plt.subplot(gs[i])
        plt.imshow(img, aspect='auto')
        plt.axis("off")
    plt.savefig(result_images)

    # 出力先ディレクトリを削除
    shutil.rmtree(temp_dir)

next()を呼び出すたびにbatch_size個の画像が生成される。今回は、入力画像が1枚の画像なのでbatch_sizeは1にしている。生成した9枚の画像は3x3のグリッドに配置し、ファイルに保存する。

早速やってみよう。入力画像はなんでもいいけど下のを入れた。ぞうさんかわいいね。

f:id:aidiary:20161212214844j:plain:w300

rotation_range

画像を指定角度の範囲でランダムに回転する。目が回るゾウ。

datagen = ImageDataGenerator(rotation_range=90)
draw_images(datagen, x, "result_rotation.jpg")

f:id:aidiary:20161212215418j:plain:w500

width_shift_range

画像を水平方向にランダムに移動する。

datagen = ImageDataGenerator(width_shift_range=0.2)
draw_images(datagen, x, "result_width_shift.jpg")

f:id:aidiary:20161212215510j:plain:w500

height_shift_range

画像垂直方向にランダムに移動する。

datagen = ImageDataGenerator(height_shift_range=0.2)
draw_images(datagen, x, "result_height_shift.jpg")

f:id:aidiary:20161212215528j:plain:w500

shear_range

シアー変換をかける。Wikipediaをざっと見たが斜めに引き延ばすような画像変換みたい。

datagen = ImageDataGenerator(shear_range=0.78)  # pi/4
draw_images(datagen, x, "result_shear.jpg")

f:id:aidiary:20161212215555j:plain:w500

zoom_range

画像をランダムにズームする。

datagen = ImageDataGenerator(zoom_range=0.5)
draw_images(datagen, x, "result_zoom.jpg")

f:id:aidiary:20161212215945j:plain:w500

channel_shift_range

画像のチャンネルをランダムに移動する。RGBの値をランダムに加えるのかな?要調査。

datagen = ImageDataGenerator(channel_shift_range=100)
draw_images(datagen, x, "result_channel_shift.jpg")

f:id:aidiary:20161212220056j:plain:w500

horizontal_flip

画像を水平方向にランダムに反転する。

datagen = ImageDataGenerator(horizontal_flip=True)
draw_images(datagen, x, "result_horizontal_flip.jpg")

f:id:aidiary:20161212220144j:plain:w500

vertical_flip

画像を垂直方向にランダムに反転する。

datagen = ImageDataGenerator(vertical_flip=True)
draw_images(datagen, x, "result_vertical_flip.jpg")

f:id:aidiary:20161212220200j:plain:w500

samplewise_center

サンプル平均を0にする。平均の正規化なんだろうけど平均0にしてimshow()しても大丈夫なのかな?imshow()でとりあえず画像出たけどあとで仕様を確認。

datagen = ImageDataGenerator(samplewise_center=True)
draw_images(datagen, x, "result_samplewise_center.jpg")

f:id:aidiary:20161212220405j:plain:w500

samplewise_std_normalization

サンプルを標準偏差で割る。画像はなんか真っ黒になっちゃった。上のと組み合わせると平均0、標準偏差1になる正規化をかけられるようだ。これもアルゴリズムを要確認。

datagen = ImageDataGenerator(samplewise_std_normalization=True)
draw_images(datagen, x, "result_samplewise_std_normalization.jpg")

f:id:aidiary:20161212220649j:plain:w500

ZCA白色化(ZCA whitening)

ZCA白色化の詳細はまだよくわかっていない。この変換は一枚の画像に対して適用するのではなく、画像集合に対して適用するようだ。なので入力は一枚の画像xではなく、X_trainなど画像集合を渡す必要がある。また、ZCA白色化を使うときはdatagen.fit()を使ってあらかじめ統計量を計算しておく必要があるので注意。試しにCIFAR-10の訓練データ画像に対してZCA白色化を適用し、適用後の画像がどうなるか調べた。

img_rows, img_cols, img_channels = 32, 32, 3
batch_size = 16
nb_classes = 10

# CIFAR-10データをロード
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# 画素値を0-1に変換
X_train = X_train.astype('float32')
X_train /= 255.0

draw(X_train[0:batch_size], 'zca_whitening_before.png')

# データ拡張
datagen = ImageDataGenerator(zca_whitening=True)

datagen.fit(X_train)
g = datagen.flow(X_train, y_train, batch_size, shuffle=False)
X_batch, y_batch = g.next()
print(X_batch.shape)
print(y_batch.shape)

draw(X_batch, 'zca_whitening_after.png')

適用前: f:id:aidiary:20161212220750p:plain:w500

適用後: f:id:aidiary:20161212220801p:plain:w500

訓練データに加えてこれらのデータ拡張で生成した画像を畳み込みニューラルネットに食わせると精度向上が期待できるようだ。長くなったので畳み込みニューラルネットワークとデータ拡張を組み合わせた実験は次回にまわそう。

参考