データ分析・機械学習

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

【競馬分析vol.6】2017年7月29日(土)

手法

SVMのみで単勝予測を行いました。

予測

小倉8R(小倉サマーJG3)の予測は以下の結果のようになりました。
SVMが予測したのは2番ソロル単勝オッズ20.9倍)でした。
この馬と次に確率の高かった4番を2頭それぞれ単勝で買いました。

馬番 SVM予測 確率
1 0 0.04
2 1 0.69
3 0 0.03
4 0 0.39
5 0 0.01
6 0 0.04
7 0 0.06
8 0 0.06
9 0 0.03
10 0 0.17
11 0 0.03
12 0 0.03
13 0 0.08
14 0 0.08

結果

【競馬分析vol.5】DeepLearning,RandomForest,SVMで競馬分析

久々の更新

しばらく更新が途絶えていましたが、その間にも機械学習をコツコツ学び、仮説・検証を繰り返してきました。
データの方も、機械学習を始めた当初は手作業で集めてせいぜい400件ほどしか扱えませんでしたが、現在では約36万件のデータをいじくりまわせるまで環境が整ってきました。

全36レース分析予測

今は詳細を書く気力がないので省きますが、今回は4月22日(土)に開催された競馬全36レースを分析・予測を行いました。

目的変数は「3着以内に入るか」。あくまで「複勝を的中させる」ことを目的とした分析です。よって、現状では回収率の最適化などは一切考慮していません。説明変数はこれまでの試行錯誤で選んだ50個ほどの変数を用いました。

使用データ数

計算時間を考慮して、約10万件としました。

使用アルゴリズム

  • RandomForest
  • SVM
  • DeepLearning
  • NaiveBayes

結果

全36レースを予測しましたが、結果から見逃すべきと判断されるレースは馬券購入しませんでした。そして残ったレースの中からさらに適当に9レースのみ選んで100円単位で購入してみました。Rのスクリプトと向き合い続けてきて、ある程度の分析ができるようになった喜びから欲を出してしまい、前半は複勝以外で買ってしまった(以前も同じ過ちを…)ためはずれが目立ちますが、後半は分析にしっかり沿って複勝のみで購入しました。

f:id:taux:20170423024202p:plain
☆マークのあるものが的中

久々の実践であり、ここまでのデータ量での実践は初でしたが、なかなか良い的中率なので満足です!
機械学習おそるべし…。すごい時代です。

過去の記事

機械学習で競馬分析を始めた経緯などはこちらから
taux.hatenablog.com

【競馬分析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の使い方も合っているか自信ないので、間違いなどがあればご指摘ください。

【競馬分析vol.3】G2札幌記念の分析結果(2016/08/21)

はじめに

昨日に引き続き、過去の分析実績です。今回もシンプルに予測と結果のみメインの掲載です。
時系列がめちゃくちゃですが、今回は2016年8月のものです。


分析対象

レース名:札幌記念
開催地 :札幌
日時  :2016年8月21日(日)11R
札幌記念|2016年08月21日 | 競馬データベース - netkeiba.com


分析予測と結果

分析予測

今回も4つのアルゴリズムで予測させました。

馬番 馬名 騎手名
15 モーリス モレイラ

Rによる予測詳細

f:id:taux:20170218024850p:plain:w400

cforestは、中でやってることは基本randomForestと同じはずなのですが、予測馬を出力しませんでした。
他3つのアルゴリズムが15番モーリス推しでした。一応、SVMだけ1番ヌーヴォレコルト(騎手:吉田隼人)を予測しています。モーリスはこのレースで1番人気みたいだったので、堅い結果なんでしょう。

人気は競馬ファンの集合知の結果と考えても良いと思うので、これはこれで機械学習の結果としてなかなか面白いのではないかと思います。

…というかrandomForestの手法でもある機械学習のバギングって、まさに学習器の集合知

競馬でいうと、

