【机器学习】实验1,基于决策树的英雄联盟游戏胜负预测(完整代码实现)

您所在的位置:网站首页 英雄联盟比赛数据查询系统 【机器学习】实验1,基于决策树的英雄联盟游戏胜负预测(完整代码实现)

【机器学习】实验1,基于决策树的英雄联盟游戏胜负预测(完整代码实现)

2024-07-05 14:27:08| 来源: 网络整理| 查看: 265

清华大学驭风计划课程链接 

学堂在线 - 精品在线课程学习平台 (xuetangx.com)

代码和报告均为本人自己实现(实验满分),此次实验开源代码,如果需要数据集可以私聊博主

有任何疑问或者问题,也欢迎私信博主,大家可以相互讨论交流哟~~求点赞+关注

后续持续更新机器学习专栏

实验说明

英雄联盟(League of Legends,LoL)是一个多人在线竞技游戏,由拳头游戏(Riot Games)公司出品。在游戏中,每位玩家控制一位有独特技能的英雄,红蓝两支队伍各有五位玩家进行对战,目标是摧毁对方的基地水晶。水晶有多座防御塔保护,通常需要先摧毁一些防御塔再摧毁水晶。玩家所控制的英雄起初非常弱,需要不断击杀小兵、野怪和对方英雄来获得金币、经验。经验可以提升英雄等级和技能等级,金币可以用来购买装备提升攻击、防御等属性。对战过程中一般没有己方单位在附近的地点是没有视野的,即无法看到对面单位,双方可以通过使用守卫来监视某个地点,洞察对面走向、制定战术。 本数据集来自Kaggle,包含了9879场钻一到大师段位的单双排对局,对局双方几乎是同一水平。每条数据是前10分钟的对局情况,每支队伍有19个特征,红蓝双方共38个特征。这些特征包括英雄击杀、死亡,金钱、经验、等级情况等等。一局游戏一般会持续30至40分钟,但是实际前10分钟的局面很大程度上影响了之后胜负的走向。作为最成功的电子竞技游戏之一,对局数据、选手数据的量化与研究具有重要意义,可以启发游戏将来的发展和改进。

本任务是希望同学们依据注释的要求,对代码中空缺部分进行填写,完成决策树模型的详细实现,根据已有的对局前10分钟特征信息,预测最后获胜方是蓝色方还是红色方,了解执行一个机器学习任务的大致流程

 导入工具包

pandas是数据分析和处理常用的工具包,非常适合处理行列表格数据。numpy是数学运算工具包,支持高效的矩阵、向量运算。sklearn是机器学习常用工具包,包括了一些已经实现好的简单模型和一些常用数据处理方法、评价指标等函数。

from collections import Counter import pandas as pd # 数据处理 import numpy as np # 数学运算 from sklearn.model_selection import train_test_split, cross_validate # 划分数据集函数 from sklearn.metrics import accuracy_score # 准确率函数 RANDOM_SEED = 2020 # 固定随机种子 读入数据

假设数据文件放在`./data/`目录下,标准的csv文件可以用pandas里的`read_csv()`函数直接读入。文件共有40列,38个特征(红蓝方各19),1个标签列(blueWins),和一个对局标号(gameId)。对局标号不是标签也不是特征,可以舍去。

csv_data = './data/high_diamond_ranked_10min.csv' # 数据路径 data_df = pd.read_csv(csv_data, sep=',') # 读入csv文件为pandas的DataFrame data_df = data_df.drop(columns='gameId') # 舍去对局标号列  数据概览

对于一个机器学习问题,在拿到任务和数据后,首先需要观察数据的情况,比如我们可以通过`.iloc[0]`取出数据的第一行并输出。不难看出每个特征都存成了float64浮点数,该对局蓝色方开局10分钟有小优势。同时也可以发现有些特征列是重复冗余的,比如blueGoldDiff表示蓝色队金币优势,redGoldDiff表示红色方金币优势,这两个特征是完全对称的互为相反数。blueCSPerMin是蓝色方每分钟击杀小兵数,它乘10就是10分钟所有小兵击杀数blueTotalMinionsKilled。在之后的特征处理过程中可以考虑去除这些冗余特征。

另外,pandas有非常方便的`describe()`函数,可以直接通过DataFrame进行调用,可以展示每一列数据的一些统计信息,对数据分布情况有大致了解,比如blueKills蓝色方击杀英雄数在前十分钟的平均数是6.14、方差为2.93,中位数是6,百分之五十以上的对局中该特征在4-8之间,等等。

