Chainerでvalidationをする際に気をつけるべきこと

はじめに

ChainerのTrainer機能が使いこなせないので,自分で訓練ループを書いてます.

先日,自分で評価ループ(Validation)を実装したときにハマったのでメモです.

Chainerにおける訓練&評価ループ

Chainerにおける訓練のプロセスってだいたい決まっていて, Chainerチュートリアル -v1.5向け- ViEW2015 にもある通りなんですが,

  1. 訓練ループを回す(勾配をゼロ初期化,順伝播してlossを計算,得られたlossをbackward, optimizerのupdate)
  2. 適当なタイミングで評価ループ(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を使いましょう.

それでは.