はじめに
先日、とある機械学習コンペに参加した。週末を何度もつぶす程全力でやってみた。その結果、入賞には至らなかったものの上位3%に入ることができ、自信につながった。まだまだコンペ経験が少ないため、今後考えが変わるかもしれないが、まずは現時点でこの経験を振り返り、メモとして残しておく。
参加の動機
機械学習については本を読んだり動画を見たりして勉強はしているものの、自分のスキルがどの程度か客観的に知りたいと思ったため。
前提
コンペの内容は詳しくは言えないが、テーブルコンペである。以下の戦闘環境で戦った。
ハードウェア(1台)
- CPU i7-8700 3.70GHz
- メモリ 16GB
- GPU NVIDIA GetForce GTX 1060 6G
ソフトウェア
- Windows 10
- Python 3.6
- scikit-learn 0.21.2
- tensorflow 1.13.2
- keras 2.3.1
- xgboost 0.9
- lightgbm, catboost etc..
- PyCharm(開発環境)
振り返り
機械学習手法については素晴らしい本がたくさんあるため、ここでは実際に自分がコンペに取り込んでみて感じた泥臭いことを列挙してみたい。
作成したスクリプト
コンペを戦うにあたり特徴選択と予測モデル作成の2つの作業については、汎用性の高いコマンドラインスクリプトを作成し、他(可視化やちょっとした前処理など)は使い捨てのコマンドで対応した。例えば予測モデル作成については、学習アルゴリズムの選択、乱数シード、クロスバリデーション数など可能な限り、引数で指定できるようにした。また、新しいアルゴリズムが追加になっても最小限の修正で追加できる作りとした。これにより、自分のやりたいことをコマンドの引数を変えるだけで変更できたり、バッチでまとめて流したりすることができ、生産性を高めることができたと考えている。
再現性
コンペでは入賞して賞金等をもらう場合に、モデルの作成や予測について再現できるスクリプトを提供が義務づけられていることが多い(と思う)。スコアがあがって入賞が見えてきたときに初めて再現方法を調べるようでは、かなりの手戻りになる可能性がある。このため、できるだけ早い段階で再現性を担保する方法を確立しておく必要がある。結果にランダム性が入るアルゴリズムには、"random_state" のようなパラメータがあることが多いので、それを調べて指定する等心掛けた。もっともGPUで計算した場合、固定させることが難しいようだ(今回、GPUはKerasだけの利用だったため、あまり深く調べなかった)。
乱数シードを固定させる対象は以下が考えられる。
- Boruta等の各特徴選択アルゴリズム
- Random ForestやXGBoost等の各機械学習アルゴリズム
- クロスバリデーション時の分割処理
ログ
ある特徴選択手法の結果を使って、そこそこよい結果が得られたのだが、最初の特徴選択をどうやって作成したか分からなくなり、結局1からやり直したことがあった。こんなことがないよう、各スクリプトではログを残しておくことが重要になる。
以下全てを実装したわけではないが、今振り返ってみると、ログに残すべきものとして以下が考えられる。
- 実行したスクリプトそのもの(これはスクリプトの修正が頻繁にあるため)
- スクリプトへの入力情報。例えば予測モデル作成の場合、以下のものが考えられる。
- 入力ファイルパス
- 出力ファイルパス
- 予測アルゴリズム
- クロスバリデーションの手法、分割数、評価指標
- ハイパーパラメータの探索手法、探索範囲など
- 乱数シード
- スクリプトの実行状況。例えば予測モデル作成の場合、グリッドサーチの途中経過等になる。これは途中でやめてパラメータ探索範囲を見直すかどうか等の判断にも非常に役に立つ。
特徴選択等、他のスクリプトも同様である。なお、ログは1つのファイルである必要はなく、1つの実行あたり1つの出力フォルダ作成し、その下に予測結果も含め、複数の種類のログファイルを出力すれば十分である。
時間を有効に使う
特徴選択のBorutaは、コンペのデータでは500イテレーションで丸1日かかった。結果によっては何度かやり直すこともあった。Borutaを何も指定せずに実行するとCPUを占有してしまい他の作業が一切できなくなることがある。このようにCPUを占有する処理の場合、引数が用意されている場合、CPU数を明示的に指定することで、他の作業の余地を残しておく。こうすることでBoruta実行中にも、他の作業(例えば別の特徴選択結果でハイパーパラメータの探索を行うなど)ができ、時間を有効に活用することができる。
予測アルゴリズムの実行速度を考慮する
今回は複数の予測アルゴリズムの出力結果を組み合わせて、最終的な予測モデルを作成するというアンサンブル学習で精度をあげることができた。アンサンブル学習では、組み合わせるものの多様性が高い程、精度があがるとされている。今回はスクリプトを汎用的に作成し、様々な予測アルゴリズムを追加することができたため、可能な限りのアルゴリズムを組み合わせた。各アルゴリズムの速度について、感覚になるが以下の通りだった。
- LightGBM, DeepLearng(Kearas)はかなり高速であり、他手法が苦戦する中、特徴選択しないデータでもかなり高速であった。
- XGB、CatBoostやExtra Treeもまずまずの速度であった。
- 線形手法(PLSやExtraNet等)は単純なアルゴリズムなためか、比較的速い。
- Support Vector Machine, Random Forestは非常に遅かった。
時間がかかるということは、パラメータ探索の組み合わせが多いと、その組み合わせの分さらに時間を要するということである。このようなことから、特徴選択をせずに使うアルゴリズム、特徴選択をした上で使うアルゴリズムを使い分けた。これによって各予測アルゴリズムの高精度のモデルを効率よく探すことができたと考えている。
クロスバリデーションを信じる
コンペの終盤、クロスバリデーションのスコアは上がるにもかかわらず、パブリックテストのスコアが上がらないという状態に見舞われた。色々悩んだが、コンペ終了時に公表される全データによるパブリックテストでは、クロスバリデーションとほぼ同じ結果になった。結局のところ、信頼できると思える評価方法を見つけたら、それを信じて精度を上げることに専念することが大切なのだと分かった。
見切りをつける
コンペ中は、色々頑張った割に効果がないこともある。例えば今回遺伝的プログラミングによる特徴量生成は全く効果がなかった(やり方の問題かもしれないが)。原因も全く思いつかなかった。こんな時はさっさと見切りをつけて、それまでに効果があったこと(今回でいえばアンサンブル学習)をつきつめてやることも重要だと思う。
おわりに
振り返ってみると当たり前のことしか書いてない気がするが、これが現在の自分のレベルということだろう。