print(data_df.iloc[0]) # 输出第一行数据 data_df.describe() # 每列特征的简单统计信息 Output exceeds the size limit. Open the full output data in a text editor blueWins 0.0 blueWardsPlaced 28.0 blueWardsDestroyed 2.0 blueFirstBlood 1.0 blueKills 9.0 blueDeaths 6.0 blueAssists 11.0 blueEliteMonsters 0.0 blueDragons 0.0 blueHeralds 0.0 blueTowersDestroyed 0.0 blueTotalGold 17210.0 blueAvgLevel 6.6 blueTotalExperience 17039.0 blueTotalMinionsKilled 195.0 blueTotalJungleMinionsKilled 36.0 blueGoldDiff 643.0 blueExperienceDiff -8.0 blueCSPerMin 19.5 blueGoldPerMin 1721.0 redWardsPlaced 15.0 redWardsDestroyed 6.0 redFirstBlood 0.0 redKills 6.0 redDeaths 9.0 ... redExperienceDiff 8.0 redCSPerMin 19.7 redGoldPerMin 1656.7 Name: 0, dtype: float64 增删特征

传统的机器学习模型大部分都是基于特征的,因此特征工程是机器学习中非常重要的一步。有时构造一个好的特征比改进一个模型带来的提升更大。这里简单展示一些特征处理的例子。首先,上面提到,特征列中有些特征信息是完全冗余的,会给模型带来不必要的计算量,可以去除。其次,相比于红蓝双方击杀、助攻的绝对值,可能双方击杀英雄的差值更能体现出当前对战的局势。因此,我们可以构造红蓝双方对应特征的差值。数据文件中已有的差值是金币差GoldDiff和经验差ExperienceDiff,实际上每个对应特征都可以构造这样的差值特征。

drop_features = ['blueGoldDiff', 'redGoldDiff', 'blueExperienceDiff', 'redExperienceDiff', 'blueCSPerMin', 'redCSPerMin', 'blueGoldPerMin', 'redGoldPerMin'] # 需要舍去的特征列 df = data_df.drop(columns=drop_features) # 舍去特征列 info_names = [c[3:] for c in df.columns if c.startswith('red')] # 取出要作差值的特征名字(除去red前缀) for info in info_names: # 对于每个特征名字 df['br' + info] = df['blue' + info] - df['red' + info] # 构造一个新的特征,由蓝色特征减去红色特征,前缀为br # 其中FirstBlood为首次击杀最多有一只队伍能获得,brFirstBlood=1为蓝,0为没有产生,-1为红 df = df.drop(columns=['blueFirstBlood', 'redFirstBlood']) # 原有的FirstBlood可删除 df  特征离散化

决策树ID3算法一般是基于离散特征的,本例中存在很多连续的数值特征,例如队伍金币。直接应用该算法每个值当作一个该特征的一个取值可能造成严重的过拟合,因此需要对特征进行离散化,即将一定范围内的值映射成一个值,例如对用户年龄特征,将0-10映射到0,11-18映射到1,19-25映射到2,25-30映射到3,等等类似,然后在决策树构建时使用映射后的值计算信息增益。

***本小节要求实现特征离散化,请补全相关代码***

discrete_df = df.copy() # 先复制一份数据 for c in df.columns[1:]: # 遍历每一列特征,跳过标签列 ''' 请离散化每一列特征,即discrete_df[c] = ... 提示: 对于有些特征本身取值就很少,可以跳过即 if ... : continue 对于其他特征,可以使用等区间离散化、等密度离散化或一些其他离散化方法 可参考使用pandas.cut或qcut ''' if c == 'blue' + 'EliteMonsters' or c == 'red' + 'EliteMonsters' or c == 'br' + 'EliteMonsters': continue if c == 'blue' + 'Dragons' or c == 'red' + 'Dragons' or c == 'br' + 'Dragons': continue if c == 'blue' + 'Heralds' or c == 'red' + 'Heralds' or c == 'br' + 'Heralds': continue if c == 'blue' + 'TowersDestroyed' or c == 'red' + 'TowersDestroyed' or c == 'br' + 'TowersDestroyed': continue if c == 'brFirstBlood': continue discrete_df[c] = pd.cut(df[c], 2, labels=[1, 2], duplicates='drop')  数据集准备

