データ分析・機械学習

〜素人の分析は玄人の直感に勝るか〜

【競馬分析vol.4】初めての単勝予測(技術的な話)

はじめに

本日は単勝予測についての結果を公開します。これまで複勝予測を基本としてきましたが、初めて単勝予測を行いました(理由は後述します)。今回は技術寄りな話が多くなっています。

ちなみに、
単勝予測とは「1着になる馬を予測すること」
複勝予測とは「3着以内に入る馬を予測すること」
です。

単勝予測を実施した理由

目的変数が「1着になる、ならない」や「3着以内に入る、入らない」のように離散的である問題を分類問題(classification)と呼びます。この分類問題を分析する上では、データの構造が大きな問題を生むことがあります。

不均衡データとそこから生じる問題点

不均衡データとは「目的変数の正例と負例の比率が偏っているデータ」のことです。今回の競馬の単勝予測でいうと、正例は「1着である馬」、負例は「1着でない馬」です。

例えば16頭出走するレースの場合、1着になる馬はもちろん1頭ですから、正例と負例の比は1:15となり、これは明らかに不均衡データとなります。 このような不均衡データの場合、そのまま機械学習にかけると正解率をあげるために「とりあえず負例(1着にならない)」と予測してしまいます。何でもかんでも「1着にはならない」と予測されては何の役にも立ちません。

そこで今回は、この不均衡データの扱い方を学ぶべく、より不均衡なデータで検証するため単勝予測に変更しました。

分析手法と結果

不均衡データのままで学習させてみる

まずは不均衡データのまま機械学習にかけて、実際に問題点を見てみたいと思います。使用する機械学習アルゴリズムはrandomForestにしました。今回、使用するデータは1965年以降のレース情報、約7万5千件です。使用した説明変数は主に馬や騎手に関する情報ですが、あまり大した情報は入れていません。今回は精度の絶対値よりも、不均衡データを適切に処理をすると精度がどのように変化するかを重点的に見ていきたいと思います。

> # 使用データ数
> n <- nrow(ud)
> n
[1] 74998
> # 学習用データ70%、検証用データ30%にランダム分割
> s <- sample(n, n * 0.7)
> # 学習用データ
> ud.train <- ud[s,]
> # 正例と負例の数
> table(ud.train$Result=="in")
FALSE  TRUE 
67855  7143
> # 検証用データ
> ud.test <- ud[-s,]
> # randomForestで分類器生成
> data.rf <- randomForest(Result~.,data=ud.train)
> # 検証用データで予測
> pre.rf <- predict(data.rf,newdata=ud.test,type="class")
> tbl.rf <- table(pre.rf,ud.test$Result)
> # 交差検証
> tbl.rf
  
pre.rf    in   out
   in    137    61
   out  1932 20370
> # 正解率(Accuracy)
> sum(diag(tbl.rf)) / sum(tbl.rf)
[1] 0.9114222
> # 適合率(Precision)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[1,2])
[1] 0.6919192
> # 再現率(Recall)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[2,1])
[1] 0.06621556


正解率91%と一見すごいように見えますが、その内訳を見るとほとんどが負例での正解になっています。実運用を考えると、1着になると予測された結果に従って馬券を購入するわけなので、「1着になると予測したものの中で、どれだけが正解しているか」を表す適合率の方が重要です。また、「実際に1着であったものの中で、それだけ正しく予測できたか」を表す再現率もみていかなければならないでしょう。

今回は適合率が70%とそれなりですが、再現率がわずか6%と非常に低いことから、実運用では馬券を買わずにスルーしてしまうレースがかなり多くなりそうです。

SMOTEアルゴリズム利用①(データ全体を調整)

不均衡データを扱う手法は様々あるようですが、今回はDMwRパッケージ(R)のSMOTE関数を利用します。
SMOTEアルゴリズムは、今回の正例(1着になる)のように少ないデータを人工的に生成して増やし、負例(1着にならない)のように多いデータをランダムに削除することでデータの不均衡を解消させる手法です。

