DeepLearning
Keras
物体検出
FineTuning
転移学習

中間層や他のモデルのパラメータを再利用する♬

物体検出のパラメータがなかなか決まらない。。。
【参考】前回の記事物体検出で遊んでみた♬~独自学習やってみた~

そして、あの中途半端にFineTuningしているコード。。。
※全体に比べれば固定するパラメータの割合は極端に少ない

どうやら物体検出の場合、膨大なパラメータのうち、物体識別部分のパラメータは別途決めて、その後物体検出する部分のパラメータを決めるという二段階とか、三段階で決めるのが正しい作法ではないのかという論理的な結論

とはいえ、これまで全体のパラメータを再利用することはあったが、ブロック毎にパラメータを再利用した経験はなかった。。。
※もちろん、転移学習などではその方が効率的だとは思っていたが、。。。

ということで、まず初めに簡単な例としてCifar10によく使う簡単なCNNモデルで試してみた。

コード

コードは以下のサイトに置いた
MuAuan/PartiallySaveParams

やり方は、以下のKerasDocumentationに記載されている。
Keras FAQ: Kerasに関するよくある質問のページのモデルの重みのみのセーブ/ロードのところに以下のコード例が記載されている。

"""
Assuming the original model looks like this:
    model = Sequential()
    model.add(Dense(2, input_dim=3, name='dense_1'))
    model.add(Dense(3, name='dense_2'))
    ...
    model.save_weights(fname)
"""

# new model
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))  # will be loaded
model.add(Dense(10, name='new_dense'))  # will not be loaded

# load weights from first model; will only affect the first layer, dense_1.
model.load_weights(fname, by_name=True)

勘のいいひとはこれでわかると思うが、問題は
①ブロック名の付け方
②fnameとはブロック名なの?
③LoadとSaveの具体的な書き方
④epoch毎に保存する場合は??

とかとか、凡人は案外悩む。。。
ということで、以下のように記載して実施するとうまくいきました。

実際のコード

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',
                 input_shape=x_train.shape[1:],name='block_1_0'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3),name='block_1_1'))
model.add(Activation('relu'))
model.add(AveragePooling2D(pool_size=(2, 2),name='block_1_2'))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same',name='block_2_0'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3),name='block_2_1'))
model.add(Activation('relu'))
model.add(AveragePooling2D(pool_size=(2, 2),name='block_2_2'))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512, name='dense_1'))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, name='dense_2'))
model.add(Activation('softmax'))

ブロック名は一意につける必要があるようです。
※怒られました
そして、保存は以下のようにしました。

# save weights every epoch
model.save_weights("block_1_0_{0:03d}".format(j))

model.save_weights("block_1_1_{0:03d}".format(j))
model.save_weights("block_1_2_{0:03d}".format(j))
model.save_weights("block_2_0_{0:03d}".format(j))
model.save_weights("block_2_1_{0:03d}".format(j))
model.save_weights("block_2_2_{0:03d}".format(j))

もちろん、最初は後ろの{0:03d}やformat(j)は無しでやりましたが、あっても以下のload文でloadできました。

>>>ここ間違っていました。このweightファイルは一個一個すべてのLayerの重みを含んでいて、loadのときby_name=Trueをつけると同じ名前のLayer分はそこに反映するという意味のようです。つまり、この記事のようにたくさん保存する必要はないということです。もちろん、以下のloadも一個で十分です。
# load weights every block
model.load_weights("block_1_0_009", by_name=True)

model.load_weights("block_1_1_009", by_name=True)
model.load_weights("block_1_2_009", by_name=True)
model.load_weights("block_2_0_009", by_name=True)
model.load_weights("block_2_1_009", by_name=True)
model.load_weights("block_2_2_009", by_name=True)

まあ、全部のblockをロードするのだと、あんまり効果はありませんが、ここでこの物体認識で決めたパラメータを物体検出で利用できるということのメリットは非常に大きいと思います。
また、転移学習する場合やモデルを拡張する場合などもこの手法は有効だろうと思います。

結果

特に必要ないところですが、以下のとおりになりました。

bus id: 0000:01:00.0, compute capability: 6.1)
1562/1562 [==============================] - 22s 14ms/step - loss: 1.8531 - acc: 0.3138 - val_loss: 1.5597 - val_acc: 0.4267
Epoch 2/2
1562/1562 [==============================] - 21s 13ms/step - loss: 1.5882 - acc: 0.4202 - val_loss: 1.3804 - val_acc: 0.4996
。。。
Epoch 1/2
1562/1562 [==============================] - 20s 13ms/step - loss: 0.8239 - acc: 0.7211 - val_loss: 0.7415 - val_acc: 0.7537
Epoch 2/2
1562/1562 [==============================] - 21s 13ms/step - loss: 0.8208 - acc: 0.7218 - val_loss: 0.7989 - val_acc: 0.7285
Test loss: 0.7988903632164002
Test accuracy: 0.7285
。。。
10回回した次の初回は以下のとおり最初から大きなacc

Epoch 1/2
1562/1562 [==============================] - 21s 13ms/step - loss: 0.6804 - acc: 0.7648 - val_loss: 0.6137 - val_acc: 0.7889
Epoch 2/2
1562/1562 [==============================] - 21s 13ms/step - loss: 0.6737 - acc: 0.7671 - val_loss: 0.6309 - val_acc: 0.7851
Test loss: 0.630942894077301
Test accuracy: 0.7851

そして、最後の回は以下のとおり、最初から大きなaccになっています。

Epoch 1/2
1562/1562 [==============================] - 21s 14ms/step - loss: 0.7216 - acc: 0.7493 - val_loss: 0.6459 - val_acc: 0.7825
Epoch 2/2
1562/1562 [==============================] - 21s 13ms/step - loss: 0.6972 - acc: 0.7581 - val_loss: 0.6149 - val_acc: 0.7901
Test loss: 0.6148864235401154
Test accuracy: 0.7901

まとめ

・ブロック毎にパラメータを再利用できるようになった
・ブロック毎の対応はload時のby_name=Trueをつけると、名称から自動的に反映されるようである

課題

・物体検出で利用する場合、必要なサイズの学習データが大量に必要である
・物体検出のデータは、一画像に複数のアノテーションが付与されており、そのままでは利用できなさそうである