How many files(0-15)

競馬の予想システムで一儲を企むおっさんのヨコシマな横顔

機械学習で競馬予想 其の009 ~回帰分析による予想システムの実装~

前回予想に使ったプログラムの実装は以下のとおりです。
loadHorseDataでtsvファイルを読込みdataShapingで読み込んだデータを回帰分析しやすいように整形して、回帰分析でパラメータを取得という流れになっています。
回帰分析の説明変数は、距離、馬番、馬体重、負担重量、目的変数はタイムとしています。回帰分析については、そのうち説明するかもしれませんが、今回は割愛します。


全ソース

# -*- coding: utf-8 -*-
import scipy as sp
import numpy as np
from scipy import linalg as LA
import os

# 馬データの読み込み
def loadHorseData(filename):
    horsedata = sp.genfromtxt(os.path.join(r"C:\Users\fumitaka\Desktop\blog\horse", filename), delimiter="\t", 
        dtype=[ ('日付','U10'),         # 0:日付
                ('開催地','U10'),       # 1:開催地
                ('レース','i2'),         # 2:レース 
                ('レース名','U50'),       # 3:レース名  
                ('距離','i4'),          # 4:距離  
                ('天候','U10'),         # 5:天候  
                ('馬番','i2'),          # 6:馬番 
                ('人気','i3'),          # 7:人気 
                ('着順','U10'),         # 8:着順 
                ('タイム','U10'),        # 9:タイム 
                ('差/事故', 'i4'),     # 10:差/事故  
                ('上3F','i4'),         # 11:上3F 
                ('通過順','U10'),      # 12:通過順
                ('体重','i4'),         # 13:体重
                ('騎手','U20'),        # 14:騎手
                ('負担重量','i4'),     # 15:負担重量
                ('調教師','U20'),      # 16:調教師
                ('獲得賞金(円)','U10')  # 17:獲得賞金
                ],                  
        converters={ 
            1 : lambda s: s.decode('utf8'),    # 開催地
            3 : lambda s: s.decode('utf8'),    # レース名
            5 : lambda s: s.decode('utf8'),    # 天候
            14 : lambda s: s.decode('utf8'),    # 騎手
            16: lambda s: s.decode('utf8'),    # 調教師
        })
    
    return np.array(horsedata.tolist(), dtype = object)

# データを整形する。
def dataShaping(horsedata):
    y = horsedata[:,9]    # タイムの配列を作成
    d = horsedata[:,4]    # 距離の配列を生成
    w1 = horsedata[:,13]  # 体重の配列を生成
    w2 = horsedata[:,15]  # 負担重量の配列を生成
    n = horsedata[:,6]     # 馬番の配列を生成
    
    # 記録が無いレースを除外
    z = np.array([idx for idx, i in enumerate(y) if i != u'' ])
    y = y[z]
    d = d[z]
    w1 = w1[z]
    w2 = w2[z]
    n = n[z]
    
    # タイムを数値変換
    for idx, time in enumerate(y):
        if len(time.split(":")) == 2:
            y[idx] = np.float32(time.split(":")[0])*60+np.float32(time.split(":")[1])
        else:
            y[idx] = np.float32(time)
                
    y = y.astype('float')

    # .Tは転置
    return np.array([d, w1, w2, n, np.ones(len(n))]).T, y

def getParam(filename):
  # 以下、main処理
    horsedata = loadHorseData(filename)
    x, y = dataShaping(horsedata)

    return LA.lstsq(x, y)[0]       # 偏回帰係数

def calctime(params, distance, weight1, weight2, no):
    t = np.int32(distance) * params[0] + np.int32(weight1) * params[1] + np.int32(weight2) * params[2] + np.int32(no) * params[3] + params[4]
    t1 = np.int32(distance) * params[0] + np.int32(weight2) * params[2] + np.int32(no) * params[3] + params[4]
    print str(t) + " = " + str(distance) + " * (" + str(params[0]) + ") + " + str(weight1) + " * (" + str(params[1]) + ") + " + str(weight2) + " * ("  + str(params[2]) + ") + " + str(no) + " * (" + str(params[3]) + ") + " + str(params[4])
    print "time = " + str(t1) + " + (" + str(params[1]) + ") * weight\n" 
    return t


# 8R
print "8R 1200m"
calctime(getParam(u"weekend.tsv"), 1200, 490, 54.0, 1)
calctime(getParam(u"nikothankyou.tsv"), 1200, 490, 56.0, 2)
calctime(getParam(u"syurudance.tsv"), 1200, 490, 55.0, 3)

以下、出走する馬の数だけcalctimeを実行するだけなので省略


それでは個々の実装について説明していきます。最初のloadHorseDataですが、機械学習 其の007で紹介したとおりです。次のdataShapingですが、説明変数を距離、馬体重、負担重量、馬番、目的変数をタイムとするので、loadHorseDataで読み込んだhorseDataから説明変数、目的変数となるものをそれぞれy,d,w1,w2,nの配列として取得しています。 取得した配列から落馬等の原因で記録が残っていないレースのものを除外しています。除外の方法ですが、単にタイムが無い(u''で比較)ものを除外するようにしています。

