情報系の手考ノート

数学とか情報系の技術とか調べたり勉強したりしてメモしていきます.

研究用のプログラムでやっておくといいこと

修士の研究を通じて,研究用のプログラム作成時に意識したりやっておくと良いことというのが,自分の中である程度確立されてきました. この記事ではそのノウハウのようなものを共有することを目的として,なにをすれば良いのか,どうしてそれをすると良いのか,という点について書いていきます.

これから研究を始める学部生あたりが対象読者になると思われます. もしかしたら修士学生でも役に立つかもしれません.

また私の研究がニューラルネットワークに関するものだったため,主にニューラルネットワークの研究を行う場合のものになります. 他の研究の場合でも使える知見はあるかもしれないですが,あまり期待しないでもらえると助かります.

拡張性の高いプログラムを心掛けよう

研究で使うプログラムは頻繁に修正されます. 手法を改良したとき,新たに実験を行うとき,とにかくいろんな時に修正されます. 常に仕様が変わり続けるわけです. そのため拡張性が高いプログラムを意識することが重要になります. 3層のネットワークを4層にする,というような状況で層数をハードコーディングしないという工夫をするだけで,簡単に層数を変えて実験できたりします. 拡張性を意識しようとすると,最初にプログラムを書くときが大変にはなります. ですが一度作ったプログラムが拡張しやすければ,後々の修正・拡張に手間がかからなくなるので,研究の期間が長ければ長いほど有益な結果になります. まぁある程度限度というものはありますが….

ログめっちゃ残せ

ログは無理のない範囲でめちゃくちゃ残しておきましょう. 標準出力にログを吐いて,リダイレクトでテキストに保存する程度で十分です. もっと管理できるにこしたことはないですが,そこまでやる必要はあまりないです. ただ生で保存するととんでもないデータ量になるので,適宜圧縮するなりしておくと良いです.

なんでこんなことを言うのかというと,実験の考察等に非常に有用だからです. 最近のニューラルネットワークの研究はアホみたいに時間がかかることがざらにあります. ものによっては1試行で1週間とか平気で使います. そんな実験で想定外の結果が出たときに,原因の考察に必要な情報が多いほうが良いに決まってます. 無いよりはあったほうがいいです. 必要な情報が無いから再度結果を取り直しなんてことになったら目も当てられません. 原因調査のために数ヶ月要するなんてのは,1年も研究できない状態では絶望的な状況です.

そのためには必要そうな情報はできるだけ出力しておく必要があります. 例えば損失関数の値です. 実験内容にもよりますが,最低各エポックごとの値を出力しておいたほうがいいでしょう. そうすれば,例えば NaN が出たとしても原因となるエポック数が特定できます. 損失関数が複数の項から構成されるなら,各項での値も出しておくと尚良いです. どの項で NaN が出たのか,というのがわかります. それだけでも考えられることが多いです.

あと重要なのは使ったパラメータを表示しておくことです. エポック数とか学習率とかです. 実行時にこれを表示して保存しておけば,その実験結果がどんな条件のものかわかります. 人間簡単に忘れたりするので,こういう情報はしっかり出力しておくと良いです. 実際私も過去の自分に幾度も救われています.

他にも必要そうな情報があれば無理のない範囲でしっかり保存しておくといいでしょう. 未来の自分を助けることができます.

乱数は固定しよう

乱数を固定できればリファクタリングやプログラムの修正を行った後に,動作確認をするのが格段に楽になります. さらに実験の再現性もある程度担保できます. やらない手は無いです. PyTorch を使うならとりあえず下記コードを最初に実行しておけばいいかと思われます.

# Python の乱数
random.seed(seed)

# Numpy の乱数
np.random.seed(seed)

# PyTorch の乱数等
torch.manual_seed(seed)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True

上記コード実行後に実行するプログラムは seed の値ごとに動作が固定されるはずです. これによってプログラム修正後に,前のプログラムと同じ動作をするか確認するという作業が可能になります. 同じ seed で同じ結果が出るなら同じプログラムと見做せます. 研究では実験結果の正当性が重要になるので,プログラム修正後の動作確認ができるのは非常に重要です. 丁寧にログを出しておけば,どこでプログラムの動作が変わったかを調べるのも簡単になります.

というか動作確認はちゃんとしましょう. ソフトウェアの開発で単体テストなんかが重要視されているのに,研究では重要ではないなんてことはあるわけがありません. できるならちゃんとテストをすべきではあります. とはいってもそんな余裕が無いかもしれません. そんな場合でも seed を固定できるようにしておけば,同じ動作をすることは保証できます.

後で述べますが,この seed の値をコマンドライン引数で指定できるようにしておくと尚便利になります.

