競馬データをいじり倒す
今回は tensorflow を理解するために競馬データをいじり倒していきます。本に書いてあるサンプルデータを競馬データに入れ替えて挙動をみていくことにします。今回は手始めにあるレースの結果から走破タイムと上がり3Fタイムの相関を調べてみたいと思います。
線形回帰分析
目的
競馬データを使って各数値に相関関係はあるか、データのない部分を予測可能かどうか、といったあたりを検証してみます。直線の1次方程式を出すところまでいっきに行きます。
手順
いくつかの本を参考にした結果以下の手順を試します。
- データの標準化
- 回帰モデルの作成
- 損失関数の作成
- 勾配アルゴリズムの実装
それぞれのコードや数学的な意味、などはいったんすっ飛ばしてまずは最短手数で結果までいきます。
データの標準化
今回のデータの趣旨は、競馬のレースを行い、勝った順にゴールまでの走破タイムを記録したものです。数値が小さい方が優秀、ということになります。
まずは、グラフ可視化から。
サンプルデータ:
タイムS:レースを行い各馬のスタートからゴールまでかかった時間。秒数表示。
上がり3F:各馬の、ゴールから約600m手前地点からゴールまでかかった時間。秒数表示。
コード:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import numpy as np import matplotlib.pyplot as plt %matplotlib inline data = np.loadtxt(fname='CSVのパス', dtype='float', delimiter=',', skiprows=1 ) train_x = data[:,0] train_y = data[:,1] plt.plot(train_x, train_y, 'o' ) plt.show() |
結果
ぱっと見では「右肩上がりの直線」か「ゆるい上昇カーブ」という印象。
競馬のレースの距離は短くても1000m、長いと3600mまであります。(障害戦だと4250m。今回のサンプルは2000m)自分の馬をどういうペース配分でレースを行うか?というのは、騎手がその馬の特性やコースの距離、他の馬の動向などを考慮して決定しています。ペース配分については、大まかには以下に大別されます。
赤枠:
「逃げ」「先行」:先に早めのペースで走って前の方に位置取りそのままゴールまで粘る、という走り方
一般に先頭が「逃げ」その後ろ2~3頭が「先行」。レーススタートしてすぐに前に出るためレース前半の方がタイムが早いが、前半に体力を使うため後半のタイムが遅い。(稀に前半早くて、後半も早いという馬がいる)サンプルデータでは縦軸の上の方に分布している。
青枠:
「中団」「後方」:レース前半はペースを抑えて集団の後ろでついていき、ゴール近くから残りの体力を使って前に位置する馬を抜いていく走り方。(当然、前半遅くて後半も遅いという馬もいる)サンプルデータでは縦軸の下の方に多く分布している。
サンプルデータの標準化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
def standardize(x): x_mean = x.mean() std = x.std() return (x - x_mean)/std import numpy as np data = np.loadtxt(fname='CSVのパス', dtype='float', delimiter=',', skiprows=1 ) train_x = data[:,0] train_y = data[:,1] train_x_std = standardize(train_x) train_y_std = standardize(train_y) plt.plot(train_x_std, train_y_std, 'o' ) plt.show() |
結果
平均=0、標準偏差=1になりました。
回帰モデルの作成
続いて回帰モデルを作成します。
Variable オブジェクトを用意
1 2 3 4 |
import tensorflow as tf a = tf.Variable(0.) b = tf.Variable(0.) |
回帰モデルの定義
1 2 3 4 |
def model(x): y = a*x + b return y |
損失関数(目的関数)の作成
損失関数の定義
1 2 3 |
def loss(y_pred, y_true): return tf.math.reduce_mean(tf.math.square(y_pred - y_true)) |
勾配アルゴリズムの実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
learning_rate = 0.1 epochs = 50 for i in range(epochs): with tf.GradientTape() as tape: y_pred = model(train_x_std) tmp_loss = loss(y_pred, train_y_std) gradients = tape.gradient(tmp_loss, [a, b]) a.assign_sub(learning_rate * gradients[0]) b.assign_sub(learning_rate * gradients[1]) if (i + 1) % 5 == 0: print('Step:{} a = {} b = {}'.format( i + 1, a.numpy(), b.numpy()) ) # 損失を出力 print('Loss = {}'.format(tmp_loss)) |
結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Step:5 a = 0.43798986077308655 b = -1.0430813546236095e-08 Loss = 0.6468018889427185 Step:10 a = 0.5815104246139526 b = -1.3411045607369942e-08 Loss = 0.5832446217536926 Step:15 a = 0.6285392045974731 b = -1.7136335017653437e-08 Loss = 0.5764201879501343 Step:20 a = 0.6439496278762817 b = -1.4901161193847656e-08 Loss = 0.5756874680519104 Step:25 a = 0.6489992737770081 b = -1.4901161193847656e-08 Loss = 0.5756087899208069 Step:30 a = 0.6506539583206177 b = -1.4901161193847656e-08 Loss = 0.5756003260612488 Step:35 a = 0.6511961221694946 b = -1.192092824453539e-08 Loss = 0.5755994319915771 Step:40 a = 0.6513738036155701 b = -1.415610206834117e-08 Loss = 0.5755993127822876 Step:45 a = 0.6514320373535156 b = -1.2665985593685036e-08 Loss = 0.5755993127822876 Step:50 a = 0.6514511108398438 b = -1.415610295651959e-08 Loss = 0.5755993127822876 |
グラフの描画
この図に回帰直線を入れます。
1 2 3 4 5 6 7 |
plt.scatter(train_x_std, train_y_std) y_learned = a*train_x_std + b plt.plot(train_x_std, y_learned, 'r') plt.grid(True) plt.show() |
結果
各データの中心を通ってるといえばそうなんですが…
今回はデータの個数が13個でしかもバラツキも大きいのでしょうがないところ。
予測してみる
数値が13個なのでそれ以外の数値ではどう予測されるか、を試してみます。
タイムS = 116.8
1 2 3 4 5 6 7 8 |
x = 116.8 x_mean = train_x.mean() std = train_x.std() x = (int(x) - x_mean)/std y = (a*x + b).numpy() y_mean = train_y.mean() y_std = train_y.std() y*y_std + y_mean |
結果
1 |
33.2790215539203 |
実際の1着のタイムS(レース走破タイム)より早くしてみると、上がり3Fタイムが33.2秒とかでることになりました…
(参考:ここ3年でこれと同じコース条件(中京芝2000m良)での最も早い走破タイム(タイムS)は117.2秒で、116.8秒は文字通り架空の記録… ただ、上がり3F 33.1秒という馬が1頭、33.2秒という馬が1頭いる)
まとめ
とりあえずサンプルコードをベースに競馬データを入力して線形回帰分析っぽいことはできました。ただ、それぞれのコードの意味、数学的なことなど、まだまだ知識として勉強しないといけないことがたくさんあります。次回そのあたりをやっていきたいと思います。
[…] 文系高卒おじさんのプログラミング日記文系高卒おじさん 「競馬の方程式… […]