> # SMOTE関数で不均衡データを調整
> ud.smote <- SMOTE(Result~.,data=ud)
> # 調整後のデータ数
> n <- nrow(ud.smote)
> n
[1] 50001
> # 調整後の正例と負例の数
> table(ud.smote$Result=="in")
FALSE  TRUE 
28572 21429       # 不均衡がある程度解消されている
> # 学習用データ70%、検証用データ30%にランダム分割
> s <- sample(n, n * 0.7)
> # 学習用データ
> ud.train <- ud.smote[s,]
> # 検証用データ
> ud.test <- ud.smote[-s,]
> # randomForestで分類器生成
> data.rf <- randomForest(Result~.,data=ud.train)
> # 検証用データで予測
> pre.rf <- predict(data.rf,newdata=ud.test,type="class")
> tbl.rf <- table(pre.rf,ud.test$Result)
> # 交差検証
> tbl.rf
      
pre.rf   in  out
   in  5119  448
   out 1324 8110
> # 正解率(Accuracy)
> sum(diag(tbl.rf)) / sum(tbl.rf)
[1] 0.8818745
> # 適合率(Precision)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[1,2])
[1] 0.9195258
> # 再現率(Recall)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[2,1])
[1] 0.7945057

かなり改善したように見えます。どれも約80〜90%程度。適合率に至っては92%と驚異的です。…が、喜ぶのはまだまだ早い。今回は学習用データと検証用データはどちらもSMOTEで調整されたものを用いています。ここでも実運用を考えると、予測したいデータは人工的に調整することはできませんので、この正解率等もあまり意味のあるものとは思えません。調整したデータに対して過学習したんでしょうか。

そこで次に、学習用データのみSMOTEで調整したものを用いて分類器を生成し、検証用データは調整していないデータを用いて予測してみます。SMOTEによって汎化性能は向上するでしょうか。

SMOTEアルゴリズム②(学習用データのみ調整)

> # 使用データ数
> n <- nrow(ud)
> n
[1] 74998
> # 学習用データ70%、検証用データ30%にランダム分割
> s <- sample(n, n * 0.7)
> # 学習用データ 
> ud.train <- ud[s,]
> # 学習用データ(調整前)の正例と負例の数
> table(ud.train$Result=="in")
FALSE  TRUE 
47374  5124 
> # 検証用データ
> ud.test <- ud[-s,]
> # SMOTE関数で学習用データを調整
> ud.smote <- SMOTE(Result~.,data=ud.train)
> # 学習用データ(調整後)の正例と負例の数
> table(ud.smote$Result=="in")
FALSE  TRUE 
20496 15372  # 正例が増え、負例が減っている
> # randomForestで分類器生成
> data.rf <- randomForest(Result~.,data=ud.smote)
> # 検証用データで予測
> pre.rf <- predict(data.rf,newdata=ud.test,type="class")
> tbl.rf <- table(pre.rf,ud.test$Result)
> # 交差検証
> tbl.rf

pre.rf    in   out
   in    723  1456
   out  1296 19025
> # 正解率(Accuracy)
> sum(diag(tbl.rf)) / sum(tbl.rf)
[1] 0.8776889
> # 適合率(Precision)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[1,2])
[1] 0.3318036
> # 再現率(Recall)
> (tbl.rf[1,1])/(tbl.rf[1,1]+tbl.rf[2,1])
[1] 0.3580981


やはり甘くはなかったようです。適合率が大幅に下がっています。しかし、不均衡データをそのまま機械学習にかけたときに比べれば、再現率は7%→36%と改善しています。不均衡データって、扱いが本当難しいんですね…。

今後の展望

SMOTE関数にもまだまだ細かい使い方があるようですし、不均衡データの扱い方もSMOTE以外にも手法が様々あるようなので、いろいろ試していきたいと思います。そもそもSMOTEの使い方も合っているか自信ないので、間違いなどがあればご指摘ください。