本笔记介绍两种分类算法——决策树和随机森林决策树,用它预测NBA篮球赛的获胜球队。比起其他算法,决策树有很多优点,其中最主要的一个优点是决策过程是机器和人都能看懂的,我们使用机器学习到的模型就能完成预测任务。

通过决策树预测NBA获胜球队,主要包括以下几个知识点:

1、数据预处理,主要是队名的转换,消除歧义

2、寻找新特征辅助预测,比如增加主客队胜负关系,上一场获胜情况,其实可以追加很多指标,比如主客场胜率,比如交叉战绩情况,比如连续获胜场次等等

3、爬取辅助数据,NBA2013年最终成绩,主要原因是NBA2013年的数据已无法获取,拷贝到excel中面临格式调整。

代码示例

  1. import pandas as pd

  2. import numpy as np

  3. import re

  4. from sklearn.tree import DecisionTreeClassifier

  5. from sklearn.cross_validation import cross_val_score

  6. from urllib.request import urlopen

  7. from bs4 import BeautifulSoup

  8. from sklearn.preprocessing import LabelEncoder

  9. from sklearn.preprocessing import OneHotEncoder

  10. from sklearn.model_selection import GridSearchCV

  11. def get_nba_2013data():

  12.    # 1、获取html内容,2BeautifulSoup进行转换,3、缩小范围再次进行匹配,

  13.    # 4、正则表达式匹配,5、再次进行BeautifulSoup进行转换转换,6、遍历tr7遍历td

  14.    # 8、将list转换为DataFrame,将DataFrame存为csv格式

  15.    #正则表达式匹配tbody之间的任意字符

  16.    pattern = re.compile('<tbody>[\s\S]*?</tbody>')#模仿html注释的正则

  17.    #获取2013NBA比赛结果URL,并转换为BeautifulSoup格式

  18.    url = 'https://www.basketball-reference.com/leagues/NBA_2013_standings.html'

  19.    html = urlopen(url).read()

  20.    bSoup = BeautifulSoup(html,'lxml')

  21.    #找到ID=all_expanded_standings之间的内容并格式化

  22.    content = bSoup.find(id='all_expanded_standings').prettify()

  23.    match = re.search(pattern,content)

  24.    str_tbody = match.group()

  25.    # str字符串传入获得html对象

  26.    html_tbody = BeautifulSoup(str_tbody,'lxml')

  27.    list = []

  28.    for tr in html_tbody.find_all('tr'):

  29.        rows = [td.text for td in tr.find_all('td')]

  30.        list.append(rows)

  31.    print(list)

  32.    #转成csv格式

  33.    file = 'NBAstanding.csv'

  34.    df_data = pd.DataFrame(data=list)

  35.    df_data.to_csv(file)


  36. #---------------------------数据集加载--------------------------------

  37. data_filename='leagues_NBA_2014_games_games.csv'

  38. dataset=pd.read_csv(data_filename, parse_dates= ['Date'])

  39. #---------------------------数据集清洗--------------------------------

  40. #重命名表头

  41. dataset.columns = ['Date','Score Type', 'Visitor Team', 'VisitorPts', 'Home Team', 'HomePts', 'OT?', 'Notes']

  42. #读取前5项数据集,数据集合形状,数据表头

  43. #print(dataset.head())

  44. print(dataset[:5])

  45. #print(dataset.shape)   # (1319, 8)

  46. print(dataset.columns)

  47. #---------------------------提取新特征--------------------------------

  48. dataset["HomeWin"] = dataset["VisitorPts"] < dataset["HomePts"]

  49. y_true = dataset["HomeWin"].values

  50. from collections import defaultdict

  51. #创建(默认)字典,存储球队上次比赛的结果

  52. won_last = defaultdict(int)

  53. #在原有数据集中增加两列,上次主队是否获胜上次客队是否获胜

  54. dataset['HomeLastWin'] = None

  55. dataset['VisitorLastWin'] = None

  56. for index, row in dataset.iterrows():

  57.    #获取本次主客队名称

  58.    home_team = row['Home Team']

  59.    visitor_team = row['Visitor Team']

  60.    #根据名称获取上次主客队胜负情况

  61.    row['HomeLastWin'] = won_last[home_team]

  62.    row['VisitorLastWin'] = won_last[visitor_team]

  63.    #更新数据集当前行

  64.    dataset.ix[index] = row

  65.    #用当前比赛的结果更新两支球队上场比赛的获胜情况, 以便下次再遍历到这两支球队时使用

  66.    won_last[home_team] = row['HomeWin']    #判断上一场是否获胜

  67.    won_last[visitor_team] =not row['HomeWin']

  68. print(dataset[:5])

  69. #-----------------------------决策树----------------------------------

  70. #决策树是一种有监督的机器学习算法,它看起来就像是由一系列节点组成的流程图

  71. # 首先是训练阶段,用训练数据构造一棵树。

  72. # 其次是预测阶段,用训练好的决策树预测新数据的类别。

  73. #scikit-learn库实现的决策树算法给出了退出方法,使用下面这两个选项就可以达到目的。

  74. # 剪枝:先创建一棵完整的树,再对其进行修剪,去掉对整个过程没有提供太多信息的节点。

  75. # 退出:是决策树的一个重要特性。构建决策树时,后几步决策仅依赖于少数个体,随意性大。

  76. # 使用特定节点作出推测容易导致过拟合训练数据,而使用退出准则可以防止决策精度过高。

  77. # min_samples_split:指定创建一个新节点至少需要的个体数量。

  78. # min_samples_leaf:指定为了保留节点,每个节点至少应该包含的个体数量

  79. # 第一个参数控制着决策节点的创建,第二个参数决定着决策节点能否被保留

  80. # 决策树的另一个参数是创建决策的标准,常用的有以下两个。

  81. # 基尼不纯度(Gini impurity):用于衡量决策节点错误预测新个体类别的比例。

  82. # 信息增益(Information gain):用信息论中的熵来表示决策节点提供多少新信息。

  83. clf = DecisionTreeClassifier(random_state=14)

  84. X_previouswins = dataset[["HomeLastWin", "VisitorLastWin"]].values

  85. scores = cross_val_score(clf, X_previouswins, y_true, scoring='accuracy')

  86. print("决策树 Accuracy: {0:.1f}%".format(np.mean(scores) * 100))

  87. #------------------------------版本2----------------------------------

  88. #-----------------使用 2013赛季的战绩作为特征取值来源-----------------

  89. #获取2013NBA排名成绩

  90. #get_nba_2013data()

  91. #创建一个新特征,创建过程与上个特征类似。

  92. # 遍历每一行,查找主场队和客场队两NBA比赛结果预测支球队的战绩。

  93. standings = pd.read_csv('NBAstanding.csv',header=None)

  94. #创建一个新特征,创建过程与上个特征类似。

  95. # 遍历每一行,查找主场队和客场队两NBA比赛结果预测支球队的战绩。

  96. standings.columns = ['Rk','Team','Overall','Home','Road','E','W','A','C','SE','NW','P','SW','Pre','Post','≤3','≥10','Oct','Nov','Dec','Jan','Feb','Mar','Apr']

  97. #print(standings .head())

  98. dataset['HomeTeamRanksHigher'] = 0

  99. for index,row in dataset.iterrows():

  100.    #获取主客队名称

  101.    home_team = row['Home Team']

  102.    visitor_team = row['Visitor Team']

  103.    #数据清洗,修改队名,两边数据保持一致

  104.    if home_team == 'New Orleans Pelicans':

  105.        home_team = 'New Orleans Hornets'

  106.    if visitor_team == 'New Orleans Pelicans':

  107.        visitor_team = 'New Orleans Hornets'

  108.    #获取主客队成绩,另增加一列进行成绩对比

  109.    home_rank = standings[standings["Team"] == home_team]["Rk"].values[0]

  110.    visitor_rank = standings[standings["Team"] ==  visitor_team]["Rk"].values[0]

  111.    row["HomeTeamRanksHigher"] = int(home_rank > visitor_rank)

  112.    dataset.ix[index] = row

  113. #用新结果集进行决策树预测

  114. X_homehigher = dataset[['HomeLastWin','VisitorLastWin','HomeTeamRanksHigher']].values

  115. clf = DecisionTreeClassifier(random_state=14)

  116. scores = cross_val_score(clf,X_homehigher,y_true,scoring='accuracy')

  117. print('决策树(参考2013年战绩)Accuracy:{0:.1f}%'.format(np.mean(scores)*100))

  118. #------------------------------版本3----------------------------------

  119. #-----用LabelEncoder转换器就能把字符串类型的球队名转化为整型。--------

  120. encoding = LabelEncoder()

  121. #将主场球队名称转化为整型:

  122. encoding.fit(dataset["Home Team"].values)

  123. #抽取所有比赛的主客场球队的球队名(已转化为数值型)

  124. home_teams = encoding.transform(dataset["Home Team"].values)

  125. visitor_teams = encoding.transform(dataset["Visitor Team"].values)

  126. #两个矩阵合并,并转置

  127. X_teams = np.vstack([home_teams, visitor_teams]).T

  128. onehot = OneHotEncoder()

  129. #在相同的数据集上进行预处理和训练操作,将结果保存起来备用。

  130. X_teams_expanded = onehot.fit_transform(X_teams).todense()

  131. #接着,像之前那样在新数据集上调用决策树分类器。

  132. clf = DecisionTreeClassifier(random_state=14)

  133. scores = cross_val_score(clf, X_teams_expanded, y_true,   scoring='accuracy')

  134. print("决策树(队名转化) Accuracy: {0:.1f}%".format(np.mean(scores) * 100))


  135. # 一棵决策树可以学到很复杂的规则。然而,很可能会导致过拟合问题——学到的规则只适用 于训练集。

  136. # 解决方法之一就是调整决策树算法,限制它所学到的规则的数量

  137. # 使用这种折中方案得到的决策树泛化 能力强,但整体表现稍弱

  138. # 随机森林的工作原理:创建多棵决策树,用它们分别进行预测,再根据少数服 从多数的原则从多个预测结果中选择终预测结果。

  139. # 装袋(bagging):每次随机从数据集中选取一部分数据用作训练集。

  140. # 随机选取部分特征作为决策依据。

  141. #------------------------版本4 随机森林-------------------------------

  142. #的随机森林算法使用估计器接口,用交叉检验方法调用它即可

  143. from sklearn.ensemble import RandomForestClassifier

  144. clf = RandomForestClassifier(random_state=14)

  145. scores = cross_val_score(clf, X_teams, y_true, scoring='accuracy')

  146. print("随机森林 Accuracy: {0:.1f}%".format(np.mean(scores) * 100))

  147. #随机森林使用不同的特征子集进行学习,应该比普通的决策树更为高效。

  148. X_all = np.hstack([X_homehigher, X_teams])

  149. clf = RandomForestClassifier(random_state=14)

  150. scores = cross_val_score(clf, X_all, y_true, scoring='accuracy')

  151. print("随机森林(采用不同特征) Accuracy: {0:.1f}%".format(np.mean(scores) * 100))

  152. #可以使用GridSearchCV类搜索佳参数

  153. #能够在指定的范围内自动搜索具有不同超参数的不同模型组合

  154. parameter_space = {    'max_features': ['auto', 'sqrt', 'log2'],

  155.                       "n_estimators": [100,],

  156.                       "criterion": ["gini", "entropy"],

  157.                       "min_samples_leaf": [2, 4, 6], }

  158. clf = RandomForestClassifier(random_state=14)

  159. grid = GridSearchCV(clf, parameter_space)

  160. grid.fit(X_all, y_true)

  161. print("随机森林(参数组合) Accuracy: {0:.1f}%".format(grid.best_score_ * 100))

  162. #输出用网格搜索找到的佳模型,查看都使用了哪些参数。

  163. print(grid.best_estimator_)

  164. #输出正确率高的模型所用到的参数

  165. '''

  166. RandomForestClassifier(bootstrap=True, class_weight=None, criterion='entropy',

  167.            max_depth=None, max_features='auto', max_leaf_nodes=None,

  168.            min_impurity_decrease=0.0, min_impurity_split=None,

  169.            min_samples_leaf=4, min_samples_split=2,

  170.            min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=1,

  171.            oob_score=False, random_state=14, verbose=0, warm_start=False)

  172. '''