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系でも確認

ZealをショートカットキーでToggleするためのshell script

DashのalternativeとしてLinuxマシンではZealを使っています。
Dashにはキーボードショートカット機能があり、指定したキーマップでDashのウィンドウのアクティブ・非アクティブをトグルすることができてとても便利です。
Zealには標準機能としてはこれがなくて、手で毎回画面を出し入れする面倒くささに真面目に対処してこなかったんですが、ちょっとUbuntuのキーボードショートカット機能とxdotoolによるウィンドウ操作を駆使して(というほどのことはしてない)一発でトグルできるようにするシェルスクリプトを書いたので自分用メモも兼ねて記録。

ウィンドウを操作するためにxdotoolが入っている必要があるのでaptで事前に入れておいてください。

Ubuntuの場合、設定方法は”Menu” -> “Keyboard shortcuts” -> “+ Add”で、このシェルスクリプトへのパスを追加するだけです(+xパーミッションをつけるかshを頭につけるかしないとエラーになります)。

Dashのキーボードショートカット機能ではDashをdeactivateするときDashウィンドウは最小化され見えなくなりますが、このシェルスクリプトでは単にZealを(このshell scriptで)activateする直前までactiveであったウィンドウを再びactiveにするのみで、Zealのウィンドウ自体はその元ウィンドウの後ろに行くだけで最小化されたり見えなくなったりはしません。
つまり、もしZealがactivateされたとき直前まで使っていたウィンドウを隠していない場合、deactivateされたZealは見えたままフォーカスだけが元のウィンドウに戻ります(元ウィンドウをZealが隠していた場合、元ウィンドウが最前面に出てくることに伴ってZealは隠されることになります)。
この挙動はデュアルスクリーンの左右でコーディングとZealを行き来するときに便利であると思います。

Dashと同じ挙動にする(Zealをdeactivateする際に最小化させる)場合、

xdotool windowactivate $LAST_ACTIVE_WINDOW_ID

となっているところを、

xdotool windowminimize $ZEAL_WINDOW_ID

と書き換えればOKです(それに伴ってCURRENT_ACTIVE_WINDOW_IDをこねこねしてる部分はまるっと不要になり、shell scriptはだいぶ軽くできます)。

Ubuntu MATE 18.04・Zeal 0.6.1・xdotool 3.20160805.1で動作を確認しています。

Chainerで書いたニューラルネットの理論計算量を推定するchainer_computational_cost

海洋の生物量の推定をするときには、実際にある空間の種ごとの個体数を1匹1匹数えるわけにはいきません。そこで、水中に残った生物の細胞のDNAすなわち環境DNAの密度を手がかりにして推計をするのだそうです(バケツ一杯の水で海洋生物の量や種類を知る)。

Chainerで書いたNNとダミー入力があるとき、そのNNのforward passの理論的な計算量・メモリ転送量を計算するchainer_computational_costを作りました。
ChainerのFunction Hookをベースにしているため、NNの定義コードに手を入れる必要は一切ありません。

Continue reading

tqdmをムリヤリprocess safeっぽくするdirty hack

生物界で最大の目(光を検知する器官)を持っているのはダイオウイカおよびダイオウホウズキイカと言われ、その大きさは30〜35cmほどにもなるのだそうです。

さて、世界70億人のPythonプログラマが手放せないのが、tqdmというプログレスバーを簡単に表示できるようにしてくれるライブラリです。
しかしながらtqdmは(当然と言えば当然ですが)複数のプロセスから1つのプログレスバーを管理することができるような設計にはなっていません。
たとえば高速化のためにmultiprocessingを用いて並列処理をしながらプログレスバーを出したいとき、tqdmはそのままでは使えないことになります。

例えば、下記のようなコードで単にtqdmのインスタンスを子プロセスに渡して中でupdateをすると…

import tqdm
import multiprocessing

def worker(pbar):
    pbar.update()

ar = [i for i in range(100)]
with tqdm.tqdm(ar) as pbar:
    jobs = []
    for t in ar:
        job = multiprocessing.Process(target=worker, args=(pbar,))
        job.start()
        jobs.append(job)
    for job in jobs:
        job.join()

実行結果はこんな感じ。

% python usage.py
  1%|▉                                                                                                 | 1/100 [00:00<00:25,  3.89it/s]

