之前寫過一篇博客講述極大似然方法, 這一方法通常適用於知道觀測數據,求解模型參數的場合,即。
但是,在更多場合除了模型參數是未知的外,還有隱變量也是未知的,即。多個隱藏模型的混合,會使得普通的極大似然方法用起來不是那麼方便,比如求解高斯混合模型(GMM), 隱馬爾可夫模型(HMM),等。這種時候,就會發覺EM算法是機器學習領域中繞不過去的一道坎。
這裏,主要基於《統計學習方法》裏對於高斯混合模型的講解,實現EM算法,並給出代碼和註釋。
在實現模型之前,觀察過其他人寫的代碼,包括sklearn內部的代碼,相比較而言,這次的實現封裝的更好,使用會更方便,結果也不錯。
先描述一下實現的原理,再說代碼
一、EM算法解高斯混合模型
假設觀測數據由3高斯混合模型生成,即
其中, 我們用EM算法估計高斯混合模型的參數
1.0 EM算法:初始化模型參數
1.1 EM算法的E步:確定Q函數
混合高斯模型的Q函數爲:
這裏需要計算,記爲
是當前模型參數下第個觀測數據來自第個分模型的概率,稱爲分模型對觀測數據的影響度
1.2EM算法的M步:更新模型參數
M步極大化Q函數,更新模型參數如下:
重複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算法是對初始值敏感的,修改初始化的值會發現模型性能變化很大
三、資源下載
微信搜索“老和山算法指南”獲取更多資源下載鏈接與技術交流羣
有問題可以私信博主,點贊關注的一般都會回覆,一起努力,謝謝支持。