实战|用决策树实现NBA获胜预测

2020-07-21 14:55:17 浏览数 (1)

本文讲解一个小的demo通过决策树和随机森林来进行NBA比赛的胜利预测。

01

数据获取

因为疫情原因导致NBA2019-2020赛季没有进行完,所以我们使用NBA2018-2019赛季的数据进行预测,数据获取方式有下面两种:

通过网站获取

我们可以通过网站去获取任意一年的所有场次比赛数据,获取方式如下:

  1. 在浏览器中访问https://www.basketball-reference.com/leagues/NBA_2019_games.html
  2. 点击Share&more
  3. 点击Get table as CSV (for Excel)
  4. 复制连带表头的所有数据到Excel的文本文件中
  5. 依次选择每一个月份,复制不带表头的数据到上一个文件后

同时我们还需要获取上赛季积分榜的数据,获取方式如下:

  1. 在浏览器中访问https://www.basketball-reference.com/leagues/NBA_2018_standings.html
  2. 点击Expander Standings,可以看到整个赛季的战况信息
  3. 点击Export链接
  4. 复制数据并保存即可

通过GitHub获取

本文中的数据和代码已经上传到GitHub上,链接如下:https://github.com/bigdatavalley/NBA_predict

02

数据准备

在进行预测的算法设计之前,我们需要先进行数据的查看和整合,由于原有数据中很多的特征名称设定的不是很好,所以我们在加载数据的时候重新设定一下列名称,代码如下:

代码语言:javascript复制
import pandas as pd

path = 'basketball.csv'
dataset = pd.read_csv(path, parse_dates=['Date'])
dataset.columns = [
    'Date', 'Start(ET)', 'Visitor Team', 'VisitorPts', 'Home Team', 'HomePts',
    'OT?', 'Score Type', 'Notes'
]
dataset.head()

运行结果如下:

我们在建立预测模型的时候通常会有一个标准,当我们的预测效果超过这个标准的时候我们就可以认为预测模型起到了作用,接下来我们来建立一个关于预测球队获胜的标准。

在NBA比赛中,通常分为主客场,当球队在主场比赛的时候往往会占据一定的优势,那么如果我们每次都预测主场球队获胜,应该也能得到不错的准确率,那么我们就可以用这个数值来当作我们的标准,代码如下:

代码语言:javascript复制
# 提取新特征
# 统计客队得分小于主队的场次
dataset['HomeWin'] = dataset['VisitorPts'] < dataset['HomePts']

# 保留成label
y_true = dataset['HomeWin'].values
# y_true
dataset['HomeWin'].mean()

运行结果如下:

代码语言:javascript复制
0.5907012195121951

现在我们就有了一个0.59作为标准,后面我们要保证建立的模型不低于这个准确率就可以了。

03

构建特征

构造特征一点一点进行,后面我们再陆续添加新的特征。

进行模型建立之前,还有一个重要的过程就是构建我们要使用的特征,第一个我们要思考的就是两只球队是否赢得了上一场的比赛,通常我们会认为赢得了上一场比赛的球队就是更厉害的。我们建立两个新的特征分别是主队/客队是否赢了上一场比赛。代码如下:

代码语言:javascript复制
from collections import defaultdict

won_last = defaultdict(int)
dataset['HomeLastWin'] = 0
dataset['VisitorLastWin'] = 0

# 时间是无序的时候dataset.sort('Date').iterrows()
for index, row in dataset.iterrows():
    home_team = row['Home Team']
    visitor_team = row['Visitor Team']
    dataset.at[index, 'HomeLastWin'] = won_last[home_team]
    dataset.at[index, 'VisitorLastWin'] = won_last[visitor_team]
    won_last[home_team] = int(row['HomeWin'])
    won_last[visitor_team] = 1 - int(row['HomeWin'])

X_previouswins = dataset[['HomeLastWin', 'VisitorLastWin']].values
X_previouswins

运行结果如下:

代码语言:javascript复制
array([[0, 0],
       [0, 0],
       [0, 0],
       ...,
       [0, 1],
       [1, 0],
       [1, 0]])

04

使用决策树

我们尝试使用简单的决策树算法进行简单的训练,并且通过交叉验证的方式获取评分。

决策树的原理:

机器学习|决策树(上)

机器学习|决策树(下)

代码如下:

代码语言:javascript复制
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

clf = DecisionTreeClassifier(random_state=42)

score = cross_val_score(clf, X_previouswins, y_true, scoring = 'accuracy')
print('score is:{0:.1f}%'.format(np.mean(score)*100))

运行结果如下:

代码语言:javascript复制
score is:59.1%

根据结果可以发现,当前的特征下,并没有使得我们的预测效果提高,接下来我们继续对数据和模型进行改进。

05

增加特征

考虑到球队获胜可能和上个赛季的战绩也有一定的关系,我们尝试加入上个赛季的数据来进行预测。

