1、簡介html
EM 的英文是 Expectation Maximization,因此 EM 算法也叫最大指望算法。git
咱們先看一個簡單的場景:假設你炒了一份菜,想要把它平均分到兩個碟子裏,該怎麼分?github
不多有人用稱對菜進行稱重,再計算一半的份量進行平分。大部分人的方法是先分一部分到碟子 A 中,而後再把剩餘的分到碟子 B 中,再來觀察碟子 A 和 B 裏的菜是否同樣多,哪一個多就勻一些到少的那個碟子裏,而後再觀察碟子 A 和 B 裏的是否同樣多……整個過程一直重複下去,直到分量不發生變化爲止。算法
你能從這個例子中看到三個主要的步驟:初始化參數、觀察預期、從新估計。首先是先給每一個碟子初始化一些菜量,而後再觀察預期,這兩個步驟實際上就是指望步驟(Expectation)。若是結果存在誤差就須要從新估計參數,這個就是最大化步驟(Maximization)。這兩個步驟加起來也就是 EM 算法的過程。數組
/*請尊重做者勞動成果,轉載請標明原文連接:*/數據結構
/* https://www.cnblogs.com/jpcflyer/p/11143638.html * /app
2、EM 算法的工做原理框架
說到 EM 算法,咱們先來看一個概念「最大似然」,英文是 Maximum Likelihood,Likelihood 表明可能性,因此最大似然也就是最大可能性的意思。函數
什麼是最大似然呢?舉個例子,有一男一女兩個同窗,如今要對他倆進行身高的比較,誰會更高呢?根據咱們的經驗,相同年齡下男性的平均身高比女性的高一些,因此男同窗高的可能性會很大。這裏運用的就是最大似然的概念。工具
最大似然估計是什麼呢?它指的就是一件事情已經發生了,而後反推更有多是什麼因素形成的。仍是用一男一女比較身高爲例,假設有一我的比另外一我的高,反推他多是男性。最大似然估計是一種經過已知結果,估計參數的方法。
那麼 EM 算法是什麼?它和最大似然估計又有什麼關係呢?EM 算法是一種求解最大似然估計的方法,經過觀測樣本,來找出樣本的模型參數。
再回過來看下開頭我給你舉的分菜的這個例子,實際上最終咱們想要的是碟子 A 和碟子 B 中菜的分量,你能夠把它們理解爲想要求得的 模型參數 。而後咱們經過 EM 算法中的 E 步來進行觀察,而後經過 M 步來進行調整 A 和 B 的參數,最後讓碟子 A 和碟子 B 的參數再也不發生變化爲止。
實際咱們遇到的問題,比分菜複雜。我再給你舉個一個投擲硬幣的例子,假設咱們有 A 和 B 兩枚硬幣,咱們作了 5 組實驗,每組實驗投擲 10 次,而後統計出現正面的次數,實驗結果以下:
投擲硬幣這個過程當中存在隱含的數據,即咱們事先並不知道每次投擲的硬幣是 A 仍是 B。假設咱們知道這個隱含的數據,並將它完善,能夠獲得下面的結果:
咱們如今想要求得硬幣 A 和 B 出現正面次數的機率,能夠直接求得:
而實際狀況是我不知道每次投擲的硬幣是 A 仍是 B,那麼如何求得硬幣 A 和硬幣 B 出現正面的機率呢?
這裏就須要採用 EM 算法的思想。
1. 初始化參數。咱們假設硬幣 A 和 B 的正面機率(隨機指定)是θA=0.5 和θB=0.9。
2. 計算指望值。假設實驗 1 投擲的是硬幣 A,那麼正面次數爲 5 的機率爲:
公式中的 C(10,5) 表明的是 10 個裏面取 5 個的組合方式,也就是排列組合公式,0.5 的 5 次方乘以 0.5 的 5 次方表明的是其中一次爲 5 次爲正面,5 次爲反面的機率,而後再乘以 C(10,5) 等於正面次數爲 5 的機率。
假設實驗 1 是投擲的硬幣 B ,那麼正面次數爲 5 的機率爲:
因此實驗 1 更有可能投擲的是硬幣 A。
而後咱們對實驗 2~5 重複上面的計算過程,能夠推理出來硬幣順序應該是{A,A,B,B,A}。
這個過程其實是經過假設的參數來估計未知參數,即「每次投擲是哪枚硬幣」。
3. 經過猜想的結果{A, A, B, B, A}來完善初始化的參數θA 和θB。
而後一直重複第二步和第三步,直到參數再也不發生變化。
簡單總結下上面的步驟,你能看出 EM 算法中的 E 步驟就是經過舊的參數來計算隱藏變量。而後在 M 步驟中,經過獲得的隱藏變量的結果來從新估計參數。直到參數再也不發生變化,獲得咱們想要的結果。
EM 聚類的工做原理
上面你能看到 EM 算法最直接的應用就是求參數估計。若是咱們把潛在類別當作隱藏變量,樣本看作觀察值,就能夠把聚類問題轉化爲參數估計問題。這也就是 EM 聚類的原理。
相比於 K-Means 算法,EM 聚類更加靈活,好比下面這兩種狀況,K-Means 會獲得下面的聚類結果。
由於 K-Means 是經過距離來區分樣本之間的差異的,且每一個樣本在計算的時候只能屬於一個分類,稱之爲是硬聚類算法。而 EM 聚類在求解的過程當中,實際上每一個樣本都有必定的機率和每一個聚類相關,叫作軟聚類算法。
你能夠把 EM 算法理解成爲是一個框架,在這個框架中能夠採用不一樣的模型來用 EM 進行求解。經常使用的 EM 聚類有 GMM 高斯混合模型和 HMM 隱馬爾科夫模型。GMM(高斯混合模型)聚類就是 EM 聚類的一種。好比上面這兩個圖,能夠採用 GMM 來進行聚類。
和 K-Means 同樣,咱們事先知道聚類的個數,可是不知道每一個樣本分別屬於哪一類。一般,咱們能夠假設樣本是符合高斯分佈的(也就是正態分佈)。每一個高斯分佈都屬於這個模型的組成部分(component),要分紅 K 類就至關因而 K 個組成部分。這樣咱們能夠先初始化每一個組成部分的高斯分佈的參數,而後再看來每一個樣本是屬於哪一個組成部分。這也就是 E 步驟。
再經過獲得的這些隱含變量結果,反過來求每一個組成部分高斯分佈的參數,即 M 步驟。反覆 EM 步驟,直到每一個組成部分的高斯分佈參數不變爲止。
這樣也就至關於將樣本按照 GMM 模型進行了 EM 聚類。
3、 如何使用 EM 工具包
在 Python 中有第三方的 EM 算法工具包。因爲 EM 算法是一個聚類框架,因此你須要明確你要用的具體算法,好比是採用 GMM 高斯混合模型,仍是 HMM 隱馬爾科夫模型。
咱們主要講解 GMM 的使用,在使用前你須要引入工具包:
1 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 種取值:
covariance_type=full,表明徹底協方差,也就是元素都不爲 0;
covariance_type=tied,表明相同的徹底協方差;
covariance_type=diag,表明對角協方差,也就是對角不爲 0,其他爲 0;
covariance_type=spherical,表明球面協方差,非對角爲 0,對角徹底相同,呈現球面的特性。
3.max_iter:表明最大迭代次數,EM 算法是由 E 步和 M 步迭代求得最終的模型參數,這裏能夠指定最大迭代次數,默認值爲 100。
建立完 GMM 聚類器以後,咱們就能夠傳入數據讓它進行迭代擬合。
咱們使用 fit 函數,傳入樣本特徵矩陣,模型會自動生成聚類器,而後使用 prediction=gmm.predict(data) 來對數據進行聚類,傳入你想進行聚類的數據,能夠獲得聚類結果 prediction。
你能看出來擬合訓練和預測能夠傳入相同的特徵矩陣,這是由於聚類是無監督學習,你不須要事先指定聚類的結果,也沒法基於先驗的結果經驗來進行學習。只要在訓練過程當中傳入特徵值矩陣,機器就會按照特徵值矩陣生成聚類器,而後就可使用這個聚類器進行聚類了。
4、 如何用 EM 算法對王者榮耀數據進行聚類
瞭解了 GMM 聚類工具以後,咱們看下如何對王者榮耀的英雄數據進行聚類。
首先咱們知道聚類的原理是「人以羣分,物以類聚」。經過聚類算法把特徵值相近的數據歸爲一類,不一樣類之間的差別較大,這樣就能夠對原始數據進行降維。經過分紅幾個組(簇),來研究每一個組之間的特性。或者咱們也能夠把組(簇)的數量適當提高,這樣就能夠找到能夠互相替換的英雄,好比你的對手選擇了你擅長的英雄以後,你能夠選擇另外一個英雄做爲備選。
咱們先看下數據長什麼樣子:
這裏咱們收集了 69 名英雄的 20 個特徵屬性,這些屬性分別是最大生命、生命成長、初始生命、最大法力、法力成長、初始法力、最高物攻、物攻成長、初始物攻、最大物防、物防成長、初始物防、最大每 5 秒回血、每 5 秒回血成長、初始每 5 秒回血、最大每 5 秒回藍、每 5 秒回藍成長、初始每 5 秒回藍、最大攻速和攻擊範圍等。
具體的數據集你能夠在 GitHub 上下載: https://github.com/cystanford/EM_data 。
如今咱們須要對王者榮耀的英雄數據進行聚類,咱們先設定項目的執行流程:
首先咱們須要加載數據源;
在準備階段,咱們須要對數據進行探索,包括採用數據可視化技術,讓咱們對英雄屬性以及這些屬性之間的關係理解更加深入,而後對數據質量進行評估,是否進行數據清洗,最後進行特徵選擇方便後續的聚類算法;
聚類階段:選擇適合的聚類模型,這裏咱們採用 GMM 高斯混合模型進行聚類,並輸出聚類結果,對結果進行分析。
按照上面的步驟,咱們來編寫下代碼。完整的代碼以下:
1 # -*- coding: utf-8 -*- 2 3 import pandas as pd 4 5 import csv 6 7 import matplotlib.pyplot as plt 8 9 import seaborn as sns 10 11 from sklearn.mixture import GaussianMixture 12 13 from sklearn.preprocessing import StandardScaler 14 15 # 數據加載,避免中文亂碼問題 16 17 data_ori = pd.read_csv('./heros7.csv', encoding = 'gb18030') 18 19 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'攻擊範圍'] 20 21 data = data_ori[features] 22 23 # 對英雄屬性之間的關係進行可視化分析 24 25 # 設置 plt 正確顯示中文 26 27 plt.rcParams['font.sans-serif']=['SimHei'] # 用來正常顯示中文標籤 28 29 plt.rcParams['axes.unicode_minus']=False # 用來正常顯示負號 30 31 # 用熱力圖呈現 features_mean 字段之間的相關性 32 33 corr = data[features].corr() 34 35 plt.figure(figsize=(14,14)) 36 37 # annot=True 顯示每一個方格的數據 38 39 sns.heatmap(corr, annot=True) 40 41 plt.show() 42 43 # 相關性大的屬性保留一個,所以能夠對屬性進行降維 44 45 features_remain = [u'最大生命', u'初始生命', u'最大法力', u'最高物攻', u'初始物攻', u'最大物防', u'初始物防', u'最大每 5 秒回血', u'最大每 5 秒回藍', u'初始每 5 秒回藍', u'最大攻速', u'攻擊範圍'] 46 47 data = data_ori[features_remain] 48 49 data[u'最大攻速'] = data[u'最大攻速'].apply(lambda x: float(x.strip('%'))/100) 50 51 data[u'攻擊範圍']=data[u'攻擊範圍'].map({'遠程':1,'近戰':0}) 52 53 # 採用 Z-Score 規範化數據,保證每一個特徵維度的數據均值爲 0,方差爲 1 54 55 ss = StandardScaler() 56 57 data = ss.fit_transform(data) 58 59 # 構造 GMM 聚類 60 61 gmm = GaussianMixture(n_components=30, covariance_type='full') 62 63 gmm.fit(data) 64 65 # 訓練數據 66 67 prediction = gmm.predict(data) 68 69 print(prediction) 70 71 # 將分組結果輸出到 CSV 文件中 72 73 data_ori.insert(0, '分組', prediction) 74 75 data_ori.to_csv('./hero_out.csv', index=False, sep=',')
運行結果以下:
1 [28 14 8 9 5 5 15 8 3 14 18 14 9 7 16 18 13 3 5 4 19 12 4 12 2 3 12 12 4 17 24 2 7 2 2 24 2 2 24 6 20 22 22 24 24 2 2 22 14 20 4 5 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):
第一列表明的是分組(簇),咱們能看到張飛、程咬金分到了一組,牛魔、白起是一組,老夫子本身是一組,達摩、典韋是一組。聚類的特色是相同類別之間的屬性值相近,不一樣類別的屬性值差別大。所以若是你擅長用典韋這個英雄,不防試試達摩這個英雄。一樣你也能夠在張飛和程咬金中進行切換。這樣就算你的英雄被別人選中了,你依然能夠有備選的英雄可使用。