构建机器学习模型前要构建训练和测试的数据集。在本例中首先需要分开标签和特征,标签是不能作为模型的输入特征的,就好比作业和试卷答案不能在做题和考试前就告诉学生。测试一个模型在一个任务上的效果至少需要训练集和测试集,训练集用来训练模型的参数,好比学生做作业获得知识,测试集用来测试模型效果,好比期末考试考察学生学习情况。测试集的样本不应该出现在训练集中,否则会造成模型效果估计偏高,好比考试时出的题如果是作业题中出现过的,会造成考试分数不能准确衡量学生的学习情况,估计值偏高。划分训练集和测试集有多种方法,下面首先介绍的是随机取一部分如20%作测试集,剩下作训练集。sklearn提供了相关工具函数`train_test_split`。sklearn的输入输出一般为numpy的array矩阵,需要先将pandas的DataFrame取出为numpy的array矩阵。

all_y = discrete_df['blueWins'].values # 所有标签数据 feature_names = discrete_df.columns[1:] # 所有特征的名称 all_x = discrete_df[feature_names].values # 所有原始特征值,pandas的DataFrame.values取出为numpy的array矩阵 # 划分训练集和测试集 x_train, x_test, y_train, y_test = train_test_split(all_x, all_y, test_size=0.2, random_state=RANDOM_SEED) all_y.shape, all_x.shape, x_train.shape, x_test.shape, y_train.shape, y_test.shape # 输出数据行列信息 决策树模型的实现

***本小节要求实现决策树模型,请补全算法代码***

