機械学習モデルを構築するときに考えることと、全体の流れ
初の機械学習プロジェクトを仕事でおこなった。その際に、各プロセスでこういう部分をみたり考えたりした方が良さそうだと思ったことをまとめる。
なお、機械学習プロジェクトの話は「仕事ではじめる機械学習」をはじめとして既に色々なところで議論されている*1。しかし、いくつか読んだがマクロな話が中心だったので、どちらかというとミクロ部分の機械学習モデルを構築する という部分を中心に書く。
なお、経験者から指摘されたり教えられた点を1度機械学習プロジェクトをやっていったんまとめただけなので、今後追加・修正される暫定版なことに注意。
ワークフロー
機械学習プロジェクトのワークフローはざっくりと以下のようになる。
1. 要件を定める
やることとしては、
- 目的
- スケジュール
- 期待値
- どの程度の精度ならよいか
- どのようなアプローチを取るか
を決める。
このあたりの話は、前述のように「仕事ではじめる機械学習」など、機械学習プロジェクトのマクロについて書かれた本に書かれているので詳細は省く。
何故これが必要かというと、目的によってアプローチは評価の仕方などが大きく変わる。また、スケジュールや精度によってもアプローチが変わる。例えば、作成したモデルに対して数TBのデータに対して推定をするとなると、推定に時間がかかるようなアルゴリズムよりも多少は精度は下がるが推定に時間がかからないアルゴリズムを選択することもありうる。
2. モデルの推定ロジックおよび、InputとOutputを決める
モデルに対してどういったデータをInputとするか、Outputされるかについて考える。
なお、ここでいう『モデル』は「Input→機械学習アルゴリズムによる推定→評価」をまとめたものとした定義している。
全フローのうち、いちばん大事な部分なのでここは時間を使って議論をする必要がある。
推定ロジック
どういうInputから最終的にどういうOutput(それぞれ後述)を作るか考える。このとき、機械学習アルゴリズムを複数使う場合はその流れやそれぞれのInput, Outputもどうするか決める。
Input
Inputとは、要は前処理後に機械学習アルゴリズムに入れるデータのことを指す。これは、データ自体の形式(特徴量や集計粒度)だったり、それらを使ってどうTrain, Test, Validationデータに分けるか、使用するデータの特徴に合わせてどう加工して利用するか、といったことを指す。
特徴量であれば、どういったものが値の違いを捉えられるかといったことを考える。例えば、2値分類問題であれば、0と1ではどういう要素で違いが生まれそうか?といった風になる。ただし、詳細は後述するがモデルがある程度固まるまではスピードを重視するために、凝った特徴量はいったん作らずに集計値などの簡単な特徴量のみを作成して用いる。
また、Inputの集計粒度に関しては最終的にこのプロジェクトで納品するもの(Output)とまったく別の粒度となる場合もある。例えば、「曜日毎の平均売上の予測をする」プロジェクトであればそのまま曜日単位粒度のデータを使ってもよい(Input粒度 = Output粒度)し、日時(YYYY-MM-DD HH)粒度のデータ(Input)を使って予測したデータを曜日粒度に再集計をする(Input粒度 ≠ Output粒度)やり方もある(Output部分で図示)。
このように、粒度を変えた場合は取るアプローチや特徴量が変わる。
更に、Train, Testデータはどれくらいの比率にするかデータ量に応じて割合なども変わるし、Cross Validationをするかも変わる。また、Cross Validationの方法もデータによってどのように分割するのが適切かが変わる(Group-k-FoldやStratified-k-Foldなど)。更に、データが不均衡な場合には対処が必要となる。このようにInputについて考えることは色々とある。これらは後の「5. EDA」で確認をおこうなう。
Output
ここでいうOutputとは、機械学習アルゴリズムによって推定された後に最終的に評価する際のデータを指す。そのため、機械学習アルゴリズムの推定結果 ≠ Outputとなることもある。
例えば、前述のInputでの「日時粒度で推定して曜日粒度に再集計」のように機械学習アルゴリズムでの推定粒度と最終的に出来上がる粒度は別となることもあり、後者の「最終的に出来上がる粒度」がOutputとなる。
また、他にも機械学習アルゴリズムによって推定された結果に対し、何かしらの加工をした結果を評価する場合などは「何かしらの加工をした結果」をOutputとしてその加工をどうするかも考える。
例えば2値分類であれば機械学習アルゴリズムによって、0/1となる確率が推定され、その結果に対して精度がよくなる閾値を選び0/1に変換を噛ませるので機械学習アルゴリズムの推定結果 ≠ Outputとなる。また、このときに「単純に評価値が一番良くなる閾値を選ぶ」こともあれば「ある条件を満たした上で評価値が一番良くなる閾値を選ぶ」場合もある。例えば、年代別の来店客数の予測をしたいときに「実測値の年代の比率にある程度沿ったアウトプットをする中で、評価値が一番良くなる閾値を選ぶ」必要がある、など。
このように、推定結果の後に「どういうことを考慮して」「どういう処理をした」結果まで考えるかということを強調するために推定結果ではなくOutputまでを考える、と書いた。
また、合わせてその結果をどう評価するか(評価関数)も決める。
3. ベースラインを定める
「2. モデルの推定ロジックおよび、InputとOutputを決める」で決めた評価方法を使ったベースラインを考える。
例えば、分類問題において、分類予測結果を乱数にしたときの評価値を作成してこれをベースラインにすることは「テキトーに予測した場合と比べてこの予測アルゴリズムは優れているかどうか」ということがわかる。
これはつまり、この機械学習プロジェクトは何と比較して良いモデルを作らないといけないかによってベースラインが変わる。例えば既存のモデルよりも良いモデルを作ることが目的であればベースラインは「既存のモデル」になるし、職人の勘による人依存の脱却が目的ならば「職人の勘」がベースラインになる。*2
4. 前処理
ローデータからInputを作るための処理。そのままなので省略。
5. EDA(探索的データ解析法:Exploratory Data Analysis)
Inputデータの分布やノイズなどの特徴を可視化し、確認することで、このデータで実現したいことは可能か?ということを見積もることができる。また、「2. モデルの推定ロジックおよび、InputとOutputを決める」で書いたようなCross Validationの取り方など、どういったことを考慮しないといけないデータかなど後工程に関わることをざっくり把握することができる。
また、EDAによってデータが当初の想定と違う場合は「2.モデルの推定ロジックおよび、InputとOutputを決める」パートに戻って考え直す必要がでることもある。
6. モデルの適用
モデルが固まるまではハイパーパラメータを細かく調整せずに、とりあえず最低限だけざっくりおこなっておく。
7. モデルの振る舞いの確認
機械学習アルゴリズムによって推定された結果およびOutputについて可視化をおこなうことで、そのモデルの振る舞いを確認することができる。
このとき、粒度などを変えられるのであれば色々と見ておく。
おそらく初心者にありがちだが、私も機械学習をやりはじめた当初は、評価関数の結果(F1-ScoreとかRMSEとか)だけを気にすればよいのでは?と思っていた。しかし、予測された値の振る舞い自体も可視化をしてみる必要がある。
例えば、0~100の連続値の予測をおこないMSEを損失関数としたときに、実データのほとんどは0となっているときに全予測が0となるように予測がされてしまうことがある。損失関数がMSEなので、変に100などを予測するよりもひたすら0と予測した方が損失関数は小さくなるためこのようなことが起きる。しかしこのとき、(評価関数がMSEとすると、)MSEだけを見るとそこそこ良い精度になっているが、モデルとして考えると「全部0と予測する」モデルは明らかに問題があることがわかる。
これを防ぐには、前述のように、 推定された結果/Outputの分布を確認する必要がある。例えば先程の例に対して実測値/予測値の散布図なり棒グラフなりで可視化をしていたら簡単に違和感に気づける。
また、それらの粒度を加工したデータの可視化をすることでもモデルの振る舞いの特徴を確認することができる。 例えば、ユーザー毎の各日時粒度がOutputだとしても、それらの時間帯(昼とか夕方とか深夜とか)毎でRMSEを再集計することで「このモデルは深夜帯の予測は悪いが、昼は良い」のようにモデルの予測がどういうときに上手くいくか/いかないか ということがよりはっきりとわかる。
つまり、モデルの振る舞いを確認することでこのモデルの改修ポイントを把握することができる。はじめはいったん確認のみおこない、8に進む。
8. 本番データに適応して時間を測る
「7. モデルの振る舞いの確認 」でいったん推定ができたので、「4. 前処理」「6. モデルの適応」と同様のことを本番データに対してもおこなう。既に「4. 前処理」「6. モデルの適応」自体が本番データの場合はそのときの時間を指す。
「実際に運用するときにどれくらいの処理時間がかかるか」を把握することが目的。
例えば、データが100億件あって多すぎるのでInputデータとしては1/1000にランダムサンプリングした100万件使い、これらを使って学習や評価をおこなったとする。そのため、最終的に100万件のInputを用いて作成されたモデルを使って100億件*3に適用して予測するという場面は往々にして存在する。
このとき、Input(100万件)を使ったときの「6. モデルの適用」は10時間ほどで学習・推定が終わったとしても、そのモデルを100億件に適用するときに、100億件のデータのInput用加工も含めて1ヶ月かかるみたいなことが起きることがある。
そのため、事前に例えば1億件だけを使って推定時間を計測して、そこからざっくり線形増加するとして100億件の場合どれくらいの時間がかかるか(1億件の100倍)を見積もり、それがプロジェクト要件やスケジュールに沿うかを確認する。
このときスケジュールなどに沿わない場合はこのモデル自体が使えないことになるので特徴量を減らしたり、アルゴリズム自体を変える必要がある。
問題がなさそうな場合は、そのまま「 7. モデルの振る舞いの確認」でわかった改善点をもとに特徴量を追加したりロジックを多少いじるなどをして再度「6. モデルの適用」をおこなっていく(6, 7を繰り返す)。
9. ハイパーパラメータチューニング
6, 7がある程度終わったら、先程までは最低限のパラメータでおこなっていたので、ここではじめて本格的にハイパーパラメータチューニングをおこなう。
チューニング後は再度「7. モデルの振る舞いの確認 」をおこない、変な振る舞いをしていないか確認をする。
10. システムに載せる & 11. ドキュメント化する
最終的にできたモデルをシステムに載せる。そのままなので省略。
最終的にできたモデルに沿ったドキュメントを記載する。そのままなので省略。
全体フロー振り返り
改めて全体フロー図を試行錯誤のループも含めて以下に図示する。
この中で大事なことは、はじめの「6. モデルの適用」は凝ったことをやらずに最低限の特徴量やハイパーパラメータチューニングをおこなってモデルの大枠を作り、改善用の「7. モデルの振る舞いの確認 」コードをいったん書いておきつつ「 8. 本番データに適応して時間を測る」までいくこと。
理由としては、モデルの大枠さえできたら、その後そのモデルをベースにして特徴量を追加したりハイパーパラメータをチューニングしても精度が今よりもめちゃくちゃ良くなるということはあまりないからです。そのため、さっさとモデルの大枠(いわゆるパイプライン *4 を作ることでそのモデルが大体どれくらいの精度/時間がかかるのか を早くから見積もることができます。また、精度が良くても本番運用する際に運用が難しくなるようなモデル(推定時間がめちゃくちゃかかるなど)ができるということもあるので、そのことも踏まえてさっさと「8. 本番データに適応して時間を測る」をする必要もあります。
そして、**「精度もそこそこいきそう」かつ「推定時間も問題なさそう」か確認をした上で、そこからより精度を上げるためにちょっと作るのが面倒な特徴量を追加したり、「7. モデルの振る舞いの確認 」で見えた改善点を反映したりをおこなうという「 6. モデルの適用(+特徴量追加や追加ロジック) 」「7. モデルの振る舞いの確認」を回していきます。
その後、内容がかたまったら最後の最後に「9. ハイパーパラメータチューニング」をおこなうというフローになる。
おまけ
レビューについて
前述のワークフローの各ターム毎にコードレビューはおこなった方が良い。理由として、機械学習プロジェクトでは試行錯誤することが多いのでスパゲッティコードになりやすい。そのため、仮にコードが間違っていた場合、間違った結果をもとに試行錯誤することになるため早めにレビューを挟んでおいた方が良い。
また、そもそも人に読ませることを前提にコードやコメントを書いておいた方がスパゲッティになりづらいし、レビュー前に自分でまとめるときにわけがわからない状態になりづらいためミスを減らすことができる。もちろん、キレイなコードを書けば書くほど時間はかかるので、「きれいに書け!」ではなく「ある程度整理されたコードを書け!」の方がニュアンスは近い。
レビューの観点としては、
- 何故それをおこなったか
- 結果(EDAや評価、ふるまい可視化など)を踏まえてどういうアクションを考えたか
- コードの意図をざっと説明
などをはじめにコードや図などを見つつ説明をしつつ議論をおこないます。そして議論内容によってコード修正が発生したらそれを再度反映して同様に説明/議論をおこないます。その後、修正が収束し次第、説明を受けた人がレビューをおこないます。
分析結果を多角的に見る/考える必要があるのでわかったことからの議論が必要なこと、(整理されたコードを書いたとしても)意図が伝わりづらいことがあるため口頭説明があった方がレビューがしやすいためこのような構成となっている。
有り体にいうと、「議論を中心にしつつ議論相手は内容もある程度わかるからレビューも一緒にやってもらう」くらいな感じです。
コーディングルール
統一できるところは統一する。
- パイプラインツールを導入してフレームワーク(フォルダ構成など)を統一
- 書き方をPEP8などに統一
- pyenvなりDockerなりで開発環境を整える
など*5。
機械学習プロジェクト(マクロ)について書かれた本
言わずもがなの有名書籍
上記の要約
みんな大好きCourseraのAndrew Ngが機械学習プロジェクトについてまとめた「Machine Learning Yearning」という書籍のドラフト版の翻訳記事
機械学習プロジェクトをうまくやるためのプラクティス。やや古めだが有名なやつらしい
Seven Steps to Success Machine Learning in Practice
ブレインパッドさんのやつ
www.slideshare.net
ハカルスさんのやつ
ML ops
有名企業はどうしているかなども紹介しつつでとてもよい
パイプラインツールとかのまとめ
テスト
テストどーすんねんという話がまとまってる
フォルダ管理
以下の記事で紹介されているようなものを使うとフォルダ構成などがチームで統一されて扱いやすい
実験管理
パラメータや結果などの実験管理ツールの比較記事