前言
笔记,记录龙珠机器学习训练营Task3,关于lightgbm的学习。与前两个阶段一样,主要重心放在实际应用上,毕竟学习这个主要是用于一些比赛。
LightGBM介绍
LightGBM是2017年由微软推出的可扩展机器学习系统,是微软旗下DMKT的一个开源项目,由2014年首届阿里巴巴大数据竞赛获胜者之一柯国霖老师带领开发。它是一款基于GBDT(梯度提升决策树)算法的分布式梯度提升框架,为了满足缩短模型计算时间的需求,LightGBM的设计思路主要集中在减小数据对内存与计算性能的使用,以及减少多机器并行计算时的通讯代价。
LightGBM可以看作是XGBoost的升级豪华版,在获得与XGBoost近似精度的同时,又提供了更快的训练速度与更少的内存消耗。正如其名字中的Light所蕴含的那样,LightGBM在大规模数据集上跑起来更加优雅轻盈,一经推出便成为各种数据竞赛中刷榜夺冠的神兵利器。
LightGBM在机器学习与数据挖掘领域有着极为广泛的应用。据统计LightGBM模型自2016到2019年在Kaggle平台上累积获得数据竞赛前三名三十余次,其中包括CIKM2017 AnalytiCup、IEEE Fraud Detection等知名竞赛。这些竞赛来源于各行各业的真实业务,这些竞赛成绩表明LightGBM具有很好的可扩展性,在各类不同问题上都可以取得非常好的效果。
同时,LightGBM还被成功应用在工业界与学术界的各种问题中。例如金融风控、购买行为识别、交通流量预测、环境声音分类、基因分类、生物成分分析等诸多领域。虽然领域相关的数据分析和特性工程在这些解决方案中也发挥了重要作用,但学习者与实践者对LightGBM的一致选择表明了这一软件包的影响力与重要性。
实战
#本次实战使用天池英雄联盟数据集
!wget https://tianchi-media.oss-cn-beijing.aliyuncs.com/DSW/8LightGBM/high_diamond_ranked_10min.csv
#同样读取数据,本次数据集的label为蓝队获胜
import pandas as pd
df = pd.read_csv('high_diamond_ranked_10min.csv')
label = df.blueWins
#首先观察一下数据类型
df.info()
本次数据集全部为整数或浮点数,其中具体数据意义表示如下
特征名称 | 特征意义 | 取值范围 |
---|---|---|
WardsPlaced | 插眼数量 | 整数 |
WardsDestroyed | 拆眼数量 | 整数 |
FirstBlood | 是否获得首次击杀 | 整数 |
Kills | 击杀英雄数量 | 整数 |
Deaths | 死亡数量 | 整数 |
Assists | 助攻数量 | 整数 |
EliteMonsters | 击杀大型野怪数量 | 整数 |
Dragons | 击杀史诗野怪数量 | 整数 |
Heralds | 击杀峡谷先锋数量 | 整数 |
TowersDestroyed | 推塔数量 | 整数 |
TotalGold | 总经济 | 整数 |
AvgLevel | 平均英雄等级 | 浮点数 |
TotalExperience | 英雄总经验 | 整数 |
TotalMinionsKilled | 英雄补兵数量 | 整数 |
TotalJungleMinionsKilled | 英雄击杀野怪数量 | 整数 |
GoldDiff | 经济差距 | 整数 |
ExperienceDiff | 经验差距 | 整数 |
CSPerMin | 分均补刀 | 浮点数 |
GoldPerMin | 分均经济 | 浮点数 |
#查看数据前五行
df.head(5)
#首先注意到本次数据feature多达40
#去除掉队伍id和label,保存x副本并观察所有数据的分布情况
x = df.drop(['gameId','blueWins'], axis=1)
x.describe()
#可以看到插眼数最大值达到250,这明显属于异常值,常识性分析,击杀的大型野怪包括史诗级野怪和峡谷先锋
#另外经过分析可以得到TotalGold等变量在大部分对局中差距不大。两支队伍的经济差和经验差是相反数。红队和蓝队拿到首次击杀的概率大概都是50%
#经过分析可以发现,存在很多相对应的数据,如红队拿一血则蓝队没拿到,所以两个相同意义的数据我们选择去掉一个,同时去掉影响不大的数据,如上面提到的gold
drop_cols = ['redFirstBlood','redKills','redDeaths'
,'redGoldDiff','redExperienceDiff', 'blueCSPerMin',
'blueGoldPerMin','redCSPerMin','redGoldPerMin']
x.drop(drop_cols, axis=1, inplace=True)
#将数据copy到data,进行数据标准化变换,0-9和9-18的feature绘制两张图
data = x
data_std = (data - data.mean()) / data.std()
data = pd.concat([y, data_std.iloc[:, 0:9]], axis=1)#前九个
data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')
fig, ax = plt.subplots(1,2,figsize=(15,5))
sns.violinplot(x='Features', y='Values', hue='blueWins', data=data, split=True,
inner='quart', ax=ax[0], palette='Blues')
fig.autofmt_xdate(rotation=45)
data = x
data_std = (data - data.mean()) / data.std()
data = pd.concat([y, data_std.iloc[:, 9:18]], axis=1)
data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')
sns.violinplot(x='Features', y='Values', hue='blueWins',
data=data, split=True, inner='quart', ax=ax[1], palette='Blues')
fig.autofmt_xdate(rotation=45)
plt.show()
从图中我们可以看出:
- 击杀英雄数量越多更容易赢,死亡数量越多越容易输(bluekills与bluedeaths左右的区别)。
- 助攻数量与击杀英雄数量形成的图形状类似,说明他们对游戏结果的影响差不多。
- 一血的取得情况与获胜有正相关,但是相关性不如击杀英雄数量明显。
- 经济差与经验差对于游戏胜负的影响较小。
- 击杀野怪数量对游戏胜负的影响并不大。
#corr(),相关性绘制heatmap,表示所有feature之间的相关性
plt.figure(figsize=(18,14))
sns.heatmap(round(x.corr(),2), cmap='Blues', annot=True)
plt.show()
#图中颜色的深浅表示相关系数的大小,颜色越深相关性越大
x['killsDiff'] = x['blueKills'] - x['blueDeaths']
x['assistsDiff'] = x['blueAssists'] - x['redAssists']
x[['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists']].hist(figsize=(12,10), bins=20)
plt.show()
#我们发现击杀、死亡与助攻数的数据分布差别不大。但是击杀减去死亡、助攻减去死亡的分布与原分布差别很大,因此我们新构造这么两个特征。
sns.set(style='whitegrid', palette='muted')
# 构造两个新特征
x['wardsPlacedDiff'] = x['blueWardsPlaced'] - x['redWardsPlaced']
x['wardsDestroyedDiff'] = x['blueWardsDestroyed'] - x['redWardsDestroyed']
data = x[['blueWardsPlaced','blueWardsDestroyed','wardsPlacedDiff','wardsDestroyedDiff']].sample(1000)
data_std = (data - data.mean()) / data.std()
data = pd.concat([y, data_std], axis=1)
data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')
plt.figure(figsize=(10,6))
sns.swarmplot(x='Features', y='Values', hue='blueWins', data=data)
plt.xticks(rotation=45)
plt.show()
#我们画出了插眼数量的散点图,发现不存在插眼数量与游戏胜负间的显著规律。猜测由于钻石分段以上在哪插眼在哪好排眼都是套路,所以数据中前十分钟插眼数拔眼数对游戏的影响不大。所以我们暂时先把这些特征去掉。
#去除和眼位相关的特征
drop_cols = ['blueWardsPlaced','blueWardsDestroyed','wardsPlacedDiff',
'wardsDestroyedDiff','redWardsPlaced','redWardsDestroyed']
x.drop(drop_cols, axis=1, inplace=True)
data = x[['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists']].sample(1000)
data_std = (data - data.mean()) / data.std()
data = pd.concat([y, data_std], axis=1)
data = pd.melt(data, id_vars='blueWins', var_name='Features', value_name='Values')
plt.figure(figsize=(10,6))
sns.swarmplot(x='Features', y='Values', hue='blueWins', data=data)
plt.xticks(rotation=45)
plt.show()
#从上图我们可以发现击杀数与死亡数与助攻数,以及我们构造的特征对数据都有较好的分类能力。
data = pd.concat([y, x], axis=1).sample(500)
sns.pairplot(data, vars=['blueKills','blueDeaths','blueAssists','killsDiff','assistsDiff','redAssists'],
hue='blueWins')
plt.show()
#一些特征两两组合后对于数据的划分能力也有提升。
x['dragonsDiff'] = x['blueDragons'] - x['redDragons']
x['heraldsDiff'] = x['blueHeralds'] - x['redHeralds']
x['eliteDiff'] = x['blueEliteMonsters'] - x['redEliteMonsters']
data = pd.concat([y, x], axis=1)
eliteGroup = data.groupby(['eliteDiff'])['blueWins'].mean()
dragonGroup = data.groupby(['dragonsDiff'])['blueWins'].mean()
heraldGroup = data.groupby(['heraldsDiff'])['blueWins'].mean()
fig, ax = plt.subplots(1,3, figsize=(15,4))
eliteGroup.plot(kind='bar', ax=ax[0])
dragonGroup.plot(kind='bar', ax=ax[1])
heraldGroup.plot(kind='bar', ax=ax[2])
print(eliteGroup)
print(dragonGroup)
print(heraldGroup)
plt.show()
#我们构造了两队之间是否拿到龙、是否拿到峡谷先锋、击杀大型野怪的数量差值,发现在游戏的前期拿到龙比拿到峡谷先锋更容易获得胜利。拿到大型野怪的数量和胜率也存在着强相关。
x['towerDiff'] = x['blueTowersDestroyed'] - x['redTowersDestroyed']
data = pd.concat([y, x], axis=1)
towerGroup = data.groupby(['towerDiff'])['blueWins']
print(towerGroup.count())
print(towerGroup.mean())
fig, ax = plt.subplots(1,2,figsize=(15,5))
towerGroup.mean().plot(kind='line', ax=ax[0])
ax[0].set_title('Proportion of Blue Wins')
ax[0].set_ylabel('Proportion')
towerGroup.count().plot(kind='line', ax=ax[1])
ax[1].set_title('Count of Towers Destroyed')
ax[1].set_ylabel('Count')
#推塔是英雄联盟这个游戏的核心,因此推塔数量可能与游戏的胜负有很大关系。我们绘图发现,尽管前十分钟推掉第一座防御塔的概率很低,但是一旦某只队伍推掉第一座防御塔,获得游戏的胜率将大大增加。
#依旧是4:1划分训练集和测试集
from sklearn.model_selection import train_test_split
data_target_part = y
data_features_part = x
x_train, x_test, y_train, y_test = train_test_split(data_features_part, data_target_part, test_size = 0.2, random_state = 2021)
#导入lgb模型并进行拟合
from lightgbm.sklearn import LGBMClassifier
clf = LGBMClassifier()
clf.fit(x_train, y_train)
train_predict = clf.predict(x_train)
test_predict = clf.predict(x_test)
#回测和测试集结果分别为0.85和0.72
sns.barplot(y=data_features_part.columns, x=clf.feature_importances_)
#我们依旧可以观察模型拟合后的参数,可以看到总经济差距等特征,助攻数量、击杀死亡数量等特征都具有很大的作用。插眼数、推塔数对模型的影响并不大。
此次对于model的调参不再过多阐述,这一次的实战使用的数据较前两次有明显不同,较多的feature需要敏感的思维去探索和发现。借此也学习到之前Titanic曾用过的通过相关的feature构建新的feature,找出无意义的特征值,分析特征值的分布情况来初步判断相关性,这些知识对于处理特征值较多且都存在一定影响的数据有很大的帮助