【ML】英雄联盟对局胜负预测任务
本样例以英雄联盟对局胜负预测任务为基础,简单展⽰了机器学习任务的⼤致流程,为机器学习⼊门者提供参考。
任务介绍
英雄联盟(League of Legends,LoL)是⼀个多⼈在线竞技游戏,由拳头游戏(Riot Games)公司出品。在游戏中,每位玩家控制⼀位有独特技能的英雄,红蓝两⽀队伍各有五位玩家进⾏对战,⽬标是摧毁对⽅的基地⽔晶。⽔晶有多座防御塔保护,通常需要先摧毁⼀些防御塔再摧毁⽔晶。玩家所控制的英雄起初⾮常弱,需要不断击杀⼩兵、野怪和对⽅英雄来获得⾦币、经验。经验可以提升英雄等级和技能等级,⾦币可以⽤来购买装备提升攻击、防御等属性。对战过程中⼀般没有⼰⽅单位在附近的地点是没有视野的,即⽆法看到对⾯单位,双⽅可以通过使⽤守卫来监视某个地点,洞察对⾯⾛向、制定战术。
本数据集来⾃,包含了9879场钻⼀到⼤师段位的单双排对局,对局双⽅⼏乎是同⼀⽔平。每条数据是前10分钟的对局情况,每⽀队伍有19个特征,红蓝双⽅共38个特征。这些特征包括英雄击杀、死亡,⾦钱、经验、等级情况等等。⼀局游戏⼀般会持续30⾄40分钟,但是实际前10分钟的局⾯很⼤程度上影响了之后胜负的⾛向。作为最成功的电⼦竞技游戏之⼀,对局数据、选⼿数据的量化与研究具有重要意义,可以启发游戏将来的发展和改进。本任务是希望构建机器学习模型根据已有的对局前10分钟特征信
息,预测最后获胜⽅是蓝⾊⽅还是红⾊⽅。
导⼊⼯具包
pandas是数据分析和处理常⽤的⼯具包,⾮常适合处理⾏列表格数据。numpy是数学运算⼯具包,⽀持⾼效的矩阵、向量运算。matplotlib和seaborn是作图常⽤⼯具包,其中seaborn是基于matplotlib的⾼级封装,使⽤⼀般更简便。sklearn是机器学习常⽤⼯具包,包括了⼀些已经实现好的简单模型和⼀些常⽤数据处理⽅法、评价指标等函数。
import pandas as pd # 数据处理
import numpy as np # 数学运算
import matplotlib.pyplot as plt # 作图
import seaborn as sns # 作图
from sklearn.linear_model import LogisticRegression # 逻辑回归
from sklearn.preprocessing import MinMaxScaler # 归⼀化函数
del_selection import train_test_split, cross_validate # 划分数据集函数
ics 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()# 每列特征的简单统计信息
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
redAssists 8.0
redEliteMonsters 0.0
redDragons 0.0
redHeralds 0.0
交通肇事罪构成要件redTowersDestroyed 0.0
redTotalGold 16567.0
redAvgLevel 6.8
redTotalExperience 17047.0
redTotalMinionsKilled 197.0四大名妓是谁
redTotalJungleMinionsKilled 55.0
redGoldDiff -643.0
redExperienceDiff 8.0
redCSPerMin 19.7
redGoldPerMin 1656.7
Name: 0, dtype: float64
blueWins blueWardsPlaced blueWardsDestroyed blueFirstBlood blueKills blueDeaths blueAssists blueEliteMonsters blueDragons blueHeralds count9879.0000009879.0000009879.0000009879.0000009879.0000009879.0000009879.0000009879.0000009879.0000009879.000000 mean0.49903822.288288 2.8248810.504808 6.183925 6.137666 6.6451060.5499540.3619800.187974 std0.50002418.019177 2.1749980.500002 3.011028 2.933818 4.0645200.6255270.4805970.390712 min0.000000 5.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.000000 25%0.00000014.000000 1.0000000.000000 4.000000 4.000000 4.0000000.0000000.0000000.000000 50%0.00000016.000000 3.000000 1.000000 6.000000 6.000000 6.0000000.0000000.0000000.000000 75% 1.00000020.000000 4.000000 1.0000008.0000008.0000009.000000 1.000000 1.0000000.000000 max 1.000000250.00000027.000000 1.00000022.00000022.00000029.000000 2.000000 1.000000 1.000000 8 rows × 39 columns
特征处理
传统的机器学习模型⼤部分都是基于特征的,因此特征⼯程是机器学习中⾮常重要的⼀步。有时构造⼀个好的特征⽐改进⼀个模型带来的提升更⼤。这⾥简单展⽰⼀些特征处理
的例⼦。⾸先,上⾯提到,特征列中有些特征信息是完全冗余的,会给模型带来不必要的计算量,可以去除。其次,相⽐于红蓝双⽅击杀、助攻的绝对值,可能双⽅击杀英雄的
差值更能体现出当前对战的局势。因此,我们可以构造红蓝双⽅对应特征的差值。数据⽂件中已有的差值是⾦币差GoldDiff和经验差ExperienceDiff,实际上每个对应特征都可
以构造这样的差值特征。
drop_features =['blueGoldDiff','redGoldDiff',
'blueExperienceDiff','redExperienceDiff',
'blueCSPerMin','redCSPerMin',
'blueGoldPerMin','redGoldPerMin']# 需要舍去的特征列小故事大道理100字
df = data_df.drop(columns=drop_features)# 舍去特征列
info_names =[c[3:]for c lumns if c.startswith('red')]# 取出要作差值的特征名字(除去red前缀)
for info in info_names:# 对于每个特征名字
df['br'+ info]= df['blue'+ info]- df['red'+ info]# 构造⼀个新的特征,由蓝⾊特征减去红⾊特征,前缀为br
特征相关性
为了了解每个特征的重要性或者特征之间的关联性,可以求两两特征或特征和标签之间的相关性。本样例以红蓝双⽅的差值特征为例,求了两两特征之间的pearson相关系数,
并可视化为热⼒图矩阵。相关系数的值在-1到1之间,越接近1表⽰正相关性越强,越接近-1表⽰负相关性越强。在相关性矩阵中,不难看出对⾓都是1,是因为⼀个特征和⾃⾝
的相关性⾃然是最强正相关。矩阵是对称的,因为特征A和特征B的相关性等价于特征B与特征A的相关性。从第⼀⾏(或列)可以看出哪些特征和标签blueWins相关。最强正相
关性的特征是队伍⾦币差brTotalGold,和标签的正相关性表⽰该特征值越⼤,标签值⼀般情况下也越⼤(越可能为1),这很⾃然因为蓝⾊⽅前⼗分钟的⾦币优势越⼤,最终获
胜的可能性也越⼤。特征和特征之间的相关性也可以从矩阵中看出来,⽐如右下⾓蓝⾊的⼀块包括队伍⾦币差brTotalGold、队伍平均等级差brAvgLevel和队伍总经验差
brTotalExperience三个特征,另外加上击杀英雄差brKills四个特征,它们两两特征之间的正相关性都⾮常⾼,这也很好理解,因为击杀英雄多了,⾦币和经验就多了,等级也
⾼了,获胜的概率也⾼了,因此和标签的正相关性也都较⾼。但是⼀般我们不希望⾼相关性的特征太多,因为希望不同特征能覆盖不同的信息,⽽不是重复冗余的信息,例如之
前去除的是完全冗余的相关性为1的特征。
plt.figure(figsize=(16,12))# 设置图像⼤⼩
# 获得相关性矩阵,pandas的DataFrame有直接的函数
corr_matrix = df[[c for c lumns if c =='blueWins'or c.startswith('br')]].corr()
cg = sns.heatmap(corr_matrix, cmap='YlGnBu', annot=True, fmt='.2f', vmin=0);# ⽤seaborn作热⼒图
数据集准备
构建机器学习模型前要构建训练和测试的数据集。在本例中⾸先需要分开标签和特征,标签是不能作为模型的输⼊特征的,就好⽐作业和试卷答案不能在做题和考试前就告诉学⽣。另外⽐较重要的⼀点是本任务中特征值的范围差距很⼤,有些特征⼤于⼀万,有些特征⼀般⼩于10,对于有些模型包括神经⽹络模型,可能会不利于参数学习,增加训练难度。因此通常会将特征值标准化到0-1之间,⽐如本例中我们使⽤sklearn提供的MinMaxScaler,将所有样本的某⼀列特征,最⼤值映射到1,最⼩值映射到0。测试⼀个模型在⼀个任务上的效果⾄少需要训练集和测试集,训练集⽤来训练模型的参数,好⽐学⽣做作业获得知识,测试集⽤来测试模型效果,好⽐期末考试考察学⽣学习情况。测试集的样本不应该出现在训练集中,否则会造成模型效果估计偏⾼,好⽐考试时出的题如果是作业题中出现过的,会造成考试分数不能准确衡量学⽣的学习情况,估计值偏⾼。划分训练集和测试集有多种⽅法,下⾯⾸先介绍的是随机取⼀部分如20%作测试集,剩下作训练集。sklearn提供了相关⼯具函数train_test_split。sklearn的输⼊输出⼀般为numpy的array矩阵,需要先将pandas的DataFrame取出为numpy的array矩阵。
all_y = df['blueWins'].values # 所有原始特征值,pandas的DataFrame.values取出为numpy的array矩阵
all_x = lumns[1:]].values # 所有标签数据
scaler = MinMaxScaler().fit(all_x)# 需要对特征作归⼀化,sklearn提供了相关⼯具
all_x = ansform(all_x)# 将特征归⼀化到01
# 划分训练集和测试集
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 # 输出数据⾏列信息
((9879,), (9879, 45), (7903, 45), (1976, 45), (7903,), (1976,))
模型训练和测试
本例以sklearn中的逻辑回归LogisticRegression为例展⽰模型的训练和测试过程,它是个线性分类器。sklearn中还有很多其他分类器如KNN、决策树、SVM等等。调⽤⾮常简单,给定⼀些超参数如最⼤迭代轮次等初始化⼀个LogisticRegression()类之后,可以通过fit()函数进⾏训练,predict()函数可以⽤来对样本进⾏预测输出预测值。本例采⽤准确率进⾏评价sklearn中有accuracy_score()函数帮助计算准确率。如果效果不好,可以尝试调整模型的超参数,如最⼤训练轮次、学习速率、正则化项权重等。
LR = LogisticRegression(random_state=RANDOM_SEED, verbose=1, max_iter=1000)# 初始化逻辑回归模型
LR.fit(x_train, y_train)# 在训练集上训练
p_test = LR.predict(x_test)# 在测试集上预测,获得预测值
print(p_test)# 输出预测值
test_acc = accuracy_score(p_test, y_test)# 将测试预测值与测试集标签对⽐获得准确率
print('accuracy: {:.4}'.format(test_acc))# 输出准确率
[0 1 0 ... 0 1 1]
accuracy: 0.7328
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 1 out of 1 | elapsed: 0.2s finished
交叉验证
在数据量不是很⼤的情况下,测试集可能也不够⼤,不能准确估计所得到的模型在未见样本上的效果,并且单次训练测试可能有偏差,⼀般需要重复多次训练测试取平均值。⼀种常⽤的重复实验的⽅式是交叉验证,它将整个数据集划分为K份,每次取其中⼀份作为测试集,其他K-1份作为训练集,取K次测试结果的平均作为最终模型的准确率。sklearn同样提供了⾮常⽅便的交叉验证的接⼝cross_validate,输⼊定义好的模型,所有训练数据以及折数K,可以得到每折训练测试的结果。例如下⾯代码中cv=5表⽰5折交叉验证。
LR = LogisticRegression(random_state=RANDOM_SEED, verbose=0, max_iter=1000)# 定义逻辑回归模型
scores = cross_validate(LR, all_x, all_y, cv=5, scoring=('accuracy'), return_train_score=True)# 5折交叉验证
print(scores)# 交叉验证的结果,是个python的dict,存有训练时间fit_time、测试时间score_time,测试准确率test_score,训练准确率train_score
print('average accuracy: {:.4f}'.an(scores['test_score'])))# 输出多折的测试准确率均值
{'fit_time': array([0.15559244, 0.16526341, 0.18318272, 0.16132855, 0.1458261 ]), 'score_time': arra
y([0.00039506, 0.0004127 , 0.00040436, 0.00039434, 0.00039506]), 'test_score': array([0.73 88664 , 0.72975709, 0.73279352, 0.7388664 , 0.72607595]), 'train_score': array([0.73326585, 0.73351892, 0.73415159, 0.73073516, 0.73380567])}
average accuracy: 0.7333
模型参数
⼤部分模型都有⼀些参数,会根据训练集训练出合适的值。在sklearn中,训练完成后可以输出模型参数进⾏查看。例如在逻辑回归模型中⽐较重要的参数是每个特征的权
重coef_。在本例中,对应特征的权重绝对值越⼤,表⽰对分类预测的贡献越⼤。正值表⽰该特征值越⼤越偏向预测蓝⾊⽅胜利,相反的负值表⽰偏向预测红⾊⽅胜利。
LR.fit(x_train, y_train)# 训练模型
print(LR.classes_.shape, LR.classes_)# 类别个数
print(LR.n_iter_.shape, LR.n_iter_)# 训练执⾏轮次
f_.shape, LR.coef_)# 特征权重
print(LR.intercept_.shape, LR.intercept_)# 分类截距
(2,) [0 1]
(1,) [193]
(1, 45) [[-0.59737104 0.06575039 0.02090261 0.26153071 -0.13618641 -0.17784935
0.10299402 0.16211285 0.04387518 -0.31606273 2.80851846 0.2661344
1.20491802 -0.00915835 0.42522354 -0.58995972 -0.06294822 -0.0257645
-0.13618641 0.26153071 -0.06571498 -0.08792058 -0.1457875 -0.03005367
0.35012894 -2.48974911 -0.20118816 -1.17832489 0.06173208 0.15223858
0.02363155 0.06900422 0.02090261 0.26264013 -0.26750203 -0.07164915
0.09302635 0.15151922 0.03453348 -0.32903876 2.91792133 0.31544563
1.61055321 -0.05467035 0.18671295]]
(1,) [-3.06028514]
可视化特征权重
研究模型的参数可以帮助我们理解模型分类预测的依据,对结果作解释。例如我们将每个特征对应的权重⽤柱状图表⽰出来,见下图。不难看到,和之前的分析结果相⼀致,最重要的特征包括蓝⾊⽅队伍⾦币blueTotalGold、红⾊⽅队伍⾦币redTotalGold、队伍⾦币差brTotalGold、队伍经验差brTotalExperience等,蓝⾊队伍⾦币经验领先越⼤,模型越可能预测蓝⾊⽅胜利。
plt.figure(figsize=(8,15))
张纪中个人资料简介sns.barplot(f_[0], lumns[1:])
董维嘉吻戏总结
⼀个完整的机器学习任务包括:确定任务、数据分析、特征⼯程、数据集划分、模型设计、模型训练和效果测试、结果分析和调优等多个阶段,本案例以英雄联盟游戏胜负预测任务为例,给出了每个阶段的⼀些简单例⼦,帮助⼤家⼊门机器学习,希望⼤家有所收获!
发布评论