KerasのModelクラスに関するプロパティとメソッドまとめ

# 概要
KerasのModelクラスまわりのプロパティとメソッドをまとめ。

Modelクラスまわりのプロパティとメソッドを知ることで、以下のようなことができる。
・モデル全体をセーブ/ロード。
・モデルの重みのみをセーブ/ロード。
・モデルの構造のみをセーブ/ロード。
・モデルから特定のレイヤーを取得。

まずプロパティとメソッドの一覧を示したあとに、コードの実行結果を説明していく。


# プロパティ
|Properties        |Description        | 
|:-----------------|:------------------|
|model.name        |modelの名前|
|model.inputs      |入力のTensorクラスのリスト|
|model.outputs     |出力のTensorクラスのリスト|
|model.layers      |Layerクラスのリスト |
|model.trainable   |modelがtrainableか(boolean)|
|model.input_shape |入力のshape      |
|model.output_shape|出力のshape      |
|model.weights     |Variableクラスのリスト|
|model.trainable_weights|学習可能なVariableクラスのリスト|
|model.non_trainable_weights |学習不可能なVariableクラスのリスト|


# メソッド
|Methods            |Description | 
|:------------------|:------------------|
|model.summary(line_length=None, positions=None, print_fn=None)    |model構造をプリント表示 |
|model.get_layer(name=None, index=None)|指定したLayerクラスを取得|
|model.get_weights()|全layerの重みのリストを取得|
|model.set_weights(weight)|get_weights()で取得した各layerの重みを設定|
|model.get_config() |モデルのコンフィグを辞書形式で取得|
|model.from_config(config, custom_objects=None) |get_config()で取得したコンフィグからモデルを生成|
|model.to_json()|モデルの構造をjson形式の文字列で取得|
|model.to_yaml()|モデルの構造をYAML形式の文字列で取得|
|model.save(filepath, overwrite=True, include_optimizer=True) |モデルの構造と重みと学習に関する設定や状態を全てHDF5形式でセーブ|
|model.save_weights(filepath, overwrite=True)|モデルの重みをHDF5形式でセーブ|
|model.load_weights(filepath, by_name=False)|save_weights()で保存した重みをモデルにロード|



# その他関連するメソッド
|Methods            |Description | 
|:------------------|:------------------|
|keras.models.load_model(filepath,custom_objects=None,compile=True)|save()で保存されたモデルの状態をロード|
|keras.models.model_from_json(json_str)|to_json()で取得したモデルの構造をロード|
|keras.models.model_from_yaml(yaml_str)|to_yaml()で取得したモデルの構造をロード|






# プロパティ 動作確認
簡単な動作確認用のモデルを生成する。

```python:test.py
import keras
from keras import models
from keras.layers import Activation, Conv2D, MaxPooling2D, Flatten, Dense

def model_sequential():
    model = models.Sequential()

    model.add(Conv2D(32, (3, 3), padding='same', name='conv1', input_shape=(28, 28 , 1)))
    model.add(Activation('relu', name='relu1'))
    model.add(MaxPooling2D((2, 2), name='pool1'))
   
    model.add(Flatten(name='flatten'))
    model.add(Dense(10, name='dense2'))
    model.add(Activation('softmax', name='softmax'))
    
    return model
```

各プロパティの戻り値を確認する。

```python:test.py
model = model_sequential()
print(model.name)
# -> sequential_1
print(model.inputs)
# -> [<tf.Tensor 'conv1_input:0' shape=(?, 28, 28, 1) dtype=float32>]
print(model.outputs)
# -> [<tf.Tensor 'softmax/Softmax:0' shape=(?, 10) dtype=float32>]
print(model.layers)
# -> [<keras.layers.convolutional.Conv2D object at 0x7f4b5d7de278>, <keras.layers.core.Activation object at 0x7f4b4ecd85c0>, <keras.layers.pooling.MaxPooling2D object at 0x7f4b4ecd8320>, <keras.layers.core.Flatten object at 0x7f4b4ecd8438>, <keras.layers.core.Dense object at 0x7f4b4e3f7588>, <keras.layers.core.Activation object at 0x7f4b4e417978>]
print(model.trainable)
# -> True
print(model.trainable_weights)
# -> [<tf.Variable 'conv1/kernel:0' shape=(3, 3, 1, 32) dtype=float32_ref>, <tf.Variable 'conv1/bias:0' shape=(32,) dtype=float32_ref>, <tf.Variable 'dense2/kernel:0' shape=(6272, 10) dtype=float32_ref>, <tf.Variable 'dense2/bias:0' shape=(10,) dtype=float32_ref>]
print(model.non_trainable_weights)
# -> []
print(model.input_shape)
# -> (None, 28, 28, 1)
print(model.output_shape)
# -> (None, 10)
print(model.weights)
# -> [<tf.Variable 'conv1/kernel:0' shape=(3, 3, 1, 32) dtype=float32_ref>, <tf.Variable 'conv1/bias:0' shape=(32,) dtype=float32_ref>, <tf.Variable 'dense2/kernel:0' shape=(6272, 10) dtype=float32_ref>, <tf.Variable 'dense2/bias:0' shape=(10,) dtype=float32_ref>]
```

