Chainerでvalidationをする際に気をつけるべきこと
はじめに
ChainerのTrainer機能が使いこなせないので,自分で訓練ループを書いてます.
先日,自分で評価ループ(Validation)を実装したときにハマったのでメモです.
Chainerにおける訓練&評価ループ
Chainerにおける訓練のプロセスってだいたい決まっていて, Chainerチュートリアル -v1.5向け- ViEW2015 にもある通りなんですが,
- 訓練ループを回す(勾配をゼロ初期化,順伝播してlossを計算,得られたlossをbackward, optimizerのupdate)
- 適当なタイミングで評価ループ(Validation)を回す (順伝搬してlossを計算,得られたlossを出力)
これだけです.当たり前ですね〜
ここでのポイントは,訓練ループも評価ループも,順伝搬してlossを計算するのは同じってところですね.
ハマりどころ
以上の流れを踏まえると,こういうコードを書きたくなります.
def train(): for batch in data: loss = model(x,t) loss.backward() optimizer.update() def validation_wrong(): #out of memoryで落ちるver. accum_loss = 0 for batch in data: accum_loss += model(x,t) return float(accum_loss.data)
accum_lossにmodel.__call__
で計算したlossを足しこんでいって,最後にlossの総和を出力するコードですね.
lossを計算するのは同じなので,素直にtrain()
からmodel(x,t)
をコピーしています.
しかし,これをそのまま実行するとout of memoryでプログラムが落ちます.
というのも,この書き方だとChainerは変数accum_lossに全ての評価データのlossを保存しようとするからです. このときlossは誤差逆伝播のために計算グラフの履歴を保持している(多分)なので,少量のデータでも結構なメモリを食います. 結果メモリがゴリゴリ消費されていって,落ちてしまうんですね.
(気持ちとしては,ミニバッチ学習をしているつもりが,全データの最適化を試みているような形になっている?)
正しい書き方
こうやって書きましょう
def validation_right(): #out of memoryならないver. accum_loss = 0 for batch in data: accum_loss += model(x,t).data return float(accum_loss)
lossの総和を出力するために必要なのは,lossのスカラ値だけなので,dataだけ保存しておけば十分です. この書き方をすれば,各バッチの計算後にメモリは開放されるので,メモリ不足にはなりません.
簡単なミスなんですが,これに気がつくのに半日くらいかかりました…
(ごくごく少量のデータで試したら落ちなかったので,全データで試す → 1epochの計算に9時間を費やした後,validationでお亡くなり → 悲しみ)
皆さんは気をつけましょう.というかChainer本体のTrainerを使いましょう.
それでは.