根据模型的数学原理进行简单的代码自我复现以及使用测试,仅作自我学习用。模型原理此处不作过多赘述,仅罗列自己将要使用到的部分公式。
如文中或代码有错误或是不足之处,还望能不吝指正。
本文的思路借鉴了GMM算法的实现_MagicGeek的博客-CSDN博客_gmm算法实现
有时某些数据不是简单地服从单个正态分布,而是服从由多个正态分布线性组合的复合分布。此时我们便使用GMM模型分析其内部的正态分布模型,将数据分为多个服从正态分布的类。
其中,为第k个正态分布的权重系数。
我们使用EM算法估算各个正态分布的期望方差矩阵。E步通过和估算后验概率,M步通过的最大化对数似然函数来倒推出新的与。
对数似然函数为:
对其和分别求偏导,得(M步)
其中,为类别k(第k个正态分布)的有效样本个数;代表第k个正态分布的后验概率,通过贝叶斯公式,计算得 (E步)。
而根据拉格朗日乘子法,可得。
import random
import math
import numpy as np
from scipy.stats import norm
from sklearn.cluster import KMeans
class GMM:
def __init__(self,X,n_components):
"""
初始化,声明要使用的变量
X:带训练矩阵
n_components:训练矩阵一共有多少个高斯分布混合
n:特征大小
class_prior:每个高斯分布的权重系数
mu:期望矩阵
covariance:方差矩阵
"""
self.n_components = n_components
self.X = X
self.n = X.shape[1]
self.class_prior = np.zeros(n_components)
self.mu = np.zeros((n_components,self.n))
self.covariance = np.zeros((n_components,self.n,self.n))
def initializer(self):
"""
初始化各个相关变量,使用KMeans聚类给出每个分布期望的初始值并计算其方差;
根据聚类结果中每个簇的多少来设定最初的分布权重系数
"""
estimator = KMeans(n_clusters=self.n_components)
estimator.fit(self.X)
centroids = estimator.cluster_centers_
self.mu = centroids
#tile:平铺 沿着X轴 Y轴方向复制n份
dist = np.tile(
np.sum(self.X*self.X,axis=1).reshape(-1,1),
(1,self.n_components)
)+np.tile(
np.sum(self.mu*self.mu,axis=1).reshape(1,-1),
(self.X.shape[0],1)
)-2*np.dot(self.X,self.mu.T)
labels = np.argmin(dist,axis=1)
for k in range(self.n_components):
cluster = self.X[labels == k,:]
self.class_prior[k] = cluster.shape[0]/self.X.shape[0]
self.covariance[k,:,:] = np.cov(cluster.T)
def LogLikelihood(self,X):
"""
根据现在的mu,covariance计算对数似然函数
"""
m = X.shape[0]
pdfs = np.zeros((m,self.n_components))
for k in range(self.n_components):
pdfs[:,k] = self.class_prior[k]*self.gaussianPDF(self.X,self.mu[k],self.covariance[k])
return np.sum(np.log(np.sum(pdfs,axis=1)))
def gaussianPDF(self,X,mu,covariance):
"""
计算概率密度
"""
covdet = np.linalg.det(covariance+np.eye(self.n)*1e-5)
covinv = np.linalg.inv(covariance+np.eye(self.n)*1e-5)
prob = 1/(np.power(np.sqrt(2*np.pi),self.n/2)*np.sqrt(np.abs(covdet)))*np.exp(-1/2.0*np.diag(np.dot(np.dot((X-mu),covinv),(X-mu).T)))
return prob
def train(self,max_Iter,threshold = 1e-7):
"""
训练,当迭代次数超过max_Iter或对数似然函数的大小变化小于threshold时结束
"""
self.initializer()
m = self.X.shape[0]
self.Pi = np.zeros((m,self.n_components)) #先验概率 p_k(x)
self.gamma = np.zeros((m,self.n_components)) #后验概率 gamma_k
preLogLikelihood = 0
IterNum = 0
Likelihoods = []
while IterNum<max_Iter:
self.E_step()
LogLikelihood = self.M_step()
Likelihoods.append(LogLikelihood)
print(IterNum,LogLikelihood)
if abs(LogLikelihood - preLogLikelihood)<threshold:
break
else:
preLogLikelihood = LogLikelihood
IterNum += 1
return Likelihoods
def E_step(self):
"""
E步,根据mu和covariance计算后验概率gamma(x)
"""
for k in range(self.n_components):
self.Pi[:,k] = self.class_prior[k]*self.gaussianPDF(self.X,self.mu[k],self.covariance[k])
self.gamma = self.Pi / np.sum(self.Pi,axis = 1).reshape(-1,1)
def M_step(self):
"""
M步,根据gamma(x)倒推更新mu和covariance
"""
m = self.X.shape[0]
class_prior = np.sum(self.gamma,axis=0)/np.sum(self.gamma) #Pi_k = N/N_k
self.class_prior = class_prior
mu = np.zeros((self.n_components,self.n))
covariance = np.zeros((self.n_components,self.n,self.n))
for k in range(self.n_components):
mu[k] = np.average(self.X,axis=0,weights=self.gamma[:,k])
cov_k = np.zeros((self.n,self.n))
for i in range(m):
cov_k += self.gamma[i,k]*np.dot((self.X[i]-mu[k]),(self.X[i]-mu[k]).T)
covariance[k,:,:] = cov_k/np.sum(self.gamma[:,k])
self.covariance = covariance
self.mu = mu
LogLikeHood = self.LogLikelihood(self.X)
return LogLikeHood
def predict(self,X):
"""
预测新的变量属于哪一个高斯分布
"""
m = X.shape[0]
pdfs = np.zeros((m,self.n_components))
for i in range(self.n_components):
pdfs[:,i] = self.gaussianPDF(X,self.mu[i],self.covariance)
labels = np.argmax(pdfs,axis=1)
return labels.reshape(-1,1)
使用自制数据集测试其功能:
X1 = [random.normalvariate(5,3) for _ in range(1000)]
X2 = [random.normalvariate(12,4) for _ in range(1000)]
mixtured = np.array(X1+X2).reshape(2000,1)
gmm = GMM(mixtured,n_components=2)
LLH = gmm.train(max_Iter=1000)
gmm.mu
"""
array([[ 5.06993054],
[12.26872974]])
"""
gmm.convariance
"""
array([[[ 8.51434473]],
[[15.20628479]]])
"""
使用random生成了符合和,GMM训练的结果为与。期望与方差均有较小的偏差,下面作图观察
X2_test = [random.normalvariate(gmm.mu[1,0],np.sqrt(gmm.covariance[1,0,0])) for _ in range(1000)]
X1_test = [random.normalvariate(gmm.mu[0,0],np.sqrt(gmm.covariance[0,0,0])) for _ in range(1000)]
from matplotlib import pyplot as plt
import matplotlib as mpl
mpl.rcParams["font.family"]="SimHei"
mpl.rcParams["axes.unicode_minus"] = False
plt.subplot(2,2,1)
plt.title("原混合数据的分布拟合")
_, bins1, _ = plt.hist(X1,density=True,bins=100,alpha = 0.5)
_, bins2, _ = plt.hist(X2,density=True,bins=100,alpha = 0.5)
plt.plot(bins1,norm.pdf(bins1,5,3),"r--")
plt.plot(bins2,norm.pdf(bins2,12,4),"g--")
plt.subplot(2,2,2)
plt.title("使用gmm模型结果采样的分布拟合")
_, bins1_test, _ = plt.hist(X1_test,density=True,bins=100,alpha = 0.5)
_, bins2_test, _ = plt.hist(X2_test,density=True,bins=100,alpha = 0.5)
plt.plot(bins1_test,norm.pdf(bins1_test,gmm.mu[0,0],np.sqrt(gmm.covariance[0,0,0])),"r--")
plt.plot(bins2_test,norm.pdf(bins2_test,gmm.mu[1,0],np.sqrt(gmm.covariance[1,0,0])),"g--")
plt.show()
从结果的图像上看,gmm结果采样的分布拟合和真实的分布差距并不明显。