### model.trainable

``model.trainable=False``に設定すると,
自動的に全てのVariableオブジェクトが``model.non_trainable_weights``に移動する。


```python:test.py
model = model_sequential()
model.trainable=False
print(model.trainable)
# -> False
print(model.trainable_weights)
# -> []
print(model.non_trainable_weights)
# -> [<tf.Variable 'conv1/kernel:0' shape=(3, 3, 1, 32) dtype=float32_ref>, <tf.Variable 'conv1/bias:0' shape=(32,) dtype=float32_ref>, <tf.Variable 'dense2/kernel:0' shape=(6272, 10) dtype=float32_ref>, <tf.Variable 'dense2/bias:0' shape=(10,) dtype=float32_ref>]
```

layer単位でも``trainable``を設定できるが、``model.trainable=False``の場合は、layer単位の設定は無視される。(設定が強制的にFalseになる。)

```python:test.py
model = model_sequential()
model.trainable=False
model.layers[0].trainable=True
print(model.trainable)
# -> False
print(model.trainable_weights)
# -> []
# layer[0]をtrainable=Trueにしているがtrainable_weightsに登録されない。
print(model.non_trainable_weights)
# -> [<tf.Variable 'conv1/kernel:0' shape=(3, 3, 1, 32) dtype=float32_ref>, <tf.Variable 'conv1/bias:0' shape=(32,) dtype=float32_ref>, <tf.Variable 'dense2/kernel:0' shape=(6272, 10) dtype=float32_ref>, <tf.Variable 'dense2/bias:0' shape=(10,) dtype=float32_ref>]
```

# メソッド 動作確認

### model.summary(line_length=None, positions=None, print_fn=None)
|Params            |Description        | 
|:-----------------|:------------------|
|line_length  |プリントされる横幅。10を設定すると各行10文字までしか表示されない。|
|positions        |よくわからない。たぶんsummaryを表示する位置に関係するもの。|
|print_fn        |summaryを表示するためのprint関数。|
モデル構造をプリントする。基本的にパラメータはデフォルトで困らないと思う。

```python:test.py
model = model_sequential()
model.summary()
'''
____________________________________________________________________________________________________
Layer (type)                                 Output Shape                            Param #        
====================================================================================================
conv1 (Conv2D)                               (None, 28, 28, 32)                      320            
____________________________________________________________________________________________________
relu1 (Activation)                           (None, 28, 28, 32)                      0              
____________________________________________________________________________________________________
pool1 (MaxPooling2D)                         (None, 14, 14, 32)                      0              
____________________________________________________________________________________________________
flatten (Flatten)                            (None, 6272)                            0              
____________________________________________________________________________________________________
dense2 (Dense)                               (None, 10)                              62730          
____________________________________________________________________________________________________
softmax (Activation)                         (None, 10)                              0              
====================================================================================================
Total params: 63,050
Trainable params: 0
Non-trainable params: 63,050
____________________________________________________________________________________________________
'''
```

### model.get_layer(name=None, index=None)

|Params            |Description        | 
|:-----------------|:------------------|
|name         |モデル構築時につけた名前で指定。|
|index        |レイヤーのインデックス番号で指定。|
両方設定すると``index``が優先される。
結果は指定したレイヤーのオブジェクトがリターンされる。

```python:test.py
model = model_sequential()
print(model.get_layer(name="conv1"))
# -> <keras.layers.convolutional.Conv2D at 0x7f4b5d7de278>
print(model.get_layer(index=0))
# -> <keras.layers.convolutional.Conv2D at 0x7f4b5d7de278>
print(model.layers[0])
# -> <keras.layers.convolutional.Conv2D at 0x7f4b5d7de278>
# property経由で触っても同じ結果になる。
```

