fast.ai の Practical Deep Learning for Coders の week2 の課題で、 勉強の理解のために、スクラッチで教えた内容を自分で実装してみてねと書かれていたので、やってみた。

はじめに

Practical Deep Learning for Coders では、 Kaggle の Compatition Dogs vs. Cats Redux| Kaggle に参加することが課題に課せられるのだが、 week1 でいきなり、上位 50% に入ってねという課題が出される。

無理だよ!と思ったのだけれども、week2 で上位 50%に入るための種明かしをしてくれる。 それが、vgg16 モデルを使った fine-tuning による転移学習。これについては、以下の記事がとても参考になった。

課題をスクラッチではなく(できないので)、与えられたコードコピペしまくりでなんとか実装してみた。元ネタこれ。

今回の課題の jupyter notebook は github に あるので、ここではコードを抜粋して載せる。

実装の抜粋

Rearrange image files into their respective directories

まずは、kaggle からデータを持ってきて、以下のようなディレクトクリ構造をつくる。 分類クラスごとにサブディレクトリ(dogs, cats, unknown)を作ることによって、 Keras の ImageDataGenerator がうまいこと処理してくれる。

├── test
│   └── unknown
├── train
│   ├── cats
│   └── dogs
└── valid
    ├── cats
    └── dogs

Finetune and Train model

VGG モデルを作成する。

def ConvBlock(layers, model, filters):
    for i in range(layers):
        model.add(ZeroPadding2D((1,1)))
        model.add(Convolution2D(filters, 3, 3, activation='relu'))
    model.add(MaxPooling2D((2,2), strides=(2,2)))

def FCBlock(model):
    model.add(Dense(4096, activation='relu'))
    model.add(Dropout(0.5))
# Mean of each channel as provided by VGG researchers
vgg_mean = np.array([123.68, 116.779, 103.939]).reshape((3,1,1))

def vgg_preprocess(x):
    x = x - vgg_mean     # subtract mean
    return x[:, ::-1]    # reverse axis bgr->rgb

def VGG_16():
    model = Sequential()
    model.add(Lambda(vgg_preprocess, input_shape=(3,224,224)))
    ConvBlock(2, model, 64)
    ConvBlock(2, model, 128)
    ConvBlock(3, model, 256)
    ConvBlock(3, model, 512)
    ConvBlock(3, model, 512)
    model.add(Flatten())
    FCBlock(model)
    FCBlock(model)
    model.add(Dense(1000, activation='softmax'))
    return model


def finetune(model, num_classes):
    # remove last layer
    model.pop()
    # set all layers untrainable.
    for layer in model.layers: layer.trainable=False
    # add new layer
    model.add(Dense(num_classes, activation='softmax'))
    model.compile(optimizer=Adam(lr=0.001),
                loss='categorical_crossentropy', metrics=['accuracy'])
    return model

def get_batches(path, dirname, gen=image.ImageDataGenerator(), shuffle=True,
                batch_size=64, class_mode='categorical'):
    return gen.flow_from_directory(path+dirname, target_size=(224,224),
                class_mode=class_mode, shuffle=shuffle, batch_size=batch_size)

そして、学習済みの重みを fast.ai のサーバから手に入れる。

wget https://files.fast.ai/models/vgg16.h5

ここ からもダウンロードできる。

なお、keras1.2.0 では、ここによると、 VGG16 モデルが keras.applications.vgg16 モジュールに実装されているらしい。

つまり、自分のやり方は古いということだ!!

model = VGG_16()

# load pre-trained weights!!!
model.load_weights('vgg16.h5')

# remove last layer and add new layer
ftmodel = finetune(model, 2)

print ftmodel.summary() で 最後の層に出力が 2 のレイヤが追加されたことを確認する。

dense_4 (Dense)                  (None, 2)             8194        dropout_2[0][0]                  

追加した層を犬猫判定ように調整する。

batch_size=64
path = DATA_HOME_DIR

batches = get_batches(path,'train', batch_size=batch_size)
val_batches = get_batches(path,'valid', batch_size=batch_size)

# train finetuned model(only last layer)
no_of_epochs=1

for epoch in range(no_of_epochs):
    print "Running epoch: %d" % epoch
    ftmodel.fit_generator(batches, samples_per_epoch=batches.nb_sample, nb_epoch=1,
                validation_data=val_batches, nb_val_samples=val_batches.nb_sample)
    latest_weights_filename = 'ft%d.h5' % epoch
    ftmodel.save_weights(latest_weights_filename)

Generate predictions

test データから犬猫の確率を推測。preds には[猫,犬]とデータが入っている。

test_batches = get_batches(path, 'test', batch_size=2*batch_size, class_mode=None)
preds = ftmodel.predict_generator(test_batches, test_batches.nb_sample)