RigelのR言語メモであーる(R言語だけとは言っていない)

RigelのR言語メモであーる(主にpython)

興味あることや趣味、やったことについて書くよ

kerasのoptimizerの状態を保存する

背景

新規データに対して逐次的に毎日学習するkerasのモデルを考える。
シンプルなモデルであればmodel.save()load_model()を使えばなんの問題もない。
参考: Kerasのノウハウ覚え書き
しかし、Lambda層が入るとmodel.save()ができなくなる。
参考: KerasのLambda層でreshapeしたとき、保存に失敗する(場合がある)話
追加の学習はせず予測のためだけに使うモデルなら、save_weights()とかで重みだけ保存しておけばよいが、毎日新規データを学習したいため微妙。
というのは、optimizerがmomentumなどの状態を持っている場合、それも保存しておかなければならない。
以下の記事を参考に、optimizerの状態の保存と読み込みを行って、各種実験をしてみる。
参考: python - Save and load model optimizer state - Stack Overflow

実験

準備

必要なライブラリのインポート

from sklearn import datasets
from sklearn import preprocessing

from keras.models import Model
from keras.layers import Input, Dense
from keras.utils import np_utils
from keras.optimizers import SGD
from keras import backend as K

データの準備

iris = datasets.load_iris()
X = iris.data
Y = iris.target
X = preprocessing.scale(X)
Y = np_utils.to_categorical(Y)

モデルを作ってコンパイルする関数

def make_model(momentum=0.0, decay=0.0, nesterov=False):
    inputs = Input(shape=(4,))
    outputs = Dense(3, activation="softmax", kernel_initializer="ones", bias_initializer="ones", name="output_layer")(inputs)
    model = Model(inputs=inputs, outputs=outputs)
    model.compile(optimizer=SGD(lr=0.01, momentum=momentum, decay=decay, nesterov=nesterov), loss='categorical_crossentropy')
    return model
実験1

optimizerの状態がない場合の学習。

model = make_model()
model.fit(X, Y, epochs=10, verbose=2, batch_size=200)
 - 0s - loss: 1.0986
Epoch 2/10
 - 0s - loss: 1.0890
Epoch 3/10
 - 0s - loss: 1.0796
Epoch 4/10
 - 0s - loss: 1.0704
Epoch 5/10
 - 0s - loss: 1.0613
Epoch 6/10
 - 0s - loss: 1.0524
Epoch 7/10
 - 0s - loss: 1.0437
Epoch 8/10
 - 0s - loss: 1.0351
Epoch 9/10
 - 0s - loss: 1.0267
Epoch 10/10
 - 0s - loss: 1.0185
実験2

optimizerの状態がない場合の学習。
epochs=1の学習をfor文で回す。
実験1と同じ結果。

model = make_model()
for i in range(10):
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0890
Epoch 1/1
 - 0s - loss: 1.0796
Epoch 1/1
 - 0s - loss: 1.0704
Epoch 1/1
 - 0s - loss: 1.0613
Epoch 1/1
 - 0s - loss: 1.0524
Epoch 1/1
 - 0s - loss: 1.0437
Epoch 1/1
 - 0s - loss: 1.0351
Epoch 1/1
 - 0s - loss: 1.0267
Epoch 1/1
 - 0s - loss: 1.0185
実験3

optimizerの状態がある場合の学習。

model = make_model(momentum=1, decay=1, nesterov=True)
model.fit(X, Y, epochs=10, verbose=2, batch_size=200)
Epoch 1/10
 - 0s - loss: 1.0986
Epoch 2/10
 - 0s - loss: 1.0795
Epoch 3/10
 - 0s - loss: 1.0610
Epoch 4/10
 - 0s - loss: 1.0414
Epoch 5/10
 - 0s - loss: 1.0209
Epoch 6/10
 - 0s - loss: 0.9997
Epoch 7/10
 - 0s - loss: 0.9781
Epoch 8/10
 - 0s - loss: 0.9563
Epoch 9/10
 - 0s - loss: 0.9343
Epoch 10/10
 - 0s - loss: 0.9125
実験4

optimizerの状態がある場合の学習。
epochs=1の学習をfor文で回す。
実験3と同じ結果。
modelがoptimizerの状態を保持している。

model = make_model(momentum=1, decay=1, nesterov=True)
for i in range(10):
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0795
Epoch 1/1
 - 0s - loss: 1.0610
Epoch 1/1
 - 0s - loss: 1.0414
Epoch 1/1
 - 0s - loss: 1.0209
Epoch 1/1
 - 0s - loss: 0.9997
Epoch 1/1
 - 0s - loss: 0.9781
Epoch 1/1
 - 0s - loss: 0.9563
Epoch 1/1
 - 0s - loss: 0.9343
Epoch 1/1
 - 0s - loss: 0.9125
実験5

optimizerの状態がない場合の学習。
モデルの作成と学習を繰り返す。
lossが変化しない。

