kerasのpre-traindedモデルにもあるVGG16をkerasで実装しました。
単純にVGG16を使うだけならpre-traindedモデルを使えばいいのですが、自分でネットワーク構造をいじりたいときに不便+実装の勉強がしたかったので実装してみました。
VGG16とは
VGGチームがILSVRC-2014で使用し、localisation and classification tasksで1位と2位を取ったモデルです。論文がarXivで公開されています。
VGG16の他にもレイヤー数が多いVGG19というのがあり、論文のTable 1のDとEがそれぞれ該当します。また名前の16と19という数字は重みがある(つまりConvとFC)レイヤの数に由来している様です。 VGG16のほうがVGG19よりよく使われていますが、これはVGG16と19の性能がそれほど変わらないのでそれならレイヤー数が少ない方で良いじゃん、という理由からだと思われます。
実装と学習・評価
Keras 2.1.5のtensorflow 1.7.0バックエンドでJupyter上で実装しました。
参考にしたのは原論文と本家のconfigファイル(prototxt)です。
本家との違いは以下の3点です。
- データはImageNetの代わりにCIFAR10を用いた
- 32x32のcifar10の画像を224x224にリサイズして入力するとメモリが足りないのかjupyterのkernelが死ぬので、最初のレイヤーでon-the-flyにアップスケールした
- cifar10の画像はサイズが揃っているので、croppingはしなかった
まず必要なものをimportし、cifar10のデータをロードします。
import keras from keras.layers import Conv2D, MaxPooling2D, Lambda, Input, Dense, Flatten, BatchNormalization from keras.models import Model from keras.layers.core import Dropout from keras import optimizers import tensorflow as tf from keras.callbacks import ReduceLROnPlateau,TensorBoard from sklearn.metrics import confusion_matrix from sklearn.metrics import classification_report from sklearn.preprocessing import OneHotEncoder from keras.datasets import cifar10 import cv2 # import gc import numpy as np print(tf.__version__) print(keras.__version__) # Prepare data (x_train, y_train), (x_test, y_test) = cifar10.load_data() enc = OneHotEncoder() y_train = enc.fit_transform(y_train).toarray() y_test = enc.fit_transform(y_test).toarray()
モデル
次にモデルを定義しましょう。
# inputs = Input(shape=(224, 224, 3)) inputs = Input(shape=(32, 32, 3)) # Due to memory limitation, images will resized on-the-fly. x = Lambda(lambda image: tf.image.resize_images(image, (224, 224)))(inputs) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(inputs) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block1_pool')(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block2_pool')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block3_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block4_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block5_pool')(x) flattened = Flatten(name='flatten')(x) x = Dense(4096, activation='relu', name='fc1')(flattened) x = Dropout(0.5, name='dropout1')(x) x = Dense(4096, activation='relu', name='fc2')(x) x = Dropout(0.5, name='dropout2')(x) # CIFAR10は10クラスなので出力の数が違う predictions = Dense(10, activation='softmax', name='predictions')(x) BATCH_SIZE = 256 sgd = optimizers.SGD(lr=0.01, momentum=0.9, decay=5e-4)#, nesterov=False) model = Model(inputs=inputs, outputs=predictions) model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])
折角なのでモデルのsummaryを見ておきましょう。
model.summary()
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) (None, 32, 32, 3) 0 _________________________________________________________________ block1_conv1 (Conv2D) (None, 32, 32, 64) 1792 _________________________________________________________________ block1_conv2 (Conv2D) (None, 32, 32, 64) 36928 _________________________________________________________________ block1_pool (MaxPooling2D) (None, 16, 16, 64) 0 _________________________________________________________________ block2_conv1 (Conv2D) (None, 16, 16, 128) 73856 _________________________________________________________________ block2_conv2 (Conv2D) (None, 16, 16, 128) 147584 _________________________________________________________________ block2_pool (MaxPooling2D) (None, 8, 8, 128) 0 _________________________________________________________________ block3_conv1 (Conv2D) (None, 8, 8, 256) 295168 _________________________________________________________________ block3_conv2 (Conv2D) (None, 8, 8, 256) 590080 _________________________________________________________________ block3_conv3 (Conv2D) (None, 8, 8, 256) 590080 _________________________________________________________________ block3_pool (MaxPooling2D) (None, 4, 4, 256) 0 _________________________________________________________________ block4_conv1 (Conv2D) (None, 4, 4, 512) 1180160 _________________________________________________________________ block4_conv2 (Conv2D) (None, 4, 4, 512) 2359808 _________________________________________________________________ block4_conv3 (Conv2D) (None, 4, 4, 512) 2359808 _________________________________________________________________ block4_pool (MaxPooling2D) (None, 2, 2, 512) 0 _________________________________________________________________ block5_conv1 (Conv2D) (None, 2, 2, 512) 2359808 _________________________________________________________________ block5_conv2 (Conv2D) (None, 2, 2, 512) 2359808 _________________________________________________________________ block5_conv3 (Conv2D) (None, 2, 2, 512) 2359808 _________________________________________________________________ block5_pool (MaxPooling2D) (None, 1, 1, 512) 0 _________________________________________________________________ flatten (Flatten) (None, 512) 0 _________________________________________________________________ fc1 (Dense) (None, 4096) 2101248 _________________________________________________________________ dropout1 (Dropout) (None, 4096) 0 _________________________________________________________________ fc2 (Dense) (None, 4096) 16781312 _________________________________________________________________ dropout2 (Dropout) (None, 4096) 0 _________________________________________________________________ predictions (Dense) (None, 10) 40970 ================================================================= Total params: 33,638,218 Trainable params: 33,638,218 Non-trainable params: 0 _________________________________________________________________
学習
それでは学習をさせてみましょう。エポック数は原論文で最終的に回した74を指定しました。学習はTesla K80上で行い、59min 24sかかりました。 本来はImageNetでやるべきなんでしょうし、画像の蒐集もある程度は行ったのですが、学習時間と手間を考えてCIFAR10でテストを行いました。CIFAR10は32*32のサイズなので最初のレイヤーにリサイズするレイヤーを追加して学習させます。
%%time # Train rlop = ReduceLROnPlateau(monitor='val_acc', factor=0.1, patience=5, verbose=1, mode='auto', epsilon=0.0001, cooldown=0, min_lr=0.00001) model.fit(x_train, y_train, batch_size=BATCH_SIZE, epochs=74, verbose=1, callbacks=[rlop], validation_data=(x_test, y_test))
評価
学習が終わったら次は評価です。
y_pred = model.predict(x_test, verbose=1) print(confusion_matrix(np.argmax(y_test, 1), np.argmax(y_pred, 1))) print(classification_report(np.argmax(y_test, 1), np.argmax(y_pred, 1)))
10000/10000 [==============================] - 7s 663us/step [[816 17 33 19 15 4 6 12 57 21] [ 10 895 2 6 3 2 6 1 19 56] [ 54 3 678 53 71 49 59 19 6 8] [ 20 6 69 593 53 150 56 29 11 13] [ 17 3 51 58 739 26 44 58 3 1] [ 5 2 37 178 40 665 22 44 4 3] [ 6 2 30 67 29 12 838 7 5 4] [ 15 1 22 44 59 50 3 797 1 8] [ 53 20 9 22 8 2 6 2 865 13] [ 25 62 7 14 3 5 5 10 22 847]] precision recall f1-score support 0 0.80 0.82 0.81 1000 1 0.89 0.90 0.89 1000 2 0.72 0.68 0.70 1000 3 0.56 0.59 0.58 1000 4 0.72 0.74 0.73 1000 5 0.69 0.67 0.68 1000 6 0.80 0.84 0.82 1000 7 0.81 0.80 0.81 1000 8 0.87 0.86 0.87 1000 9 0.87 0.85 0.86 1000 avg / total 0.77 0.77 0.77 10000
結果を見ると3と5のクラスが当てられていないようですね。
スポンサーリンク
改良
今時(もう古い?)はBatchNormalizationを入れないと、ということでBatchNormalizationを入れたモデルを回してみました。自分で組むと簡単にネットワークをいじれていいですね。
モデル
各Conv. レイヤーのactivationの後にのみBatchNormalizationを入れました。Dropoutがあるところでは両方入れなくてもいいだろうということで入れていません。
# inputs = Input(shape=(224, 224, 3)) inputs = Input(shape=(32, 32, 3)) # Due to memory limitation, images will resized on-the-fly. x = Lambda(lambda image: tf.image.resize_images(image, (224, 224)))(inputs) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(inputs) x = BatchNormalization()(x) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block1_pool')(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x) x = BatchNormalization()(x) x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block2_pool')(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) x = BatchNormalization()(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) x = BatchNormalization()(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block3_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x) x = BatchNormalization()(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x) x = BatchNormalization()(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block4_pool')(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x) x = BatchNormalization()(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x) x = BatchNormalization()(x) x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block5_pool')(x) flattened = Flatten(name='flatten')(x) x = Dense(4096, activation='relu', name='fc1')(flattened) x = Dropout(0.5, name='dropout1')(x) x = Dense(4096, activation='relu', name='fc2')(x) x = Dropout(0.5, name='dropout2')(x) predictions = Dense(10, activation='softmax', name='predictions')(x)
学習と評価
結果は以下のとおりです。
10000/10000 [==============================] - 8s 764us/step [[847 10 33 18 11 4 3 7 42 25] [ 11 899 1 6 3 5 4 0 15 56] [ 52 4 697 49 72 52 42 23 3 6] [ 14 10 59 658 50 129 41 20 10 9] [ 15 4 63 39 779 17 31 44 4 4] [ 11 2 20 157 41 708 12 39 6 4] [ 9 2 37 49 22 22 849 3 3 4] [ 7 1 22 31 44 37 5 846 0 7] [ 47 19 9 10 4 3 1 2 891 14] [ 19 48 5 9 3 2 3 12 21 878]] precision recall f1-score support 0 0.82 0.85 0.83 1000 1 0.90 0.90 0.90 1000 2 0.74 0.70 0.72 1000 3 0.64 0.66 0.65 1000 4 0.76 0.78 0.77 1000 5 0.72 0.71 0.72 1000 6 0.86 0.85 0.85 1000 7 0.85 0.85 0.85 1000 8 0.90 0.89 0.89 1000 9 0.87 0.88 0.87 1000 avg / total 0.81 0.81 0.81 10000
全体のf1も上昇していて、クラス3と5も改善しています。やはりBatchNormalizationは強力ですね。
まとめ
今回はVGG16をkerasとtensorflowで実装しました、楽しかったので他のモデルでもやりたいですね。 ただImageNetを用意するのが大変そうなので、データは恐らくまたcifar10とかに為ると思います。 notebookは気が向いたら公開します。