EnsekiTT Blog

EnsekiTTが書くブログです。

勤労感謝の日なのでChainerの勤労(Training)に感謝してextensionsを全部試した話

つまりなにしたの

Chainerの抽象度を飛躍的に高めたTrainerにはextensionsがある。
せっかくだからextensionsを全部使ってみた。

2017年11月10日 追記
Extensionを自分で作ってみました。
ensekitt.hatenablog.com

背景

最近やっとChainerのバージョンを上げました。
qiita.com
を読んで、すごい勢いで僕のソースコードも抽象化したわけですが、
僕はまだまだextensionsの素敵機能を素敵に使いこなせてない!

というわけで、勤労感謝の日にChainerの勤労(Training)に感謝してextensionsを全部試した。

dump_graph

概要

グラフをDOT Languageで描画してくれる

使い方

trainer.extend(extensions.dump_graph(root_name="main/loss", out_name="cg.dot"))

使った結果

cg.dotがResultディレクトリに出力される。

Cg.dotの使い方
sudo apt install graphviz

今は論文に埋め込むわけではないので、Dotファイルをpng形式に変換したい

dot -Tpng cg.dot -o cg.png
dot -Tフォーマット dotファイル -o 出力ファイル名

こんな感じになる
f:id:ensekitt:20161124010032p:plain

まとめ

これを自動的にpngまで変換してウェブから参照できるようにするもよし、プレゼン資料や論文に載せるのに使うもよしです。

Evaluator

概要

Training中でも評価することができる。

使い方

trainer.extend(extensions.Evaluator(test_iter, model, device=0))

テスト用のイテレータとモデルを指定する。

使った結果

このあと紹介するLogReportと合わせて使うと、テスト結果がLogに現れる。
可視化するとこんな感じ。
f:id:ensekitt:20161124010051p:plain

まとめ

テストしながら見る必要がある。

Exponential Shift

概要

最適化のパラメータを指数関数的に変更することができる

使い方

trainer.extend(extensions.ExponentialShift("alpha", 1.000001))
ここではAdamを使っているため学習率を左右するαを指数関数的に変更してみている。

使った結果

ExponentialShiftなし

ExponentialShiftあり(1)

trainer.extend(extensions.ExponentialShift("alpha", 1.000001))

ExponentialShiftあり(2)

trainer.extend(extensions.ExponentialShift("alpha", 1.0001))

f:id:ensekitt:20161124010106p:plain
※事故です。

Adamのざっくりした仕組みが「あんまり触ってない変数いじって局所に落ちないようにしよう!」
なので、学習率が指数関数的に急増すると落ちる方に引っ張られるんじゃないかと思ってる。
学習率を下げる方向にすればよかった。

まとめ

指数関数的に変化するので扱いは難しそう。

使い方の例に出せる論文が見つかったら追記する。いい感じ例をご存じの方はコメントを頂けるとうれしいです。

LinearShift

概要

オプティマイザのパラメータを線形に変更させることができる

使い方

trainer.extend(extensions.LinearShift("alpha", (0.01,0.001), (20000,30000)))

attr:対象とするパタメータ、 value_range:変更幅、time_range:変更するiterationレンジ
ここではAdamを使っているため学習率を左右するαを指数関数的に変更してみている。

使った結果

LinearShiftなし

LinearShiftあり(1)

trainer.extend(extensions.LinearShift("alpha", (0.001,0.0001), (20000,30000)))

LinearShiftあり(2)

trainer.extend(extensions.LinearShift("alpha", (0.01,0.001), (20000,30000)))

f:id:ensekitt:20161124010122p:plain

まとめ

序盤は荒く、終盤は細かくといったことを実現するときとか逆にオーバーフィットし始めるあたりからパラメータを調整するなどもできそう。

LogReport

概要

trainerの途中経過をログファイルに蓄積する。

使い方

trainer.extend(extensions.LogReport(logname="log", trigger=(1, 'epoch')))

