機器學習教程 之 EM算法 :高斯混合模型聚類算法 (python基於《統計學習方法》實現,附數據集和代碼)

之前寫過一篇博客講述極大似然方法, 這一方法通常適用於知道觀測數據YY,求解模型參數θ\theta的場合,即P(Yθ)P(Y|\theta)
但是,在更多場合除了模型參數是未知的外,還有隱變量ZZ也是未知的,即P(Y,Zθ)P(Y,Z|\theta)。多個隱藏模型的混合,會使得普通的極大似然方法用起來不是那麼方便,比如求解高斯混合模型(GMM), 隱馬爾可夫模型(HMM),等。這種時候,就會發覺EM算法是機器學習領域中繞不過去的一道坎。

這裏,主要基於《統計學習方法》裏對於高斯混合模型的講解,實現EM算法,並給出代碼和註釋。
在實現模型之前,觀察過其他人寫的代碼,包括sklearn內部的代碼,相比較而言,這次的實現封裝的更好,使用會更方便,結果也不錯。

先描述一下實現的原理,再說代碼

一、EM算法解高斯混合模型

假設觀測數據y1,y2,...,yNy_{1},y_{2},...,y_{N}由3高斯混合模型生成,即
P(yθ)=k=1Kakϕ(yθk)P(y|\theta)=\sum_{k=1}^{K}a_{k}\phi(y|\theta_{k})
其中θ=(a1,a2,...,ak;θ1,θ2,...,θk)\theta=(a_{1},a_{2},...,a_{k};\theta_{1},\theta_{2},...,\theta_{k}), 我們用EM算法估計高斯混合模型的參數θ\theta

1.0 EM算法:初始化模型參數

1.1 EM算法的E步:確定Q函數

