TensorFlow + Kerasでフレンズ識別する - その2: 簡単なCNNを使った学習編

  • 2
    いいね
  • 0
    コメント

学習データが用意できたら、学習を行う。

Kerasを使った画像分類についてはBuilding powerful image classification models using very little dataがとてもわかりやすく、こちらに従って進めていった。

ImageDataGeneratorを使った読み込み

学習用/テスト用の画像データの読み込みにはImageDataGeneratorを使用する。

train_datagen = ImageDataGenerator(rescale=1./255, 
                                   zoom_range=0.2, 
                                   horizontal_flip=True, 
                                   rotation_range=5)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(image_size, image_size),
        batch_size=batch_size,
        class_mode="categorical")
test_generator = test_datagen.flow_from_directory(
        test_dir,
        target_size=(image_size, image_size),
        batch_size=batch_size,
        class_mode="categorical")

それぞれtrain_dir(./data/train)とtest_dir(./data/test)で指定したディレクトリから、データを読み込む。

まず、下のtrain_generatortest_generatorの方から見て行く。flow_from_directoryはその名の通り、指定したディレクトリから画像を取り出していってくれる。

画像一式はtarget_sizeで指定した大きさに自動的にリサイズされて読み込まれる。ここでは、横image_size * 縦image_sizeのサイズになるように指定している。image_sizeは後述するニューラルネットワークの入力層のサイズになる。image_sizeが小さすぎると判定の精度が下がり、大きすぎると学習がうまくいかなかったり、処理が重くなりすぎてしまう。今回のフレンズ識別器では128にしているが、ここは扱う内容とマシンスペックなどに合わせて調整する。

batch_sizeはミニバッチ学習に使われるバッチのサイズ。これも値は任意に調整すれば良いが、テスト用データの枚数を割り切れる数にしないとおかしなことになる可能性があるので注意する。

class_modeは学習に使われる「正解ラベル」をどのように生成するか、に影響する。チュートリアル記事は「犬か猫か」の2値(0か1)の判定になるため、binaryを指定しているが、今回のように複数クラスの分類の場合はcategoricalを指定する。categoricalを指定すると、カテゴリ(ディレクトリ)ごとに正解フラグ(1)がたったone-hotベクトルが生成される。(one-hotベクトルについては、TensorFlowのMNISTのチュートリアルに詳しい説明がある)

今回はカラーで学習/識別させたいのでその機能は使っていないが、color_modeという設定を使うとグレースケール化して読み込むこともできる。

さて、ImageDataGeneratorにはデータオーギュメンテーションのための機能が用意されている。画像の読み込みの際に、ランダムに回転させたり傾けたり引き延ばしたりする機能で、これを使うと過剰適合を防いだり、ロバスト性を改善する(入力画像がちょっと傾いてたり偏っていても判定できる)効果があると言われている。

学習に使うデータを生成するためのtrain_datagenでは、この機能を使って引き延ばし(zoom_range)や左右反転(horizontal_flip)、回転(rotation_range)を行いながら画像を読み込んで行くようになっている。今回は使わないが、vertical_flipshear_rangeなど、他にも色々データオーギュメンテーションのための機能は用意されているので、入力画像の性質に合わせて利用すると良い。

なお、rescaleは正規化のための仕組みで、RGB(0〜255)で読み込まれた各画素のRGB値を0.0〜1.0の間に収まるように正規化している。

本当に便利だね、ImageDataGenerator。

ImageDataGenerator

モデルの設定

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(image_size, image_size, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(units=num_categories, activation="sigmoid"))

adam = Adam(lr=1e-5)
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])

ここで使っているモデルは、入力層と出力層を除いて基本的にチュートリアルの内容そのままとなっている。

入力層は、チュートリアルの方では(3, 150, 150)となっているが、最新のTensorFlow + Kerasの組み合わせだとこれでは動かない。調べていくと、どうやらKerasはもともとTensorFlowではなくTheanoという別のライブラリをバックエンドに使って動くように設計されていたらしく、TheanoのConvolutionレイヤーとTensorFlowのConvolutionレイヤーで配列の並びに違いがあることが原因のようだ。(おそらくこの記事はTheanoを使う時代に書かれた)。TensorFlowでは(横, 縦, チャンネル数(RGB))の順番で指定してやるのが正しいので、(image_size, image_size, 3)を指定している。

出力層はカテゴリ数分のsigmoidとした。通常、カテゴリ分類ではsoftmaxを使うことが多いが、softmaxを使うと「どの特徴にも当てはまらない」ものも高い数字が出力されてしまう可能性があり、対象外のフレンズ(のけもの)の画像が入って来たときにおかしなことになってしまう。

OptimizerにはAdamを利用。これも記事ではrmspropを使っているが、特に理由がないのでより性能が良いと言われているAdamを利用している。サンプルが少ないせいか、モデルとマッチしないせいか、学習が発散する傾向にあったので、学習レートは若干低めの1e-5を指定した。

損失関数はcategorical_crossentropy。これも記事中では2値の判定のためbinary_crossentropyが使われていたが、今回は複数カテゴリの分類なので、複数カテゴリ用のcategorical_crossentropyを使う。

学習

checkpoint_cb = ModelCheckpoint("snapshot/{epoch:03d}-{val_loss:.5f}.hdf5", save_best_only=True)

model.fit_generator(
        train_generator,
        steps_per_epoch=num_train_images // batch_size,
        epochs=200,
        validation_data=test_generator,
        validation_steps=num_test_images // batch_size,
        callbacks=[checkpoint_cb])

200エポックほど学習を行う。

本質的ではない割に長いコードになるので省略したが、学習用の画像の枚数とテスト用のデータの枚数をnum_train_imagesnum_test_imagesにセットしている。ImageDataGeneratorにこの値を自動取得する機能が欲しいのだが、なぜか用意されていない。ここだけ不便。

良い結果が得られたら、その時点のスナップショット(ModelCheckpoint)をsnapshotディレクトリに保存するようにしている。save_best_onlyを指定すると、損失関数のスコア(val_loss)が下がった際に、その時点のモデルのスナップショットをhdf5形式で保存してくれる。(指定しないと、全てのエポックのスナップショットが保存される)

なおあとで、このhdf5を使ってモデルを復元することができるので、分類時にはスコアの良いスナップショットを選んで利用することになる。

学習完了

学習が完了したら、次はこの学習済みモデルを使って未学習の入力画像を判定する仕組みを作ってみる。

TensorFlow + Kerasでフレンズ識別する - その3: 分類編