研究のためのディープラーニング系pythonソースコードデバッグTips

最近論文を読んでコードを動かすことが多くなって、よく使うデバッグTipsについてまとめてみた。

論文のソースコードって、動かないものが多いよねぇ。ドキュメントがないのは普通だし、あってもあてにならないし、そもそも依存関係を記述していなかったり、データのとり方が古かったり、GoogleのMagentaプロジェクトに至っては同じリポジトリ内のソースコードなのにPython2でしか動かないものとPython3でしか動かないものがあったり（実は私、このリポジトリのファンである）。Dockerあたりが普及すれば、このあたりの事情も少しずつ改善されるのでは、と期待しているのだが、当分これに付き合っていくことになりそうである。

まあ、フロンティアはそんなもの。道なき道で開拓をするのだから道しるべがないのは当然。そういう道をゆく際に最も大事なのは問題の箇所をできるだけ時間をかけずに特定してなんとかすること。コードが動かないから論文を読んでゼロから作ることも選択肢の一つだが、大体の場合、直した方が楽。なので、デバッグしてさくっと直そう。

# ソースコードにデバッグコードを埋め込む

printデバッグ、ではだいたいのDeep系ソースコードでは通用しないか、作業効率が悪いと思う。

デバッグ用のモジュールを使えば、その前後の変数を色々いじれる。ヨウスルに、
`import pdb; pdb.set_trace()`
をアヤシイところに入れて実行すればIDEでブレークポイントを設定した場合と同じようにそこで止まって、コンソールに変数を入れて中身を確認したり、弄ったり、いろいろできるワケ。自分がコーディングしているプロジェクトならいいが、あっちこっちで取ってきたプロジェクトを毎度毎度IDEで設定するのがめんどい、ソースコードはサーバーにあるからIDEでどうすんのかわからん、とかのときにvimでさくっとひらいて、コピペすればいいので、意外と使いやすい。

使い方の詳細はこちら
https://docs.python.org/ja/3/library/pdb.html
のドキュメントにありますが、とりあえず次の２つを覚えておけばわりと最強、だったりする

- `n`:next 次の行を実行
- `c`:continue 次のブレークポイントへ

Deep系のソースコードが動かないのが多くの場合、マヌケなバグだったり、ひとひねりするだけで解決するところだったりするので、意外と使える。

- 長所：場所を選ばず、環境を選ばず、エディタを選ばず。エラーメッセージからファイルを開いてさくっと原因を究明してさくっと直せる。
- 短所：「そんな簡単に原因がわかって直せたら苦労しねぇよ、俺の苦労なめんなよ」ってな状況だとか、「このレイヤーのこういうところをみたい」「あのレイヤーのあれをこうしてやったらどうだろう」という感じでとにかくたくさんわがまましたいとき、これで対応しきれないこともある。

# JupyterLab

Jupyter notebookでもいいし、jupyterlabでもいい。とりあえずソースコードをコピペして一行ずつ実行すれば、問題の箇所を見つけたり、色々試すだけでなく、バグの直し方が自明ではなくて、何度も試す必要がある場合、その場でテストしたり、結果を確認できる。

- 長所：いろいろ試して記録を残せるのでR&D向きである。試行錯誤が多くなりそうなときはこれがいい。
- 短所：いちいちコピペするのに時間かかる。jupyter notebook上で実行するのにいろいろ入力を整える必要があったりする（解決法はコマンドライン引数のところ）。人によってはJupyterNotebookの仕様に慣れないことがある。

## Tips

### 環境Issue

プロジェクトごとに環境を使っている場合、いちいち別々にjupyter serverを立てたくない。一つのjupyter serverでnotebookごとに環境を切り替えたい。

この場合ipykernelがいい。設定すればワンクリックで環境を切り替えられる
https://ipython.readthedocs.io/en/stable/install/kernel_install.html

### ライブラリIssue

他の場所にあるソースコードをライブラリのように使いたい、でもいちいちpipでインストールしたくない、ソースコードの場所を動かすのも嫌。

この場合、
1. `import sys; sys.path += ['{libpath}']`を使う
1. symbolic linkを使う

