机器学习模型自我代码复现:GMM

根据模型的数学原理进行简单的代码自我复现以及使用测试,仅作自我学习用。模型原理此处不作过多赘述,仅罗列自己将要使用到的部分公式。

如文中或代码有错误或是不足之处,还望能不吝指正。

本文的思路借鉴了GMM算法的实现_MagicGeek的博客-CSDN博客_gmm算法实现

有时某些数据不是简单地服从单个正态分布,而是服从由多个正态分布线性组合的复合分布。此时我们便使用GMM模型分析其内部的正态分布模型,将数据分为多个服从正态分布的类。

p(x)=\Sigma_{k=1}^K\pi_kN(x|\mu_k,\Sigma_k)

其中,\pi_k为第k个正态分布的权重系数。

我们使用EM算法估算各个正态分布的期望\mu_k方差矩阵\Sigma_k。E步通过\mu_k\Sigma_k估算后验概率\gamma_k(x),M步通过\gamma_k(x)的最大化对数似然函数来倒推出新的\mu_k\Sigma_k

对数似然函数为:

lnp(D|\pi,\mu,\Sigma)=\Sigma_{n=1}^Nln\{\Sigma_{k=1}^K \pi_kN(x_n|\mu_k,\Sigma_k)\}

对其\mu_k\Sigma_k分别求偏导,得\mu_k=\frac{1}{N_k}\Sigma_{i=1}^N\gamma_k(x_i)x_i\Sigma_k=\frac{1}{N_k}\Sigma_{i=1}^N\gamma_k(x_i)(x_i-\mu_k)(x_i-\mu_k)^T(M步)

其中,N_k为类别k(第k个正态分布)的有效样本个数\Sigma_{i=1}^N\gamma_k(x_i)\gamma_k代表第k个正态分布的后验概率,通过贝叶斯公式,计算得\gamma_k(x)=p(k|x)=\frac{p(k)p(x|k)}{p(x)}=\frac{\pi_kN(x|\mu_k,\Sigma_k)}{\Sigma_{j=1}^K\pi_jN(x|\mu_j,\Sigma_j)} (E步)。

而根据拉格朗日乘子法,可得\pi_k = \frac{1}{N}\gamma_k(x_i)

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生成了符合N(5,3^2)N(12,4^2),GMM训练的结果为N(5.06993054,8.51434473)N(12.26872974,15.20628479)。期望与方差均有较小的偏差,下面作图观察

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结果采样的分布拟合和真实的分布差距并不明显。

猜你喜欢

转载自blog.csdn.net/thorn_r/article/details/124626293