機(jī)器學(xué)習(xí)-9 EM 與 GMM【附代碼】

返回主頁


EM (Expectation Maximization)算法是一種迭代算法,由 Dempster 等人于1977年提出。EM 算法最大的價(jià)值在于解決含隱變量的概率模型參數(shù)的極大似然估計(jì)問題,通過 E步更新后驗(yàn)概率,通過 M步求參數(shù)極大似然估計(jì),EM算法常用于無監(jiān)督學(xué)習(xí),如聚類問題。


EM 算法的難點(diǎn)在于要理解目標(biāo)函數(shù)的解并非問題的最終解,這一點(diǎn)與許多經(jīng)典機(jī)器學(xué)習(xí)算法不同,問題的最終解是隱變量 z 的后驗(yàn)分布 Q 分布,而目標(biāo)函數(shù)的解是參數(shù) theta 和 P 分布,theta 往往用正態(tài)分布的參數(shù)表示。Q 分布無法通過傳統(tǒng)的極大似然估計(jì)求解,所以只能先通過目標(biāo)函數(shù)求 theta 和 P,再間接的求出 Q,更新 Q 之后再刷新目標(biāo)函數(shù)更新 theta 和 P,循環(huán)往復(fù)直至收斂。所以,theta 和 P 就像一組橋梁,連接了目標(biāo)函數(shù)與問題假設(shè)。


0、先導(dǎo)知識:Jeson 不等式

Jeson 不等式是理解EM算法目標(biāo)函數(shù)變形的關(guān)鍵一步

1、數(shù)據(jù)集與特征空間

2、目標(biāo)函數(shù)(極大似然估計(jì))

借助Jeson不等式推出目標(biāo)函數(shù)(凹函數(shù))的下界

此刻,如果能使 >= 變?yōu)?= ,那么目標(biāo)函數(shù)就明確了,因此繼續(xù)往下推:

此時(shí),等號得以成立,更新目標(biāo)函數(shù):

推出當(dāng)?shù)忍柍闪r(shí)目標(biāo)函數(shù)的實(shí)際表達(dá)式

此時(shí),可見目標(biāo)函數(shù)是關(guān)于 mu, sigma, P 的函數(shù),P 是每個(gè)隱變量所占的權(quán)重,因此 P 的和 = 1。下面自然就是求極大似然估計(jì):

3、假設(shè)空間
EM 算法的難點(diǎn)在于要理解目標(biāo)函數(shù)的解并非問題的最終解,這一點(diǎn)與許多經(jīng)典機(jī)器學(xué)習(xí)算法不同,問題的最終解是隱變量 z 的后驗(yàn)分布 Q 分布,而目標(biāo)函數(shù)的解是參數(shù) theta 和 P 分布,theta 往往用正態(tài)分布的參數(shù)表示。Q 分布無法通過傳統(tǒng)的極大似然估計(jì)求解,所以只能先通過目標(biāo)函數(shù)求 theta 和 P,再間接的求出 Q,更新 Q 之后再刷新目標(biāo)函數(shù)更新 theta 和 P,循環(huán)往復(fù)直至收斂。所以,theta 和 P 就像一組橋梁,連接了目標(biāo)函數(shù)與問題假設(shè)。

問題的假設(shè)可以理解為: Q 分布是關(guān)于 theta 和 P 分布的函數(shù),Q* 是問題的最終解,也是假設(shè)函數(shù)的最優(yōu)值;theta* 和 P* 是目標(biāo)函數(shù)的最優(yōu)解,也是假設(shè)函數(shù)的最優(yōu)解。

EM 算法的假設(shè)函數(shù)

4、優(yōu)化步驟
4.1、參數(shù)初始化(EM 對初始值敏感)
4.2、更新 Q 分布(E步)
4.3、更新 mu, sigma, P(M步)
4.4、循環(huán)4.2、4.3,直到誤差足夠小,算法停止
4.5、得到最終的 Q 分布,取其大者為隱變量最終的類別標(biāo)記

5、算法收斂性證明


手寫 GMM 并與 Sklearn 對比