次に読み込んだhorseDataのタイムは、「1:14.7」のような表記となっているため単純な数値として処理するには不適切なので、これを数値に変換しています。ここで、数値変換の前に、if len(time.split(":") == 2という条件を追加しているのは、レースの距離が800mのように短いレースの場合1分を超えない記録が残っていることがあり、そういったデータの場合には、timeを":"でsplitしても、分の部分と秒の部分とで配列が分かれずエラーとなるため、splitするケースとしないケースをわけています。

そして、最後に[d, w1, w2, n]の配列を転置したものとy(タイム)を返却しています。


データ整形部分

# データを整形する。
def dataShaping(horsedata):
    y = horsedata[:,9]    # タイムの配列を作成
    d = horsedata[:,4]    # 距離の配列を生成
    w1 = horsedata[:,13]  # 体重の配列を生成
    w2 = horsedata[:,15]  # 負担重量の配列を生成
    n = horsedata[:,6]     # 馬番の配列を生成
    
    # 記録が無いレースを除外
    z = np.array([idx for idx, i in enumerate(y) if i != u'' ])
    y = y[z]
    d = d[z]
    w1 = w1[z]
    w2 = w2[z]
    n = n[z]
    
    # タイムを数値変換
    for idx, time in enumerate(y):
        if len(time.split(":")) == 2:
            y[idx] = np.float32(time.split(":")[0])*60+np.float32(time.split(":")[1])
        else:
            y[idx] = np.float32(time)
                
    y = y.astype('float')

    # .Tは転置
    return np.array([d, w1, w2, n, np.ones(len(n))]).T, y


転置する前とした後のイメージが湧きにくいかもしれないので、念のためイメージを以下に示します。


転置前の配列の内容

[[1200 1200 1200 1200 1400 1400 1400 1400 1400 -1 1400 1400 1400 1400 1400
  1400 1400 1300 1300 1600 1600 1200 -1]
 [502 496 493 501 500 499 493 490 494 493 499 498 493 491 495 501 502 506
  509 498 500 486 482]
 [53 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54]
 [1 1 9 8 3 3 10 9 8 8 1 6 1 7 1 5 9 8 1 8 5 9 14]
 [1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
  1.0 1.0 1.0 1.0 1.0]]


転置後の配列の内容

[[1200 502 53 1 1.0]
 [1200 496 54 1 1.0]
 [1200 493 54 9 1.0]
 [1200 501 54 8 1.0]
 [1400 500 54 3 1.0]
 [1400 499 54 3 1.0]
 [1400 493 54 10 1.0]
 [1400 490 54 9 1.0]
 [1400 494 54 8 1.0]
 [-1 493 54 8 1.0]
 [1400 499 54 1 1.0]
 [1400 498 54 6 1.0]
 [1400 493 54 1 1.0]
 [1400 491 54 7 1.0]
 [1400 495 54 1 1.0]
 [1400 501 54 5 1.0]
 [1400 502 54 9 1.0]
 [1300 506 54 8 1.0]
 [1300 509 54 1 1.0]
 [1600 498 54 8 1.0]
 [1600 500 54 5 1.0]
 [1200 486 54 9 1.0]
 [-1 482 54 14 1.0]]

転置前の配列をAとしたとき、A[0]、A[1]でアクセスできるのは、それぞれ距離の配列、馬体重の配列となります。 これでも使えないことは無いのですが、レース単位でデータを処理することを考えると、転置後の配列(A’とします)のようにA'[0]で1つ目のレース情報となっていた方が、このあとで使うlstsqのパラメータとして使えるし、今後も何かと都合がよさそうなのでこのようにしています。(その都度転置しても良いので完全に好みの世界ですが、、、)

そしてdataShapingで得られた説明変数の配列と目的変数の配列をもとにしてlstsq(最小二乗法)を使い説明変数の係数(偏回帰係数)を求めています。ちなみに、lstsqの戻りの配列の1番目の要素が最小二乗法の解です。 詳しくはnumpy.linalg.lstsq — NumPy v1.9 Manualを参照してみてください。


回帰分析

def getParam(filename):
  # 以下、main処理
    horsedata = loadHorseData(filename)
    x, y = dataShaping(horsedata)

    return LA.lstsq(x, y)[0]       # 偏回帰係数


最期に、説明変数としていた、距離、馬体重、重量負担、馬番をパラメータとしてタイムを予測する関数calctimeを実装しています。馬体重だけは、レース開始の30分前くらいにならないと発表されないので、馬体重の関数となるような式をprintしているため冗長になっていますが、その辺は気にしないでください。


タイム予想

def calctime(params, distance, weight1, weight2, no):
    t = np.int32(distance) * params[0] + np.int32(weight1) * params[1] + np.int32(weight2) * params[2] + np.int32(no) * params[3] + params[4]
    t1 = np.int32(distance) * params[0] + np.int32(weight2) * params[2] + np.int32(no) * params[3] + params[4]
    print str(t) + " = " + str(distance) + " * (" + str(params[0]) + ") + " + str(weight1) + " * (" + str(params[1]) + ") + " + str(weight2) + " * ("  + str(params[2]) + ") + " + str(no) + " * (" + str(params[3]) + ") + " + str(params[4])
    print "time = " + str(t1) + " + (" + str(params[1]) + ") * weight\n" 
    return t

次回はこの予想システムで実際に馬券を買ってみたのでその報告と結果分析を行おうと考えています。