Tensorflowで転移学習



# 初めに
Tensorflowで転移学習を利用しようと思い検索をかけたところ，Kerasを使った記事ばかり出てきた．KerasはTensorflowバックエンドなので間違いではないが，個人的にKerasだとTensorflowのソースコードとの互換性やらデータの受け渡しやらで拡張性が薄く，個人的に都合が悪いのでできるだけTensorflowのみで行いたい．そこで資料がなくかなり悩んだので記録しておく．今回の記事は以下のリンクのものを参考にさせていただいた．

[Use Keras Pretrained Models With Tensorflow](http://zachmoshe.com/2017/11/11/use-keras-models-with-tf.html)

今回作成したソースコードの全体像は[こちら](https://github.com/sey323/tf-fineturning)参照

# TensorflowからKerasAPIの利用
結論から言うとTensorflowのモジュールのみで学習済みモデルを読み込むことはできない．そのため学習済みモデルを利用するにはKerasのAPIをTensorflowから読み込むことになる．Tensorflowにはkerasのアプリケーションを呼ぶクラス`tf.keras.applications`があるので，こちらを利用して学習済みモデルを呼び出す．どうやら[こちら](https://github.com/tensorflow/tensorflow/tree/r1.8/tensorflow/python/keras/_impl/keras/applications)にあるネットワークは全て利用できそう．

利用できそうなネットワークは以下の通り(2018/11/17現在)．

- densenet
- imagenet utils
- inception resnet v2
- inception v3
- mobilenet
- nasnet
- resnet50
- vgg16
- vgg19
- xception

例えばこの中の`VGG19`のモデルをTensorflow上で利用する場合は以下のように呼び出す．

```python
vgg19 = tf.keras.applications.VGG19(include_top=True,
                                    weights='imagenet',
                                    input_tensor=None,
                                    input_shape=None,
                                    pooling=None,
                                    classes=1000)
```

それぞれのオプションの意味を[こちら](https://github.com/tensorflow/tensorflow/blob/r1.8/tensorflow/python/keras/_impl/keras/applications/vgg19.py#L63)を参考にしてまとめた．

- `include_top`  
入力層を含めるかどうか，imagenetの重みをダウンロードした場合は，入力が[224,224,3]に固定される．転移学習などで独自の画像を学習したい時は画像サイズに合わせてTrue or Falseを選択．
- `weights`  
imagenet or None or モデルのパスを選択して入力する．imagenetの学習済みモデルを利用したい場合はimagenetを指定するとPCにダウンロードされる．
- `input_shape`  
`include_top = False`の時のみ必要．入力画像の`(x,y,チャネル数)`．`weights`にimagenetを指定した場合，チャネルが3に固定されるなど，読み込むモデルに応じて制限がある場合がある．
- `input_tensor`  
ネットワークに入力するデータ．
- `pooling`  
`include_top`がFalseの時のみ指定可能．最後の特徴抽出の層のmax_poolingの手法を以下の3つから選択できる．
  - `None`  
  通常のmaxpooling，出力は4次元のTensor
  - `avg`  
  Global average pooling，出力は2次元のTensor
  - `max`  
  Global max pooling，出力は2次元のTensor
- `classes`  
`include_top`がTrueの時のみ指定可能．読み込むウェイトファイルのクラス分類数．ImageNetの時は1000．

これにより呼び出した学習済みモデルの出力層を利用することで転移学習行える。，

## 転移学習の例
今回は，VGG19のimagenetを学習したモデルを用いて転移学習を行う．imagenetを学習した重みを用いて特徴抽出を行い，FC層のみ自前で作成し学習を行う．VGG19の事前学習済みモデルをオプションに従って以下のように修正した．

```python:./model.py
vgg19 = tf.keras.applications.VGG19(include_top=False,
                                    weights='imagenet',
                                    input_shape=(self.input_size[0], self.input_size[1], self.channel),
                                    input_tensor=input,
                                    pooling=None)
```

Tensorflowの学習においてパラメータを更新たくない場合，レイヤーの`trainable = False`に設定することで学習中にパラメータを更新しなくなる．転移学習を行う場合は`trainable = True`にすれば良い．今回はVGG19を転移学習の特徴抽出器として用いるため`trainable = False`を適応する．

```python:./model.py
for vgg_layer in vgg19.layers:
  vgg_layer.trainable = False

vgg19_out = tf.identity(vgg19.layers[-1].output, name='output')
```

ここで得た`vgg19_out`が抽出された特徴量となる．この特徴量を利用し，全結合層のみ学習を行うモデルを作成する．最終的なVGG19の転移学習を行うモデル構築の部分のソースコードは以下の通り．

```python:./model.py
    def vgg19(self , input , reuse = False , freeze = True,name =''):
        '''
        VGG19
        '''
        print('[SETUPMODEL]\tVGG19')
        with tf.variable_scope("VGG19" + name , reuse=reuse) as scope:
            if reuse: scope.reuse_variables()
            with tf.variable_scope("VGG19"):
                vgg19     = tf.keras.applications.VGG19(include_top=False,
                                                        weights='imagenet',
                                                        input_shape=(self.input_size[0], self.input_size[1], self.channel),
                                                        input_tensor=input
                                                        )
                if freeze:# 下位層のみ学習する場合
                    for vgg_layer in vgg19.layers:
                        vgg_layer.trainable = False
                
                vgg19_out = tf.identity(vgg19.layers[-1].output, name='output')

            '''
            自作でFC層のみ学習する．
            '''
            with tf.variable_scope("FC{0}".format(1)):
                flat       = self.flatten(vgg19_out,self.nBatch)
                fc1 = self.fc( flat , 4096 , "1",BatchNorm = False)
                fc2 = self.fc( fc1 ,  4096 , "2",BatchNorm = False)
                fc3 = self.fc( fc2 ,  1000 , "3",BatchNorm = False)
                output = self.fc( fc3 , self.labelSize , "out",BatchNorm = False)

        return output
```

# 実行
ソースコードの実行は以下のコマンドで行う．

```sh:
python fit.py --img_path={実行したいフォルダのパス} \
              --train_num={学習に用いる画像の枚数} \
              --gray={グレースケールにするかどうか(True or False)} \
              --test_num={テスト用の画像の枚数} \
              --learning_rate={学習率} \
              --batch_size={minibatchサイズ} \ 
              --max_epoch={最大学習数}
```

# 終わりに
正直資料がなさすぎて[こちら](http://zachmoshe.com/2017/11/11/use-keras-models-with-tf.html)の方の記事がなかったらわからなかった．
[公式](https://www.tensorflow.org/api_docs/python/tf/keras/applications)の`tf.keras.applications`のドキュメントもかなり薄いので，Tensorflow2.0で削除されるのかもしれない．