for i in range(10):
    model = make_model()
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
実験6

optimizerの状態がある場合の学習。
モデルの作成と学習を繰り返す。
実験5と同様にlossが変化しない。

for i in range(10):
    model = make_model(momentum=1, decay=1, nesterov=True)
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0986
実験7

optimizerの状態がない場合の学習。
モデルの作成と学習を繰り返す。
モデル作成後に重みを読み込む。
実験1や実験2と同じ結果。

for i in range(10):
    model = make_model()
    if i != 0:
        model.get_layer("output_layer").set_weights(weights_dense)
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
    weights_dense = model.get_layer("output_layer").get_weights()
Epoch 1/1
 - 1s - loss: 1.0986
Epoch 1/1
 - 1s - loss: 1.0890
Epoch 1/1
 - 1s - loss: 1.0796
Epoch 1/1
 - 1s - loss: 1.0704
Epoch 1/1
 - 1s - loss: 1.0613
Epoch 1/1
 - 1s - loss: 1.0524
Epoch 1/1
 - 1s - loss: 1.0437
Epoch 1/1
 - 1s - loss: 1.0351
Epoch 1/1
 - 2s - loss: 1.0267
Epoch 1/1
 - 2s - loss: 1.0185
実験8

optimizerの状態がある場合の学習。
モデルの作成と学習を繰り返す。
モデル作成後に重みを読み込む。
optimizerの状態の違いのため、実験3や実験4と異なる結果。

for i in range(10):
    model = make_model(momentum=1, decay=1, nesterov=True)
    if i != 0:
        model.get_layer("output_layer").set_weights(weights_dense)
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
    weights_dense = model.get_layer("output_layer").get_weights()
Epoch 1/1
 - 2s - loss: 1.0986
Epoch 1/1
 - 2s - loss: 1.0795
Epoch 1/1
 - 2s - loss: 1.0703
Epoch 1/1
 - 2s - loss: 1.0612
Epoch 1/1
 - 2s - loss: 1.0523
Epoch 1/1
 - 2s - loss: 1.0350
Epoch 1/1
 - 2s - loss: 1.0266
Epoch 1/1
 - 2s - loss: 1.0183
Epoch 1/1
 - 2s - loss: 1.0102
Epoch 1/1
 - 2s - loss: 1.0023
実験9

optimizerの状態がある場合の学習。
モデルの作成と学習を繰り返す。
モデル作成後に重みとoptimizerの状態を読み込む。
実験3や実験4と同じ結果。

for i in range(10):
    model = make_model(momentum=1, decay=1, nesterov=True)
    model._make_train_function()
    if i != 0:
        model.get_layer("output_layer").set_weights(weights_dense)
        model.optimizer.set_weights(weights_optimizer)
    model.fit(X, Y, epochs=1, verbose=2, batch_size=200)
    weights_dense = model.get_layer("output_layer").get_weights()
    weights_optimizer = model.optimizer.get_weights()
Epoch 1/1
 - 2s - loss: 1.0986
Epoch 1/1
 - 0s - loss: 1.0795
Epoch 1/1
 - 0s - loss: 1.0610
Epoch 1/1
 - 0s - loss: 1.0414
Epoch 1/1
 - 0s - loss: 1.0209
Epoch 1/1
 - 0s - loss: 0.9997
Epoch 1/1
 - 0s - loss: 0.9781
Epoch 1/1
 - 0s - loss: 0.9563
Epoch 1/1
 - 0s - loss: 0.9343
Epoch 1/1
 - 0s - loss: 0.9125

まとめ

  1. model.save()が使える場合
    -> model.save()を使えば良い。
  2. model.save()が使えない場合
    1. 再学習しない場合
      -> モデルの重みのみ保存すれば良い。
    2. 再学習する必要がある場合
      -> モデルの重みとoptimizerの状態を保存すれば良い。

ちなみに、weights_denseweights_optimizerの中身は以下。

weights_dense
[array([[0.9180371 , 1.00887   , 1.0730928 ],
        [1.0696087 , 0.94541067, 0.9849805 ],
        [0.8941715 , 1.0231149 , 1.0827136 ],
        [0.8982664 , 1.0132247 , 1.0885084 ]], dtype=float32),
 array([0.9999076, 1.000105 , 0.9999874], dtype=float32)]
weights_optimizer
[10, array([[-0.00934379,  0.00099744,  0.00834635],
        [ 0.00797938, -0.0062994 , -0.00167998],
        [-0.01209093,  0.00263835,  0.00945257],
        [-0.01162081,  0.00148986,  0.01013095]], dtype=float32), 
array([-2.1416694e-05,  2.4464378e-05, -3.0475137e-06], dtype=float32)]

なので、pickleとかで保存すれば良い。

model._make_train_function()は何をしているかわからない。
けど、それがないとmodel.optimizer.set_weights(weights_optimizer)でエラーになる。