学習済みkerasモデルのレイヤーの流用

#はじめに
この記事は、自分の学習用メモです。
なにかお気付きの点がありましたら、ご指摘いただけますと幸いです。

今回の学習はこれ:
kerasで作成して学習させたモデルからレイヤーを取り出して再利用して、新しく作ったモデルに使うことができるでしょうか？
#学習済みのモデル
[このページ](https://qiita.com/yoho/items/e9a65b10ca510ab50a36) にあるモデルを使います。
変数名はmodelです。

```001.py
model.summary()

#Layer (type)                 Output Shape              Param #   
#=================================================================
#dense_1 (Dense)              (None, 100)               1100      
#_________________________________________________________________
#dense_2 (Dense)              (None, 100)               10100     
#_________________________________________________________________
#dense_3 (Dense)              (None, 40)                4040      
#_________________________________________________________________
#dense_4 (Dense)              (None, 20)                820       
#_________________________________________________________________
#dense_5 (Dense)              (None, 2)                 42        
#=================================================================
#Total params: 16,102
#Trainable params: 16,102
#Non-trainable params: 0
_________________________________________________________________
```
modelがどんな変数を持っているかをvars()で調べます。

```002.py
vars(model)
```
ここには示しませんが、modelの持つ変数がわかります。なかでも_layersという変数が、レイヤオブジェクトのリストを保持しているようです。

アクセスしてみます。

```003.py
model._layers
#見やすいように出力を改行で区切っています。
#[<keras.engine.input_layer.InputLayer object at 0x138144208>, 
#<keras.layers.core.Dense object at 0x13811af98>, 
#<keras.layers.core.Dense object at 0x138144320>, 
#<keras.layers.core.Dense object at 0x1381443c8>, 
#<keras.layers.core.Dense object at 0x138144518>, 
#<keras.layers.core.Dense object at 0x1381741d0>]
```
また、model.layersとしてもアクセスできますね。getterメソッドが定義されているのでしょうか？
この場合は、model._layersとした場合と比べると先頭のInput_Layerがないようです。

```004.py
model.layers
#見やすいように出力を改行で区切っています。
#[<keras.layers.core.Dense object at 0x13811af98>, 
#<keras.layers.core.Dense object at 0x138144320>, 
#<keras.layers.core.Dense object at 0x1381443c8>, 
#<keras.layers.core.Dense object at 0x138144518>, 
#keras.layers.core.Dense object at 0x1381741d0>]
```
#最後のレイヤーを調べてみる
一番パラメータが少ない、最後のレイヤーはどうなっているでしょうか？
見やすいようにpprintを使います。

```005.py

import pprint
pprint.pprint(vars(model.layers[4]))
#以下は出力。
"""
{'_built': True,
 '_inbound_nodes': [<keras.engine.base_layer.Node object at 0x138174e10>],
 '_initial_weights': None,
 '_losses': [],
 '_metrics': [],
 '_non_trainable_weights': [],
 '_outbound_nodes': [],
 '_per_input_losses': {},
 '_per_input_updates': {},
 '_trainable_weights': [<tf.Variable 'dense_5/kernel:0' shape=(20, 2) dtype=float32, numpy=
array([[-0.07533632,  0.20118327],
       [-0.17458896, -0.44313124],
       [ 0.4008763 ,  0.3295961 ],
       [-0.40597808, -0.02159814],
       [ 0.59269255, -0.15129048],
       [-0.14078082, -0.44002545],
       [ 0.18300773,  0.17778364],
       [ 0.3685053 , -0.36274177],
       [-0.28516215, -0.0659026 ],
       [ 0.45126018, -0.2892398 ],
       [ 0.19851999, -0.39362603],
       [ 0.2631754 ,  0.40239784],
       [ 0.08184562, -0.08194606],
       [-0.43493706,  0.18896711],
       [ 0.36158973,  0.20016526],
       [-0.05036243, -0.20633343],
       [-0.41589907,  0.57210416],
       [-0.10199612, -0.37373352],
       [ 0.30416492, -0.19923651],
       [ 0.02667725, -0.5090254 ]], dtype=float32)>,
                        <tf.Variable 'dense_5/bias:0' shape=(2,) dtype=float32, numpy=array([0.05854932, 0.07379959], dtype=float32)>],
 '_updates': [],
 'activation': <function linear at 0x1380400d0>,
 'activity_regularizer': None,
 'bias': <tf.Variable 'dense_5/bias:0' shape=(2,) dtype=float32, numpy=array([0.05854932, 0.07379959], dtype=float32)>,
 'bias_constraint': None,
 'bias_initializer': <keras.initializers.Zeros object at 0x138180400>,
 'bias_regularizer': None,
 'dtype': 'float32',
 'input_spec': InputSpec(min_ndim=2, axes={-1: 20}),
 'kernel': <tf.Variable 'dense_5/kernel:0' shape=(20, 2) dtype=float32, numpy=
array([[-0.07533632,  0.20118327],
       [-0.17458896, -0.44313124],
       [ 0.4008763 ,  0.3295961 ],
       [-0.40597808, -0.02159814],
       [ 0.59269255, -0.15129048],
       [-0.14078082, -0.44002545],
       [ 0.18300773,  0.17778364],
       [ 0.3685053 , -0.36274177],
       [-0.28516215, -0.0659026 ],
       [ 0.45126018, -0.2892398 ],
       [ 0.19851999, -0.39362603],
       [ 0.2631754 ,  0.40239784],
       [ 0.08184562, -0.08194606],
       [-0.43493706,  0.18896711],
       [ 0.36158973,  0.20016526],
       [-0.05036243, -0.20633343],
       [-0.41589907,  0.57210416],
       [-0.10199612, -0.37373352],
       [ 0.30416492, -0.19923651],
       [ 0.02667725, -0.5090254 ]], dtype=float32)>,
 'kernel_constraint': None,
 'kernel_initializer': <keras.initializers.VarianceScaling object at 0x138174b70>,
 'kernel_regularizer': None,
 'name': 'dense_5',
 'stateful': False,
 'supports_masking': True,
 'trainable': True,                  #######  ここ重要  #######
 'units': 2,
 'use_bias': True}
"""
```
最後のレイヤーは1つ前のレイヤーからの出力20個を受け取って、2個の出力をするので、ウエイトパラメーターの数は20 x 2=40 個、バイアスパラメーターが2個です。

よくわかりませんが、ウエイトパラメーターとして、同じものを2つ(_trainable_weights[0]とkernel)持っているようです。

ウエイトパラメータの実態はなんでしょうか？
_trainable_weights[0]をtype()で調べてみます。

```006.py
type( model.layers[4]._trainable_weights[0] )
#<class 'tensorflow.python.ops.resource_variable_ops.ResourceVariable'>
```
tensorflowのResourceVariableオブジェクトのようですが、ここではこれ以上調べません。

ウエイトとバイアスを格納したリストをget_weights()で取り出せるようです。
リストの要素数は2です。

```007.py
type(model.layers[4].get_weights())
#<class 'list'>
len(model.layers[4].get_weights())
#2
```
このリストの中身はndarrayです。
中身を見ておきます。

```008.py
type(model.layers[4].get_weights()[0])#リストの0番目の確認
#<class 'numpy.ndarray'>
model.layers[4].get_weights()[0]#リストの0番目
"""
array([[-0.07533632,  0.20118327],
       [-0.17458896, -0.44313124],
       [ 0.4008763 ,  0.3295961 ],
       [-0.40597808, -0.02159814],
       [ 0.59269255, -0.15129048],
       [-0.14078082, -0.44002545],
       [ 0.18300773,  0.17778364],
       [ 0.3685053 , -0.36274177],
       [-0.28516215, -0.0659026 ],
       [ 0.45126018, -0.2892398 ],
       [ 0.19851999, -0.39362603],
       [ 0.2631754 ,  0.40239784],
       [ 0.08184562, -0.08194606],
       [-0.43493706,  0.18896711],
       [ 0.36158973,  0.20016526],
       [-0.05036243, -0.20633343],
       [-0.41589907,  0.57210416],
       [-0.10199612, -0.37373352],
       [ 0.30416492, -0.19923651],
       [ 0.02667725, -0.5090254 ]], dtype=float32)
"""

type(model.layers[4].get_weights()[1])#リストの1番目の確認
#<class 'numpy.ndarray'>
model.layers[4].get_weights()[1]#リストの1番目
#array([0.05854932, 0.07379959], dtype=float32)
```

#レイヤーを取り出して、新しくモデルを作ってみる。
モデルを作る時に、どのタイミングでパラメーターが初期化されるのか、よくわかっていないので、モデルに組み込む時に初期化されてしまうかどうかチェックすることにします。

ここでは、モデルの5つのレイヤーのうち、最後のレイヤーを流用して、新しいモデルの2番目のレイヤーとすることにします。

流用するレイヤーは20個の入力を受け取るレイヤーなので、新しいモデルの入力レイヤーは20個の入力を受け付けるようにします。

```009.py
inputs = keras.layers.Input(shape=(20,))
x = model.layers[4](inputs)
model_new = keras.Model(inputs=inputs, outputs=x)
```
つながりました。

レイヤーが、先のモデルの5番目のレイヤーと同じレイヤーであるかと、
パラメーターが変わっていないかどうかチェックします。

```010.py
model_new.layers[1]
<keras.layers.core.Dense object at 0x1381741d0>#Objectのアドレスが同じ。
model_new.layers[1].get_weights()[0]#パラメーターが変わっていないかチェック-->変わっていない。
"""
array([[-0.07533632,  0.20118327],
       [-0.17458896, -0.44313124],
       [ 0.4008763 ,  0.3295961 ],
       [-0.40597808, -0.02159814],
       [ 0.59269255, -0.15129048],
       [-0.14078082, -0.44002545],
       [ 0.18300773,  0.17778364],
       [ 0.3685053 , -0.36274177],
       [-0.28516215, -0.0659026 ],
       [ 0.45126018, -0.2892398 ],
       [ 0.19851999, -0.39362603],
       [ 0.2631754 ,  0.40239784],
       [ 0.08184562, -0.08194606],
       [-0.43493706,  0.18896711],
       [ 0.36158973,  0.20016526],
       [-0.05036243, -0.20633343],
       [-0.41589907,  0.57210416],
       [-0.10199612, -0.37373352],
       [ 0.30416492, -0.19923651],
       [ 0.02667725, -0.5090254 ]], dtype=float32)
"""
```
このレイヤをvars()して調べてみたところ、
\_inbound_nodesをkeyとするリストの値だけが異なりました。

```011.py
pprint.pprint(model.layers[1]._inbound_nodes)
#[<keras.engine.base_layer.Node object at 0x138144be0>]
pprint.pprint(model_new.layers[1]._inbound_nodes)
#[<keras.engine.base_layer.Node object at 0x138174e10>,
# <keras.engine.base_layer.Node object at 0x110cfe198>]#これが増えている。
```
keras.engine.base_layer.Nodeについてはちょっと保留です。

このレイヤーのパラメーターを学習時に変更しないように設定(レイヤーをフリーズさせる)するには、
trainableプロパティにFalseをセットします。

```012.py
model_new.layers[1].trainable=False
```
モデルはcompileする必要があります。
#まとめ


1.学習済みのkerasのNNモデルから、好きなレイヤーを取り出して他のモデルに利用することができる。
2.レイヤーのパラメーターが変更されないように、freezeすることができる。