Chainerの学習ログをSlackに流すchainer-slack-reportを作った

魚類の中でもマンボウはとても多くの数の卵を生むことで知られており、それが産卵数とイコールなのかは諸説あるものの3億個もの卵を抱えていると言われているのだそうです。

さて、Chainerで学習してるとその進捗をSlackで見られるようにしたいですよね。
そこで、ChainerのPrintReport(標準出力への学習ログレポートExtension)を拡張して同じ内容をSlackに送るExtensionをさくっと作りました。

belltailjp/chainer_slack_report

screencast

Chainerの学習ログをSlackに流そう!という発想自体は当然いくらでもあって、ネットで軽く探しただけでとても優れた先例がたくさんありますし、野良で作って自分で使っている人も少なくないと思います。

DeepLearningの学習経過をslackで受け取れるようにする – Qiita
Chainerの学習の様子をリモートで確認するExtensionを作った – のんびりしているエンジニアの日記
dtaniwaki/chainer-watchdog: Watchdog extension for Chainer

ぼく個人が自分のユースケースにおいて一番困っていた点、そしてchainer-slack-reportで解決しかった点は、いろんな学習を並行して回してその全てをSlackに流しても表示や時系列が崩れないようにしたかったことです。上のスクリーンショットを見ていただくとSlackReportが複数の学習のレポートをうまくさばいている様子がわかるかと思います。

技術的には全く高度なことはしていなくて、「(SlackReportオブジェクトのライフタイムにおいて)初回はSlackに新規メッセージを投げる」「2回め以降はそのメッセージを編集する」というふうにしているだけです。何も難しくないです。

他に実装上の違いとして、ChainerのPrintReportをそのまま継承して画面表示部分のみを乗っ取る実装にしたため、テキストの整形やシリアライズの仕組みはまるごとPrintReportがやってくれるという超シンプルな作りになっています。
何もしないでもserialize・deserializeに対応している点も地味に助かります。たとえばPrintReportではtrainerのsnapshotをロードして学習を途中から再開したとき過去の学習記録をまるごと再表示してくれますが、SlackReportもそのまま同じように動いてくれます。

使い方
% pip install chainer-slack-report

学習コードで使うには、PrintReportと同じようにtrainerに差し込むだけでOKです。もちろんPrintReportと共存できますし、複数のチャンネルに同時に投げたい場合はchannel_idを変えて別々のSlackReportを作りそれぞれをtrainerにextendすればOKです。

PrintReportの初期化時に渡せるような引数はすべてSlackReportの初期化時にも渡せます。
それに加えてSlackに投稿するために必要な情報を渡す必要があり、またSlackでの通知内容の細かな制御のためのオプションを渡すこともできます。

必須の情報として、SlackのWeb APIを叩くためのアクセストークンおよび送信先となるChannelをさすIDがあります。
これらの情報の取得・確認方法はGithubでスクリーンショット付きで解説しています。
どちらかがNoneの場合は単純に何もしないようになっています。
そのため環境変数から取ってくるなどのイディオムを採用するのがよいでしょう。

複数の学習が破綻なくSlackに表示されるのはいいけど、どれがどれかわからないと(数が多いときは)意味がありませんね。
SlackReportではデフォルトでスクリプト名とその引数(要するにargv)をレポートの頭に表示します。
Jupyterで学習を回している場合はこれでは実質無意味な情報になるので、マニュアルで指定することもできます。その場合はSlackReportの初期化時にlabelという名前付き引数に任意の文字列を指定すればOKです。

また、SlackReportのイニシャライザのfinish_mentionsという引数にユーザのアカウント名(@何々)を文字列ないし文字列のリストで渡すと、学習が終了したときにmentionを飛ばしてくれる機能もあります。学習の終了に気づきやすくなるので便利でしょう。何も指定しないと(あるいは指定されたユーザが存在しないと)、普通のレポートのみが表示されて終了します。
Slackのメッセージ長さ制限(2000文字)に引っかからない限り何人でも大丈夫です。ただしユーザグループは未対応です。

import os
from chainer_slack_report import SlackReport
...
r = SlackReport(os.environ.get("SLACK_ACCESS_TOKEN", None),
                os.environ.get("SLACK_CHANNEL_ID", None),
                ['epoch', 'main/loss', 'validation/main/loss',
                 'main/accuracy', 'validation/main/accuracy', 'elapsed_time'],
                finish_mentions="@someone")
trainer.extend(r, trigger=(1, 'epoch'))
...
trainer.run()

FAQ
  • トークンが正しいはずなのにUnauthorizedになります

    • chainer-slack-report初公開時と今で(1日差ですが)、Slack APIを叩くために必要な権限が変わりました。
    • App typeをBotとする設定をしなおしてみてください
    • ちなみにSlackに詳しい方はGithub上のinstructionに従わず通常のAppとしてpermission settingしてもOKです。

      • chat:write:bot, channels:read, users:readの権限が必要です
  • Trainerを使っていないコードでも使えますか?

    • __call__関数で表示までしているのですがこれがtrainerを受け取っているという形で依存しているので、基本的にはTrainerを使っていただけたらと思います。
断念した機能

ChainerにはPlotReportという学習曲線を随時グラフ(PNG画像)にして保存するExtensionがあります。
これもSlackReportで乗っ取ってSlackに投げるようにしてみよう!と思ったのですが、SlackのAPIには「画像を上書きアップロードする」という機能がないため、強引に実現しても「時系列が狂わない」という基本コンセプトが守れないので、断念しました。
いい見せ方ができるアイデアが何かあればよいのですが。

TODO
  • テストする
  • Chainer v5系でも確認