第3章:用决策树预测获胜球队
基于《python数据挖掘入门与实践》这一书的学习笔记,其中数据集和源码可以去图灵社区下载。
一、关于数据集
根据书中的事例,使用NBA2013-2014赛季的比赛数据。
https://www.basketball-reference.com/leagues/NBA_2014_games.html
按照书中的方式已经下载不到数据了,因为原网站已经做了改版。因此,本人去图灵社区下载了配套资料,其中有各个月的比赛数据。我们可以分别读取这几个月的数据,然后合并在一起,也能达到书中的效果。
1、加载数据
import numpy as np
import pandas as pd
data1 = pd.read_csv('十月数据.csv')
data2 = pd.read_csv('十一月数据.csv')
data3 = pd.read_csv('十二月数据.csv')
data4 = pd.read_csv('一月数据.csv')
data5 = pd.read_csv('二月数据.csv')
data6 = pd.read_csv('三月数据.csv')
data7 = pd.read_csv('四月数据.csv')
data8 = pd.read_csv('五月数据.csv')
data9 = pd.read_csv('六月数据.csv')
data = pd.concat([data1,data2,data3,data4,data5,data6,data7,data8,data9])
data = data.reset_index(drop=True)#记得要重置索引
results = data
results.loc[:5]#loc函数很特殊0:5显示的行数包括第5行
该数据集比书中数据集多一列,描述比赛时间。
2、数据清洗
日期格式不合格,表的列属性有些没有定义
#将日期格式转换
results['Date'] = pd.to_datetime(results['Date'])
results.columns = ["Date","Start Time", "Visitor Team", "VisitorPts", "Home Team", "HomePts", "Score Type","OT?", "Notes"]
results.loc[:5]
3、特征提取
虽然数据中没有表明输赢,但是可以通过分数来判断。
找出主场获胜的球队:
results["HomeWin"] = results["VisitorPts"] < results["HomePts"]
#y_true存放类别数据
y_true = results["HomeWin"].values
results.loc[:5]
print("主场胜率: {0:.1f}%".format(100 * results["HomeWin"].sum() / results["HomeWin"].count()))
主场胜率: 57.9%
创建两个进行预测的特征,分别是两支队伍上场比赛的胜负情况。赢得上场比赛,大致可以说明该队伍水平较高。
因此在数据集上新增两个属性HomeLastWin,VisitorLastWin
results["HomeLastWin"] = False
results["VisitorLastWin"] = False
results.loc[:5]
首先我们应该了解利用df.iterrows()对dataframe进行遍历
for index,row in df.iterrows():
返回一个元组(index,row)
index:就是索引值0,1,2…
row:Series,索引是列名,如{‘Date’: ,‘Start Time’: …}
# 遍历每行数据,判断主客场队伍上次比赛输赢情况
from collections import defaultdict
won_last = defaultdict(int)#存储上次比赛结果,键为球队,值为输赢的情况
for index, row in results.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
row["HomeLastWin"] = won_last[home_team]
row["VisitorLastWin"] = won_last[visitor_team]
results.loc[index] = row #更新数据集(新的row要更新)
#当前比赛输赢情况更新到字典里
won_last[home_team] = row["HomeWin"]
won_last[visitor_team] = not row["HomeWin"]
results.loc[20:25]
二、决策树
这类知识建议阅读李航的《统计学习方法》。
决策树基础知识
决策树与随机森林
决策树思想,实际上就是寻找最纯净的划分方法,这个最纯净在数学上叫纯度,纯度通俗点理解就是目标变量要分得足够开每种方法也形成了不同的决策树方法,比如ID3算法使用信息增益作为不纯度;C4.5算法使用信息增益率作为不纯度;CART算法使用基尼系数作为不纯度。
1)信息熵:
D为数据集,pk为第K类的样本所占的比例,熵值越大,混乱程度越高,纯度越低。
2)信息增益:
将数据集D按照特征a,划分成V个不同的节点数据集。定义为集合原来的经验熵Ent(D)与特征a给定条件下D的经验熵之差。
|D|为D的样本个数。信息增益值越大,划分的特征就最优。
由于信息增益原则对于每个分支节点,都会乘以其权重,所以分支节点分的越多,即每个节点数据越小,纯度可能越高。这样会导致信息熵准则偏爱那些取值数目较多的属性(数据集划分的越多)。
3)信息增益率
其中IV(a)是固定值。
4)基尼指数
基尼指数最小的特征为最优特征。划分数据集时化分成两个部分(这一点和前面两个算法不同)。
综上所述,这三种方法都是为了选择最优特征而准备的。
1.首先,计算该数据集各个特征划分时的结果(信息增益或信息增益率或基尼指数)
2.根据不同的算法选择最佳特征和最优切分点,从现在节点按照最优切分点,把数据集分成两个子节点中去。
3.重复1,2
4.生成决策树
5)剪枝
一味地提高对训练集数据分类,会生成复杂的决策树。从而会出现过拟合的现象,决策树不能对测试集分类的如训练集般准确。因此需要对决策树进行简化,剪去一些子树或叶节点,从而提高模型的泛化能力。
三、NBA比赛结果预测
1)首先根据主客场球队上场比赛输赢来进行预测:
#导入决策树(cart树)
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state=14)
from sklearn.model_selection import cross_val_score
# 获取所需特征,每场球队上场比赛输赢情况
X_previouswins = results[["HomeLastWin", "VisitorLastWin"]].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, X_previouswins, y_true, scoring='accuracy')#交叉验证
print("根据球队上场比赛输赢情况预测比赛结果")
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
根据球队上场比赛输赢情况预测比赛结果
准确率: 57.5%
2)把队伍连胜也考虑进来
# 那么把连胜作为特征呢?
results["HomeWinStreak"] = 0
results["VisitorWinStreak"] = 0
# 字典键为主客场,值为是否连胜
from collections import defaultdict
win_streak = defaultdict(int)
for index, row in results.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
row["HomeWinStreak"] = win_streak[home_team]
row["VisitorWinStreak"] = win_streak[visitor_team]
results.loc[index] = row #更新数据集
# 更新字典
#和lastwin不同,lastwin的值只有0或者1,winstreak的值为0和连胜的次数,一旦输了,表示连胜断了,值会归零
if row["HomeWin"]:
win_streak[home_team] += 1
win_streak[visitor_team] = 0
else:
win_streak[home_team] = 0
win_streak[visitor_team] += 1
clf = DecisionTreeClassifier(random_state=14)
X_winstreak = results[["HomeLastWin", "VisitorLastWin", "HomeWinStreak", "VisitorWinStreak"]].values
scores = cross_val_score(clf, X_winstreak, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 57.1%
和1)比起来并没有好多少。
3)创建一个“主场队是否通常比对手水平高”的特征。如果一支球队2013赛季排名在对手前面,我们就认为它的水平更高。
加载2013赛季的战绩数据
https://www.basketball-reference.com/leagues/NBA_2013_standings.html#expanded_standings::none
将表格转换成csv格式,自己复制下来然后保存成NBA_2013.csv文件
第一行数据为脏数据,读取时可以跳过这一行。
# 加载2013赛季个球队战绩
ladder_filename = ("NBA_2013.csv")
ladder = pd.read_csv(ladder_filename,skiprows=[0,0])#skiprows跳读第一行
ladder
# 创建一个新特征 -- HomeTeamRanksHigher
results["HomeTeamRanksHigher"] = 0
for index, row in results.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
#这里有队名改变了,因此要统一
if home_team == "New Orleans Pelicans":
home_team = "New Orleans Hornets"
elif visitor_team == "New Orleans Pelicans":
visitor_team = "New Orleans Hornets"
home_rank = ladder[ladder["Team"] == home_team]["Rk"].values[0]
#print(home_rank)
visitor_rank = ladder[ladder["Team"] == visitor_team]["Rk"].values[0]
row["HomeTeamRanksHigher"] = int(home_rank > visitor_rank)#主场上个赛季水平更高
results.loc[index] = row
results[:5]
X_homehigher = results[["HomeLastWin", "VisitorLastWin", "HomeTeamRanksHigher"]].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, X_homehigher, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 59.7%,较前面有所提高。
#网络参数寻优gridsearchcv
from sklearn.model_selection import GridSearchCV
parameter_space = {
"max_depth": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
}
clf = DecisionTreeClassifier(random_state=14)
grid = GridSearchCV(clf, parameter_space)
grid.fit(X_homehigher, y_true)
print("准确率: {0:.1f}%".format(grid.best_score_ * 100))
准确率: 59.7%。这里利用了模型调参器gridSearchCV
gridSearchCV相关介绍
4)不考虑主客场情况,统计两支球队上场比赛情况,作为一个特征。
#忽略主客场
last_match_winner = defaultdict(int)
results["HomeTeamWonLast"] = 0
for index, row in results.iterrows():
home_team = row["Home Team"]
visitor_team = row["Visitor Team"]
#英文字母顺序对球队名字进行排序,确保两支球队无论主客场作战,都是用相同的键
teams = tuple(sorted([home_team, visitor_team])) #teams作为字典的键:为一个二元组,sort后(1,2)=(2,1)忽略主客场
row["HomeTeamWonLast"] = 1 if last_match_winner[teams] == row["Home Team"] else 0
results.loc[index] = row
winner = row["Home Team"] if row["HomeWin"] else row["Visitor Team"]
last_match_winner[teams] = winner
results.loc[:5]
X_home_higher = results[["HomeTeamRanksHigher", "HomeTeamWonLast"]].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, X_home_higher, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 57.8%,效果不是很好。
5)那么把球队名作为特征呢?
sklearn库中的决策树模型要求对原始特征进行处理后才能进行训练
这里用到编码和解码的知识。
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
encoding = LabelEncoder()
encoding.fit(results["Home Team"].values)#字符串类型的球队名转化成整型
#抽取所有比赛的主客场球队的球队名
home_teams = encoding.transform(results["Home Team"].values)#[a,b,c,d....]
visitor_teams = encoding.transform(results["Visitor Team"].values)#[A,B,C,D...]
X_teams = np.vstack([home_teams, visitor_teams]).T
#vstack纵向连接两个数组 转置后 [a,A] [主,客]
# [b,B]
# .........
onehot = OneHotEncoder()#整数转换成二进制
X_teams = onehot.fit_transform(X_teams).todense()
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, X_teams, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 59.5%。
四、随机森林
随机森林
这部分需要明白集成学习,强化学习和弱化学习,同时可以参考李航的《统计学习方法》中的提升方法。
随机森林属于集成学习(Ensemble Learning)中的bagging算法。集成学习对一个复杂的分类问题,通过训练多个分类器,利用这些分类器来解决同一个问题,在集成学习中,主要分为bagging算法和boosting算法。我们先看看这两种方法的特点和区别。
Bagging(套袋法)
bagging的算法过程如下:
从原始样本集中使用Bootstraping方法随机抽取n个训练样本,共进行k轮抽取,得到k个训练集。(k个训练集之间相互独立,元素可以有重复)
对于k个训练集,我们训练k个模型(这k个模型可以根据具体问题而定,比如决策树,knn等)
对于分类问题:由投票表决产生分类结果;对于回归问题:由k个模型预测结果的均值作为最后预测结果。(所有模型的重要性相同)
Boosting(提升法)
boosting的算法过程如下:
对于训练集中的每个样本建立权值wi,表示对每个样本的关注度。当某个样本被误分类的概率很高时,需要加大对该样本的权值。
进行迭代的过程中,每一步迭代都是一个弱分类器。我们需要用某种策略将其组合,作为最终模型。(例如AdaBoost给每个弱分类器一个权值,将其线性组合最为最终分类器。误差越小的弱分类器,权值越大)
Bagging,Boosting的主要区别
样本选择上:Bagging采用的是Bootstrap随机有放回抽样;而Boosting每一轮的训练集是不变的,改变的只是每一个样本的权重。
样本权重:Bagging使用的是均匀取样,每个样本权重相等;Boosting根据错误率调整样本权重,错误率越大的样本权重越大。
预测函数:Bagging所有的预测函数的权重相等;Boosting中误差越小的预测函数其权重越大。
并行计算:Bagging各个预测函数可以并行生成;Boosting各个预测函数必须按顺序迭代生成。
下面是将决策树与这些算法框架进行结合所得到的新的算法:
1)Bagging + 决策树 = 随机森林
2)AdaBoost + 决策树 = 提升树
3)Gradient Boosting + 决策树 = GBDT
随机森林
它是一种重要的基于Bagging的集成学习方法,可以用来做分类、回归等问题。
1.随机森林有许多优点:
具有极高的准确率
随机性的引入,使得随机森林不容易过拟合
随机性的引入,使得随机森林有很好的抗噪声能力
能处理很高维度的数据,并且不用做特征选择
既能处理离散型数据,也能处理连续型数据,数据集无需规范化
训练速度快,可以得到变量重要性排序
容易实现并行化
2.随机森林的缺点:
当随机森林中的决策树个数很多时,训练时需要的空间和时间会较大
随机森林模型还有许多不好解释的地方,有点算个黑盒模型
3.与上面介绍的Bagging过程相似,随机森林的构建过程大致如下:
从原始训练集中使用Bootstraping方法随机有放回采样选出m个样本,共进行n_tree次采样,生成n_tree个训练集
1)对于n_tree个训练集,我们分别训练n_tree个决策树模型
2)对于单个决策树模型,假设训练样本特征的个数为n,那么每次分裂时根据信息增益/信息增益比/基尼指数选择最好的特征进行分裂
3)每棵树都一直这样分裂下去,直到该节点的所有训练样例都属于同一类。在决策树的分裂过程中不需要剪枝,将生成的多棵决策树组成随机森林。对于分类问题,按多棵树分类器投票决定最终分类结果;对于回归问题,由多棵树预测值的均值决定最终预测结果。
#导入集成学习库中的随机森林分类器
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, X_teams, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 60.0%
上面只是训练了一个特征(5中的特征),这里再加入一个特征(4中的特征)X_home_higher
X_all = np.hstack([X_home_higher, X_teams])
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print("准确率: {0:.1f}%".format(np.mean(scores) * 100))
准确率: 59.9%
利用模型调参器gridSearchCV,看看效果
parameter_space = {
"max_features": [2, 10, 'auto'],#特征个数
"n_estimators": [100,], #估计器个数
"criterion": ["gini", "entropy"],#训练算法CRAT,基尼指数和信息熵
"min_samples_leaf": [2, 4, 6],#最少的叶子树
}
clf = RandomForestClassifier(random_state=14)
grid = GridSearchCV(clf, parameter_space)
grid.fit(X_all, y_true)#训练
print("准确率: {0:.1f}%".format(grid.best_score_ * 100))
print(grid.best_estimator_)
准确率: 64.1%
RandomForestClassifier(bootstrap=True, class_weight=None, criterion=‘entropy’,
max_depth=None, max_features=2, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=2, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=None,
oob_score=False, random_state=14, verbose=0, warm_start=False)
五、思考
1.特征值的选取对模型的训练结果影响很大。
2.参数调优是个值得思考的问题。
3.模型的过拟合和泛化能力。
4.集成学习,强弱分类器。
5.sklearn中的估计器,交叉验证,参数调优器。
六、参考文献
1.随机森林算法学习(RandomForest)
https://blog.csdn.net/qq547276542/article/details/78304454
2.《统计学习方法》
3.Bagging和Boosting 概念及区别
https://www.cnblogs.com/liuwu265/p/4690486.html
4.简单易学的机器学习算法——集成方法(Ensemble Method) https://blog.csdn.net/google19890102/article/details/46507387
5. 决策树系列(一)——基础知识回顾与总结 https://www.cnblogs.com/yonghao/p/5061873.html
6. 决策树与随机森林 https://www.cnblogs.com/fionacai/p/5894142.html