# -*- coding: utf-8 -*-
import os
import copy
#import random as rd
import numpy as np
import pandas as pd
#from sklearn.utils import shuffle
#from sklearn.model_selection import train_test_split
#from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.mixture import GaussianMixture
from scipy.stats import norm
from sklearn.metrics import silhouette_score


class GMM(object):
    def __init__(self, z, tol, max_iter, init_params, verbose):
        self.z = z
        self.tol = tol
        self.max_iter = max_iter
        self.init_params = init_params
        self.verbose = verbose
        
        
    def fit(self, x):
        '''模型訓(xùn)練'''
        # 參數(shù)初始化
        N, n = x.shape
        Q = np.zeros([N, self.z])
        pdf = np.ones([N, self.z])
        P = np.random.uniform(low=0, high=1, size=(self.z, 1))
        
        if self.init_params == "kmeans":
            km = KMeans(n_clusters=self.z, init="k-means++")
            km.fit(x)
            mu = km.cluster_centers_
        elif self.init_params == "random":
            mu = np.random.randn(self.z, n) * 0.01
        else:
            raise ValueError("init_params must be 'kmeans' or 'random'")
            
        std = np.std(x, axis=0)
        std = np.tile(std, self.z).reshape(self.z, n)
        
        mu_update = np.zeros_like(mu)
        std_update = np.ones_like(std)
        
        err_mu_res = []
        err_std_res = []
        
        for epoch in range(self.max_iter):
            # E
            for j in range(self.z):
                # 隱變量外循環(huán)
                for col in range(n):
                    # 列內(nèi)循環(huán),計(jì)算聯(lián)合分布
                    pdf[:, j] *= norm.pdf(x[:, col], mu[j, col], std[j, col])
                Q[:, j] = pdf[:, j] * P[j]
            # 計(jì)算Q分布
            Q = Q / np.sum(Q, axis=1).reshape(-1, 1)
            
            # M
            for j in range(self.z):
                # 更新每個(gè)隱變量的參數(shù)值
                mu_update[j] = x.T.dot(Q[:, j]) / np.sum(Q[:, j])
                std_update[j] = np.sqrt(Q[:, j].dot((x - mu[j])**2) / np.sum(Q[:, j]))
                P[j] = np.mean(Q[:, j])
            
            # 停止條件
            err_mu = np.sum(np.abs(mu - mu_update))
            err_std = np.sum(np.abs(std - std_update))
            
            if self.verbose:
                print(f"epoch {epoch}  err_mu {err_mu}  err_std {err_std}")
            
            err_mu_res.append(err_mu)
            err_std_res.append(err_std)
            
            if err_mu <= self.tol and err_std <= self.tol:
                break
            else:
                mu = copy.deepcopy(mu_update)
                std = copy.deepcopy(std_update)
        return Q, P, err_mu_res, err_std_res
    
    
    def predict(self, Q):
        '''判斷類別'''
        labels = np.argmax(Q, axis=1)
        return labels
        
                

if __name__ == "__main__":
    file_path = os.getcwd()
    dataSet = pd.read_csv(file_path + "/swiss.csv")
    x = dataSet[["Fertility", "Agriculture", "Catholic", "InfantMortality"]].values
    
    # 手寫模型
    model = GMM(z=3, tol=0.001, max_iter=1000, init_params="kmeans", verbose=True)
    Q, P, err_mu_res, err_std_res = model.fit(x)
    labels = model.predict(Q)
    
    score = silhouette_score(x, labels, metric="euclidean")
    print(f"手寫模型輪廓系數(shù) {score}")
    
    fig, ax = plt.subplots(figsize=(8, 4))
    ax.plot(err_mu_res)
    plt.xlabel("epoch")
    plt.ylabel("error")
    plt.title("mu")
    plt.show()
    
    fig, ax = plt.subplots(figsize=(8, 4))
    ax.plot(err_std_res)
    plt.xlabel("epoch")
    plt.ylabel("error")
    plt.title("std")
    plt.show()
    
    # sklearn
    gmm = GaussianMixture(n_components=4, tol=0.001, max_iter=1000, init_params="kmeans")
    gmm.fit(x)
    gmm.predict_proba(x)
    labels = gmm.predict(x)
    print(f"sklearn 模型輪廓系數(shù) {score}")

返回主頁

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容