# 定义决策树类 class DecisionTree(object): def __init__(self, classes, features, max_depth=10, min_samples_split=10, impurity_t='entropy'): ''' 传入一些可能用到的模型参数,也可能不会用到 classes表示模型分类总共有几类 features是每个特征的名字,也方便查询总的共特征数 max_depth表示构建决策树时的最大深度 min_samples_split表示构建决策树分裂节点时,如果到达该节点的样本数小于该值则不再分裂 impurity_t表示计算混杂度(不纯度)的计算方式,例如entropy或gini ''' self.classes = classes self.features = features self.max_depth = max_depth self.min_samples_split = min_samples_split self.impurity_t = impurity_t self.root = None # 定义根节点,未训练时为空 ''' 请实现决策树算法,使得fit函数和predict函数可以正常调用,跑通之后的测试代码, 要求之后测试代码输出的准确率大于0.6。 提示: 可以定义额外一些函数,例如 impurity()用来计算混杂度 gain()调用impurity用来计算信息增益 expand_node()训练时递归函数分裂节点,考虑不同情况 1. 无需分裂 或 达到分裂阈值 2. 调用gain()找到最佳分裂特征,递归调用expand_node 3. 找不到有用的分裂特征 fit函数调用该函数返回根节点 traverse_node()预测时遍历节点,考虑不同情况 1. 已经到达叶节点,则返回分类结果 2. 该特征取值在训练集中未出现过 3. 依据特征取值进入相应子节点,递归调用traverse_node 当然也可以有其他实现方式。 ''' def impurity(self, feature): # 获取数据总数 data_num = len(feature) # 使用 Counter 统计每个取值出现的次数 cnt = Counter(feature) # 计算每个取值的概率 P_ele = np.array([cnt[l] / data_num for l in cnt]) if self.impurity_t == 'gini': # 计算基尼不纯度 impurity = 1 - np.sum(np.square(P_ele)) elif self.impurity_t == 'entropy': # 计算熵 impurity = -np.sum(np.multiply(P_ele, np.log2(P_ele))) else: # 如果不支持该种不纯度类型,则引发异常 raise ValueError("Unsupported impurity type: {}".format(self.impurity_t)) return impurity def gain(self, feature, label): # 获取特征数目 num_features = len(feature[0]) # 计算原始标签集的不纯度 sum_impurity = self.impurity(label) # 初始化最佳信息增益和最佳特征索引 best_gain = 0 best_feature = -1 # 遍历每一个特征 for i in range(num_features): # 获取特征列表和唯一值 feature_list = [example[i] for example in feature] unique_vals = set(feature_list) sub_impurity = 0 # 对于每一个唯一值,计算其子集的不纯度并加权求和 for val in unique_vals: # 划分出当前特征值对应的子集 sub_feature, sub_label = self.dividefeat(feature, i, label, val) # 计算该子集的不纯度并乘以该子集占总集合的比例 sub_impurity += self.impurity(sub_label) * len(sub_label) / len(feature) # 计算当前特征的信息增益 now_gain = sum_impurity - sub_impurity # 更新最佳信息增益和最佳特征索引 if now_gain > best_gain: best_gain = now_gain best_feature = i # 返回最佳特征索引 return best_feature def dividefeat(self, feature, tagfeature, lable, value): # 初始化一个空列表,存储特征 refeature = [] # 初始化一个空列表,存储标签 relable = [] # 将 feature 和 lable 转换成列表形式,方便操作 features = feature.tolist() lables = lable.tolist() # 遍历 feature 中的每个数据样本 for i in range(len(feature)): # 取出第 i 个数据样本 feaVec = features[i] # 判断该数据样本的 tagfeature 特征是否等于 value if feaVec[tagfeature] == value: # 如果等于,则将其余特征组成新的列表 subfeat subfeat = feaVec[:tagfeature] subfeat.extend(feaVec[tagfeature + 1:]) # 将 subfeat 和对应的标签 lables[i] 加入 refeature 和 relable 列表中 refeature.append(subfeat) relable.append(lables[i]) # 将 refeature 和 relable 转换成 numpy 数组返回 return np.array(refeature), np.array(relable) def expand_node(self, feature, lable, depth): # 计算标签的计数 classcount = Counter(lable) # 如果样本属于同一类别,返回该类别 if len(classcount) == 1: return lable[0] # 如果样本数量小于等于 min_samples_split,返回任意一个标签 if len(feature[0]) = self.max_depth: return lable[0] # 选取最佳特征 bestFeat = self.gain(feature, lable) # 获取最佳特征的所有取值 featValue = [example[bestFeat] for example in feature] uniqueval = set(featValue) subDT = {bestFeat:{}} # 对于每个取值,继续递归生成子树 for value in uniqueval: # 获取当前特征取值为value的子集 subfeature, sublable = self.dividefeat(feature, bestFeat, lable, value) # 递归生成子树 subDT[bestFeat][value] = self.expand_node(subfeature, sublable, depth=depth + 1) return subDT def traverse_node(self, node, feature): # 获取当前节点的特征名 featname = list(node.keys())[0] # 获取当前节点特征的可选值 featvals = list(node[featname].keys()) # 如果样本中该特征的值不在可选值里,就将其置为可选值中的第一个值 if feature[featname] not in featvals: feature[featname] = featvals[0] # 如果当前节点的取值是类别标签,则返回该标签 if node[featname][feature[featname]] in self.classes: return node[featname][feature[featname]] # 递归遍历子节点 return self.traverse_node(node[featname][feature[featname]], feature) def fit(self, feature, label): assert len(self.features) == len(feature[0]) # 输入数据的特征数目应该和模型定义时的特征数目相同 self.root = self.expand_node(feature, label, depth=1) ''' 训练模型 feature为二维numpy(n*m)数组,每行表示一个样本,有m个特征 label为一维numpy(n)数组,表示每个样本的分类标签 提示:一种可能的实现方式为 self.root = self.expand_node(feature, label, depth=1) # 从根节点开始分裂,模型记录根节点 ''' def predict(self, feature): assert len(feature.shape) == 1 or len(feature.shape) == 2 # 只能是1维或2维 if len(feature.shape) == 1: # 如果是一个样本 return self.traverse_node(self.root, feature) # 从根节点开始路由 return np.array([self.traverse_node(self.root, f) for f in feature]) # 如果是很多个样本 ''' 预测 输入feature可以是一个一维numpy数组也可以是一个二维numpy数组 如果是一维numpy(m)数组则是一个样本,包含m个特征,返回一个类别值 如果是二维numpy(n*m)数组则表示n个样本,每个样本包含m个特征,返回一个numpy一维数组 提示:一种可能的实现方式为 if len(feature.shape) == 1: # 如果是一个样本 return self.traverse_node(self.root, feature) # 从根节点开始路由 return np.array([self.traverse_node(self.root, f) for f in feature]) # 如果是很多个样本 ''' # 定义决策树模型,传入算法参数 DT = DecisionTree(classes=[0,1], features=feature_names, max_depth=5, min_samples_split=10, impurity_t='gini') DT.fit(x_train, y_train) # 在训练集上训练 p_test = DT.predict(x_test) # 在测试集上预测,获得预测值 print(p_test) # 输出预测值 test_acc = accuracy_score(p_test, y_test) # 将测试预测值与测试集标签对比获得准确率 print('accuracy: {:.4f}'.format(test_acc)) # 输出准确率

模型调优

第一次模型测试结果可能不够好,可以先检查调试代码是否有bug,再尝试调整参数或者优化计算方法。

总结

一个完整的机器学习任务包括:确定任务、数据分析、特征工程、数据集划分、模型设计、模型训练和效果测试、结果分析和调优等多个阶段,本案例以英雄联盟游戏胜负预测任务为例,给出了每个阶段的一些简单例子,帮助大家入门机器学习,希望大家有所收获!



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