聚类——高斯混合模型及EM算法以及Python实现

高斯混合模型

核心思想

假设数据集是按照一定统计过程产生的,那么聚类的过程就是通过样本学习相应统计分布模型的参数

混合模型简介

混合模型将数据看作是从不同的概率分布得到的概率的观测值的集合。通常采用高斯分布,称之为高斯混合模型。一个数据的产生可以分成两个过程:
1. 选择分模型k, 概率为归一化后的 α k (分模型的个数就是簇的个数)
2. 由分模型根据概率分布 ϕ ( y | θ k ) 生成数据。
故最终一个数据产生的概率为:

P ( y | θ ) = k = 1 K α k ϕ ( y | θ k )

这是一个含有隐变量 α k 的概率模型,故需要采用 EM算法学习模型参数。

EM算法简介

EM算法就是含有隐变量模型参数的极大似然估计法。只不过直接求解极大似然函数的极值对应的参数比较困难,故采用迭代逐步近似极大化似然函数。具体实现是先获得似然函数的一个下限,那么逐步极大化下限,即可近似获得似然函数的极大值及对应参数。
似然函数

L ( θ ) = l o g P ( Y | θ ) = l o g Z P ( Y , Z | θ ) = l o g ( Z P ( Y | Z , θ ) P ( Z | θ ) )
其中,Z是隐变量。
根据Jensen不等式可获得, L ( θ ) 的下限:
L ( θ ) B ( θ , θ i ) B ( θ , θ i ) = L ( θ i ) + Z P ( Z | Y , θ i ) l o g P ( Y | Z , θ ) P ( Z | θ ) P ( Z | Y , θ i ) P ( Y | θ i )
可见每次迭代,最大化 B ( θ , θ i ) 即可逐渐逼近 L ( θ ) 的极大值。进一步化简,极大化 B ( θ , θ i ) 等同于极大化
Q ( θ , θ i ) = Z P ( Z | Y , θ i ) l o g P ( Y , Z | θ )
Q函数是整个EM算法的核心,其意义就是 l o g P ( Y , Z | θ ) 关于 P ( Z | Y , θ i ) 的期望。故EM算法分为两步:
1.E步。求Q函数。
2.M步。最大化Q函数,得到相应的参数 θ
将EM算法应用于高斯混合模型的具体推导请参考李航《统计学习方法》

算法流程

  • Input: 簇的个数K, 阈值epsilon, 最大迭代次数
  • Output: 混合模型参数(包括所有分模型构成的均值向量,alpha向量,方差向量)
  • Step1: 采用Kmeans方法初始化混合模型参数
  • Step2:EM算法学习参数
  • Step3: 根据学习得到的参数,计算每个样本对各个分模型的相应度(即样本来子分模型的概率),样本归属于相应度最大的分模型对应的簇

代码

"""
高斯混合模型+EM算法
以alpha(k)的概率选择第k个高斯模型,再以该高斯模型概率分布产生数据。其中alpha(k)就是隐变量
"""
import numpy as np
import math
import copy
from collections import defaultdict
from sklearn.cluster import KMeans