競馬 randomForest(バギング)
様々な視点で分析する個々のファン ランダムに選択した特徴量で作られた独立な弱学習器の決定木
人気(多くの人が支持している) randomForestにおける決定木の結果の多数決

と対応しているような気がしています。まだアルゴリズムの理解が怪しい。
この解釈があっているか、機械学習に詳しい方教えてください。

それにしても、一個人でもデータさえあれば集合知と同じ結果「も」得られるというのはワクワクします。
「だけ」では面白くないですが。

レース結果

着順 馬番 馬名 騎手名 的中
1 13 ネオリアリズム ルメール -
2 15 モーリス モレイラ
3 2 レインボーライン 福永祐一 -
4 1 ヌーヴォレコルト 吉田隼人 -


騎手ルメール強し…。
SVMだけが予測した1番ヌーヴォレコルト(騎手:吉田隼人)は惜しくも4位でした。

馬券購入

複勝で1.1倍でした。
回収率を上げるという意味ではこの倍率は面白くないのかもしれませんが、利率10%と考えれば魅力的…?
もちろん「100%当たる」ということには絶対ならないので、単純に利率10%としてしまってはいけませんが。


仮に一番人気を複勝で買い続けたときの的中率(回収率ではなく)はどれくらいなんだろう?
その的中率に、自分の機械学習による的中率が勝っていないとまだまだ魅力的とは言えないですね。


ちなみに、昨日公開したエリザベス女王杯の分析で用いた説明変数とはまた異なるので、これも詳細を整理できたら追記します。

【競馬分析vol.2】G1エリザベス女王杯の分析結果(2016/11/13)

はじめに

2016年11月に実際に分析を行った予測と結果を公開します。
分析を始めて半年弱経過した頃のものです。

分析対象

レース名:エリザベス女王杯
開催地 :京都
日時  :2016年11月13日(日)11R
エリザベス女王杯|2016年11月13日 | 競馬データベース - netkeiba.com


分析予測と結果

分析予測

randomForest、cforest、SVM、naiveBayseの4つの機械学習アルゴリズムによる結果を単純多数決でまとめました。
次の3頭が3着以内に入る可能性が高いという予測結果になりました。

馬番 馬名 騎手名
1 ミッキークイーン 浜中俊
3 クイーンズリング M.デムーロ
15 パールコード 川田将雅

Rによる予測詳細

f:id:taux:20170217222351p:plain

レース結果

着順 馬番 馬名 騎手名 的中
1 3 クイーンズリング M.デムーロ
2 9 シングウィズジョイ ルメール -
3 1 ミッキークイーン 浜中俊
4 15 パールコード 川田将雅 -

んー…15が惜しかった…!

馬券購入

実はこのレース、馬券を購入していました。
f:id:taux:20170217223425p:plain:w400

まさに完全に欲を出してしまった結果です。
目的変数を「3着以内に入るか、入らないか」という複勝のための問題設定に自分でしたのにも関わらず、3連単を買ってしまったあたりが非常に情けないです。馬券購入に関しては一切機械学習は関係ないので、これが競馬ど素人の結果ですね…。

しかし、分析しているうちに騎手の「ルメールはなんかやばい」ってことくらいはわかってきました。名前しか知りませんが…。
ちなみにこのレースの2着9番シングウィズジョイ(騎手:ルメール)は一切予測結果では出てきませんでした。

時間があればこの分析で用いた説明変数や重要度などを追記したいと思います。

【競馬分析vol.1】問題設定と分析環境

競馬分析の問題設定

目的変数の設定

まず最初に、分析する上でどのように問題設定をするかを決めなければいけません。
今回の競馬分析では問題をよりシンプルにするため、

3着以内に入るか、入らないか
 
を目的変数とし、2値の分類問題として分析・予測を行っていきます。複勝を的中させるための分析といってもよいでしょう。回収率については今のところ考慮していません。

おそらく競馬好きな方からは

複勝なんてつまらない(当てるのは簡単)
的中率より回収率が大事

