機器學習經典演算法之EM
一、簡介
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):
第一列代表的是分組(簇),我們能看到張飛、程咬金分到了一組,牛魔、白起是一組,老夫子自己是一組,達摩、典韋是一組。聚類的特點是相同類別之間的屬性值相近,不同類別的屬性值差異大。因此如果你擅長用典韋這個英雄,不防試試達摩這個英雄。同樣你也可以在張飛和程咬金中進行切換。這樣就算你的英雄被別人選中了,你依然可以有備選的英雄可以使用。
&n