CategoryResourceRepost/极客时间专栏/数据分析实战45讲/第二模块:数据分析算法篇/29丨EM聚类(下):用EM算法对王者荣耀英雄进行划分.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

191 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="29丨EM聚类用EM算法对王者荣耀英雄进行划分" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/d6/6879af42f10088cac0d936d2dec92dd6.mp3"></audio>
今天我来带你进行EM的实战。上节课我讲了EM算法的原理EM算法相当于一个聚类框架里面有不同的聚类模型比如GMM高斯混合模型或者HMM隐马尔科夫模型。其中你需要理解的是EM的两个步骤E步和M步E步相当于通过初始化的参数来估计隐含变量M步是通过隐含变量来反推优化参数。最后通过EM步骤的迭代得到最终的模型参数。
今天我们进行EM算法的实战你需要思考的是
<li>
如何使用EM算法工具完成聚类
</li>
<li>
什么情况下使用聚类算法?我们用聚类算法的任务目标是什么?
</li>
<li>
面对王者荣耀的英雄数据EM算法能帮助我们分析出什么
</li>
## 如何使用EM工具包
在Python中有第三方的EM算法工具包。由于EM算法是一个聚类框架所以你需要明确你要用的具体算法比如是采用GMM高斯混合模型还是HMM隐马尔科夫模型。
这节课我们主要讲解GMM的使用在使用前你需要引入工具包
```
from sklearn.mixture import GaussianMixture
```
我们看下如何在sklearn中创建GMM聚类。
首先我们使用gmm = GaussianMixture(n_components=1, covariance_type=full, max_iter=100)来创建GMM聚类其中有几个比较主要的参数GMM类的构造参数比较多我筛选了一些主要的进行讲解我分别来讲解下
1.n_components即高斯混合模型的个数也就是我们要聚类的个数默认值为1。如果你不指定n_components最终的聚类结果都会为同一个值。
2.covariance_type代表协方差类型。一个高斯混合模型的分布是由均值向量和协方差矩阵决定的所以协方差的类型也代表了不同的高斯混合模型的特征。协方差类型有4种取值
<li>
covariance_type=full代表完全协方差也就是元素都不为0
</li>
<li>
covariance_type=tied代表相同的完全协方差
</li>
<li>
covariance_type=diag代表对角协方差也就是对角不为0其余为0
</li>
<li>
covariance_type=spherical代表球面协方差非对角为0对角完全相同呈现球面的特性。
</li>
3.max_iter代表最大迭代次数EM算法是由E步和M步迭代求得最终的模型参数这里可以指定最大迭代次数默认值为100。
创建完GMM聚类器之后我们就可以传入数据让它进行迭代拟合。
我们使用fit函数传入样本特征矩阵模型会自动生成聚类器然后使用prediction=gmm.predict(data)来对数据进行聚类传入你想进行聚类的数据可以得到聚类结果prediction。
你能看出来拟合训练和预测可以传入相同的特征矩阵,这是因为聚类是无监督学习,你不需要事先指定聚类的结果,也无法基于先验的结果经验来进行学习。只要在训练过程中传入特征值矩阵,机器就会按照特征值矩阵生成聚类器,然后就可以使用这个聚类器进行聚类了。
## 如何用EM算法对王者荣耀数据进行聚类
了解了GMM聚类工具之后我们看下如何对王者荣耀的英雄数据进行聚类。
首先我们知道聚类的原理是“人以群分,物以类聚”。通过聚类算法把特征值相近的数据归为一类,不同类之间的差异较大,这样就可以对原始数据进行降维。通过分成几个组(簇),来研究每个组之间的特性。或者我们也可以把组(簇)的数量适当提升,这样就可以找到可以互相替换的英雄,比如你的对手选择了你擅长的英雄之后,你可以选择另一个英雄作为备选。
我们先看下数据长什么样子:
<img src="https://static001.geekbang.org/resource/image/3c/a0/3c4e14e7b33fc211f96fe0108f6196a0.png" alt=""><br>
这里我们收集了69名英雄的20个特征属性这些属性分别是最大生命、生命成长、初始生命、最大法力、法力成长、初始法力、最高物攻、物攻成长、初始物攻、最大物防、物防成长、初始物防、最大每5秒回血、每5秒回血成长、初始每5秒回血、最大每5秒回蓝、每5秒回蓝成长、初始每5秒回蓝、最大攻速和攻击范围等。
具体的数据集你可以在GitHub上下载[https://github.com/cystanford/EM_data](https://github.com/cystanford/EM_data)。
现在我们需要对王者荣耀的英雄数据进行聚类,我们先设定项目的执行流程:
<img src="https://static001.geekbang.org/resource/image/8a/78/8af94562f6bd3ac42036ec47f5ad2578.jpg" alt="">
<li>
首先我们需要加载数据源;
</li>
<li>
在准备阶段,我们需要对数据进行探索,包括采用数据可视化技术,让我们对英雄属性以及这些属性之间的关系理解更加深刻,然后对数据质量进行评估,是否进行数据清洗,最后进行特征选择方便后续的聚类算法;
</li>
<li>
聚类阶段选择适合的聚类模型这里我们采用GMM高斯混合模型进行聚类并输出聚类结果对结果进行分析。
</li>
按照上面的步骤,我们来编写下代码。完整的代码如下:
```
# -*- coding: utf-8 -*-
import pandas as pd
import csv
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
# 数据加载,避免中文乱码问题
data_ori = pd.read_csv('./heros7.csv', encoding = 'gb18030')
features = [u'最大生命',u'生命成长',u'初始生命',u'最大法力', u'法力成长',u'初始法力',u'最高物攻',u'物攻成长',u'初始物攻',u'最大物防',u'物防成长',u'初始物防', u'最大每5秒回血', u'每5秒回血成长', u'初始每5秒回血', u'最大每5秒回蓝', u'每5秒回蓝成长', u'初始每5秒回蓝', u'最大攻速', u'攻击范围']
data = data_ori[features]
# 对英雄属性之间的关系进行可视化分析
# 设置plt正确显示中文
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
# 用热力图呈现features_mean字段之间的相关性
corr = data[features].corr()
plt.figure(figsize=(14,14))
# annot=True显示每个方格的数据
sns.heatmap(corr, annot=True)
plt.show()
# 相关性大的属性保留一个,因此可以对属性进行降维
features_remain = [u'最大生命', u'初始生命', u'最大法力', u'最高物攻', u'初始物攻', u'最大物防', u'初始物防', u'最大每5秒回血', u'最大每5秒回蓝', u'初始每5秒回蓝', u'最大攻速', u'攻击范围']
data = data_ori[features_remain]
data[u'最大攻速'] = data[u'最大攻速'].apply(lambda x: float(x.strip('%'))/100)
data[u'攻击范围']=data[u'攻击范围'].map({'远程':1,'近战':0})
# 采用Z-Score规范化数据保证每个特征维度的数据均值为0方差为1
ss = StandardScaler()
data = ss.fit_transform(data)
# 构造GMM聚类
gmm = GaussianMixture(n_components=30, covariance_type='full')
gmm.fit(data)
# 训练数据
prediction = gmm.predict(data)
print(prediction)
# 将分组结果输出到CSV文件中
data_ori.insert(0, '分组', prediction)
data_ori.to_csv('./hero_out.csv', index=False, sep=',')
```
运行结果如下:
<img src="https://static001.geekbang.org/resource/image/db/fb/dbe96b767d7f3ff2dd9f44b651cde8fb.png" alt="">
```
[28 14 8 9 5 5 15 8 3 14 18 14 9 7 16 18 13 3 5 4 19 12 4 12
12 12 4 17 24 2 7 2 2 24 2 2 24 6 20 22 22 24 24 2 2 22 14 20
14 24 26 29 27 25 25 28 11 1 23 5 11 0 10 28 21 29 29 29 17]
```
同时你也能看到输出的聚类结果文件hero_out.csv它保存在你本地运行的文件夹里程序会自动输出这个文件你可以自己看下
我来简单讲解下程序的几个模块。
**关于引用包**
首先我们会用DataFrame数据结构来保存读取的数据最后的聚类结果会写入到CSV文件中因此会用到pandas和CSV工具包。另外我们需要对数据进行可视化采用热力图展现属性之间的相关性这里会用到matplotlib.pyplot和seaborn工具包。在数据规范化中我们使用到了Z-Score规范化用到了StandardScaler类最后我们还会用到sklearn中的GaussianMixture类进行聚类。
**数据可视化的探索**
你能看到我们将20个英雄属性之间的关系用热力图呈现了出来中间的数字代表两个属性之间的关系系数最大值为1代表完全正相关关系系数越大代表相关性越大。从图中你能看出来“最大生命”“生命成长”和“初始生命”这三个属性的相关性大我们只需要保留一个属性即可。同理我们也可以对其他相关性大的属性进行筛选保留一个。你在代码中可以看到我用features_remain数组保留了特征选择的属性这样就将原本的20个属性降维到了13个属性。
**关于数据规范化**
我们能看到“最大攻速”这个属性值是百分数不适合做矩阵运算因此我们需要将百分数转化为小数。我们也看到“攻击范围”这个字段的取值为远程或者近战也不适合矩阵运算我们将取值做个映射用1代表远程0代表近战。然后采用Z-Score规范化对特征矩阵进行规范化。
**在聚类阶段**
我们采用了GMM高斯混合模型并将结果输出到CSV文件中。
这里我将输出的结果截取了一段设置聚类个数为30
<img src="https://static001.geekbang.org/resource/image/5c/ce/5c74ffe6741f1bf1bfdf7711932d47ce.png" alt=""><br>
第一列代表的是分组(簇),我们能看到张飞、程咬金分到了一组,牛魔、白起是一组,老夫子自己是一组,达摩、典韦是一组。聚类的特点是相同类别之间的属性值相近,不同类别的属性值差异大。因此如果你擅长用典韦这个英雄,不防试试达摩这个英雄。同样你也可以在张飞和程咬金中进行切换。这样就算你的英雄被别人选中了,你依然可以有备选的英雄可以使用。
## 总结
今天我带你一起做了EM聚类的实战具体使用的是GMM高斯混合模型。从整个流程中可以看出我们需要经过数据加载、数据探索、数据可视化、特征选择、GMM聚类和结果分析等环节。
聚类和分类不一样聚类是无监督的学习方式也就是我们没有实际的结果可以进行比对所以聚类的结果评估不像分类准确率一样直观那么有没有聚类结果的评估方式呢这里我们可以采用Calinski-Harabaz指标代码如下
```
from sklearn.metrics import calinski_harabaz_score
print(calinski_harabaz_score(data, prediction))
```
指标分数越高,代表聚类效果越好,也就是相同类中的差异性小,不同类之间的差异性大。当然具体聚类的结果含义,我们需要人工来分析,也就是当这些数据被分成不同的类别之后,具体每个类表代表的含义。
另外聚类算法也可以作为其他数据挖掘算法的预处理阶段,这样我们就可以将数据进行降维了。
<img src="https://static001.geekbang.org/resource/image/43/d7/43b35b8f49ac83799ea1ca88383609d7.png" alt=""><br>
最后依然是两道思考题。针对王者荣耀的英雄数据集我进行了特征选择实际上王者荣耀的英雄数量并不多我们可以省略特征选择这个阶段你不妨用全部的特征值矩阵进行聚类训练来看下聚类得到的结果。第二个问题是依然用王者荣耀英雄数据集在聚类个数为3以及聚类个数为30的情况下请你使用GMM高斯混合模型对数据集进行聚类并得出Calinski_Harabaz分数。
欢迎在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。