という声が聞こえてきそうですが、そもそも的中しなければ回収もクソもないですし、さらに競馬素人の分析ということで、このような問題設定としました。

説明変数の設定

説明変数(特徴量)の選択については、最初のうちは主観・想像に頼らざるを得ないので、馬と騎手の勝率など予測に使えそうなものを適当に選んでいきます。
そして分析の中で、以下に述べるrandomForestやcforestの重要度計算を用いて特徴量を選別していきます。

分析環境

使用言語

主にR言語を用いています。最近、少しだけPythonも触り始めました。

使用パッケージ(機械学習アルゴリズム

現状、使用している機械学習アルゴリズムは以下のものです。※{  }はRのパッケージ名

  • randomForest {randomForest}
  • forestFloor {forestFloor}
  • cforest {party}
  • SVM {kernlab}
  • naiveBayes {e1071}
  • DeepLearning {h2o}

当初は、使いやすいと言われているrandomForestだけで予測を行なっていました。
現在は上記全てのアルゴリズムで予測をさせ、最後に多数決をとることとしています。

次回以降、実際の分析結果を古いものから順に公開していきたいと思います。

前回記事

taux.hatenablog.com

【競馬分析vol.0】素人がデータと機械学習だけで勝てるか

データ分析を始めた経緯

個人的な体感として、2013年頃から統計学に関係する書籍を本屋でよく見かけるようになった気がするのですが、世の流れに沿って私も統計学を改めて(?)少しずつ勉強していました。


その延長線上として2016年の年明け頃からR言語を用いて機械学習をいじくるようになり、実データも用いて分析するようになってから最近少しずつ素人なりにノウハウがたまってきたので、これから徐々に公開していきたいと思います。
(個人でもいろいろな強力ツールを無料で使える時代に最近感動しています)


このようなコツコツ系作業は継続がなにより大事ですし、何よりもせっかくデータ分析を実践的に学んでいくからには少しでもワクワクしながら行いたいというのが正直なところ。


そこで最初は
「データ分析・機械学習を継続的に学び、基本スキルを身につける」という目的
のもと、元々ギャンブルに興味のない(センスも全くない)私がデータと機械学習だけでどこまでギャンブルからギャンブル要素を排除できるか(投資に変貌させられるのか)をモチベーションに分析していこうと考えました。

分析対象の選定

選定する上での前提

  • ギャンブルが目的ではないので、大勝ちなどを狙った分析はしない

 欲を出さない、欲は判断を鈍らせる敵

  • 「ただ分析・予測して終わり」もつまらないので予測に基づいて少額購入してみる

 ここでも欲は出さずにあくまでも分析結果に忠実に従う

  • 個人的な主観を極力入れない、客観データ主導の分析にする

 偏った事前知識のない、これまで触れたことのないものが望ましい

  • 公式データが豊富に存在するものとする

 分析する上での大前提

  • 完全にコンピュータ制御された反復試行的なものは扱わない

 ナンバーズなどの分析?は、もはやオカルトに感じます…

  • 参考文献がある程度存在するもの

 素人が真っ先にすべきことは「先駆者を真似をすること」から

選定案

上記の前提を踏まえて、分析対象をまずは以下の2つに絞りました。

  1. サッカーくじtoto
  2. 競馬

サッカー自体は子どもの頃にやっていたこともあり馴染みのあるものでしたが、Jリーグに関しては久しく見ておらず、各チームのイメージももはや何も残っていなかったため選びました。(しかし、最初に実践したサッカー分析はスキル0の状態での分析だったため失敗に終わります…今後分析結果を公開していきます)


競馬に関しては、これまでも機械学習エンジニアが分析対象として選んでいること、そして何より競馬に関する事前知識が0であることが逆に様々なメリットや面白みを生むのではないかと考え選びました。


まだまだ分析をブラッシュアップしていっている途中ではありますが、今後はこれらの分析の経過などを公開していきます。
データ分析・機械学習という視点だけではなく、競馬ファンの方も楽しめるような内容を目指します!