代码语言:javascript复制
# 加入上赛季数据
standings = pd.read_excel('standings.xls')
# 建立新的特征(排名情况)
dataset['HomeTeamRanksHigher'] = 0

for index, row in dataset.iterrows():
    home_team = row['Home Team']
    visitor_team = row['Visitor Team']
    home_rank = standings[standings['Team'] == home_team]['Rk'].values[0]
    visitor_rank = standings[standings['Team'] == visitor_team]['Rk'].values[0]
    row['HomeTeamRanksHigher'] = int(home_rank > visitor_rank)
    dataset.at[index, 'HomeTeamRanksHigher'] = int(home_rank < visitor_rank)
代码语言:javascript复制
clf = DecisionTreeClassifier(random_state=42)
X_homehigher = dataset[[
    'HomeLastWin', 'VisitorLastWin', 'HomeTeamRanksHigher'
]].values

score = cross_val_score(clf, X_homehigher, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))

运行结果如下:

代码语言:javascript复制
score is:62.2%

很明显,我们加入上个赛季的战绩的时候,预测的准确率提升了3个百分点。

接下来我们继续加入交手的两只球队中哪一个在上一次交手中胜出的数据,代码如下:

代码语言:javascript复制
last_match_winner = defaultdict(int)
dataset['HomeTeamWonLast'] = 0
for index, row in dataset.iterrows():
    home_team = row['Home Team']
    visitor_team = row['Visitor Team']
    teams = tuple(sorted([home_team, visitor_team]))
    home_team_won_last = 1 if last_match_winner[teams] == row[
        'Home Team'] else 0
    dataset.at[index, 'HomeTeamWonLast'] = home_team_won_last
    winner = row['Home Team'] if row['HomeWin'] else row['Visitor Team']
    last_match_winner[teams] = winner
    
代码语言:javascript复制
clf = DecisionTreeClassifier(random_state=42)
X_lastwinner = dataset[[
    'HomeLastWin', 'VisitorLastWin', 'HomeTeamRanksHigher', 'HomeTeamWonLast'
]].values

score = cross_val_score(clf, X_lastwinner, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))

运行结果如下:

代码语言:javascript复制
score is:61.7%

结果和上面差异不大,没有关系,我们继续尝试使用其他的策略来进行改进。

06

加入队伍数据

我们之前做的工作都是根据一些比赛数据来进行的,下面我们尝试加入队伍数据来检验一下我们的运行成果,由于我们不能直接对字符数据进行计算,所以先把数据通过LabelEncoder转化为数值类型,但是一般数值类型又会有大小造成的影响,所以我们再进行一次OneHotEncoder的操作即可。代码如下:

代码语言:javascript复制
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder

encoding = LabelEncoder()
onehot = OneHotEncoder()

encoding.fit(dataset['Home Team'].values)
home_teams = encoding.transform(dataset['Home Team'].values)
visitor_teams = encoding.transform(dataset['Visitor Team'].values)
X_teams = np.vstack([home_teams, visitor_teams]).T
X_teams = onehot.fit_transform(X_teams).todense()
X_all = np.hstack([X_lastwinner, X_teams])
代码语言:javascript复制
clf = DecisionTreeClassifier(random_state=42)
score = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))

运行结果如下:

代码语言:javascript复制
score is:60.7%

很显然虽然比我们直接预测主队获胜的效果要好,但是也没有高出很多。不要慌,我们继续使用更好的算法来尝试一下。

07

使用随机森林

随机森林是一种以决策树为基分类器的集成学习算法,理论上讲,我们所得到的效果要好于决策树。

随机森林的简单原理:

机器学习|集成学习(简介)

代码如下:

代码语言:javascript复制
clf = RandomForestClassifier(random_state=42)
score = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print('score is:{0:.1f}%'.format(np.mean(score) * 100))

运行结果如下:

代码语言:javascript复制
score is:63.6%

尝试使用网格搜索,代码如下:

代码语言:javascript复制
from sklearn.model_selection import GridSearchCV

parameter_space = {
    'max_features': [2, 10, 'auto'],
    'n_estimators': [100, 200],
    'criterion': ['gini', 'entropy'],
    'min_samples_leaf': [2, 4, 6]
}
clf = RandomForestClassifier(random_state=42)
grid = GridSearchCV(clf, parameter_space)
grid.fit(X_all, y_true)
print('score is:{0:.1f}%'.format(np.mean(score) * 100))

运行结果如下:

代码语言:javascript复制
score is:63.6%

可以看到根据目前我们所构造的几个简单的特征就已经可以得到显著的准确率提升了。同时收到球员转会、受伤等情况的影响,对于不同赛季的数据使用这些方法会得到不同的结果,有兴趣的读者可以自己获取其他赛季的数据来尝试一下。

08

进一步考虑

你可能对当前的结果还不是很满意,可以尝试进行下面几个方向的参考:

  • 上一次比赛距离本次比赛的时间
  • 最近半个月的比赛状态
  • 双方在最近几场比赛的战绩
  • 每个球队在客场和主场的表现情况

0 人点赞