はじめに

Vgg16 モデルによる転移学習で、kaggle で結構いい成績がでているので、 転移学習すごくない?と思っている今日この頃。

Vgg16 モデルを使って、kaggle の MNIST コンペ に挑戦したらどうなるだろうと思っていろいろ試してみた。 結論からいうと、無謀な挑戦で挫折したのだけれども、得たことをまとめて置く。

Vgg16 モデルには、Batch Normalization が使われていない

Batch Normalization は、overfitting を防ぎ、学習をスピードアップさせるよい方法なのだが、 Vgg16 モデルには、Batch Normalization が使われていない。

なぜか?それは、VGG16 モデルができたとき、Batch Normalization は存在しなかったから。

Fast AI での解決方法

fast.ai の講義では Lesson4,5 で、VGG に Batch Normalization を組み込む方法を紹介している。

以下の ipynb がそれ。

以下の処理で、Vgg16 モデルの最後の Convolution レイヤのインデックス値を調べて、 FC 層を取っ払う。ここでコールしている Vgg16 クラスは、独自関数(keras とは関係ない)ので注意。

vgg = Vgg16()
model=vgg.model
last_conv_idx = [i for i,l in enumerate(model.layers) if type(l) is Convolution2D][-1]
conv_layers = model.layers[:last_conv_idx+1]
conv_model = Sequential(conv_layers)

取っ払った層(30 層)のみで、学習をする。

conv_feat = conv_model.predict_generator(batches, batches.nb_sample)
conv_val_feat = conv_model.predict_generator(val_batches, val_batches.nb_sample)
conv_test_feat = conv_model.predict_generator(test_batches, test_batches.nb_sample)

以下のような、BatchNorm と Dropout を含む層を作成する。

def get_bn_layers(p):
    return [
        MaxPooling2D(input_shape=conv_layers[-1].output_shape[1:]),
        Flatten(),
        Dropout(p/2),
        Dense(128, activation='relu'),
        BatchNormalization(),
        Dropout(p/2),
        Dense(128, activation='relu'),
        BatchNormalization(),
        Dropout(p),
        Dense(10, activation='softmax')
        ]
p=0.8
bn_model = Sequential(get_bn_layers(p))
bn_model.compile(Adam(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

この層をつかって、前に学習した conv_feat から学習を再開する。

bn_model.fit(conv_feat, trn_labels, batch_size=batch_size, nb_epoch=1, 
             validation_data=(conv_val_feat, val_labels))

うーむ、うなるような職人技だ。

Keras では、オプション指定で可能

Keras では、VGG16 がデフォルトで使うことが出来る。以下の記事が詳しい。

VGG16 というクラスが用意されている。

このクラスをつかうときに、include_top オプションを False に指定することで、 ネットワークの出力層側にある 3 つの全結合層を含まないように指定できるのだ。

このオプションをつかうと以下のように組み込むことが出来る。

def pretrained_model(img_shape, num_classes):
    model_vgg16_conv = VGG16(weights='imagenet', include_top=False)
    #model_vgg16_conv.summary()

    #Create your own input format
    keras_input = Input(shape=img_shape, name = 'image_input')

    #Use the generated model 
    output_vgg16_conv = model_vgg16_conv(keras_input)

    #Add the fully-connected layers 
    x = Flatten(name='flatten')(output_vgg16_conv)
    x = Dropout(0.8)(x)
    x = Dense(256, activation=layer_type, name='fc1')(x)
    x = BatchNormalization()(x)
    x = Dropout(0.8)(x)
    x = Dense(256, activation=layer_type, name='fc2')(x)
    x = BatchNormalization()(x)
    x = Dropout(0.8)(x)
    x = Dense(num_classes, activation='softmax', name='predictions')(x)

    #Create your own model 
    pretrained_model = Model(inputs=keras_input, outputs=x)
    pretrained_model.compile(optimizer=Adam(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

    return pretrained_model

元にした情報元は以下のリンク

VGG16 モデルで画像の入力サイズを変更したい

VGG16 は デフォルトで 226x226 のサイズの画像を処理するように設計されているけれども、 上のソースを使うことで、input shape を変更できる。

よし、これで MNIST に挑戦するぞ!と思ったら、なんとサイズに制限があった!!

公式ドキュメントによると、48x48 以上でなければいけないのだ。

正確に 3 つの入力チャンネルをもつ必要があり,width と height は 48 以上にする必要があります

MNIST は 28x28 なので、使えない。痛恨!!

今回の記事でとちゅうまで書いた中途半端な ipynb は以下です。