こんなふうに、100%まで行ってくれません。
端的に説明すると、100個forkされた内部カウント0の状態をそれぞれ+1するのが100回走るだけ、だからです。

じゃあ内部カウントが常によろしく増えるために、内部カウントを無視して共有メモリで常に正しくカウントしてればいいよね、っていう単純な発想による汚いハックをしてみました。

Continue reading

BundlerによるStructure from MotionでKAZE局所特徴量を使ってみた

一応お魚キャラとしてやっているわけですが,私のアイコンの魚を前からみるとどうなるの?とときどき聞かれます.
回答としては,このお魚は”幅を持たない”形状をしています.二次元です。

さて,大量の未整列な多視点画像からの三次元形状復元ついでにカメラの内外部パラメータの推定まで一気にしちゃう手法のことをStructure from Motion(SfM)と言い,結構長いこと研究されているホットな話題であります.

そんなSfMを行うためのツールにBundlerがあります.r○byのbundlerとは全く関係ありません.
Bundlerは局所特徴量を用いたsparseな画像間対応からSfMをするもので,一般的にはSIFTが使われていますが,対応付けは独立プログラムに委譲されてるので,うまくやればどんな局所特徴量でもSfMをお楽しみいただける設計になっています.

ということで,Computer Vision Advent Calendar 2012で書いたさかな前線 » ECCV2012で発表されたKAZE局所特徴量を試してみたの続きという事で,KAZE特徴量を用いたBundlerでのSfMを試してみました.実用的な例がほしかったのでw
CVAdventCalendarの@yasutomo57jpさんの記事の二番煎じです…

Bundler(やPMVS)のアルゴリズムについては他に譲ります(←

20121209_bundler_example_pmvs

※結論から言うとやり方がまずいのでto be continuedです…

Continue reading

研究の全てをgitで管理してたら( ・∀・)イイ!!

ここ1ヶ月半くらいかな,研究データの全てをgitで管理してみるようにしました.

画像系の研究なので,テスト画像だったり,テスト画像毎の処理結果だったり,実験を走らせるためのスクリプトだったり,プログラムだったり.
今まではreadme的なテキストファイルに「これはいついつこういう目的でやった実験だよ!(>ヮ<*)」と書いてるだけだったんですが,時間軸方向の管理能力には限界があったのです.

全てをひとつのリポジトリにぶっ込むとリポジトリが肥大化しすぎて(それこそ画像数万枚の実験を何百回となく行うので,バイト数でいうとLinuxカーネルどころじゃない),速度的にさすがに問題が出てくるので,ひとまとまりの実験をひとつのリポジトリにして,全体はsubmoduleとして管理します.
特定の実験のためのアドホックなプログラムはさらにそいつのsubmoduleで,みたいな.

まだ日は浅く管理してる要素の数もそれほどですが,
ドジっ子おさかなさんはしょっちゅう実験ミスするので,こういう形であらゆる実験結果を検証できるようにしておくと非常によいです.

みなさんもいかがでしょうか?

標準入力の数値列から統計量(総和とか平均とか分散とか)を計算するコマンドaccを作った

問:カレントディレクトリより下の階層にある全ての.cppファイルの平均サイズをバイト単位で求めよ

解答例(こういうのあまり得意ではないのでヘンかもしれませんw許してくださいw):

% du -ab | grep "\.cpp\$" | grep -o "^[0-9]\+" | acc --mean -f "%.0f"

研究の仮のデータ集計とかにこういうUNIXライクな小さいプログラム,欲しかったけどありそうでなかったので書きました.
accコマンドと名付け,githubに公開しています.

https://github.com/sakanazensen/acc_command

入力は数値がずらずらとstdinにやってくればOK.double型の範囲を超えないように.
現時点では,総和,最小値,最大値,平均値,分散,中央値の出力ができるようになっています.
中ではboost::accumulatorに計算を丸なげしてるだけです.
よって,boost::accumulatorにない項目(最瀕値や標準偏差)には当面対応できません.
計算量等の問題についてはboost::accumulatorのマニュアルを参照してください.

当然無保証です.MITライセンスとします.
本記事コメントで対応はする考えはあります.pull requestがあれば反応するかもしれません.

実装に対するツッコミもあったら嬉しいかも.
あまり自分のコードを人に見せたことがなくてよくない.