### model.get_weights()
``model.get_weights()``は、モデル内の重みを全てがリターンされる。要素がndarray形式のリストで返ってくる。今回は、重みを持つレイヤーが``Conv2D``と``Dense``の2つで、各レイヤーは重みとバイアスを持つのでリストの要素数は4で返ってくる。

```python:test.py
model = model_sequential()
w = model.get_weights()
print(len(w))
# -> 4
print(w[0].shape)
# -> (3, 3, 1, 32)
# Conv2Dの重み
print(w[1].shape)
# -> (32,)
# Conv2Dのバイアス
print(w[2].shape)
# -> (6272, 10)
# Denseの重み
print(w[3].shape)
# -> (10,)
# Denseのバイアス
```
### model.set_weights()
``model.set_weight()``は、layerの重みを設定することができる。設定する形式は、``model.get_weights()``で取得できる形式。


```python:test.py
# 各layerの新しい重みを定義
conv_weights = np.ones(w[0].shape) * 1
conv_bias = np.ones(w[1].shape) * 2
dense_weights = np.ones(w[2].shape) * 3
dense_bias = np.ones(w[3].shape) * 4

# 各layerの重みをlayerの順番でリスト化
new_weights = []
new_weights.append(conv_weights)
new_weights.append(conv_bias)
new_weights.append(dense_weights)
new_weights.append(dense_bias)

# 新しいモデルを生成
new_model = model_sequential()
# 重みをロード
new_model.set_weights(new_weights)

print(new_model.get_weights()[3])
# -> [4. 4. 4. 4. 4. 4. 4. 4. 4. 4.]
```


### model.to_json() / model.to_yaml()
モデルの構造をjson形式、yaml形式で取得できる。構造だけなので重みや学習に関する設定は取得されない。

```python:test.py
model = model_sequential()
# json形式でモデル構造を出力
json_str = model.to_json()
# json形式のモデル構造からモデルを再構築
new_json_model = keras.models.model_from_json(json_str)

# yaml形式でモデル構造を出力
yaml_str = model.to_yaml()
# yaml形式のモデル構造からモデルを再構築
new_yaml_model = keras.models.model_from_yaml(yaml_str)
```


### model.save(filepath, overwrite=True, include_optimizer=True)

|Params            |Description        | 
|:-----------------|:------------------|
|filepath          |saveするファイルパス。|
|overwrite         |指定したfilepathが既に存在していた場合、上書きするかどうか。|
|include_optimizer |optimizerの状態もsaveするかどうか。|

``overwrite=False``にすると既に同じファイルがある場合に``[WARNING] test.hdf5 already exists - overwrite? [y/n]``のように上書きするかどうか聞かれる。
``include_optimizer=False``にするとoptimizer関連の情報が保存されない。なのでセーブしたファイルをロードした後に``compile``をする必要がある。


```python:test.py
model = model_sequential()
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])
# model情報をセーブする。optimizerの設定も保存される。
model.save("test.hdf5") 

# "test.hdf5"を使ってモデルを再構築する。
# load時にcompileも実行されている
new_model = keras.models.load_model(model_path) 
```


### model.save_weights(filepath, overwrite=True)

|Params            |Description        | 
|:-----------------|:------------------|
|filepath          |saveするファイルパス。|
|overwrite         |指定したfilepathが既に存在していた場合、上書きするかどうか。|

モデルの重みだけをHDF5形式で保存する。


### model.load_weights(filepath, by_name=False)

|Params            |Description        | 
|:-----------------|:------------------|
|filepath          |saveするファイルパス。|
|by_name           |名前が一致がしたlayerだけ重みをロードするかどうか。|
``by_name=False``にするとネットワーク構造に基づいて重みがロードされる。``by_name=True``するとlayerの名前が同じ箇所だけ重みをロードする。これは転移学習 or ファインチューニングするときに使用される。

```python:test.py
# 新しいモデルを生成して重みをセーブする。
model = model_sequential()
model.save_weights("test_weights.hdf5")

# 新しいモデルを生成して重みをロードする。
new_model = model_sequential()
new_model.load_weights("test_weights.hdf5")

# 重みが一致していることを確認。
print(np.all(new_model.get_weights()[0] == model.get_weights()[0]))
# -> True
print(np.all(new_model.get_weights()[1] == model.get_weights()[1]))
# -> True
print(np.all(new_model.get_weights()[2] == model.get_weights()[2]))
# -> True
print(np.all(new_model.get_weights()[3] == model.get_weights()[3]))
# -> True
```