本笔记介绍两种分类算法——决策树和随机森林决策树,用它预测NBA篮球赛的获胜球队。比起其他算法,决策树有很多优点,其中最主要的一个优点是决策过程是机器和人都能看懂的,我们使用机器学习到的模型就能完成预测任务。
通过决策树预测NBA获胜球队,主要包括以下几个知识点:
1、数据预处理,主要是队名的转换,消除歧义
2、寻找新特征辅助预测,比如增加主客队胜负关系,上一场获胜情况,其实可以追加很多指标,比如主客场胜率,比如交叉战绩情况,比如连续获胜场次等等
3、爬取辅助数据,NBA2013年最终成绩,主要原因是NBA2013年的数据已无法获取,拷贝到excel中面临格式调整。
代码示例
代码语言:javascript复制import pandas as pd
import numpy as np
import re
from sklearn.tree import DecisionTreeClassifier
from sklearn.cross_validation import cross_val_score
from urllib.request import urlopen
from bs4 import BeautifulSoup
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import GridSearchCV
def get_nba_2013data():
# 1、获取html内容,2、BeautifulSoup进行转换,3、缩小范围再次进行匹配,
# 4、正则表达式匹配,5、再次进行BeautifulSoup进行转换转换,6、遍历tr,7遍历td
# 8、将list转换为DataFrame,将DataFrame存为csv格式
#正则表达式匹配tbody之间的任意字符
pattern = re.compile('<tbody>[sS]*?</tbody>')#模仿html注释的正则
#获取2013年NBA比赛结果URL,并转换为BeautifulSoup格式
url = 'https://www.basketball-reference.com/leagues/NBA_2013_standings.html'
html = urlopen(url).read()
bSoup = BeautifulSoup(html,'lxml')
#找到ID=all_expanded_standings之间的内容并格式化
content = bSoup.find(id='all_expanded_standings').prettify()
match = re.search(pattern,content)
str_tbody = match.group()
# 将str字符串传入获得html对象
html_tbody = BeautifulSoup(str_tbody,'lxml')
list = []
for tr in html_tbody.find_all('tr'):
rows = [td.text for td in tr.find_all('td')]
list.append(rows)
print(list)
#转成csv格式
file = 'NBAstanding.csv'
df_data = pd.DataFrame(data=list)
df_data.to_csv(file)
#---------------------------数据集加载--------------------------------
data_filename='leagues_NBA_2014_games_games.csv'
dataset=pd.read_csv(data_filename, parse_dates= ['Date'])
#---------------------------数据集清洗--------------------------------
#重命名表头
dataset.columns = ['Date','Score Type', 'Visitor Team', 'VisitorPts', 'Home Team', 'HomePts', 'OT?', 'Notes']
#读取前5项数据集,数据集合形状,数据表头
#print(dataset.head())
print(dataset[:5])
#print(dataset.shape) # (1319, 8)
print(dataset.columns)
#---------------------------提取新特征--------------------------------
dataset["HomeWin"] = dataset["VisitorPts"] < dataset["HomePts"]
y_true = dataset["HomeWin"].values
from collections import defaultdict
#创建(默认)字典,存储球队上次比赛的结果
won_last = defaultdict(int)
#在原有数据集中增加两列,上次主队是否获胜上次客队是否获胜
dataset['HomeLastWin'] = None
dataset['VisitorLastWin'] = None
for index, row in dataset.iterrows():
#获取本次主客队名称
home_team = row['Home Team']
visitor_team = row['Visitor Team']
#根据名称获取上次主客队胜负情况
row['HomeLastWin'] = won_last[home_team]
row['VisitorLastWin'] = won_last[visitor_team]
#更新数据集当前行
dataset.ix[index] = row
#用当前比赛的结果更新两支球队上场比赛的获胜情况, 以便下次再遍历到这两支球队时使用
won_last[home_team] = row['HomeWin'] #判断上一场是否获胜
won_last[visitor_team] =not row['HomeWin']
print(dataset[:5])
#-----------------------------决策树----------------------------------
#决策树是一种有监督的机器学习算法,它看起来就像是由一系列节点组成的流程图
# 首先是训练阶段,用训练数据构造一棵树。
# 其次是预测阶段,用训练好的决策树预测新数据的类别。
#scikit-learn库实现的决策树算法给出了退出方法,使用下面这两个选项就可以达到目的。
# 剪枝:先创建一棵完整的树,再对其进行修剪,去掉对整个过程没有提供太多信息的节点。
# 退出:是决策树的一个重要特性。构建决策树时,后几步决策仅依赖于少数个体,随意性大。
# 使用特定节点作出推测容易导致过拟合训练数据,而使用退出准则可以防止决策精度过高。
# min_samples_split:指定创建一个新节点至少需要的个体数量。
# min_samples_leaf:指定为了保留节点,每个节点至少应该包含的个体数量
# 第一个参数控制着决策节点的创建,第二个参数决定着决策节点能否被保留
# 决策树的另一个参数是创建决策的标准,常用的有以下两个。
# 基尼不纯度(Gini impurity):用于衡量决策节点错误预测新个体类别的比例。
# 信息增益(Information gain):用信息论中的熵来表示决策节点提供多少新信息。
clf = DecisionTreeClassifier(random_state=14)
X_previouswins = dataset[["HomeLastWin", "VisitorLastWin"]].values
scores = cross_val_score(clf, X_previouswins, y_true, scoring='accuracy')
print("决策树 Accuracy: {0:.1f}%".format(np.mean(scores) * 100))
#------------------------------版本2----------------------------------
#-----------------使用 2013赛季的战绩作为特征取值来源-----------------
#获取2013年NBA排名成绩
#get_nba_2013data()
#创建一个新特征,创建过程与上个特征类似。
# 遍历每一行,查找主场队和客场队两NBA比赛结果预测支球队的战绩。
standings = pd.read_csv('NBAstanding.csv',header=None)
#创建一个新特征,创建过程与上个特征类似。
# 遍历每一行,查找主场队和客场队两NBA比赛结果预测支球队的战绩。
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']
#print(standings .head())
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':
home_team = 'New Orleans Hornets'
if 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.ix[index] = row
#用新结果集进行决策树预测
X_homehigher = dataset[['HomeLastWin','VisitorLastWin','HomeTeamRanksHigher']].values
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf,X_homehigher,y_true,scoring='accuracy')
print('决策树(参考2013年战绩)Accuracy:{0:.1f}%'.format(np.mean(scores)*100))
#------------------------------版本3----------------------------------
#-----用LabelEncoder转换器就能把字符串类型的球队名转化为整型。--------
encoding = LabelEncoder()
#将主场球队名称转化为整型:
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
onehot = OneHotEncoder()
#在相同的数据集上进行预处理和训练操作,将结果保存起来备用。
X_teams_expanded = onehot.fit_transform(X_teams).todense()
#接着,像之前那样在新数据集上调用决策树分类器。
clf = DecisionTreeClassifier(random_state=14)
scores = cross_val_score(clf, X_teams_expanded, y_true, scoring='accuracy')
print("决策树(队名转化) Accuracy: {0:.1f}%".format(np.mean(scores) * 100))
# 一棵决策树可以学到很复杂的规则。然而,很可能会导致过拟合问题——学到的规则只适用 于训练集。
# 解决方法之一就是调整决策树算法,限制它所学到的规则的数量
# 使用这种折中方案得到的决策树泛化 能力强,但整体表现稍弱
# 随机森林的工作原理:创建多棵决策树,用它们分别进行预测,再根据少数服 从多数的原则从多个预测结果中选择终预测结果。
# 装袋(bagging):每次随机从数据集中选取一部分数据用作训练集。
# 随机选取部分特征作为决策依据。
#------------------------版本4 随机森林-------------------------------
#的随机森林算法使用估计器接口,用交叉检验方法调用它即可
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, X_teams, y_true, scoring='accuracy')
print("随机森林 Accuracy: {0:.1f}%".format(np.mean(scores) * 100))
#随机森林使用不同的特征子集进行学习,应该比普通的决策树更为高效。
X_all = np.hstack([X_homehigher, X_teams])
clf = RandomForestClassifier(random_state=14)
scores = cross_val_score(clf, X_all, y_true, scoring='accuracy')
print("随机森林(采用不同特征) Accuracy: {0:.1f}%".format(np.mean(scores) * 100))
#可以使用GridSearchCV类搜索佳参数
#能够在指定的范围内自动搜索具有不同超参数的不同模型组合
parameter_space = { 'max_features': ['auto', 'sqrt', 'log2'],
"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: {0:.1f}%".format(grid.best_score_ * 100))
#输出用网格搜索找到的佳模型,查看都使用了哪些参数。
print(grid.best_estimator_)
#输出正确率高的模型所用到的参数
'''
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='entropy',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=4, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=1,
oob_score=False, random_state=14, verbose=0, warm_start=False)
'''