PreTrainedなモデルで新たなSSD300を作る♬

前回の記事でVGG16ベースのPreTrained_SSD300を作成した。

そこでは、単に世にあるVGG16ライクなSSD300モデルの前段にVGG16をInput_layerとして採用した。
ここでは、その入力を最適化するとともにその学習手法を考える。
また、VGG16のみでなく、他のPreTrainedなモデルを利用して、同様なSSD300モデルを作成しようと思う。
あくまでも仮説だが、メリットは少なくとも以下の二つがあると思う。
①大量なカテゴリーに対する物体認識としての精度がある程度保証される
②学習時間を大幅に削減できる可能性がある
　少なくとも最初に物体認識でPre-Trainingする必要が無い

ということで、まず全体的な構造を見てみよう
###PreTrained_SSD300の構造
####1. SSDモデルの入力部分のコードは以下のとおり

```
# Block 1
input_shape = (input_shape[1], input_shape[0], 3)

input = Input(input_shape)
vgg16 = VGG16(input_shape=input_shape, include_top=False, weights='imagenet')
FeatureExtractor = Model(inputs=vgg16.input, outputs=vgg16.get_layer('block3_pool').output)

pool3 = FeatureExtractor(input)

# Block 4
conv4_1 = Conv2D(512, (3, 3),activation='relu',padding='same',name='conv4_1')(pool3)
```
####2. この部分のmodel.summary()は以下のとおり

```model.summary()
Layer (type)                    Output Shape         Param #     Connected to
=============================================================================================
input_2 (InputLayer)            (None, 300, 300, 3)  0
_____________________________________________________________________________________________
model_1 (Model)                 multiple             1735488     input_2[0][0]
_____________________________________________________________________________________________
conv4_1 (Conv2D)                (None, 37, 37, 512)  1180160     model_1[1][0]
_____________________________________________________________________________________________
```
####3. 普通のSSD300の対応するmodel.summary()は以下のとおり

```SSD300のmodel.summary()
Layer (type)                    Output Shape         Param #     Connected to
=============================================================================================
input_1 (InputLayer)            (None, 300, 300, 3)  0
_____________________________________________________________________________________________
conv1_1 (Conv2D)                (None, 300, 300, 64) 1792        input_1[0][0]
_____________________________________________________________________________________________
conv1_2 (Conv2D)                (None, 300, 300, 64) 36928       conv1_1[0][0]
_____________________________________________________________________________________________
pool1 (MaxPooling2D)            (None, 150, 150, 64) 0           conv1_2[0][0]
_____________________________________________________________________________________________
conv2_1 (Conv2D)                (None, 150, 150, 128 73856       pool1[0][0]
____________________________________________________________________________________________
conv2_2 (Conv2D)                (None, 150, 150, 128 147584      conv2_1[0][0]
_____________________________________________________________________________________________
pool2 (MaxPooling2D)            (None, 75, 75, 128)  0           conv2_2[0][0]
_____________________________________________________________________________________________
conv3_1 (Conv2D)                (None, 75, 75, 256)  295168      pool2[0][0]
_____________________________________________________________________________________________
conv3_2 (Conv2D)                (None, 75, 75, 256)  590080      conv3_1[0][0]
_____________________________________________________________________________________________
conv3_3 (Conv2D)                (None, 75, 75, 256)  590080      conv3_2[0][0]
_____________________________________________________________________________________________
pool3 (MaxPooling2D)            (None, 38, 38, 256)  0           conv3_3[0][0]
_____________________________________________________________________________________________
conv4_1 (Conv2D)                (None, 38, 38, 512)  1180160     pool3[0][0]
_____________________________________________________________________________________________
```
####4.VGG16.pyの構造と設計的考察
一つ目は、

