本章介绍另一种分类算法——决策树,用它预测NBA篮球赛的获胜球队。比起其他算法,决策树有很多优点,其中最主要的一个优点是决策过程是机器和人都能看懂的,我们使用机器学习到的模型就能完成预测任务。正如我们将在本章讲到的,决策树的另一个优点则是它能处理多种不同类型的特征。
一、数据处理
我们使用2013-2014赛季的比赛数据。数据集链接:http://www.basketball-reference.com/leagues/NBA_2014_games.html 。使用pandas加载数据集。
从链接下载的数据集的列包括{Date、Start(ET)、Vistor/Neutral、PTS、Home/Neutral、PTS.1、Unnamed:6、Unnamed:7、Attend.、Unnamed:9、Notes}
import numpy as np
import pandas as pd
data_filename = './basketball2013-2014.csv'
dataset = pd.read_csv(data_filename)
pd.set_option('display.max_columns', 20) # 设置显示的最大列数为20列
![](https://i-blog.csdnimg.cn/blog_migrate/659b7e5924c60e7744b2fcb6534ec732.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7f80b726d89b3a90a31f555121d74f92.png)
数据清洗
上面截图可知数据集有以下两个问题:
(1)字符串格式的日期转换为日期对象(数据格式) parse_dates=[0] --代表第0列的数据日期格式进行转换。
(2)修改列名,防止看不清客队和主队的分数。
# (1)加载数据集时对日期列格式进行修改
dataset = pd.read_csv(data_filename, parse_dates=[0])
# 删除不需要的列
dataset.drop('Unnamed: 9', axis=1, inplace=True)
# (2)对已有的列名进行修改
dataset.columns = ['Date', 'Start', 'Visitor Team', 'VisitorPts',
'Home Team', 'HomePts', 'Score Type', 'OT?', 'Attend.', 'Notes']
特征抽取
给dataset增加三列数据,分别是:
HomeWin:判断home是否战胜了visitor;
HomeLastWin:判断home是否赢得上一场比赛;
VisitorLastWin:判断home是否赢得上一场比赛。
# 增加列1-HomeWin:判断home是否战胜了visitor
dataset['HomeWin'] = dataset['HomePts'] > dataset['VisitorPts']
# 遍历每一行数据,记录获胜球队。当到达一行新数据时,分别查看该行数据中的两支球队在各自的上一场比赛中有没有获胜的
from collections import defaultdict
# 字典的键为球队,值为是否赢得上一场比赛
won_last = defaultdict(int)
for index, row in dataset.sort_values('Date').iterrows(): # 遍历所有行,index是行号,row是具体内容。
home_team = row['Home Team'] # 提取主队名
visitor_team = row['Visitor Team'] # 提取客队名
row['HomeLastWin'] = won_last[home_team] # 第n行的主队上一轮否赢球进行赋值,默认为0(False)
row['VisitorLastWin'] = won_last[visitor_team] # 第n行的客队上一轮否赢球进行赋值,默认为0(False)
dataset['HomeLastWin'] = row['HomeLastWin'] # 给dataset增加列homelastwin、visitorlastwin,不然后面dataset.loc[index] = row修改不了
dataset['VisitorLastWin'] = row['VisitorLastWin']
dataset.loc[index] = row
won_last[home_team] = row['HomeWin'] # 更新defaultdict
won_last[visitor_team] = not row['HomeWin'] # 更新defaultdict
# print(dataset.loc[0:25])
二、决策树
1.决策树简介
决策树是一种有监督的机器学习算法。作为一种积极学习的算法,决策树也分为两大步骤:训练阶段和预测阶段。
创建决策树:大多通过迭代生成一棵树。它们从根节点开始,选取最佳特征,用于第一个决策,到达下一个节点,选择下一个最佳特征,以此类推。当发现无法从增加树的层级中获得更多信息时,算法启动退出机制。
决策树中的参数: (1)min_samples_split:指定创建一个新节点至少需要的个体数量 。控制着决策节点的创建。
(2)min_samples_leaf:指定为了保留节点,每个节点至少应该包含的个体数量。决定着决策节点能否被保留。
(3)决策树的另一个参数是创建决策的标准,常用的有以下两个:
基尼不纯度(Gini impurity):用于衡量决策节点错误预测新个体类别的比例。
信息增益(Information gain):用信息论中的熵来表示决策节点提供多少新信息。
2.决策树代码实现
使用sklearn.tree.DecisionTreeClassifier 创建决策树。
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state=14)
用我们抽取的特征计算分类正确率(分别用fit和predict方法,cross_val_score方法预测)。
x_previouswins = dataset[["HomeLastWin", "VisitorLastWin"]].values
y_true = dataset['HomeWin'].values
# 决策树估计器的fit和predict
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x_previouswins, y_true, random_state=14)
clf.fit(x_train, y_train)
y_predict = clf.predict(x_test)
accuracy = np.mean(y_predict == y_test) * 100
print('The accuracy is {0:1f}%'.format(accuracy))
# cross_val_score预测
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, x_previouswins, y_true, scoring='accuracy')
print('The accuracy is {0:1f}%'.format(np.mean(scores)*100))
![](https://i-blog.csdnimg.cn/blog_migrate/ae1ea20fe75277a5b667ea152632823e.png)
3.NBA比赛结果预测
从数据集中构建有效特征(Feature Engineering,特征工程)是数据挖掘的难点所在,好的特征直接关系到结果的正确率——甚至比选择合适的算法更重要。
尝试使用不同的特征,用cross_val_score测试不同特征的分类正确率。
(1)增加特征1:"HomeTeamRanksHigher"--主场队是否通常比对手水平高。
(2)增加特征2:“HomeTeamWonLast"--统计两支球队上场比赛的情况。
对增加特征1,我们增加一个数据集,使用2013赛季的战绩作为特征取值来源。数据来源:http://www.basketball-reference.com/leagues/NBA_2013_standings.html
# (1)增加特征1:"HomeTeamRanksHigher"--主场队是否通常比对手水平高
standings = pd.read_csv('./leagues_NBA_2013_standings_expanded-standings.csv')
# print(standings)
dataset["HomeTeamRanksHigher"] = 0
for index, row in dataset.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
if(home_team == "New Orleans Pelicans"): # 2013年的球队名字在2014年可能修改
home_team = "New Orleans Hornets"
elif(visitor_team == "New Orleans Pelicans"):
visitor_team = "New Orleans Hornets"
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.loc[index] = row
# 用cross_val_score测试该特征的分类正确率
x_homeHigher = dataset[["HomeLastWin", "VisitorLastWin", "HomeTeamRanksHigher"]].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, x_homeHigher, y_true, scoring="accuracy")
print("The accuracy of the 'HomeTeamRanksHigher' is {0:2f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/05e3f237d05c673b9f7ccdbed58e5228.png)
# (2)增加特征2:“HomeTeamWonLast"--统计两支球队上场比赛的情况
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]))
# 判断在以往的比赛上,(hometeam,visitorteam)是否是hometeam赢
row["HomeTeamWonLast"] = 1 if last_match_winner[teams] == row["Home Team"] else 0
dataset.loc[index] = row
winner = row["Home Team"] if row["HomeWin"] else row["Visitor Team"]
# 更新last_match_winner字典,值为两支球队在当前场次比赛中的胜出者,两支球队再相逢时可将其作为参考
last_match_winner[teams] = winner
# 用cross_val_score测试该特征的分类正确率
x_LastWinner = dataset[["HomeTeamRanksHigher", "HomeTeamWonLast"]].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, x_LastWinner, y_true, scoring='accuracy')
print("The accuracy of 'HomeTeamRanksHigher and HomeTeamWonLast' is {0:1f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/84aaf9a9c067f4ac935030e5a572fab5.png)
4.决策树在训练数据量很大的情况
增加球队,以验证决策树在训练数据量很大的情况下,能否得到有效的分类模型。
用sklearn.preprocessing.LabelEncoder转换器就能把字符串类型的球队名转化为整型。使用sklearn.preprocessing.OneHotEncoder转换器把这些整数转换为二进制数字。
# 用LabelEncoder转换器就能把字符串类型的球队名转化为整型
from sklearn.preprocessing import LabelEncoder
encoding = LabelEncoder()
home_team = encoding.fit_transform(dataset["Home Team"].values)
visitor_team = encoding.fit_transform(dataset["Visitor Team"].values)
# 抽取所有比赛的主客场球队的球队名(已转化为数值型)并将其组合(在NumPy中叫作“stacking”,是向量组合的意思)起来,形成一个矩阵
x_team = np.vstack([home_team, visitor_team]).T
# 使用OneHotEncoder转换器把这些整数转换为二进制数字
from sklearn.preprocessing import OneHotEncoder
onehot = OneHotEncoder()
x_teams_expanded = onehot.fit_transform(x_team).todense()
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, x_teams_expanded, y_true, scoring='accuracy')
print("The accuracy of 'x_teams_expanded' is {0:1f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/e282156547611489030365d5809613e4.png)
三、随机森林
随机森林简介
装袋(bagging)流程:给定包含m个样本的数据集,我们先随机取出一个样本放入采样集中,再把该样本放回初始数据集,使得下次采样时该样本仍有可能被选中,这样,经过m次随机采样操作,我们得到含m个样本的采样集,初始训练集中有的样本在采样集里多次出现,有的则从未出现。照这样,我们可采样出T个含m个训练样本的采样集,然后基于每个采样集训练出一个基学习器,再将这些基学习器进行结合。
在对预测输出进行结合时,bagging通常对分类任务使用简单投票法,对回归任务使用简单平均法。
投票法:即选择这k个样本中出现最多的类别标记作为预测结果。
平均法:即将k个样本的实值输出标记平均值作为预测结果。
随机森林工作原理:一颗决策树会导致过拟合问题。用随机森林解决这个问题,创建多颗决策树,用他们分别进行预测,再根据少数服从多数的原则从多个预测结果中选择最终预测结果。
随机森林算法的参数:
n_estimators:用来指定创建决策树的数量。该值越高,所花时间越长,正确率(可能)也越高。
oob_score:如果设置为真,测试时将不使用训练模型时用过的数据。
n_jobs:采用并行计算方法训练决策树时所用到的内核数量。
随机森林代码实现
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, x_team, y_true, scoring='accuracy')
print("The accuracy of 'x_team' is {0:1f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/621b7f0288689209a7480ef11b75b5bc.png)
# 更换特征子集
x_all = np.hstack([x_homeHigher, x_team])
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, x_all, y_true, scoring='accuracy')
print("the accuracy of 'x_homeHigher and x_team' is :{0:.1f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/343f009bdf123c3fad279bb5094586e7.png)
使用GridSearchCV搜索最佳参数。
from sklearn.model_selection import GridSearchCV
parameter_space = {
'max_features': [2, 10, 'auto'],
'n_estimators': [100, ],
'criterion': ["gini", "entropy"],
"min_samples_leaf": [2, 4, 6],
}
clf = RandomForestClassifier(random_state=14)
grid = GridSearchCV(clf, parameter_space)
grid.fit(x_all, y_true)
print("Accuracy after GridSearchCV :{0:.1f}%".format(grid.best_score_ * 100))
print(grid.best_estimator_)
![](https://i-blog.csdnimg.cn/blog_migrate/61b9195c2834a33347db736c15b797a9.png)
创建3个新特征并验证正确率
(1)球队上次打比赛距今有多长时间?短期内连续作战,容易导致球员疲劳。
(2)两支球队过去五场比赛结果如何?这两个数据要比HomeLastWin和VisitorLastWin更能反映球队的真实水平。
(3)球队是不是跟某支特定球队打比赛时发挥得更好?例如,球队在某个体育场里打比赛,即使是客场作战也能发挥得很好。
针对新建特征1:球队上次打比赛距今有多长时间。增加HomeLastTime、VisitorLastTime列。
dataset["HomeLastTime"] = 0
dataset["VisitorLastTime"] = 0
team_time = defaultdict(int)
Dataframe中的时间是不能直接进行相加减的。如果将两列时间进行加减,会弹出类型错误(时间戳)。所以需要先用pandas的to_datetime()方法,将时间戳转化成时间格式进行加减,加减后得到的是timedelta类型。提取特征时用到timedelta属性(week、day、second、milliseconds、microseconds和nanoseconds等)。
for index, row in dataset.iterrows():
# row["Date"] = pd.to_datetime(row["Date"])
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
if team_time[home_team] == 0:
row["HomeLastTime"] = 100
else:
# Dataframe中的时间是不能直接进行相加减的。如果将两列时间进行加减,会弹出类型错误
# 所以需要先用pandas的to_datetime()方法,将时间戳转化成时间格式进行加减,加减后得到的是timedelta类型。
row["HomeLastTime"] = (pd.to_datetime(row["Date"]) - pd.to_datetime(team_time[home_team])).days
if team_time[visitor_team] == 0:
row["VisitorLastTime"] = 100
else:
row["VisitorLastTime"] = (pd.to_datetime(row["Date"]) - pd.to_datetime(team_time[visitor_team])).days
dataset.loc[index] = row
team_time[home_team] = row["Date"]
team_time[visitor_team] = row["Date"]
# print(dataset["HomeLastTime"])
x_last_time = dataset[["HomeLastTime", "VisitorLastTime"]].values
x_all = np.hstack([x_last_time, x_team])
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, x_all, y_true, scoring="accuracy")
print("the accuracy of 'LastTime and x_team' is: {0:1f}%".format(np.mean(scores) * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/4c5dbce0b169a33342f95d8949d5666c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1299d09eb36ddcfc309022853f2eb199.png)
针对新建特征2:两支球队过去五场比赛结果如何?--赢得场数。增加HomePastFiveWin列和VisitorPastFiveWin列。创建一个字典Wincount记录球队获胜的场次数。
dataset["HomePastFiveWin"] = 0
dataset["VisitorPastFiveWin"] = 0
WinCount = defaultdict(int)
for index, row in dataset.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
row["HomePastFiveWin"] = WinCount[home_team]
row["VisitorPastFiveWin"] = WinCount[visitor_team]
dataset.loc[index] = row
if (row['HomePts'] > row['VisitorPts']):
WinCount[home_team] += 1
else:
WinCount[visitor_team] += 1
if WinCount[home_team] % 5 == 0:
WinCount[home_team] = 0
if WinCount[visitor_team] % 5 == 0:
WinCount[visitor_team] = 0
x_past_five_win = dataset[["HomePastFiveWin", "VisitorPastFiveWin"]].values
x_all = np.hstack([x_past_five_win, x_team])
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, x_all, y_true, scoring='accuracy')
print("The accuracy of PastFiveWin is {0:1f}%".format(np.mean(scores) * 100))
parameter_space = {
'max_features': [2, 10, 'auto'],
'n_estimators': [100, ],
'criterion': ['gini', 'entropy'],
'min_samples_leaf': [2, 4, 6],
}
grid = GridSearchCV(clf, parameter_space)
grid.fit(x_all, y_true)
print("The accuracy of PastFiveWin after GridSearchCV is {0:.1f}%".format(grid.best_score_ * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/5cf58f2db6648ef392399198706a8522.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3e1950edb22b9ad090aa859ecd16a35e.png)
针对新建特征3:球队是不是跟某支特定球队打比赛时发挥得更好? 增加HomeSpecificTeam列和VisitorSpecificTeam列。创建一个SpecificTeam字典,统计两两球队获胜情况{键:(球队1,球队2),值:球队1胜球队2的场数}
SpecificTeam = defaultdict(int) # 统计两两球队获胜情况{键:(球队1,球队2),值:球队1胜球队2的场数}
for index, row in dataset.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
# 以队伍名的元组计数,胜者在前,败者在后
if row['HomePts'] > row['VisitorPts']:
SpecificTeam[tuple(sorted([home_team, visitor_team]))] += 1
else:
SpecificTeam[tuple(sorted([visitor_team, home_team]))] += 1
# print(SpecificTeam)
# # print(list(SpecificTeam.keys())[0][1])
"""
SpecificTeam:
{ ('Indiana Pacers', 'Orlando Magic'): 4,
('Chicago Bulls', 'Miami Heat'): 4,
('Los Angeles Clippers', 'Los Angeles Lakers'): 4,
...
}
SpecificTeam.key():
dict_keys([('Indiana Pacers', 'Orlando Magic'),
('Chicago Bulls', 'Miami Heat'),
('Los Angeles Clippers', 'Los Angeles Lakers'),...])
list(SpecificTeam.keys())[0]:取列表里的第一对元组
list(SpecificTeam.keys())[0][0]:取列表里的第一对元组里的第一个元素
"""
# 球队的特定队默认值设置为自己队
dataset["HomeSpecificTeam"] = dataset["Home Team"]
dataset["VisitorSpecificTeam"] = dataset["Visitor Team"]
for index, row in dataset.iterrows():
for i in range(len(SpecificTeam)):
# 寻找列表里元组的第一个元素(胜者)的队伍名,然后找到与Home Team和Visitor Team特定的队伍
if list(SpecificTeam.keys())[i][0] == row["Home Team"]:
row["HomeSpecificTeam"] = list(SpecificTeam.keys())[i][1]
elif list(SpecificTeam.keys())[i][0] == row["Visitor Team"]:
row["VisitorSpecificTeam"] = list(SpecificTeam.keys())[i][1]
dataset.loc[index] = row
# 使用sklearn.proccessing.LabelEncoder转换器将字符串转化为整数
HomeSpecificTeam = encoding.fit_transform(dataset["HomeSpecificTeam"].values)
VisitorSpecificTeam = encoding.fit_transform(dataset["VisitorSpecificTeam"].values)
x_specific = np.vstack([HomeSpecificTeam, VisitorSpecificTeam]).T
# 使用sklearn.proccessing.OneHotEncoder转换器将整数转化为二进制数
x_specific_team = onehot.fit_transform(x_specific).todense()
x_all = np.hstack([x_specific_team, x_team])
clf = RandomForestClassifier(random_state=14)
score_specific = cross_val_score(clf, x_specific_team, y_true, scoring='accuracy')
scores_all = cross_val_score(clf, x_all, y_true, scoring='accuracy')
print("The accuracy of specific_team is {0:.1f}%".format(np.mean(scores) * 100))
print("The accuracy of x_all is {0:.1f}%".format(np.mean(scores_all) * 100))
# 使用GridSearchCV搜索最佳参数
parameter_space = {
'max_features': [2, 10, 'auto'],
'n_estimators': [10, ],
'criterion': ['gini', 'entropy'],
'min_samples_leaf':[2, 4, 6],
}
grid = GridSearchCV(clf, parameter_space)
grid.fit(x_specific_team, y_true)
print("The accuracy of specific_team after GridSearchCV is {0:.1f}%".format(grid.best_score_ * 100))
grid.fit(x_all, y_true)
print("The accuracy of x_all after GridSearchCV is {0:.1f}%".format(grid.best_score_ * 100))
![](https://i-blog.csdnimg.cn/blog_migrate/2c44297613490dc70eb2da4761bc5775.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4e6ebb1d5c33d2790d407c851bc4f2ca.png)
![](https://i-blog.csdnimg.cn/blog_migrate/53eb59fd24ad9d4dfdf52bd2dfec9f13.png)
从上面截图可以看到,新建特征3在随机森林估计器上的分类准确率最高,达到65.1%。
四、小结
1.本章使用scikit-learn库的另一个分类器DecisionTreeClassifier,并介绍了如何用 pandas处理数据。我们分析了真实的NBA赛事的比赛结果数据,创建新特征用于分类,并在这个 过程中发现即使是规整、干净的数据也可能存在一些小问题。 2.我们发现好的特征对提升正确率很有帮助,还使用了一种集成算法——随机森林,进一步提 升正确率 。