trigger=(1, 'epoch’)なら1epochごとに出力される。

trigger=(1, 'iteration’)なら1iterationごとに出力される。

使った結果

表示はされないが、
./result/log
JSON形式でログが出力される。

中身はこんな感じ。

[
    {
        "elapsed_time": 1.9452495574951172,
        "epoch": 1,
        "main/accuracy": 0.9067500013237199,
        "main/loss": 0.33314116835594176,
        "iteration": 600
    },
]

まとめ

実効の過程を可視化するとモデルの比較ができるので、常に保存しておきたい。

実行時のタイムスタンプやモデルの識別子を追加しておくと、上書きされないので良い。

snapshot

概要

trainerをTriggerに応じてスナップショットする

使い方

trainer.extend(extensions.snapshot())

使った結果

./result/の中に

snapshot_iter_12000  snapshot_iter_18600  snapshot_iter_25200  snapshot_iter_31800  snapshot_iter_38400  snapshot_iter_45000  snapshot_iter_51600  snapshot_iter_58200
snapshot_iter_12600  snapshot_iter_19200  snapshot_iter_25800  snapshot_iter_32400  snapshot_iter_39000  snapshot_iter_45600  snapshot_iter_52200  snapshot_iter_58800

こんなのが出てくる。これらは特に指定しなければsave_npzが使われるため、

serializers.load_npz('./result/snapshot_iter_50000', trainer)

で呼び出して

trainer.run()

を実行すると

epoch       main/loss   main/accuracy  elapsed_time
68          0.000235376  1              163.02
69          0.00452771  0.998514       165.511
70          0.00306396  0.999117       167.919
71          0.00277979  0.9992         170.311

こんな感じで途中のepochから始める。

まとめ

中断しても途中からスタートできるのでめちゃくちゃ時間がかかるときなどはやっておいたほうが安心。

snapshot_object

概要

指定したオブジェクト(optimizerとかmodel)をTriggerに応じてスナップショットする

使い方

trainer.extend(extensions.snapshot_object(optimizer, 'optimizer_snapshot_{.updater.epoch}', trigger=(10,'epoch')))

optimizerを10 epochごとに’optimizer_snapshot_{エポック数}の名前でスナップショットすることができる。

使った結果

./result/に

optimizer_snapshot_10   optimizer_snapshot_20  optimizer_snapshot_40  optimizer_snapshot_60  optimizer_snapshot_80
optimizer_snapshot_100  optimizer_snapshot_30  optimizer_snapshot_50  optimizer_snapshot_70  optimizer_snapshot_90

こんなのが出てくる。これらは特に指定しなければsave_npzが使われるため、

serializers.load_npz('./result/optimizer_snapshot_50', optimizer)

で呼び出して

trainer.run()

を実行すると保存したoptimizerが使われる。

まとめ

ここではoptimizerなのであんまり意味はないけれども、trainerとは別に動く物があればそれを保存しておけるので便利そう。

PrintReport

概要

LogReportの中身を出力する。

使い方

trainer.extend(extensions.LogReport()
trainer.extend(extensions.PrintReport( entries=['epoch', 'main/loss', 'main/accuracy', 'elapsed_time' ]))

entriesにLogReportで出力される項目の名前を入れる。

使った結果

epoch       main/loss   main/accuracy  elapsed_time
1           0.334851    0.904833       1.94594
2           0.13853     0.959233       3.97545
3           0.0977319   0.97095        6.07704
4           0.0765065   0.97635        8.14663
5           0.0601246   0.9816         10.2338

まとめ

進み具合がすぐに分かるので序盤でミスに気づくことができるし結果を待っている側としては心に優しい。

ProgressBar

概要

進捗どうですか?にChainerが答えてくれる。

使い方

trainer.extend(extensions.ProgressBar())

使った結果

total [*..............................................]  8.50%
this epoch [*.........................] 50.00%
5100 iter, 8 epoch / 100 epochs
292.74 iters/sec. Estimated time to finish: 0:03:07.535695.

まとめ

進んでいる感じがするのでとても心に優しい。

今回書いたコード

gist.github.com

さいごに

勤労に感謝してたら勤労感謝の日が終わっていた。

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。