概要

logistic regressionを使ってみる。classifyとprobabilityを出すあたりまで。

分類する内容

野球で7回までの得点を入れると、勝敗の確率が出るようなものでも作ってみる。

こちらのMLBのデータを利用
http://www.retrosheet.org/gamelogs/index.html

上記ページの2013年データを利用した。MLBはデータが落とせるサイトがいろいろあってありがたい。

コードの説明

sklearnは、データ入れて、fitして、出来上がった分類器でpredictするだけで動く。簡単で便利。

落としてきたファイルを適当に変換して、下記のような形式にする。

0,7,2
0,1,0
1,2,3
1,2,5
1,0,2

左から、勝ち負け(0がホーム勝ち、1がビジター勝ち), ホームチームの7回までの得点, ビジターチームの7回までの得点。使うのはこの3つの数値だけ。引き分けは無視。

コードは下記。

# -*- coding: utf-8 -*-

import numpy as np
import pylab as pl
import csv
from sklearn.linear_model import LogisticRegression

# 元ファイルを適当に整形しておく
with open('GL2013.TXT') as f:
  with open('GL2013.TXT.tmp', 'wb') as writer:
    reader = csv.reader( f, delimiter=',' )
    for line in reader:
      toint = lambda d : int(d) if d.isdigit() else 0
      visitor7 = sum( map( toint, list( line[19] )[0:7] ) )
      visitor = sum( map( toint, list( line[19] ) ) )
      home7 = sum( map( toint, list( line[20] )[0:7] ) )
      home = sum( map( toint, list( line[20] ) ) )
      # home勝ち=0, visitor勝ち=1
      if home != visitor:
        win = 0 if home > visitor else 1
        writer.write( "{0},{1},{2}\n".format(win, home7, visitor7 ) )

# 整形したデータを読み込む
data = np.genfromtxt( 'GL2013.TXT.tmp', delimiter=',', dtype=np.uint8 )
cls = data[:, 0]
features = data[:, 1:3]

# l2 logistict legression で fit する
classifier = LogisticRegression(C=1.0, penalty='l2')
classifier.fit( features, cls ) 

# 確率を確認
print( "0-0 : {0}".format( classifier.predict_proba( [0, 0] ) ) )
print( "3-2 : {0}".format( classifier.predict_proba( [3, 2] ) ) )
print( "2-3 : {0}".format( classifier.predict_proba( [2, 3] ) ) )
print( "1-9 : {0}".format( classifier.predict_proba( [1, 9] ) ) )
print( "3-3 : {0}".format( classifier.predict_proba( [3, 3] ) ) )
print( "4-3 : {0}".format( classifier.predict_proba( [4, 3] ) ) )

整形後の処理は、classifier用意して、fitして、predictしてるだけ。実に簡単。

これを実行すると、下記のような結果になる。

0-0 : [[ 0.52235099  0.47764901]]
3-2 : [[ 0.75421393  0.24578607]]
2-3 : [[ 0.28452784  0.71547216]]
1-9 : [[  3.14671968e-04   9.99685328e-01]]
3-3 : [[ 0.52537046  0.47462954]]
4-3 : [[ 0.75496118  0.24503882]]

0-0や3-3などの同点の際はほぼ互角だけど若干ホーム寄り。1-9とかだとほぼ試合終了。3-2や4-3のような1点差で7回を終えた場合は、ホームチームは7割5分勝てる。ビジター(2-3)は7割1分5厘と少し差がある。全体的にホーム補正が存在するのか、それとも適用するデータを増やすとなだからになるのか。

ちなみに対象データでは、ホームチームの勝率は5割3分7厘だった。

hist = np.histogram( cls, bins=[0, 1, 2] )[0]
win_home = hist[0]
win_visi = hist[1]
win_home = float(win_home + win_visi)
0.537638831756

coefficientの参照

fitした結果がどうなっているのかも見てみる。coef_とintercept_で見れるらしい。

classifier.coef_
classifier.intercept_

結果

[[-1.02368064  1.01964548]]
[-0.0894636]

試しにて入力で計算してみる。

>>> x = -1.02368064
>>> y = 1.01964548
>>> z = -0.0894636

>>> 3 * x + 4 * y + z
0.9180764000000006

>>> 4 * x + 4 * y + z
-0.10560423999999971

これで分類できるのはわかるけど、probabilityはどうやって出せばいいのか。scikit-learnのソースコード(githubにいる)を見たら、1. / (1. + np.exp(-self.decision_function(X))) と書いてあった。

ということは、こんなコードで良いわけか。

def prob( h, v ):
  prob = h * -1.02368064 + v * 1.01964548 - 0.0894636
  print( "{0}-{1} : {2}".format( h, v, 1. / (1. + np.exp(-prob) ) ) )

prob( 0, 0 )
prob( 3, 2 )
prob( 2, 3 )

実行してみたところ、predict_probaを使った時と同じ結果になった。

0-0 : 0.477649005633
3-2 : 0.245786064646
2-3 : 0.715472155171

fitした結果のsave/load

save/loadのコードも書いておく。compressを付けないとファイルがたくさん出力されたりする。

# 保存
from sklearn.externals import joblib
joblib.dump(classifier, 'mlb_stats.pkl', compress=9) 

# ロード
clf = joblib.load('mlb_stats.pkl')

print( "0-0 : {0}".format( clf.predict_proba( [0, 0] ) ) )