混合高斯模型的Q函數爲:
Q(θ,θi)=k=1K{j=1N(Eγjk)logak+j=1N(Eγjk)[log(1/2π)log(σk)(1/2σk2)(yjuk)2]Q(\theta,\theta^{i})=\sum_{k=1}^{K}\{\sum_{j=1}^{N}(E_{\gamma_{jk}})loga_{k}+\sum_{j=1}^{N}(E_{\gamma_{jk}})[log(1/\sqrt{2\pi})-log(\sigma_{k})-(1/2\sigma_{k}^{2})(y_{j}-u_{k})^{2}]
這裏需要計算E(γjky,θ)E(\gamma_{jk}|y,\theta),記爲γ^jk\hat{\gamma}_{jk}
γ^jk=akϕ(yjθk)k=1Kakϕ(yjθk),j=1,2,...,N;k=1,2,...,K\hat{\gamma}_{jk}=\frac{a_{k}\phi(y_{j}|\theta_{k})}{\sum_{k=1}^{K}a_{k}\phi(y_{j}|\theta_{k})},j=1,2,...,N;k=1,2,...,Kγ^jk\hat{\gamma}_{jk}是當前模型參數下第jj個觀測數據來自第kk個分模型的概率,稱爲分模型kk對觀測數據yjy_{j}的影響度

1.2EM算法的M步:更新模型參數

M步極大化Q函數,更新模型參數如下:
u^k=j=1Nγ^jkyjj=1Nγ^jk,k=1,2...,K\hat{u}_{k}=\frac{\sum_{j=1}^{N}\hat{\gamma}_{jk}y_{j}}{\sum_{j=1}^{N}\hat{\gamma}_{jk}},k=1,2...,Kσ^k2=j=1Nγ^jk(yjuk)2j=1Nγ^jk,k=1,2...,K\hat{\sigma}_{k}^{2}=\frac{\sum_{j=1}^{N}\hat{\gamma}_{jk}(y_{j}-u_{k})^{2}}{\sum_{j=1}^{N}\hat{\gamma}_{jk}},k=1,2...,Ka^k=j=1Nγ^jkN,k=1,2...,K\hat{a}_{k}=\frac{\sum_{j=1}^{N}\hat{\gamma}_{jk}}{N},k=1,2...,K
重複E步和M步直到模型收斂

二、EM算法求解高斯混合模型

from sklearn.datasets import load_digits, load_iris
from scipy.stats import multivariate_normal
from sklearn import preprocessing
from sklearn.metrics import accuracy_score
import numpy as np

class GMM_EM():
    def __init__(self,n_components,max_iter = 1000,error = 1e-6):
        self.n_components = n_components  #混合模型由幾個gauss模型組成
        self.max_iter = max_iter  #最大迭代次數
        self.error = error  #收斂誤差
        self.samples = 0
        self.features = 0
        self.alpha = []   #存儲模型權重
        self.mu = []   #存儲均值
        self.sigma = []   #存儲標準差
    
    def _init(self,data):   #初始化參數
        np.random.seed(7)
        self.mu = np.array(np.random.rand(self.n_components, self.features))
        self.sigma = np.array([np.eye(self.features)/self.features] * self.n_components)
        self.alpha = np.array([1.0 / self.n_components] * self.n_components)
        print(self.alpha.shape,self.mu.shape,self.sigma.shape)
            
    def gauss(self, Y, mu, sigma):  #根據模型參數和數據輸出概率向量
        return multivariate_normal(mean=mu,cov=sigma+1e-7*np.eye(self.features)).pdf(Y)
    
    def preprocess(self,data):   # 數據預處理
        self.samples = data.shape[0]
        self.features = data.shape[1]
        pre = preprocessing.MinMaxScaler()
        return pre.fit_transform(data)
    
    def fit_predict(self,data):   #擬合數據
        data = self.preprocess(data)
        self._init(data)
        
        weighted_probs = np.zeros((self.samples,self.n_components))
        for i in range(self.max_iter):
            prev_weighted_probs = weighted_probs
            
            weighted_probs = self._e_step(data)
            change = np.linalg.norm(weighted_probs - prev_weighted_probs) 
            if change < self.error:
                break
            
            self._m_step(data,weighted_probs)
        
        return weighted_probs.argmax(axis = 1)
    
    def _e_step(self,data):   # E步
        probs = np.zeros((self.samples,self.n_components))
        for i in range(self.n_components):
            probs[:,i] = self.gauss(data, self.mu[i,:], self.sigma[i,:,:])
        
        weighted_probs = np.zeros(probs.shape)
        for i in range(self.n_components):
            weighted_probs[:,i] = self.alpha[i]*probs[:,i]
            
        for i in range(self.samples):
            weighted_probs[i,:] /= np.sum(weighted_probs[i,:])
        
        return weighted_probs
            
    def _m_step(self,data,weighted_probs):  #M步
        for i in range(self.n_components):
            sum_probs_i = np.sum(weighted_probs[:,i])
            
            self.mu[i,:] = np.sum(np.multiply(data, np.mat(weighted_probs[:, i]).T), axis=0) / sum_probs_i
            self.sigma[i,:,:] = (data - self.mu[i,:]).T * np.multiply((data - self.mu[i,:]), np.mat(weighted_probs[:, i]).T) / sum_probs_i
            self.alpha[i] = sum_probs_i/data.shape[0]
    
    def predict_prob(self,data):  #預測概率矩陣
        return self._e_step(data)
    
    def predict(self,data):  #輸出類別
        return self._e_step(data).argmax(axis = 1)
        
            
dataset = load_iris()
data = dataset['data']
label = dataset['target']

gmm = GMM_EM(3)
pre_label = gmm.fit_predict(data)
print(accuracy_score(pre_label,label))
# EM算法是對初始值敏感的,修改初始化的值會發現模型性能變化很大

三、資源下載

微信搜索“老和山算法指南”獲取更多資源下載鏈接與技術交流羣
在這裏插入圖片描述
有問題可以私信博主,點贊關注的一般都會回覆,一起努力,謝謝支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章