機械学習の開発における記録について
記録の問題の多様さとむずかしさ
機械学習の試行錯誤は何らかの形で記録したい。 この形はあまり定番が無い気がする。
まず機械学習のプロジェクトと一口にいっても、問題の不確かさが結構みんな違う。 数日調査して結果をレポートにまとめて終わり、で良ければ何か特別な記録が必要、という事は無い。
一方でさっぱり分からない類の問題について、いろいろ試したがそれぞれ別々にダメでした、 みたいな奴は、記録が凄く難しい。一日のうち、試した事が10個とかある。 構造化せずに単に列挙していけば一か月分くらいになるともう理解不能になってしまう。 手が式はもう2ヶ月くらいやってて、まだまだ先は長いので、当然これではダメだ。
自分がこれまで見た職場だと、こういう一日の単位ですら試行錯誤が多く、 結論も曖昧な物だと、割と個人の中にノウハウが蓄積されがちに思う。 Aという仮説に対しての結果は分からないが、Bを以前試してこういうバグがあった時にCだったからたぶんこうなるんじゃないか、みたいなのの積み重ねになり、この「たぶん」もAの内容によって曖昧さが大分違う。
issueはダメだった
今回は、最初はモデルを試す都度、issueにidとTensorBoardのスクリーンショットを貼ってたが面倒になってやめてしまった。
だが、tensorboardのスクリーンショットを残すのはなかなか良いアイデアと思っている。 tensorboardのデータ自体はcloud storageに残っているのでスクリーンショットをとらなくてもこれらを見る事は出来るのだが、 複数並べてみたいとか気軽に確認したい、みたいな事は結構あるので、必要なスナップショットをとって並べる事自体は結構必要と思っている。
issueは記録にかけるコストとしては大きすぎてダメだった。
gistにモデルのコードを貼る
最近はモデルのコードをgistに貼って、そのファイルの短縮urlを記録する、という事をやっている。 これは面倒だが、記録としてはなかなか良い。例えば以下。
https://gist.github.com/karino2/b889ef4d70226b829d86ac26390bb40e
githubに履歴が残るのだから二重管理なのだが、colabの該当場所だけ読み取るのは結構面倒くさい。 例えばEnoderにはCNN系列のもRNN系列のもTCN系列のもself attention系列のもあって、 いろいろ取り替えて実行している。 この時に、今回のモデルに関係ない上記の構成要素がコードに入るので、その結果のモデルがどういうコードなのかを読み取るには解読が必要になる。
gistならその時必要な事だけを抜き出せる。このその時点で記録したい事だけを抜き出して、それを時系列で見れる、というのは必要な事に思う。
ただgistでいまいちなのは、必要になる前に整理しなきゃいけない所。 やはり必要になった時に必要になった所だけを本当は整理したい。でもこのやり方ではgistの履歴が時系列にならないのでやはり毎回整理する必要がある。 本当は各モデルのidごとにgithubの履歴があって、そこから必要な奴だけ整理したものをあとから記録出来たらいいんだが。
モデルのコードというのは毎回、だいたい一部しか変更されない。 gistに貼る良い所というのは、この一部だけを更新出来る、という所にある。 しかも記録としてはその時点のちゃんとした全モデルコードが残る。
管理したいのはハイパーパラメータ「では無い」。 コードにはバグが含まれていて、そのバグも含めて管理したい。 ようするに同じハイパーパラメータのバグあり版とバグ無し版の両方を管理できる必要がある。
さらに、モデルの構造は頻繁に変わる。 例えばresidual connectionをブロック間に入れていたのを、最初と現在のブロックに変えたり、ブロックの内部にresidual connectionを足したり、といういろいろな構造を試す。 さらにこれらにもバグが多く含まれる。
そして効果の無かった構造は基本的にはgitには無くしていきたい。 フラグで両方試せる、みたいな事にはしたくない(コードが複雑になるから)。
ただし両方試せる程度に残しておく価値のある場合もあるので、 gitに残すものはコードの管理でベストなように選びたい(記録の事情で歪ませたくない)。
という事でハイパーパラメータを管理したい訳では無い。
復元するのと記録に残すのは違う
githubには各リビジョンの完全なコードが残るのだから、DRY的にはこれでなんとかならないか?という気持ちになる。 cloud storageにはTensorboardの全データが残っているのだから、スクリーンショットをとらずに、このデータを使えばいいのでは?とか。
実際通常のソフトウェア開発では、なるべくgitに全部集約する方向になっていると思う。 だいたいWikiとかのドキュメントはサーバーのシステムが変わるとかの良く分からない事情によって、 プロジェクトの歴史のどっかで失われる。 レポジトリだけが残るというのをベテラン開発者は皆知っている。
だが、復元出来る必要性と記録の必要性は違う、というのが自分のここまでの結論。
復元は同じ状態を再現出来るのが大切だ。 一方で記録は再現するのではなくて、それらを眺めて考える材料にするものだ。 必要な事だけを並べて、それを見て考えたい。
そもそもに記録を見て考える、というのが、機械学習の試行錯誤で一番難しい事を実行している所だと思う。 ここが十分に行えれば、目的は達成できる。 逆にこれが不十分では、他がどんなに完璧でも結局リリースまではいけない。
現状の機械学習で、ありがちな応用以外がだいたい失敗しているのは、 ようするにこの記録を見て考える、という所が必要な水準に到達出来てない、という事を意味していると思う。 だからここの必要な条件に妥協をしてはいけないと思う。 復元に必要な情報も少し目に入って邪魔だけど我慢してね、ではいけない。 それなら記録や管理の面倒さを受け入れる方がマシだ。
記録は並べて考えるものなので、目に入る量というのが結構大切になる。 並べてみる為には、一つ一つは結構短くないと並べて見れない。 だから復元に必要だが考えるのに要らない情報とういのがあるのは、ダメな気がしている。 また、並べて考える為には閲覧の利便性が大切と思う。 具体的にはスクロールとかが早くないとダメだ。 使いやすさが重要なので、ツールを作りこむのは相当ガチでやらないと厳しいかなぁ、という気がしている。
以上から、必要な所だけを抜き出す、というのは記録という点では必要だと思う。 復元には考えるのに要らない事も必要。一方で考える時にはそれらはあってはいけない。 抜き出す感じに出来れば良いとも思うので、 何か専用のgit viewer的なものとメタデータみたいな組み合わせで解決するのが理想とは思うが、 そういうのは中途半端な物があっても使い物にはならないので、 とりあえずフリーフォーマットでやっていく方が現実的と思う。
試行錯誤が多いと、全部gitにpushするのもいまいちなんだよなぁ。履歴の意味が分かりにくくなるので。 試行錯誤が多い機械学習の開発では、commitの粒度と試行錯誤の粒度は明らかに合ってない。 区切りが良ければfeature branch的に運用するのがいいのだろうが、機械学習の実験は残念な事に区切りという概念はあまり無い。 さらにcolabはgitとの統合がいまいち使いにくいのでブランチマネージメントとかもやりにくい。
既存の機械学習の記録系のツールはだいたい復元の事を中心に考えるので使い物にならない。 そこはgitで十分足りている。
ブログ的な作業記録とモデルのid
試行錯誤が多いと、各モデルを簡潔に記述するのは不可能だ。 モデル自体がどうであるかを厳密に記述するには、結局コードと同じだけの物が必要になる。
何か実験をする時は、そのモデルに識別子を振るのが良い。 この識別子はそれだけで何か分かれば理想だが、だいたいはモデルは曖昧な仮定がいろいろ混ざるので識別子だけでは分からない物も避けられないと思う。 だいたい試行錯誤は前の結果を受けて一部を変える、という形式なので、 最新版のモデルを理解するには、最初のバージョンから順番に変更を適用していく、というプロセスが必要になる。 それをidに全て込めるのは出来ない。
モデルのidに対して、それが何なのかの記述は自然言語でフリーフォーマットでやるのが良いと思う。 その時考えている事、試した事、など。その他関係無い事もだらだら書いておく。
何故この形式が良いのかというと、モデルの試行錯誤というのは、単体ではあまり意味が無いからだと思う。 だからスポットのgitのリビジョンというものに、必要な情報を全て込める事が出来ない。 あくまで前のバージョンを受けてそこで何かをやるのであって、この前のバージョンとのつながりの方が本質になる。
しかもこの形式の良い所は、あとから追記が出来る、という所もある。 試行錯誤は基本的には多くの間違いや勘違いを含む。 自分の間違いや勘違いを理解する過程とすら言えるくらいだ。 だから勘違いをあらかじめ想定していないシステムはダメだと思う。
gitのコミットログはあとから書きかえる事を前提とはしていない。 だからこれだけでは不十分。
ただ記録をあんまり書き換えてしまうのも良く無いので、追記くらいにいしておくのが良い気がする。 勘違いと思ってたのが勘違いの場合もあるし、さらに違う勘違いも入っているのが普通なので。
という事でモデルのidを振り、それについての自然言語による雑感とかを書いておく、というのが良い記録じゃないか、と思っている。
以下のブログなどが作業記録の例。 手書き数式認識ソフト、tegashiki開発その5
試行錯誤のコストが最重要
試行錯誤が一番多くの労力を費やす所なので、これを大変にしては本末転倒だ。 記録の面倒さはいつも試行錯誤の面倒さの次に来る優先度と思う。
試行錯誤をやりにくくしては本末転倒なので、colabセントリックにしてプロセスの方をそちらに合わせたい所。 例えばモデルを別.pyファイルに出して毎回コミットするようにして、構成要素とモデルを別ファイルにしてモデルのファイルの履歴でgistと同じようなものが見れるようにする、 みたいなのは、gistを作る手間が省かれるがcolab上での取り回しが悪くなって試行錯誤が劇的にやりにくくなるのでいけない。
例えば、コミットの粒度と試行錯誤の粒度があってない時に、試行錯誤の方を変更してはいけない。
コードを整理する、みたいなのが試行錯誤時点で必要になるアイデアは全部ダメだと思う。 試行錯誤というのは本質的にカオスな物で、多くの勘違いの中で行われるものなので、そういう性質が無かったら~的なプロセスは使い物にならない。 そしてソフトウェア開発の多くは、ここまで勘違いが多いものを想定していないので、 そのまま流用するのはだいたい間違っている。
記録のコストが次に重要
記録を考える時には、コストが凄く大切だ。 例えば各モデルについて、その時のabstractを数行で書いて、そのあと実際の構成を全部書いて、結果をちゃんとまとめて、あとで何か勘違いが分かったらその訂正も分かるようにちゃんと更新する、みたいなのはダメだと思う。
そうなってる記録があったら素晴らしいが、そんな物を作るのは大変すぎる。
また、記録は本質的に、見るまでは価値が無い。 記録のうち必要になるのは一部で、それはあとから何か調べたい時に初めて判明する。
理想的には見られる所だけが記録になっているのが望ましい。 それは事前には分からないので、出来たら必要になったら整理出来る方が良いと思う。 これはメモの類で最初に構造化するのがうまく行かない、という話と通じるものがあると思う。 wikiとかの議論で良くあるやね。
なるべく事後的に、だんだんと望む記録になっていくようなやり方が理想で、 事前にいろいろやらせるのは良く無い。
だがこの理想的な形は自分には良く分からない。
とにかく、
- 記録の多くは結局見られない
- 見られない記録を作るのは完全に無駄なので、記録を全てちゃんと書く、という路線はダメ
- でも必要になった時には良い記録を見たい
という性質がある。
現状まとめ
そこで現状は
- モデルのid
- tensorboardのスクリーンショットとaccuracyの記録
- そのモデルidのコードを抜き出したgist
- その時に考えた事などの作業ログ的な文章
を用意しておいて、必要になったら作業ログ的な文章に追記していって整理をしている。 ただ4の方はどうしてもその直前との差分的な情報しか残らないので、もうちょっと構造化されて欲しい。
1と2は大した事無いのでいいのだが、3と4はもうちょっとなんとかならないもんですかね。
普段の活動のカオスさを思うと、フリーテキストである必要はあると思う。 シーケンシャルなブログとして書いていくのだが、 どこかで項目ごとにまとまっていく仕組みが欲しいなぁ。
あと2のスクリーンショットは現状OneDriveに置きっぱなしで公開出来てない。 記録と合わせて公開したいが面倒くさいのは嫌だなぁ。