読者です 読者をやめる 読者になる 読者になる

ニートの言葉

元ニートがやってみたこと・その過程で学んだこと・考えたこと・技術メモあたりを主に書いています。情報革命が起きた後に訪れるであろう「一億総ニート時代」の生き方を考え中です。

【ディープラーニング】少ないデータで効率よく学習させる方法:1から作るCNN編

人工知能

こんにちは、あんどう(@t_andou)です。 

前回から、少ないデータで効率よく学習させるfinetuningの記事を翻訳していますが、今回は全3回中の第2回です。

全3回の予定
  1. データの準備・データの水増し
  2. 1から小さな畳み込みニューラルネットワークを作ってみる(<-今回はここ)
  3. 学習済みネットワークを流用する
前回の記事はこちら 

andoo.hatenablog.com

前回は学習に使うデータの準備を行いました。
今回は小さな畳み込みニューラルネットワークを1から作って、前回準備したデータを学習させてみます。

小さなCNNを0から作る:40行のコードで80%の正解率

画像のクラス分類器にふさわしいツールは畳み込みニューラルネットワーク(以降CNN)です。それでは最初の一歩として、犬・猫のデータを学習させてみましょう。

今回は少ない画像で学習を進めますので、一番の懸念点は過学習でしょう。

過学習とは、少量の学習データに特化し過ぎ、一般化できていないことで、新しく入ってくるデータに対応できない状態になることです。

例えば、一般的な人であれば「木こり」と「船乗り」の画像を3枚ずつ見て、その次に一人だけ(船乗りの?)帽子をかぶった木こりの写真を見たらそれは何らかのサインだと考え始めるでしょう。「木こり」と「船乗り」の分類としてはモヤっとするはずです。(※よくわかりませんでした)

データの水増しは過学習に対する一つの方法ですが十分ではありません。なぜなら、我々の水増ししたデータはまだ元画像によく似ているからです。あなたが過学習対策として集中するべきなのはモデルのentropic capacity*1です。つまり、あなたのモデルがどのくらいの情報を格納できるのかということです。
大量の情報を格納できるモデルはより多くの特徴を参照できるので、精度が高くなります。しかし、それは同時に無意味な特徴の参照から開始するリスクがあるということでもあります。
一方で、少量の特徴だけ格納しているモデルは最も重要な特徴量にフォーカスすることができます。それらの特徴量は相関があり、より一般化するために正しいものでしょう。

entropic capacityを調節するには二つの方法があります。一つはハイパーパラメーターの選択です。例えば、レイヤーの数・各レイヤーのサイズなど。他には、L1やL2正則化のような重みの正則化を使う方法もあります。これはモデルの重みがより小さな値を取るようになっています。

今回は少しの層と各層にちょっとのフィルターから作られている非常に小さなCNNをデータの水増し・ドロップアウトと一緒に使います。ドロップアウトは同じパターンを二回見ることを防ぐので、過学習を防ぐ手助けになります。つまり、ドロップアウトはデータ水増しと同じような機能・効果を持つのです。(ドロップアウトもデータ水増しも共に、分類中の特徴データを解析する中で発生した(識別に)無関係な共通点を排除してくれる傾向があると言えます)

 

下のコードは、活性化関数(ReLU)とMaxプーリング層、畳み込み層を三つずつ重ねた、シンプルなモデルです。これが最初に試すモデルになります。

これはYann LeCunが1990年代に提唱した画像分類のアーキテクチャと非常によく似ています。(ReLUを使うところは別)

全部のコードはこちらからどうぞ。

from keras.models import Sequential
from keras.layers import Convolution2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense

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

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

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

# the model so far outputs 3D feature maps (height, width, features)

最後に2つの全結合層をくっつけました。ネットワークの最後は一つのユニットで、シグモイド関数という活性化関数で終わらせます。これは2値分類にはぴったりの関数です。また、2値分類をするために、binary_crossentropyという損失関数も使います。

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

次にデータの準備をしましょう。それぞれのフォルダにあるJPGのファイルから直接、画像データ(とラベル)のバッチ(=1度に処理する塊のこと)を生成するために.flow_from_directory()を使います。

# this is the augmentation configuration we will use for training
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

# this is the augmentation configuration we will use for testing:
# only rescaling
test_datagen = ImageDataGenerator(rescale=1./255)

# this is a generator that will read pictures found in
# subfolers of 'data/train', and indefinitely generate
# batches of augmented image data
train_generator = train_datagen.flow_from_directory(
        'data/train',  # this is the target directory
        target_size=(150, 150),  # all images will be resized to 150x150
        batch_size=32,
        class_mode='binary')  # since we use binary_crossentropy loss, we need binary labels

# this is a similar generator, for validation data
validation_generator = test_datagen.flow_from_directory(
        'data/validation',
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

これらのジェネレータは私たちのモデルを学習させるためにすぐに使えます。1EpochあたりGPUを使えば20~30秒、CPUであれば300~400秒くらいで出来ます。ですので、急いでない限り、CPUでも実行可能な速度ですね。

model.fit_generator(
        train_generator,
        samples_per_epoch=2000,
        nb_epoch=50,
        validation_data=validation_generator,
        nb_val_samples=800)
model.save_weights('first_try.h5')  # always save your weights after training or during training

このアプローチで50epochを学習させると検証の精度は79~81%になります。(50epochは任意の回数です。この時点ではモデルも小さく、積極的にドロップアウトを利用しているので、過学習を起こすことはあまりありません。)

つまり、Kaggleのコンペティションが始まった時点で言われていた「最先端」になったということになります。(しかも、特にレイヤーやハイパーパラメータの最適化の努力もしていない上に、たった8%のデータだけで)

実際、このモデルはKaggleのコンペティションで100位くらいのスコアを取るでしょう(215の参加者のうち)。おそらく、それより下の115の参加者はディープラーニングを使ってないのでしょう。

検証の精度の分散はかなり高いです。なぜなら、精度は分散が大きいmetricで、私たちは800しか検証データを利用していないからです。検証するのにいい方法はk-fold cross-validationを行うことでしょう。しかし、これは検証の時に毎回k modelsを学習させる必要があります。(※よくわかりませんでした)  


今回はCNNを一から作って学習までやってみました。

次回は学習済みモデルを流用することでより高精度な分類をやります。(一週間後くらいに公開します。)

元記事はこちら

Building powerful image classification models using very little data

*1:エントロピーの容量?どれくらいの種類のデータを持つかという意味でしょうか