前者は慣れれば結構使える。ついでにsys.pathの中身を覗いてpythonへの理解を深めるのもいいかも。

注意点：

- 機械学習のバグはエラーメッセージにならないので、デバッグという行為には通常のプログラミングのデバッグに加えて、データの整合性を見たり、データの統計性を検証したりすることが重要で、そこから知見を得ることも多々あるので、JupyterLabが強力な武器となりえる。試したことがない方は一度試してみる価値はある。使いこなせれば効率が数倍あがるはず。
- JupyterNotebookは便利だけれど、気をつけないとすぐに長大になって手がつけられなくなるので、適宜自分ライブラリ化する場合も、外に`.py`を作ってJupyterNotebookから呼び出して確認するのが便利である。POCを回す人など、一気にライブラリを作り上げるのではなく、クラスや関数など、パーツパーツを作って案件で使いまわし使いながら全体を組み立てるようなやり方もしやすい。

### クラウドIssue

GPUが必要だよね。クラウドはどこでも使えて安いからいいよね。Colabもいいですが、データやカスタマイズなど何かと不便だよね。AWSでもGCPでもDeepLearning用のインスタンスイメージみたいなのがあって、環境が手軽に手に入る。しかし、サーバー上なので、ブラウザが自動に立ち上がってくれない。どうすんのよ。

この場合、Jupyterはサーバとクライアントに分かれているので、AWS上のJupyterNotebookに手持ちのPCからアクセスすればよい。

愚直にやるなら１．サーバ側のポートを開けて、２.Jupyternotebookの認証やセキュリティ設定をして、3.アドレスを取得して、手持ちのPCからアクセスする。一度やっておけばそれなりに便利、特にドメイン取得してアドレス固定すればどこからでも気軽に使えるようになる（携帯からでも弄れる、すげぇやりづらいけれど、ちょっとだけイカした気分になる）。

ただ、上記のやりかたは思いのほか、設定するのが面倒だったりする。セキュリティ上色々問題があるばあいもある（JupyterNotebookって最強のバックドアだよね）。

もっと簡単なやりかたはsshでログインする際にポートフォワードすること。で手持ちのPCから自分が指定したところにアクセスすればJupyterNotebookが使える。ポートフォワーディングは`-L`オプション。gcpの場合はgoogleのcliツールにも似たオプションがある。

注意点：

JupyterNotebookはブラウザとサーバの通信が途切れるとプログラムも止まるので、サーバでやる場合はあくまでも動作確認だったり、調査用で。スクリプトで実行するときは、JupyterNotebookから実行可能ファイルとして.pyファイルをexportできる。

### メイン関数とコマンドライン引数

ディープラーニングはハイパーパラメータワンダーランド。何かと後で変更して試したいパラメータが多くて、その変数をどこかに分離保存されるようなお上品な作りになっていないことがほとんどだ。だいたい皆さん環境変数のparserのデフォルト値にそのまま、ポン、そんで渡してきた変数だけを加えて`main(arg)`へと、ドン、としかもあろうことかあっちこっちのモジュールで`arg.hoge`を使っていたり。

んで、JupyterNotebookから特定の関数を呼び出したいとなると、`args`探しの世界へ迷いこんでしまうわけだ。

この場合、Jupyter notebookでもargsオブジェクトを作って使えばいい。`main`関数のarg周りのコードをコピーしてきて、`args.parse()`じゃなくて`args.parse(command)`みたいにすればオーケー。commandには['main.py', '-gpu', 'true', ...]的な感じでいろいろ渡してあげればよい。

- tensorflowのflag問題

みんなargsを使っているならいいのだが、tensorflow系のソースコードにtf.flags云々があったりする。これもjupyter notebook上でargsと同じ要領で扱えるが、時たま`Unknown command line flag 'f'`的なエラーを吐く。fフラグがないとかイミフなことを言っているが、まあ、定義してしまえ

```
tf.app.flags.DEFINE_string('f', '', 'kernel')
```

# 最後に

githubランドには楽しいdeeplearningの宝箱がたくさん。しかし宝箱を開けるにはちょっとばかりの苦労と工夫が必要。デバッグ達人になって宝を集めよう。いぇい！