先日こちらの学生データ分析コンペの表彰式に、プレゼンター&解説者として登壇してまいりました。正直言って、データを提供して下さったData Stadium社の皆様からも「これほどまでの結果になるとは」という感嘆の声が上がるほどハイレベルな戦いぶりで、参加者の皆さん大変素晴らしい結果を残して下さったと思います。この場を借りて、改めてコンペ参加者の学生の皆さんお疲れ様でした、そして入賞者の皆さんおめでとうございます。
ところで、この表彰式に来れなかったという*1方々から「講評をブログにまとめてもらえないか」という依頼をちょこちょこいただきましたので、簡単ですがまとめておきます。なお、Data Stadium様とCrowd Solving様のご厚意で今回の本番用学習データセット&テストデータセットをいただくことができたので、最後の方に僕が自分で試してみた結果も載せてあります。
課題について
この記事をお読みの方の中には当然コンペに参加されてない方もおられるかと思いますので簡単に概要を説明しておきますが、端的に言えば「2015年JリーグJ1最終節の個々のチームの得点全てをできる限り精確に予測せよ」*2というものです。そのためのデータセットはData Stadium様から提供されたもの。
説明変数としては、2014&2015年(ただし最終節を除く)シーズンのJ1全試合における対戦チーム組み合わせ・出場選手とそのポジション・シュートやFKなどゴールチャンスに絡むアクションをした選手のリスト、そしてCBP (Chance Building Point)という主要なプレーの評価指標が与えられています。
このCBPは過去の試合(=学習用データセット)では与えられていますが、肝心の最終節の試合(=テストデータセット)ではそもそもコンペ締め切り時点でまだ開催されていないので全く与えられておらず、あくまでも最終節の対戦チーム組み合わせの表しか与えられていません。これをどうするか?というのも参加者の腕の見せ所だったのではないかと思います。
線形回帰や順序ロジットやポアソン回帰よりもただの多クラス分類の方が良さそう
優勝者のモデルなんですが、提出していただいたレポートを見たら何と。
- モデル:多項ロジット
- 説明変数:CBPのチーム総和のみ(守備CBPとセーブCBPは相手チームのものを採用)
- 変数選択:AICでステップワイズ
たったのこれだけでした。優勝者インタビューで曰く「5時間ぐらいしかかけなかった」とのことで、これがベストモデルだとはご本人も想像だにしなかったそうです。ただコメントとして「順序ロジットでは精度が悪かったので多項ロジットにした」という話があり、そこがポイントかなと思いました。
というのは、確かに全体の得点分布を見るとポアソン分布に近い感じがするんですが、チームごとに得点分布を見るとこんな感じで割とバラバラです。実際手元で試してみた感じだとポアソン回帰でも負の二項分布回帰でもあまり結果が良くないので(下記)、普通にGLM系の手法で試しても合わないのかなと。
もっともこれに階層ベイズみたいな手法を適用したところで、肝心の「テストデータに対する予測」をどうしたら良いかという問題にぶち当たるので、いずれにせよ回帰系の手法よりは多クラス分類系の手法の方が良さそうだ、ということは言えるかなと思いました。
テストデータが「ない」なら、時系列モデルで生成してみる
今回は実際に「コンペ締め切りよりも未来の時点で開催される予定のJ1最終節の結果」を予測した結果が最終評価対象だったため、この手のデータ分析コンペとしては非常識な「テストデータが全く『ない』」と課題をやらされるというとんでもない状況だったのでした(汗)。
ということで皆さん色々工夫を凝らしていたのですが、優勝者の方曰くは「チームごとのCBP総和全てについてARモデルを推定した上で最終節のCBP総和の値を予測して生成した」とのこと。これはなかなか捻った手だなぁと思いました。
これはちょっと考えれば分かるかと思いますが、CBPのような「ヒトの行動」に関連するものは往々にして平均回帰的に振る舞います。
なので、定常過程であると仮定して未来値(=テストデータにおけるCBP)を予測するというのが良いアプローチではないかなと思いました。ちなみに他の方は概ね学習データのCBPの平均値か中央値を用いていたんですが、それでもそれなりの精度が出ていたところを見るに平均回帰性を仮定するのは妥当だったのではないかなと。
多クラス分類を使うなら不均衡データなのでクラス重み付けで補正してみるのも一手
上記の通り、順序ロジットも連続値で線形回帰もポアソン回帰もあまり精度が良くないとなれば多クラス分類を選択するわけですが、チーム間でcollapseするとポアソン分布に見えるくらいなのでそのクラス分布にはかなりの偏りがあります。
となればやるべきことはたった一つ、不均衡データに対するクラス重み付け(class weight)です。これは以前このブログでも取り上げたことがあるので、ご記憶の方も多いかもしれません。
RでもPython scikit-learnでもクラス重み付けを追加するための引数は様々な関数についているので、適当にサクサクやれるのではないかと思います。ただし、xgboostのようにそもそもクラス重み付けを必要としない分類器もあるので、そこは注意が必要かなと。
モデル性能評価は交差検証で
これ意外と失念していた人が多いようで、訓練誤差でしか評価してない人がかなり目立ちました。ただし数名の方だけが例えば10-fold cross validationなど交差検証を用いて評価していたようです。
今回のデータは上記の通り結構複雑で、回帰でやろうと分類でやろうと、訓練誤差だけ見ていてもよく分からないモデルになるケースが多かったのではないかと睨んでいます。もちろんAICなどである程度汎化性能を想定しながらモデル評価を行うこともできますが、多少面倒でも交差検証でやった方が良いのではないかなと思いました。
その他もろもろ
中にはDeep Learning (DNN)で試した方もいたようですが、結果は芳しくなかったようです。これは表彰式イベント中のディスカッションの席で聞くことができたんですが、やはりチューニングが大変だったと。今回のデータは学習データでも600行以下と小さかったこともあり、特徴量次元もサンプルサイズも小さいデータでベストのチューニングを叩き出すのは難しかったのではないかと思います。Deep Learningの主戦場はやはり特徴量次元・サンプルサイズもでかくなるセンサデータ系かなと。
一方、割と古典的な手法がずらりと並んだ*3割に、かなり高い予測精度*4を叩き出した参加者が多かったところを見るに、改めて「スモールデータといえども侮れないな」という思いを新たにした次第です。
ただ、これはあくまでも結果論なんですが。。。テストデータでは何と5点も取ってしまったチームが2チーム、4点も取ったチームが1チームあったせいで、結構難しい予測課題になってしまったというのは否めないかもな、と(汗)。
自分で試してみた結果
ちなみに今大会の全期間学習データ&本番用テストデータが手に入ったので自分でもやってみたんですが、まずポアソン回帰とか負の二項分布回帰とか、はたまたクラス重み付けありのランダムフォレストとか線形SVMとかで色々やってみたもののRMSEが1.5を切れず全く良いところなしという結果にorz そこで、
- 多クラス分類(重み付けなし)
- 分類器はxgboost
- テストデータはVARモデルで予測
- 守備・セーブCBPのみ敵味方入れ替える*5
として死ぬほどチューニングしてみた結果たどり着いたRMSEが1.433721、3位入賞できませんorz そして上記のうち、
- テストデータはCBPの過去の平均値
としてやはり死ぬほどチューニングしてみた*6結果のベストRMSEが1.354006で辛うじて1位のスコアを上回ったという*7。入賞者の皆さん、恐れ入りました(笑)。
なお、今回僕が試した環境としては以下の通りです。
時系列分析にVARモデルを用いた理由としては、おそらく個々のCBPは独立ではなく個別にARモデルを立てても精度が悪いんじゃないかなと思ったため。これは僕も面倒でData Stadiumさんのサイトを見に行っておらず*9実際のところどうかはチェックしていないので、興味のある方はご自分で調べてみて下さい(笑)。
最後の最後に、実は自分でやっていて気になったのが「得点を予測してRMSEで評価する」という課題で本当に良かったのかなぁ?というポイント。上記のRMSE = 1.354006というベストスコアを出した時でも、勝敗で見ると9試合中7試合の的中に留まったという。。。これは勝敗をきちんと予想させた方が良かったのかも。こんな感じで、このコンペはまだまだ向上させられる余地があるのかな?という印象を持ちました。
*1:おそらくコンペ参加者の
*2:評価はRMSEの低さ
*3:ランダムフォレストを使った人が多かった印象
*4:ここではテストデータに対するRMSE
*5:優勝者の方の方針と揃えた
*6:objective='multi:softmax', eta=0.1, eval_metric='mlogloss', num_class=8, subsample=0.45, max_depth=5, nrounds=20として100回回してベストスコアを選んだ
*7:しかもテストデータを既に持っているので何度でもスコアが良くなるまで試行できるという状況下でこの結果
*8:この程度の行数のもののためにRedshift立ち上げたりPostgreSQLに入れるとかアホらしかったので
*9:怠慢でごめんなさい