class GEM:
    def __init__(self, maxstep=1000, epsilon=1e-3, K=4):
        self.maxstep = maxstep
        self.epsilon = epsilon
        self.K = K  # 混合模型中的分模型的个数

        self.alpha = None  # 每个分模型前系数
        self.mu = None  # 每个分模型的均值向量
        self.sigma = None  # 每个分模型的协方差
        self.gamma_all_final = None  # 存储最终的每个样本对分模型的响应度,用于最终的聚类

        self.D = None  # 输入数据的维度
        self.N = None  # 输入数据总量

    def inin_param(self, data):
        # 初始化参数
        self.D = data.shape[1]
        self.N = data.shape[0]
        self.init_param_helper(data)
        return

    def init_param_helper(self, data):
        # KMeans初始化模型参数
        KMEANS = KMeans(n_clusters=self.K).fit(data)
        clusters = defaultdict(list)
        for ind, label in enumerate(KMEANS.labels_):
            clusters[label].append(ind)
        mu = []
        alpha = []
        sigma = []
        for inds in clusters.values():
            partial_data = data[inds]
            mu.append(partial_data.mean(axis=0))  # 分模型的均值向量
            alpha.append(len(inds) / self.N)  # 权重
            sigma.append(np.cov(partial_data.T))  # 协方差,D个维度间的协方差
        self.mu = np.array(mu)
        self.alpha = np.array(alpha)
        self.sigma = np.array(sigma)
        return

    def _phi(self, y, mu, sigma):
        # 获取分模型的概率
        s1 = 1.0 / math.sqrt(np.linalg.det(sigma))
        s2 = np.linalg.inv(sigma)  # d*d
        delta = np.array([y - mu])  # 1*d
        return s1 * math.exp(-1.0 / 2 * delta @ s2 @ delta.T)

    def fit(self, data):
        # 迭代训练
        self.inin_param(data)
        step = 0
        gamma_all_arr = None
        while step < self.maxstep:
            step += 1
            old_alpha = copy.copy(self.alpha)
            # E步
            gamma_all = []
            for j in range(self.N):
                gamma_j = []    # 依次求每个样本对K个分模型的响应度

                for k in range(self.K):
                    gamma_j.append(self.alpha[k] * self._phi(data[j], self.mu[k], self.sigma[k]))

                s = sum(gamma_j)
                gamma_j = [item/s for item in gamma_j]
                gamma_all.append(gamma_j)

            gamma_all_arr = np.array(gamma_all)
            # M步
            for k in range(self.K):
                gamma_k = gamma_all_arr[:, k]
                SUM = np.sum(gamma_k)
                # 更新权重
                self.alpha[k] = SUM / self.N  # 更新权重
                # 更新均值向量
                new_mu = sum([gamma * y for gamma, y in zip(gamma_k, data)]) / SUM  # 1*d
                self.mu[k] = new_mu
                # 更新协方差阵
                delta_ = data - new_mu   # n*d
                self.sigma[k] = sum([gamma * (np.outer(np.transpose([delta]), delta)) for gamma, delta in zip(gamma_k, delta_)]) / SUM  # d*d
            alpha_delta = self.alpha - old_alpha
            if np.linalg.norm(alpha_delta, 1) < self.epsilon:
                break
        self.gamma_all_final = gamma_all_arr
        return

    def predict(self):
        cluster = defaultdict(list)
        for j in range(self.N):
            max_ind = np.argmax(self.gamma_all_final[j])
            cluster[max_ind].append(j)
        return cluster

if __name__ == '__main__':

    def generate_data(N=500):
        X = np.zeros((N, 2))  # N*2, 初始化X
        mu = np.array([[5, 35], [20, 40], [20, 35], [45, 15]])
        sigma = np.array([[30, 0], [0, 25]])
        for i in range(N): # alpha_list=[0.3, 0.2, 0.3, 0.2]
            prob = np.random.random(1)
            if prob < 0.1:  # 生成0-1之间随机数
                X[i, :] = np.random.multivariate_normal(mu[0], sigma, 1)  # 用第一个高斯模型生成2维数据
            elif 0.1 <= prob < 0.3:
                X[i, :] = np.random.multivariate_normal(mu[1], sigma, 1)  # 用第二个高斯模型生成2维数据
            elif 0.3 <= prob < 0.6:
                X[i, :] = np.random.multivariate_normal(mu[2], sigma, 1)  # 用第三个高斯模型生成2维数据
            else:
                X[i, :] = np.random.multivariate_normal(mu[3], sigma, 1)  # 用第四个高斯模型生成2维数据
        return X


    data = generate_data()
    gem = GEM()
    gem.fit(data)
    # print(gem.alpha, '\n', gem.sigma, '\n', gem.mu)
    cluster = gem.predict()

    import matplotlib.pyplot as plt
    from itertools import cycle
    colors = cycle('grbk')
    for color, inds in zip(colors, cluster.values()):
        partial_data = data[inds]
        plt.scatter(partial_data[:,0], partial_data[:, 1], edgecolors=color)
    plt.show()

猜你喜欢

转载自blog.csdn.net/slx_share/article/details/80208792