functional APIを使って自分で作ったモデルを転移学習やファインチューニングしたい
自分が阿呆なのもあるが、思ったより手こずってしまった。
VGG16などの誰かが用意したモデルを使った例はあったのですが、自作のモデルを使う例があまりなく、include_top=Falseなどは使えないので、これどうすればいいんや?となることが多かったため、メモ的に残しておくことにしました。
作ったモデルを保存する
これはググったら簡単に出てきますけど一応。
base_modelという自作のモデルが既に学習済みだとします。
#base_modelというfunctional APIで作った自作のモデルが既に学習済みであるとする
base_model.save('保存したいpath')
モデルを作る時に、以下のように各層にnameで名前をつけたほうが良いかもしれません。
# こんな感じでnameを使って全部の層に名前をつけておいた方が後々被らなくて良いかも
x = layers.Dense(100, activation='relu', name='base_model_dense1')(inputs)
x = layers.Dense(100, activation='relu', name='base_model_dense2')(x)
outputs = layers.Dense(10, name='base_model_dense3')(x)
base_model.summary()で見れますが、名前をつけなかったらdense1などと番号を増やしながら自動で名前をつけてくれます。
保存する際にはその名前ごと保存されます。
この後、読み込んで、新たな層を追加して学習する際に、同じ名前の層があるとエラーが生じて困りました。
モデルをロードする
保存したモデルを読み込みます。ググったら簡単に出てきます。
base_model = keras.models.load_model('保存したpath')
ロードしたモデルを使う
ここからがググってもあまり出てこなかったです。
いくつかの中間層の重みを固定したい時
どの層まで重みを固定し、どの層から再学習を行うかを決めたい時
for layer in base_model.layers[:-2]: #何層まで再学習不可にするか。
layer.trainable = False #この場合、最後の2層以外は重みを固定し、最後の2層のみ再学習する
base_model.summary() #←コレでTrainable paramsとNon-trainable paramsの数を確認できる。
base_model.summary()でTrainable paramsとNon-trainable paramsの数を見れば確認できます。
参考にしたnote.nkmk.me|TensorFlow, Kerasで転移学習・ファインチューニング
層数など変更せず学習しなおして使う場合
そのまま学習しなおしたいなら、base_model.compileしてbase_model.fitすれば良いと思われる。
一部の層を削除し、新たに層を追加する場合
一部を削除した新しいモデルを作って、それに新しい層を追加して学習し直したい場合。
一部の層を削除
input_tensor = base_model.input
output_tensor = base_model.layers[-2].output # [-2]なので、最後の2層を削除したモデルができるらしい
new_model = keras.Model(inputs=input_tensor, outputs=output_tensor) # 元々のモデルの最後の2層を消したモデル
new_model.summary() # ←コレで確認できる
新たに層を追加
xx = new_model.output
xx = layers.Dense(50, activation='relu')(xx) # 普段の使い方で好きに層を追加できるはず
outputs2 = layers.Dense(10)(xx) # 10はテキトーな数字、1行上の50もテキトーな数字
new_model = keras.Model(inputs=base_model.input, outputs=outputs2) #層を追加した新しいモデル
new_model.summary() # ←コレで確認できる
2行目のxx = のところには普段のfunctional APIの使い方と同じで、持ってきたモデルに好きな層を追加できるはずです。
inputs=base_model.inputの所は、kerasのテンソルの形のものを入れないといけないようです。
転移学習の元々のモデルbase_modelに入力するのと同じサイズのものを入れるので、inputs=base_model.inputで良いということだと思われる。
参考にした@kotai2003(ソンフン セルゲイ)様|学習済みモデルから任意の特徴量を抽出する。
転移学習する元々のモデルbase_modelの層に名前をつけなかったら、今回新たに追加した層の番号がbase_modelの層の名前とかぶったらエラーになるようです。
私の場合は、base_modelの1層目がdense36だったので、繰り返し実行しているうちにdense36が被ってるでというエラーがでました。
optunaでハイパーパラメータチューニングしようとした際には、繰り返すうちに番号が被ってエラーになって途中で止まったりしました。
base_modelの番号次第ですが、番号を毎回リセットするという対処法でもなんとかなりました。
from tensorflow.keras import backend as K
K.clear_session() #コレで層の番号がまた1から振られるようになる。
例えばoptunaでハイパーパラメータチューニングしたいが、番号がかぶるエラーが生じた場合には、optunaのstudy.optimize(objective,n_trials=100)のobjectiveの関数を定義している中にK.clear_session()を入れておくと毎回リセットしてくれて、エラーが生じなくなった。