```
conv4_1 (Conv2D)                (None, 37, 37, 512)  1180160    model_1[1][0]

conv4_1 (Conv2D)                (None, 38, 38, 512)  1180160     pool3[0][0]
```
の比較である。
わずかであるが、Tensorのサイズが異なる。PreTrainなモデルでは(None, 37, 37, 512)であり、通常のものは (None, 38, 38, 512)である。
どうせなら、ここは一致させておいた方が良い。
ということで、ここはあの１個まで調整できるUpsampling?である[Deconvolution](https://qiita.com/MuAuan/items/69dda8bd4013007d7b18)を使おう。
そのコードは以下のようなものになるが、次元はどうしようかな？
つまり、滑らかにつなげるなら254だし、元々のVGG16モデルを考えると512でも良さそう。
※こういうのは両方やってみていい方をとるべきなんだろね

```
conv4_0 = Conv2DTranspose(256, 2, 2, activation='relu',   border_mode='valid',　name='conv4_0')(pool3)
```

二つ目は以下のリンクを見てもらうと

```
FeatureExtractor = Model(inputs=vgg16.input, outputs=vgg16.get_layer('block3_pool').output)
```
この'block3_pool'の意味が分かると思う。この場合、この部分までのLayerを再利用している。それ以降のblock4,block5はSSDのモデルと被っているのでこの場合は、使っていない。
逆にいうと、物体認識としては半分しか使っていないから本来精度は出ない。
だから、実験的な意味ではblock5までしっかり使おうよというのがあると思う。
【参考】
[keras / keras / applications / vgg16.py](https://github.com/keras-team/keras/blob/master/keras/applications/vgg16.py)

三つめは、とにかく

```
conv4_1 (Conv2D)                (None, 38, 38, 512)  1180160    model_1[1][0]
```
になりさえすれば、上はどんなモデルが乗っていてもとりあえず動くということです。
そして、できればVGG16のようにFeature Blockの一部をDetection部分に入れて準備するのがいいのでしょう。
ここでは、複雑なこと考えたくないので（時間がかかりすぎるので）簡単に上に乗せるだけにしようと思います。

###簡単な４つのモデル
ここでは以下４つのモデルを作成してみました。
※以下はいずれも[物体認識でFineTuning](https://qiita.com/MuAuan/items/6c0e95ac7538ddc6c19a)の記事と[コーディング](https://github.com/MuAuan/DL-FineTuning/blob/master/akb_VGG16.py)したモデルです。
今回は上記の考察を考慮して、以下のようなモデルになると思います。
４つのモデルの変更箇所を以下に記載します。
[ssd300VGG16_alt.py](https://github.com/MuAuan/SSD/blob/master/FineTunedSSD/ssd300VGG16_alt.py)

```
# Block 1 
input_shape = (input_shape[1], input_shape[0], 3) 
input = Input(input_shape) 

vgg16 = VGG16(input_shape=input_shape, include_top=False, weights='imagenet') 
FeatureExtractor = Model(inputs=vgg16.input, outputs=vgg16.get_layer('block3_pool').output) 

pool3 = FeatureExtractor(input) 
conv4_0 = Conv2DTranspose(512, (2, 2), name='conv4_0', activation='relu', border_mode='valid')(pool3) 
```
[ssd300VGG19_alt.py](https://github.com/MuAuan/SSD/blob/master/FineTunedSSD/ssd300ResNet50_alt.py)

```
vgg19 = VGG19(input_shape=input_shape, include_top=False, weights='imagenet') 
FeatureExtractor = Model(inputs=vgg19.input, outputs=vgg19.get_layer('block3_pool').output) 
```

[ssd300ResNet53_alt.py](https://github.com/MuAuan/SSD/blob/master/FineTunedSSD/ssd300ResNet50_alt.py)

```
resnet50 = ResNet50(input_shape=input_shape,include_top=False, weights='imagenet')
FeatureExtractor = Model(inputs=resnet50.input, outputs=resnet50.get_layer('add_7').output)
```
[ssd300InceptionV3_alt.py](https://github.com/MuAuan/SSD/blob/master/FineTunedSSD/ssd300_inceptionV3_alt.py)
上記と比較すると、出力が異なるのでDecovolutionが異なりますが、基本はほぼ同じです。

```
inceptionV3 = InceptionV3(input_shape=input_shape, include_top=False, weights='imagenet') 
FeatureExtractor = Model(inputs=inceptionV3.input, outputs=inceptionV3.get_layer('mixed2').output)

pool3 = FeatureExtractor(input)
conv4_0 = Conv2DTranspose(512, (4, 4), name='conv4_inceptionv3', activation='relu', border_mode='valid')(pool3) #for inceptionV3
```
###学習
>・物体認識部分はPreTrainだから、再学習は必要なく物体認識精度が保証される
>>「再学習は必要ない」は、どうも思惑が外れた

つまり、PreTrain部分を固定して学習しようとすると、非常に小さなaccからスタートして一向に上がってこないという現象が発生。その結果、やはり全部を学習するということとした。
それでも、PreTrain部分が一定のパラメータになっているので、学習は進みやすいように感じる。
比較のために、SSD300_smallなるもの：これは以前「[ぶっちゃけ変なモデル。。。^^;](https://qiita.com/MuAuan/items/532b80f19e6f0124c47d)」でベースにした、比較的小さいけど、検出精度の高いVGG16ライクなモデルを使って学習してみた。
※これ、実はオリジナルのSSD300と比較するとconv3_3が無いことと、MaxPoolingをAveragePoolingを使っていること、そしてBatchNormalizationを使っていることがことなるが、ほぼ似たような構成だとわかる
[ssd300small_alt.py](https://github.com/MuAuan/SSD/blob/master/FineTunedSSD/ssd300small_alt.py)

>>「物体認識精度が保証される」も、どうも思惑が外れたようだ

よく見ると、PreTrainで利用しているネットワークが案外小さい部分しか使っていない。
これでは、全体としてパラメータフィッティングされているのだから、保証というところまでは言えないようだ。
もちろん、学習当初の発散みたいな現象はあまり（固定すると発生した）なかったが。

>・学習時間を大幅に削減できる可能性

上の書いたように、ほとんどPreTrainなWeightsの効果が今一つということで、結局全体を学習した方が精度もよく、早いという結論。
つまり、パラメータフィッティングはしないが、結局Tensorの計算を実施する必要がある。まあ、一応バックプロパゲーションの計算はその部分はしていないはずだが、それでも２０％程度の削減にしかなっていない。（1060で1200s⇒1000s位）

実は物体検出のPGを大きく物体認識部と検出部と考えれると、実はあるモデルで物体検出全体のパラメータが決まっていて、かつ入力部分の物体認識部をPreTrainなモデルに変更した場合、やり方によっては全くTraining無しに正しく物体検出してくれる可能性が大いにあると思うが、今のところそのアイディアを実現していない。
※Layer毎にパラメータを反映するということができるといいなぁ～
※```model.load_weights('weights_SSD300.hdf5', by_name=True)```でやれるのかと思ったら、Weights数が異なると怒られて、已矣哉！

###結果
合計５つのモデルのパラメータフィッティングを実施しているが、一番いいのがVGG19でvaccで５５％程度のところ、そのほかはやっと４５％に到達するかというところでこれはほんとに時間がかかる。
VGG19はよく見るとBlock3までのところは、VGG16と変わらないので、実際フィッティングできたとしてもあまり面白くない。
ということで、ここではInceptionV3とsmallの結果を示したいと思う。

まず、smallから、これはVGG16と似ていると思いましたが、45％程度でaccが飽和してしまって、上がりません。Base_IrなどのパラメータやそもそもAdamでなくてSGDとかでやれば変わるかもしれませんが、現状は以下の通りです。
![recognized_picture002__001.png](https://qiita-image-store.s3.amazonaws.com/0/233744/8fe71570-8c45-a42c-e23d-d55685946ae4.png)
![recognized_picture001__005.png](https://qiita-image-store.s3.amazonaws.com/0/233744/846ca245-a3b6-1df8-4c0f-d62ec1213079.png)
そこから、３０epoch（１０時間位）回すとどうにかこうにか（もうほとんど上がりませんが）５０．５％になりました。そして、上記よりは少しマシかな？？
![recognized_picture002__003.png](https://qiita-image-store.s3.amazonaws.com/0/233744/5db777ea-a050-a206-384e-f7d8353a0f49.png)
![recognized_picture001__005.png](https://qiita-image-store.s3.amazonaws.com/0/233744/c13e6631-2e60-0989-8261-1505d7b5161a.png)

一方、InceptionV3では50％超えたところ（５１％辺り）で、足踏みしていますが、もう少し上がるような気がしますが、１２時間以上回したので、このあたりで一度上げておきます。
![recognized_picture007.png](https://qiita-image-store.s3.amazonaws.com/0/233744/1d09c9c2-76e0-e88d-07a9-3d54ccd5c267.png)
![recognized_picture008.png](https://qiita-image-store.s3.amazonaws.com/0/233744/5cf44b3b-7e7f-750c-9abc-325316d85216.png)
![recognized_picture006.png](https://qiita-image-store.s3.amazonaws.com/0/233744/6c0b9a71-f799-9e82-0264-10e4d97f76fa.png)
こちらもさらに３５epoch（１０時間位）回すと、６４％超えしましたが、物体検出は以下のようにまだまだです。
![recognized_picture002__004.png](https://qiita-image-store.s3.amazonaws.com/0/233744/ec796077-6882-adce-64b7-517f997c4de9.png)
![recognized_picture001__005.png](https://qiita-image-store.s3.amazonaws.com/0/233744/4edb2542-1c7d-2262-bf3a-699b6730d732.png)


###まとめ
・まだまだ精度はいまいちだが、５つのモデルについてPretrainなモデルや既知のネットワークを利用したSSD300モデルを構築した。
・学習の仕方（既知のWeightの使い方）やネットワークの構築はまだまだ改良の余地がありそうである
・ｍAP　vs　fptについては計測したいと思う