コマンドライン引数で動作の切り替えをできると便利

実験をするとき様々な条件で比較をするというのは当たり前に行われることだと思います. 例えばエポック数を変えるとか,学習率を変えるとか,バッチサイズを変えるとかいくらでも条件はあります. いわゆるハイパーパラメータというやつです. このハイパーパラメータを変えて実験を行うとき,毎回プログラムを変えるのは非効率的です. それにどのパラメータで実験した結果かもわかりにくかったりします.

このようなパラメータをコマンドライン引数で指定できると,プログラムを変えずエポック数や学習率といったパラメータを実行時に指定して実験ができるようになります. 例えば以下のような感じです.

> python3 main.py --epoch 10 --lr 0.1
...
> python3 main.py --epoch 30 --lr 0.01
...

Python なら標準でコマンドライン引数をパースするパッケージがあるので,それを使えば大して難しいことでもありません. コストパフォーマスは間違いなく良いです. 特に修士に進むつもりの学部生のように,ある程度の期間研究をするなら尚更コストパフォーマンスは良くなります.

実験の実行やデータの集計や整形はスクリプト化すると便利

上で述べたようなコマンドライン引数による実験条件の指定などをできるようにしておくと,もっと便利にできるようになります. それは実験のスクリプト化です. 実験をする時に,シェルスクリプトを書いてそれを実行するだけにしておくというものです. これは複数試行実験する場合に真価を発揮します. シェルスクリプトにコメントとして,いつどんな目的でどういう考えに基づいて実験したのかを書いておけば,あとでその実験の意図などがわかるようになります. 結局は後々の自分のためです.

またデータの集計なんかもスクリプト化すると楽ができます. 複数試行実験した場合,精度等の平均や標準誤差等を計算することになります. この計算をスクリプト化することで,毎回の実験では実行用のスクリプトを動かすだけでよくなります. 無駄な手間が減って実験に集中できるうえに,人的ミスを減らせます. このような機械的な作業は計算機にやらせてしまうのが効率的です.

機械的な作業といえばデータの可視化などもあります. 精度等の平均をグラフにプロットする,という操作も当然スクリプト化できます. これもやっておくと,大量に実験した結果を機械的に可視化できます. いちいち Excel なり Google Spread Sheet なりを開いて可視化するよりよっぽど効率的です. それに,やはり人的ミスを減らせます.

実験結果から考察等をする時に,人的ミスがまず間違いなく起こります. 人間そんな完璧じゃないです. そんな人的ミスのリスクを減らせるうえに,研究の重要な部分に時間をかけられるようになるという意味で,実験の実行,データの集計,可視化等のスクリプト化はかなり重要です. そしてそのためにも,コマンドライン引数による実験条件の実行時指定をできるようにしておくのは大切と言えます.

git を使って再現性を確保

実験の実行から可視化まで全てをスクリプト化すると,git の有用性が顕著になります. git を使ってきれいにブランチを分けて開発をしろという意味ではなく,commit してバージョン管理をするだけで十分です.

実験用のプログラムは頻繁に更新されます. そんなプログラムを使って実験をすると,実験に使ったプログラムは遠い過去へと消えていきます. git でバージョン管理をしておけば,過去の実験で使ったプログラムがそのまま復元できるようになります. これが一番の強みです. なので研究用途では,最低限実験前に commit するだけで十分です. commit メッセージだって"何月何日の実験"とだけ書いておけば十分です. これを GitHub なり GitLab なりに上げておけば,バックアップにもなって尚良いです. 昔の実験をやり直すような場合には,対応する commit まで戻ればいいです. プログラムを修正していたら動かなくなった,そういう場合には動いていた commit まで戻ればいいです. それだけの用途でも git は十分に便利です.

また実験の実行や可視化のスクリプト化を行っているなら,そのスクリプトも git で管理することができます. そうすれば対応する commit に戻ってスクリプトを実行するだけで,そのときと同じ実験ができるようになります. ここまでやれば実験データを損失した場合にも対処できるようになります. 安心感がとんでもないことになります.

データの保存媒体は卒研や修論の締切に壊れるなんてのは良く聞く話です. 間違ってデータを消してしまうこともあります. git を使って管理しておけばそういう場合への対処も可能になります. やはり未来の自分を救うことができます.

まとめ

以上が私が研究を通じてやっておいてよかったと感じた点等になります. どちらかというと何かあったときの対処がしやすい工夫,という感じの内容が多いですが,それだけ想定外の事態が多発していたということでしょう. 想定外の事態への対処を想定していると,ある程度安心して研究を進められるので,精神的にも効果的だったのかとも思います. これから研究を始める学部生は,思ったよりイレギュラーが多いぞということを気に留めておくと良いかもしれません.