競馬予想AI作ってみた
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
検索
|
最終更新
|
ヘルプ
|
ログイン
]
開始行:
#contents
*はじめに [#w12b0834]
機械学習の勉強がてら、以前からやってみたかった「競馬予想...
競馬予想ソフト。これまでたくさんありましたし、AI(機械学...
第一見で全体を見渡し、よさそうな馬を「ぱっと見」で抽出し...
これを技術的に落とし込むと
''予想データを、数値の集まり(テーブルデータ)でなく、1枚...
そんなアイデアを手掛かりに、従来の競馬予想AIとは少し違う...
この記事はその開発過程を記した一種のドキュメンタリーです。
*手始めに [#ge5a1389]
とりあえず機械学習も深層学習も聞きかじりの知識しかなかっ...
競馬の予想を行うためのディープラーニングモデルは、多くの...
また、適切なデータセットの作成や特徴量の 選択も重要です...
1.データの収集:
競馬の結果や競走馬の情報、天気などのデータを収集します...
2.データの前処理:
データを前処理し、モデルの学習に適した形式に変換します...
3.モデルの構築:
Kerasなどのライブラリを使用して、ディープラーニングモ...
4.モデルの学習:
モデルにデータを与えて学習させます。学習には、過学習を...
5.モデルの評価:
モデルの性能を評価するために、テストセットで評価します...
6.予測の実行:
新しいデータを与えて、モデルによる予測を行います。予測...
以上の手順に従って、競馬の予想をディープラーニングで行う...
うーん、抽象的すぎてよく分からない。とりあえずデータの収...
*データの収集 [#q061ccaf]
今回は勉強用ということで、他の方々に倣って、netkeiba.com...
**スクレイピング [#h80081a3]
ChatGPTにサンプルコードもらいました
質問:
Jsoupでhttps://race.netkeiba.com/top/race_list.html?kaisa...
回答:
以下は、Java言語を使用してJsoupライブラリを使ってNetkeib...
..長いので以下省略..
試してみると、htmlタグの要素から特定の文字を抜き出すコー...
質問:
Jsoupで <td class="Barei Txt_C">牝3</td> を切り出すJava...
回答:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JsoupExample {
public static void main(String[] args) {
String html = "<table><tr><td class=\"Barei Txt_C...
Document doc = Jsoup.parse(html);
Elements tdElements = doc.select("td.Barei.Txt_C"...
for (Element tdElement : tdElements) {
System.out.println(tdElement.text()); // "牝3...
}
}
}
doc.select("td.Barei.Txt_C")~
なんてドット連結する記法、直感じゃ思いつかないな~
*前処理いろいろ [#c05c5339]
次は前処理。
**エンコーディング [#uea830c7]
どうやら機械学習に与えるデータは数値でないとダメらしい。...
仕方ない。元が数値のデータ以外は、その文字列のハッシュ値...
**欠損値 [#g5b7ae73]
今回の開発のそもそもの発端は「競馬新聞の馬柱表を見たとき...
仮想のレースを走ったことにしてみた。
その他、競馬データを眺めてみるといろいろ気づいた点があり:
-新馬戦は直近5走が1個もないため、学習も予測対象もせず、全...
-障害戦もサンプル数が少ないので除外
-17頭立てと18頭立てもレース数が少なすぎるので除外
ちなみに「馬柱」は「うまばしら」と訓読みするの知ってまし...
**完成したテーブルデータ [#abeb48e0]
htmlから抜き出した要素を仕分けして保存するコードをごにょ...
細かくファイルに分かれてる各要素をマージして一つのテーブ...
2018年東京競馬場のレースデータ(抜粋)
&ref(./racetable_201805.csv,100%);
1行が1レース。列数=1803。1列目が勝ち馬の馬番、すなわち分...
この先すべての基礎になるデータです。
*(寄り道)競馬オッズの基礎データ [#jdfc7278]
データが集まったのでちょっと寄り道していろいろ調べてみま...
**2023年前半の単勝平均オッズは? [#c2a53344]
全レース数...1216~
単勝オッズ平均...9.51倍
**一番人気だけを買い続けた場合の勝率、平均オッズ、収支率...
的中率...33.7%~
平均オッズ...2.3倍~
回収率...77.8%
JRA殿が最初から20%控除してるので、普通なら収支70-80%くら...
**各人気ごとの勝率と平均オッズは? [#vcc6fdb8]
|人気|平均オッズ|勝率(%)|
|1|2.29|33.7|
|2|4.0|19.1|
|3|5.98|13.0|
|4|8.21|10.1|
|5|11.29|7.2|
|6|14.7|4.2|
|7|23.64|4.0|
|8|23.28|2.5|
|9|34.23|2.4|
|10|47.37|1.6|
|11|67.45|0.6|
|12|74.52|0.7|
|13|112.36|0.4|
|14|68.0|0.2|
|15|157.8|0.2|
回収100%を上回る美味しい払い戻しは、どの人気でも得られな...
*モデルの構築(準備体操) [#n6ac7464]
今回の競馬予想プログラムはシンプルに「どの馬が勝つか」=...
単勝の予想とは、対象レースの直近5走データから、勝ち馬の馬...
いきなり画像の深層学習をやるには敷居が高かったので段階を...
+シンプルな機械学習でcsv形式のテーブルデータを学習、予測...
+感触を掴んだら、馬柱の画像表現を考えて、これをCNNで学習...
+結果を比較してみる
**ツールの選択 [#v0a4fa79]
そこでまずは分類問題をチャチャっとできるだけ楽して実行で...
JavaはないのかJavaはっ!と思って探すと「Deep Learning for...
結果的にいうとDL4Jが勉強用としては最高でした。
***DL4J[#f0403ce4]
セットアップ方法はネットを検索してください。IntelliJ IDEA...
まず、付属する大量のサンプルプログラムが「深層学習で何が...
あのよく見るMNISTのサンプルデータと共に機械学習のトピック...
プロジェクトを実行すると、ニューラルネットの学習過程(勾...
DL4J最高すぎて感動を覚えました。思わずポチったオライリー...
***LightGBM (Python) [#r83ce3b2]
軽い。速い。簡単。サンプルも解説もいっぱいある。もう全部...
***XGBoost [#yab76fcf]
OracleがJava向け機械学習ライブラリ「Tribuo」をリリースし...
本開発に関する限り、LightGBMに比べて結果精度はさほど変わ...
**評価方法 [#sb46a7f6]
-トレーニングは2018-2022年までの過去5年間のレース結果
-テスト(検証)は2023年1~4月までの4か月のレース結果
とし、テストデータの予測結果を実際の結果と比較して「的中...
-的中率...的中レース数/購入レース数
-回収率...各レース単勝100円1点買いをしたとして、払い戻し...
ただし前章でも書いた通り、新馬戦、障害戦、17頭・18頭立て...
**LightGBM [#e2621216]
***LightGBMパラメータ [#v3bc2665]
params = {
'objective':'multiclass',
'metric':{'multi_error'},
'num_class':17,
'num_leaves':30,
'min_child_samples':10,
'max_depth':10,
'seed':1234,
'boosting_type':'gbdt',
'subsample_freq':2,
}
正直、これらの値に根拠はないです。試行錯誤の結果です。
***評価結果 [#m95fb1b7]
confusion matrix =
[[23 5 8 6 4 3 9 3 1 1 3 3 0 2 1 0]
[ 5 22 3 5 3 7 13 2 6 6 2 4 3 1 0 0]
[ 5 6 28 4 6 9 1 2 4 1 2 1 1 3 0 0]
[ 9 6 8 35 6 7 9 7 5 8 1 0 2 4 0 0]
[12 7 5 5 24 8 10 8 8 2 0 3 2 1 1 0]
[ 9 14 4 11 5 30 4 9 8 2 4 4 2 3 2 0]
[ 1 6 5 10 7 6 26 7 5 3 2 2 2 1 2 1]
[ 6 7 5 6 8 8 8 32 6 4 2 2 1 1 1 0]
[ 6 4 4 1 3 13 2 7 18 3 4 3 1 2 1 0]
[ 5 4 7 6 7 8 6 8 6 24 0 2 3 3 0 0]
[ 1 5 3 9 7 6 5 5 2 7 15 3 2 2 1 0]
[ 3 9 9 2 4 7 6 3 6 2 2 20 0 3 3 1]
[ 4 0 2 2 6 2 3 5 3 4 4 1 11 2 0 1]
[ 4 5 4 2 3 0 4 4 4 0 2 2 0 7 1 0]
[ 2 1 5 4 5 5 4 6 0 1 1 4 2 1 4 1]
[ 1 3 2 2 2 0 2 2 4 4 4 1 4 2 2 5]]
的中率...26.6%~
回収率...76.8%
**ニューラルネット [#d92a891e]
***DL4J feedforwardパラメータ [#iecf0133]
DL4Jのfeedforwardサンプルを元に同じテーブルデータをニュー...
ネットワーク構造:ノード数(numHiddenNodes)=500の3層完全結...
MultiLayerConfiguration conf = new NeuralNetConfigura...
.l2(1e-6)
.seed(seed)
.weightInit(WeightInit.XAVIER)
/*
参考資料
SGD (Stochastic Gradient Descent):確率的勾配降下法。...
AdaGrad:適応的学習率法。過去の勾配の二乗和のルートを...
AdaMax:AdaGradの一種であり、重みの更新に最大値を使用...
AdaDelta:Adaptive Learning Rateの一種であり、勾配の...
Adam:Adaptive Moment Estimationの一種であり、勾配の...
AMSGrad:Adamの改良版で、重みの更新に最大値を使用しま...
Nadam:Nesterov Accelerated GradientとAdamを組み合わ...
Nesterovs:Nesterov Accelerated Gradientを使用します。
*/
//.updater(new Nesterovs(learningRate, 0....
.updater(new Nesterovs(new MapSchedule(Sc...
//.updater(new Adam(1e-3))
//.updater(new AdaDelta())
//.updater(new Nadam())
//.updater(new Adam())
.list()
//RELUの方がスコアは下がる
//Accuracyはたいして変わらない
.layer(new DenseLayer.Builder().nIn(numIn...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
.layer(new BatchNormalization())
.layer(new DenseLayer.Builder().nIn(numHi...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
.layer(new BatchNormalization())
.layer(new DenseLayer.Builder().nIn(numHi...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
learningRateSchedule.put(0, 0.001);
learningRateSchedule.put(1000, 0.0005);
learningRateSchedule.put(2000, 0.0001);
learningRateSchedule.put(3000, 0.00001);
learningRateSchedule.put(3500, 0.000005);
この書き方Builderパターンって言うんだそうですがメッセージ...
***評価結果 [#l3d3f592]
的中率...17%~
回収率...69.3%
========================Evaluation Metrics===============...
# of classes: 17
Accuracy: 0.1719
Precision: 0.1718 (1 class excluded from average)
Recall: 0.1726 (1 class excluded from average)
F1 Score: 0.1615 (1 class excluded from average)
=========================Confusion Matrix================...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
----------------------------------------------------
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0 = 0
0 8 3 11 1 4 5 10 4 6 2 3 4 5 4 1 1 | 1 = 1
0 2 7 5 4 1 9 12 3 3 6 7 10 9 1 2 1 | 2 = 2
0 1 2 13 3 5 11 7 3 3 1 4 3 9 6 1 1 | 3 = 3
0 2 3 9 19 5 7 11 7 6 7 7 6 8 6 3 1 | 4 = 4
0 4 2 6 7 17 10 9 10 7 3 2 4 7 5 2 1 | 5 = 5
0 4 4 6 2 6 24 17 6 6 3 3 6 14 5 4 1 | 6 = 6
0 1 5 2 7 5 10 26 3 2 3 3 6 8 4 1 0 | 7 = 7
0 4 6 10 5 7 8 10 13 4 3 5 6 6 2 4 4 | 8 = 8
0 1 3 5 2 4 10 8 8 7 6 2 1 9 3 1 2 | 9 = 9
0 3 7 5 4 7 3 8 4 5 12 2 4 14 6 3 2 | 10 ...
0 2 4 2 5 6 6 6 7 1 2 8 4 10 6 4 0 | 11 ...
0 1 3 2 2 5 3 10 2 5 3 6 16 13 4 4 1 | 12 ...
0 2 2 0 1 2 1 3 2 1 1 4 1 22 3 3 2 | 13 ...
0 1 1 1 1 1 3 6 1 5 0 1 4 6 9 1 1 | 14 ...
0 0 2 0 0 5 5 3 3 2 2 2 2 10 5 5 0 | 15 ...
0 0 1 0 0 3 4 4 1 2 2 6 0 8 4 2 3 | 16 ...
**結果考察 [#p7dc8038]
目標値である「的中率33%以上、回収率78%以上」を上回る結果...
GPUを積んでないノートPCでやってる自分が悪いのですが、ニュ...
この結果を得る過程で、テーブルデータの列項目に何を入れる...
LightGBM では木構造を決めるパラメータが重要だろうと勝手に...
ニューラルネットワークでは階層数、ノード数、最適化アルゴ...
個人的にはニューラルネットを使った分類の方が良い結果と期...
結果が期待ほどでもなかった理由を振り返って考えてみると
+前処理でのエンコーディング問題の可能性(馬名などの名前を...
+そもそも前5走データではこの程度の的中率が限界という可能性
+スクレイピングがしくじってゴミが大量に紛れ込んでいる可能性
各データを再調査して、イレギュラーな形式(「騙馬」とか「...
*深層学習への展開(本番) [#z6427c54]
**データの画像化 [#wadf7a61]
繰り返しですが本来の目的は「出走表をぱっと見たときの直観...
-表形式の学習データをなんらかの方法で画像化して表現する
-表形式の学習データから「馬柱表」を自分で作成してこれを描...
-netkeibaの馬柱表ページのスクリーンショットをまるごとその...
**テーブルの画像表現 [#u5bca360]
現在の学習データ列数は1803。これの各項目をピクセル値と考...
結果としてcsvの1行が81*18のRGB画像に変換されます。トレー...
2018年 中山競馬場 第1回開催 1日目 第1レース
&ref(./201806_0.png,100%);
拡大すると
&ref(./201806_0.png,500%);
実際の馬柱表はこちら:
https://race.netkeiba.com/race/shutuba_past.html?race_id=...
このレースは15番コウギョウブライトが勝ちましたので分類ラ...
1レースがこれだけ小さい画像に圧縮できるって面白いですね...
**疑似馬柱画像 [#vacc51ca]
同様に、テーブルデータを、「前5走-馬柱風」に描画して保存...
2018年 東京競馬場 第1回開催 1日目 第1レース
&ref(./201805_0.png,30%);~
(クリックで拡大)
実際の馬柱表はこちら:
https://race.netkeiba.com/race/shutuba_past.html?race_id=...
このレースは6番マサノカバーガールが勝ちましたので分類ラベ...
**馬柱そのまま [#xd2ef388]
Selenuimというライブラリを使うと特定のウェブページを、「...
サンプルはこちら:
&ref(./201810_1_1_1.png,10%);~
(クリックで拡大)
*学習と評価 [#i2952862]
**ローカルPC [#w2f80fdd]
テーブルデータの学習程度はGPUのないノートPCでもできました...
**Prediction One [#f0cda501]
ソニーの機械学習クラウドサービス。
ちょうどいいタイミングでニュースリリースが流れてきたので...
このサービスのいいところは、事前に定番モデルが沢山用意さ...
層の追加や中間処理の挿入などもGUI操作で行え、パッドを選択...
込み入ったことやろうとすると少々面倒だったり、そもそもパ...
学習データは指定のディレクトリ構造に仕分けしてアップロー...
***無料の罠 [#t317f3cb]
無料枠で一通りお試しはできる一方で、無料期間を過ぎると作...
新規に学習や予測ができないのは分かりますが、結果を振り返...
自分はこれに気づかず放置していたので、実験結果はなくなっ...
**Google Colaboratory[#aaed94f8]
これ使うのも初めてでした。要するにPythonのコマンドプロン...
**モデル抜粋 [#db05b471]
一番オーソドックスなモデルのサンプルをネットから拝借しま...
class Net(nn.Module):
def __init__(self, input_size, output_size):
super(Net, self).__init__()
self.relu = nn.ReLU()
#self.relu = nn.Identity()
self.pool = nn.MaxPool2d(2, stride=2)
self.conv1 = nn.Conv2d(num_channels,8,2)
self.conv2 = nn.Conv2d(8,16,2)
self.conv3 = nn.Conv2d(16,32,2)
#self.conv4 = nn.Conv2d(24,32,2)
self.fc1 = nn.Linear(256, 32)
self.fc2 = nn.Linear(32, output_size)
self.bn1 = nn.BatchNorm2d(8)
self.bn2 = nn.BatchNorm2d(16)
self.bn3 = nn.BatchNorm2d(32)
#self.bn4 = nn.BatchNorm2d(32)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.relu(x)
x = self.pool(x)
x = self.conv3(x)
x = self.bn3(x)
x = self.relu(x)
x = self.pool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
**問題発覚 [#se95025d]
データ(画像)は3種類揃いました。これらを順にニューラルネ...
まず2番目の「疑似馬柱画像」。画像サイズと枚数が多すぎてPr...
次に3番目の「馬柱そのまま」。よく見ると出走頭数によって画...
**評価結果 [#becbda74]
以下はすべてGoogle Colaboratoryで実行。Prediction Oneでは...
***テーブル画像 [#r0a9e9ff]
Loss: 3.924950735909598, Accuracy: 7.795918367346939% (38...
Confusion Matrix:~
&ref(./matrix.png,50%);
・・・精度7.8%・・・
今回は対象レースを「16頭立て以下」に絞ったので、サイコロ...
つまりこれサイコロ振ったのとほぼ同じで、全然、学習できて...
***疑似馬柱画像 [#cfa2d6e1]
Loss: 2.7520341139573317, Accuracy: 10.843373493975903% (...
あれ、11%もある!と思ったのはぬか喜び。以下のように予測は...
入力画像枚数を大幅に減らした影響が原因の一つとは思うけれ...
&ref(./matrix2.png,50%);
**結果考察 [#tef294fe]
サイコロに勝てないという残念な結果に終わりました。
まずテーブル画像法。馬名、騎手名などの扱い。画像というこ...
次に疑似馬柱画像法。そもそもCNNが得意とする画像(猫とか犬...
モデル抜粋は簡単な3階層CNNですが、Prediction Oneではもっ...
結果として、構造やパラメータを変えても、それほど顕著な性...
*実戦で試す [#je6bee51]
せっかくなので実際のレースで試してみましょう。結果が比較...
-4月4週~5月4週 の約一か月半(2023/4/17-5/30)
-対象は中山と阪神
-新馬戦、障害戦、17,18頭立レースは除外
-前日に、翌日分の予想をしておく、翌日の夕方に答え合わせを...
-各レース単勝1点買い
結果です。
|開催|的中率|回収率|
|4月4週|35%|87%|
|4月5週|33%|88%|
|5月1週|47%|101%|
|5月2週|38%|69%|
|5月3週|30%|76%|
|5月4週|26%|65%|
|トータル|34%|81%|
**競馬予想特有の難しさ [#zba4750d]
オッズや人気の関係を調べて痛感しました。こんにちの勝ち馬...
つまり競馬予想は
-たんに勝ち馬を予想するだけでなく
-収支を最大にする最適な馬券の買い方を見つける
という2段構えの発見が必要で、なかなか一筋縄ではいかなさそ...
**何が重要か [#xfa235dc]
LightGBMでは学習で「効いている」パラメータ(特徴量)を表...
&ref(./importance.png,30%);~
(クリックで拡大)
意外にも、オーナー=馬主名が重要視されてました。
レース結果の様々な数値が一番重要と思ってたのですが、着差...
この現象、ほんとかよと思って、改めてnetkeibaで馬主検索し...
つまり「目利きできる馬主がオーナー」=「おめがねにかなっ...
そう考えるとぶちゃけレースデータを一生懸命集めるのは徒労...
*今後の展望 [#heeda925]
仕事の合間、主に土日を使ってコツコツ開発して、正味2か月く...
この先、データサイズを増やす、階層数を深くする、もっと複...
昨今話題のLLM。馬柱表の文字表現をXとして、「Xの勝ち馬はY...
**余談 [#i332a0eb]
今回、半ば無理やりChatGPT使いながら開発を進めて1つ気づい...
チャットでの情報収集には「横の広がり」がまったくない。
例えばLightGBMのウェブサイトを読んでる時にふとサイドメニ...
聞かなくてはヒントをくれないチャットに対してウェブ構造は...
この事態の元凶である「入力を指示して出力を得る」インター...
かつてインターネットがハイパーリンクの発明で大きく変貌し...
**余談その2 [#kce5acd0]
ニューラルネットではちょっとした処理の違いが全然違う結果...
例えば「バッチノーマライズ」という中間処理をCNNの層間に挟...
そういう意味で本記事は、途中経過や試行錯誤をかなり端折っ...
正解があるかもわからない中で、これらを手当たりしだい組み...
きっついなー
というのが正直な感想。暗中模索って言葉がぴったり。精神的...
そこでふと世の中に目を向けてみると、昨今話題のLLM。もし今...
いや、実際には世界中のリスクマネーは全力の倍プッシュでギ...
*参考文献 [#r3e171e0]
城崎 哲[[「AI競馬 人工知能は馬券を制することができるか?」...
*謝辞 [#o7a805db]
全競馬ファンのためスクレイピングを恐らく黙認しているであ...
*文責 [#o7a805db]
本文章の文責は(株)スカイリー・ネットワークスにあります。 ...
製品、サービス等の名称にはそれぞれの団体または企業のトレ...
終了行:
#contents
*はじめに [#w12b0834]
機械学習の勉強がてら、以前からやってみたかった「競馬予想...
競馬予想ソフト。これまでたくさんありましたし、AI(機械学...
第一見で全体を見渡し、よさそうな馬を「ぱっと見」で抽出し...
これを技術的に落とし込むと
''予想データを、数値の集まり(テーブルデータ)でなく、1枚...
そんなアイデアを手掛かりに、従来の競馬予想AIとは少し違う...
この記事はその開発過程を記した一種のドキュメンタリーです。
*手始めに [#ge5a1389]
とりあえず機械学習も深層学習も聞きかじりの知識しかなかっ...
競馬の予想を行うためのディープラーニングモデルは、多くの...
また、適切なデータセットの作成や特徴量の 選択も重要です...
1.データの収集:
競馬の結果や競走馬の情報、天気などのデータを収集します...
2.データの前処理:
データを前処理し、モデルの学習に適した形式に変換します...
3.モデルの構築:
Kerasなどのライブラリを使用して、ディープラーニングモ...
4.モデルの学習:
モデルにデータを与えて学習させます。学習には、過学習を...
5.モデルの評価:
モデルの性能を評価するために、テストセットで評価します...
6.予測の実行:
新しいデータを与えて、モデルによる予測を行います。予測...
以上の手順に従って、競馬の予想をディープラーニングで行う...
うーん、抽象的すぎてよく分からない。とりあえずデータの収...
*データの収集 [#q061ccaf]
今回は勉強用ということで、他の方々に倣って、netkeiba.com...
**スクレイピング [#h80081a3]
ChatGPTにサンプルコードもらいました
質問:
Jsoupでhttps://race.netkeiba.com/top/race_list.html?kaisa...
回答:
以下は、Java言語を使用してJsoupライブラリを使ってNetkeib...
..長いので以下省略..
試してみると、htmlタグの要素から特定の文字を抜き出すコー...
質問:
Jsoupで <td class="Barei Txt_C">牝3</td> を切り出すJava...
回答:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JsoupExample {
public static void main(String[] args) {
String html = "<table><tr><td class=\"Barei Txt_C...
Document doc = Jsoup.parse(html);
Elements tdElements = doc.select("td.Barei.Txt_C"...
for (Element tdElement : tdElements) {
System.out.println(tdElement.text()); // "牝3...
}
}
}
doc.select("td.Barei.Txt_C")~
なんてドット連結する記法、直感じゃ思いつかないな~
*前処理いろいろ [#c05c5339]
次は前処理。
**エンコーディング [#uea830c7]
どうやら機械学習に与えるデータは数値でないとダメらしい。...
仕方ない。元が数値のデータ以外は、その文字列のハッシュ値...
**欠損値 [#g5b7ae73]
今回の開発のそもそもの発端は「競馬新聞の馬柱表を見たとき...
仮想のレースを走ったことにしてみた。
その他、競馬データを眺めてみるといろいろ気づいた点があり:
-新馬戦は直近5走が1個もないため、学習も予測対象もせず、全...
-障害戦もサンプル数が少ないので除外
-17頭立てと18頭立てもレース数が少なすぎるので除外
ちなみに「馬柱」は「うまばしら」と訓読みするの知ってまし...
**完成したテーブルデータ [#abeb48e0]
htmlから抜き出した要素を仕分けして保存するコードをごにょ...
細かくファイルに分かれてる各要素をマージして一つのテーブ...
2018年東京競馬場のレースデータ(抜粋)
&ref(./racetable_201805.csv,100%);
1行が1レース。列数=1803。1列目が勝ち馬の馬番、すなわち分...
この先すべての基礎になるデータです。
*(寄り道)競馬オッズの基礎データ [#jdfc7278]
データが集まったのでちょっと寄り道していろいろ調べてみま...
**2023年前半の単勝平均オッズは? [#c2a53344]
全レース数...1216~
単勝オッズ平均...9.51倍
**一番人気だけを買い続けた場合の勝率、平均オッズ、収支率...
的中率...33.7%~
平均オッズ...2.3倍~
回収率...77.8%
JRA殿が最初から20%控除してるので、普通なら収支70-80%くら...
**各人気ごとの勝率と平均オッズは? [#vcc6fdb8]
|人気|平均オッズ|勝率(%)|
|1|2.29|33.7|
|2|4.0|19.1|
|3|5.98|13.0|
|4|8.21|10.1|
|5|11.29|7.2|
|6|14.7|4.2|
|7|23.64|4.0|
|8|23.28|2.5|
|9|34.23|2.4|
|10|47.37|1.6|
|11|67.45|0.6|
|12|74.52|0.7|
|13|112.36|0.4|
|14|68.0|0.2|
|15|157.8|0.2|
回収100%を上回る美味しい払い戻しは、どの人気でも得られな...
*モデルの構築(準備体操) [#n6ac7464]
今回の競馬予想プログラムはシンプルに「どの馬が勝つか」=...
単勝の予想とは、対象レースの直近5走データから、勝ち馬の馬...
いきなり画像の深層学習をやるには敷居が高かったので段階を...
+シンプルな機械学習でcsv形式のテーブルデータを学習、予測...
+感触を掴んだら、馬柱の画像表現を考えて、これをCNNで学習...
+結果を比較してみる
**ツールの選択 [#v0a4fa79]
そこでまずは分類問題をチャチャっとできるだけ楽して実行で...
JavaはないのかJavaはっ!と思って探すと「Deep Learning for...
結果的にいうとDL4Jが勉強用としては最高でした。
***DL4J[#f0403ce4]
セットアップ方法はネットを検索してください。IntelliJ IDEA...
まず、付属する大量のサンプルプログラムが「深層学習で何が...
あのよく見るMNISTのサンプルデータと共に機械学習のトピック...
プロジェクトを実行すると、ニューラルネットの学習過程(勾...
DL4J最高すぎて感動を覚えました。思わずポチったオライリー...
***LightGBM (Python) [#r83ce3b2]
軽い。速い。簡単。サンプルも解説もいっぱいある。もう全部...
***XGBoost [#yab76fcf]
OracleがJava向け機械学習ライブラリ「Tribuo」をリリースし...
本開発に関する限り、LightGBMに比べて結果精度はさほど変わ...
**評価方法 [#sb46a7f6]
-トレーニングは2018-2022年までの過去5年間のレース結果
-テスト(検証)は2023年1~4月までの4か月のレース結果
とし、テストデータの予測結果を実際の結果と比較して「的中...
-的中率...的中レース数/購入レース数
-回収率...各レース単勝100円1点買いをしたとして、払い戻し...
ただし前章でも書いた通り、新馬戦、障害戦、17頭・18頭立て...
**LightGBM [#e2621216]
***LightGBMパラメータ [#v3bc2665]
params = {
'objective':'multiclass',
'metric':{'multi_error'},
'num_class':17,
'num_leaves':30,
'min_child_samples':10,
'max_depth':10,
'seed':1234,
'boosting_type':'gbdt',
'subsample_freq':2,
}
正直、これらの値に根拠はないです。試行錯誤の結果です。
***評価結果 [#m95fb1b7]
confusion matrix =
[[23 5 8 6 4 3 9 3 1 1 3 3 0 2 1 0]
[ 5 22 3 5 3 7 13 2 6 6 2 4 3 1 0 0]
[ 5 6 28 4 6 9 1 2 4 1 2 1 1 3 0 0]
[ 9 6 8 35 6 7 9 7 5 8 1 0 2 4 0 0]
[12 7 5 5 24 8 10 8 8 2 0 3 2 1 1 0]
[ 9 14 4 11 5 30 4 9 8 2 4 4 2 3 2 0]
[ 1 6 5 10 7 6 26 7 5 3 2 2 2 1 2 1]
[ 6 7 5 6 8 8 8 32 6 4 2 2 1 1 1 0]
[ 6 4 4 1 3 13 2 7 18 3 4 3 1 2 1 0]
[ 5 4 7 6 7 8 6 8 6 24 0 2 3 3 0 0]
[ 1 5 3 9 7 6 5 5 2 7 15 3 2 2 1 0]
[ 3 9 9 2 4 7 6 3 6 2 2 20 0 3 3 1]
[ 4 0 2 2 6 2 3 5 3 4 4 1 11 2 0 1]
[ 4 5 4 2 3 0 4 4 4 0 2 2 0 7 1 0]
[ 2 1 5 4 5 5 4 6 0 1 1 4 2 1 4 1]
[ 1 3 2 2 2 0 2 2 4 4 4 1 4 2 2 5]]
的中率...26.6%~
回収率...76.8%
**ニューラルネット [#d92a891e]
***DL4J feedforwardパラメータ [#iecf0133]
DL4Jのfeedforwardサンプルを元に同じテーブルデータをニュー...
ネットワーク構造:ノード数(numHiddenNodes)=500の3層完全結...
MultiLayerConfiguration conf = new NeuralNetConfigura...
.l2(1e-6)
.seed(seed)
.weightInit(WeightInit.XAVIER)
/*
参考資料
SGD (Stochastic Gradient Descent):確率的勾配降下法。...
AdaGrad:適応的学習率法。過去の勾配の二乗和のルートを...
AdaMax:AdaGradの一種であり、重みの更新に最大値を使用...
AdaDelta:Adaptive Learning Rateの一種であり、勾配の...
Adam:Adaptive Moment Estimationの一種であり、勾配の...
AMSGrad:Adamの改良版で、重みの更新に最大値を使用しま...
Nadam:Nesterov Accelerated GradientとAdamを組み合わ...
Nesterovs:Nesterov Accelerated Gradientを使用します。
*/
//.updater(new Nesterovs(learningRate, 0....
.updater(new Nesterovs(new MapSchedule(Sc...
//.updater(new Adam(1e-3))
//.updater(new AdaDelta())
//.updater(new Nadam())
//.updater(new Adam())
.list()
//RELUの方がスコアは下がる
//Accuracyはたいして変わらない
.layer(new DenseLayer.Builder().nIn(numIn...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
.layer(new BatchNormalization())
.layer(new DenseLayer.Builder().nIn(numHi...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
.layer(new BatchNormalization())
.layer(new DenseLayer.Builder().nIn(numHi...
//.activation(Activation.RELU)
.activation(Activation.IDENTITY)
.build())
learningRateSchedule.put(0, 0.001);
learningRateSchedule.put(1000, 0.0005);
learningRateSchedule.put(2000, 0.0001);
learningRateSchedule.put(3000, 0.00001);
learningRateSchedule.put(3500, 0.000005);
この書き方Builderパターンって言うんだそうですがメッセージ...
***評価結果 [#l3d3f592]
的中率...17%~
回収率...69.3%
========================Evaluation Metrics===============...
# of classes: 17
Accuracy: 0.1719
Precision: 0.1718 (1 class excluded from average)
Recall: 0.1726 (1 class excluded from average)
F1 Score: 0.1615 (1 class excluded from average)
=========================Confusion Matrix================...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
----------------------------------------------------
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0 = 0
0 8 3 11 1 4 5 10 4 6 2 3 4 5 4 1 1 | 1 = 1
0 2 7 5 4 1 9 12 3 3 6 7 10 9 1 2 1 | 2 = 2
0 1 2 13 3 5 11 7 3 3 1 4 3 9 6 1 1 | 3 = 3
0 2 3 9 19 5 7 11 7 6 7 7 6 8 6 3 1 | 4 = 4
0 4 2 6 7 17 10 9 10 7 3 2 4 7 5 2 1 | 5 = 5
0 4 4 6 2 6 24 17 6 6 3 3 6 14 5 4 1 | 6 = 6
0 1 5 2 7 5 10 26 3 2 3 3 6 8 4 1 0 | 7 = 7
0 4 6 10 5 7 8 10 13 4 3 5 6 6 2 4 4 | 8 = 8
0 1 3 5 2 4 10 8 8 7 6 2 1 9 3 1 2 | 9 = 9
0 3 7 5 4 7 3 8 4 5 12 2 4 14 6 3 2 | 10 ...
0 2 4 2 5 6 6 6 7 1 2 8 4 10 6 4 0 | 11 ...
0 1 3 2 2 5 3 10 2 5 3 6 16 13 4 4 1 | 12 ...
0 2 2 0 1 2 1 3 2 1 1 4 1 22 3 3 2 | 13 ...
0 1 1 1 1 1 3 6 1 5 0 1 4 6 9 1 1 | 14 ...
0 0 2 0 0 5 5 3 3 2 2 2 2 10 5 5 0 | 15 ...
0 0 1 0 0 3 4 4 1 2 2 6 0 8 4 2 3 | 16 ...
**結果考察 [#p7dc8038]
目標値である「的中率33%以上、回収率78%以上」を上回る結果...
GPUを積んでないノートPCでやってる自分が悪いのですが、ニュ...
この結果を得る過程で、テーブルデータの列項目に何を入れる...
LightGBM では木構造を決めるパラメータが重要だろうと勝手に...
ニューラルネットワークでは階層数、ノード数、最適化アルゴ...
個人的にはニューラルネットを使った分類の方が良い結果と期...
結果が期待ほどでもなかった理由を振り返って考えてみると
+前処理でのエンコーディング問題の可能性(馬名などの名前を...
+そもそも前5走データではこの程度の的中率が限界という可能性
+スクレイピングがしくじってゴミが大量に紛れ込んでいる可能性
各データを再調査して、イレギュラーな形式(「騙馬」とか「...
*深層学習への展開(本番) [#z6427c54]
**データの画像化 [#wadf7a61]
繰り返しですが本来の目的は「出走表をぱっと見たときの直観...
-表形式の学習データをなんらかの方法で画像化して表現する
-表形式の学習データから「馬柱表」を自分で作成してこれを描...
-netkeibaの馬柱表ページのスクリーンショットをまるごとその...
**テーブルの画像表現 [#u5bca360]
現在の学習データ列数は1803。これの各項目をピクセル値と考...
結果としてcsvの1行が81*18のRGB画像に変換されます。トレー...
2018年 中山競馬場 第1回開催 1日目 第1レース
&ref(./201806_0.png,100%);
拡大すると
&ref(./201806_0.png,500%);
実際の馬柱表はこちら:
https://race.netkeiba.com/race/shutuba_past.html?race_id=...
このレースは15番コウギョウブライトが勝ちましたので分類ラ...
1レースがこれだけ小さい画像に圧縮できるって面白いですね...
**疑似馬柱画像 [#vacc51ca]
同様に、テーブルデータを、「前5走-馬柱風」に描画して保存...
2018年 東京競馬場 第1回開催 1日目 第1レース
&ref(./201805_0.png,30%);~
(クリックで拡大)
実際の馬柱表はこちら:
https://race.netkeiba.com/race/shutuba_past.html?race_id=...
このレースは6番マサノカバーガールが勝ちましたので分類ラベ...
**馬柱そのまま [#xd2ef388]
Selenuimというライブラリを使うと特定のウェブページを、「...
サンプルはこちら:
&ref(./201810_1_1_1.png,10%);~
(クリックで拡大)
*学習と評価 [#i2952862]
**ローカルPC [#w2f80fdd]
テーブルデータの学習程度はGPUのないノートPCでもできました...
**Prediction One [#f0cda501]
ソニーの機械学習クラウドサービス。
ちょうどいいタイミングでニュースリリースが流れてきたので...
このサービスのいいところは、事前に定番モデルが沢山用意さ...
層の追加や中間処理の挿入などもGUI操作で行え、パッドを選択...
込み入ったことやろうとすると少々面倒だったり、そもそもパ...
学習データは指定のディレクトリ構造に仕分けしてアップロー...
***無料の罠 [#t317f3cb]
無料枠で一通りお試しはできる一方で、無料期間を過ぎると作...
新規に学習や予測ができないのは分かりますが、結果を振り返...
自分はこれに気づかず放置していたので、実験結果はなくなっ...
**Google Colaboratory[#aaed94f8]
これ使うのも初めてでした。要するにPythonのコマンドプロン...
**モデル抜粋 [#db05b471]
一番オーソドックスなモデルのサンプルをネットから拝借しま...
class Net(nn.Module):
def __init__(self, input_size, output_size):
super(Net, self).__init__()
self.relu = nn.ReLU()
#self.relu = nn.Identity()
self.pool = nn.MaxPool2d(2, stride=2)
self.conv1 = nn.Conv2d(num_channels,8,2)
self.conv2 = nn.Conv2d(8,16,2)
self.conv3 = nn.Conv2d(16,32,2)
#self.conv4 = nn.Conv2d(24,32,2)
self.fc1 = nn.Linear(256, 32)
self.fc2 = nn.Linear(32, output_size)
self.bn1 = nn.BatchNorm2d(8)
self.bn2 = nn.BatchNorm2d(16)
self.bn3 = nn.BatchNorm2d(32)
#self.bn4 = nn.BatchNorm2d(32)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.relu(x)
x = self.pool(x)
x = self.conv3(x)
x = self.bn3(x)
x = self.relu(x)
x = self.pool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
**問題発覚 [#se95025d]
データ(画像)は3種類揃いました。これらを順にニューラルネ...
まず2番目の「疑似馬柱画像」。画像サイズと枚数が多すぎてPr...
次に3番目の「馬柱そのまま」。よく見ると出走頭数によって画...
**評価結果 [#becbda74]
以下はすべてGoogle Colaboratoryで実行。Prediction Oneでは...
***テーブル画像 [#r0a9e9ff]
Loss: 3.924950735909598, Accuracy: 7.795918367346939% (38...
Confusion Matrix:~
&ref(./matrix.png,50%);
・・・精度7.8%・・・
今回は対象レースを「16頭立て以下」に絞ったので、サイコロ...
つまりこれサイコロ振ったのとほぼ同じで、全然、学習できて...
***疑似馬柱画像 [#cfa2d6e1]
Loss: 2.7520341139573317, Accuracy: 10.843373493975903% (...
あれ、11%もある!と思ったのはぬか喜び。以下のように予測は...
入力画像枚数を大幅に減らした影響が原因の一つとは思うけれ...
&ref(./matrix2.png,50%);
**結果考察 [#tef294fe]
サイコロに勝てないという残念な結果に終わりました。
まずテーブル画像法。馬名、騎手名などの扱い。画像というこ...
次に疑似馬柱画像法。そもそもCNNが得意とする画像(猫とか犬...
モデル抜粋は簡単な3階層CNNですが、Prediction Oneではもっ...
結果として、構造やパラメータを変えても、それほど顕著な性...
*実戦で試す [#je6bee51]
せっかくなので実際のレースで試してみましょう。結果が比較...
-4月4週~5月4週 の約一か月半(2023/4/17-5/30)
-対象は中山と阪神
-新馬戦、障害戦、17,18頭立レースは除外
-前日に、翌日分の予想をしておく、翌日の夕方に答え合わせを...
-各レース単勝1点買い
結果です。
|開催|的中率|回収率|
|4月4週|35%|87%|
|4月5週|33%|88%|
|5月1週|47%|101%|
|5月2週|38%|69%|
|5月3週|30%|76%|
|5月4週|26%|65%|
|トータル|34%|81%|
**競馬予想特有の難しさ [#zba4750d]
オッズや人気の関係を調べて痛感しました。こんにちの勝ち馬...
つまり競馬予想は
-たんに勝ち馬を予想するだけでなく
-収支を最大にする最適な馬券の買い方を見つける
という2段構えの発見が必要で、なかなか一筋縄ではいかなさそ...
**何が重要か [#xfa235dc]
LightGBMでは学習で「効いている」パラメータ(特徴量)を表...
&ref(./importance.png,30%);~
(クリックで拡大)
意外にも、オーナー=馬主名が重要視されてました。
レース結果の様々な数値が一番重要と思ってたのですが、着差...
この現象、ほんとかよと思って、改めてnetkeibaで馬主検索し...
つまり「目利きできる馬主がオーナー」=「おめがねにかなっ...
そう考えるとぶちゃけレースデータを一生懸命集めるのは徒労...
*今後の展望 [#heeda925]
仕事の合間、主に土日を使ってコツコツ開発して、正味2か月く...
この先、データサイズを増やす、階層数を深くする、もっと複...
昨今話題のLLM。馬柱表の文字表現をXとして、「Xの勝ち馬はY...
**余談 [#i332a0eb]
今回、半ば無理やりChatGPT使いながら開発を進めて1つ気づい...
チャットでの情報収集には「横の広がり」がまったくない。
例えばLightGBMのウェブサイトを読んでる時にふとサイドメニ...
聞かなくてはヒントをくれないチャットに対してウェブ構造は...
この事態の元凶である「入力を指示して出力を得る」インター...
かつてインターネットがハイパーリンクの発明で大きく変貌し...
**余談その2 [#kce5acd0]
ニューラルネットではちょっとした処理の違いが全然違う結果...
例えば「バッチノーマライズ」という中間処理をCNNの層間に挟...
そういう意味で本記事は、途中経過や試行錯誤をかなり端折っ...
正解があるかもわからない中で、これらを手当たりしだい組み...
きっついなー
というのが正直な感想。暗中模索って言葉がぴったり。精神的...
そこでふと世の中に目を向けてみると、昨今話題のLLM。もし今...
いや、実際には世界中のリスクマネーは全力の倍プッシュでギ...
*参考文献 [#r3e171e0]
城崎 哲[[「AI競馬 人工知能は馬券を制することができるか?」...
*謝辞 [#o7a805db]
全競馬ファンのためスクレイピングを恐らく黙認しているであ...
*文責 [#o7a805db]
本文章の文責は(株)スカイリー・ネットワークスにあります。 ...
製品、サービス等の名称にはそれぞれの団体または企業のトレ...
ページ名: