一、簡介
EM 的英文是 Expectation Maximization,所以 EM 算法也叫最大期望算法。
我們先看一個簡單的場景:假設你炒了一份菜,想要把它平均分到兩個碟子里,該怎么分?
很少有人用稱對菜進行稱重,再計算一半的分量進行平分。大部分人的方法是先分一部分到碟子 A 中,然后再把剩余的分到碟子 B 中,再來觀察碟子 A 和 B 里的菜是否一樣多,哪個多就勻一些到少的那個碟子里,然后再觀察碟子 A 和 B 里的是否一樣多……整個過程一直重復下去,直到份量不發生變化為止。
你能從這個例子中看到三個主要的步驟:初始化參數、觀察預期、重新估計。首先是先給每個碟子初始化一些菜量,然后再觀察預期,這兩個步驟實際上就是期望步驟(Expectation)。如果結果存在偏差就需要重新估計參數,這個就是最大化步驟(Maximization)。這兩個步驟加起來也就是 EM 算法的過程。
/*請尊重作者勞動成果,轉載請標明原文鏈接:*/
/* https://www.cnblogs.com/jpcflyer/p/11143638.html * /
二、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 聚類。
三、 如何使用 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。
你能看出來擬合訓練和預測可以傳入相同的特征矩陣,這是因為聚類是無監督學習,你不需要事先指定聚類的結果,也無法基於先驗的結果經驗來進行學習。只要在訓練過程中傳入特征值矩陣,機器就會按照特征值矩陣生成聚類器,然后就可以使用這個聚類器進行聚類了。
四、 如何用 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):
第一列代表的是分組(簇),我們能看到張飛、程咬金分到了一組,牛魔、白起是一組,老夫子自己是一組,達摩、典韋是一組。聚類的特點是相同類別之間的屬性值相近,不同類別的屬性值差異大。因此如果你擅長用典韋這個英雄,不防試試達摩這個英雄。同樣你也可以在張飛和程咬金中進行切換。這樣就算你的英雄被別人選中了,你依然可以有備選的英雄可以使用。