机器学习Chapter3-(聚类分析)详解高斯混合模型与EM算法(Python实现)

高斯混合聚类

k-means是用原型向量来刻画聚类,高斯混合(Mixture-of-Gaussian)聚类采用概率模型来表达聚类原型。

不一样参数下,高斯分布如下:

对于多元高斯分布, n n 维样本空间 X X 中的随机向量 x x ,概率密度函数为

p(x|μ,Σ)=1(2π)n2|Σ|12e12(xμ)TΣ1(xμ) p ( x | μ , Σ ) = 1 ( 2 π ) n 2 | Σ | 1 2 e − 1 2 ( x − μ ) T Σ − 1 ( x − μ )
其中 μ μ n n 维均值向量, Σ Σ n×m n × m 的协方差矩阵。高斯概率密度函数由 μ μ Σ Σ 两个参数决定.

高斯混合分布为:

f(x)=i=1kαip(x|μ,Σ) f ( x ) = ∑ i = 1 k α i p ( x | μ , Σ )
该分布由 k k 个高斯分布混合而成, αi α i 为混合系数, αi0 α i ≥ 0 ki=1αi=1 ∑ i = 1 k α i = 1 .(权重和为1)

下面是一个高斯模型学习过程简图,先有个感性的认识:

那么该怎么求解各个权重系数,常用的方法是EM算法。



EM算法

初识EM算法

硬币问题

先看一个抛硬币问题,如果我们有AB两个不均匀硬币,选择任意一个硬币抛10次(这里我们知道选择是的哪一个硬币),共计选择5次。正面记为H,背面记为T。记录实验结果,求AB再抛正面向上的概率?

使用极大似然估计(Maximum likelihood)来算:

  • 统计出每次实验,正反面的次数
  • 多次实验结果相加
  • 相除得到结果, P(A)=0.8,P(B)=0.45 P ( A ) = 0.8 , P ( B ) = 0.45

这样一看极大似然估计还是很简单的嘛~

但是在实际过程中,很有可能我们只知道有两个硬币,不知道每次选择的哪一个硬币,问是否能求出每个硬币抛出正面的概率?

是不是第一感觉这个问题很无解,我都不知道选择是哪个硬币,我怎么求解啊?
事实上是可以求出的。这里使用的时EM算法。

使用期望最大值(Expectation maximization,EM)算法来算:

  • 假设 θ^(0)A=0.6,θ^(0)B=0.5 θ ^ A ( 0 ) = 0.6 , θ ^ B ( 0 ) = 0.5
  • 统计每次的实验结果,记录正反面
  • 通过贝叶斯公式,估计每次实验选择的A硬币或是B硬币的概率
  • 依据计算出的选择硬币概率得到该概率下的正反面结果
  • 相加,相除得到 θ^(1)A0.71,θ^(1)B0.58 θ ^ A ( 1 ) ≈ 0.71 , θ ^ B ( 1 ) ≈ 0.58
  • 重复上面的过程,例如迭代10次后,得到 θ^(10)A0.8,θ^(10)B0.52 θ ^ A ( 10 ) ≈ 0.8 , θ ^ B ( 10 ) ≈ 0.52
  • θ^(10)A,θ^(10)B θ ^ A ( 10 ) , θ ^ B ( 10 ) 就是使用EM算法计算出的概率值

这里比较难理解的是:如何利用贝叶斯公式计算每次实验选择的A硬币或是B硬币的概率?


那么先看下面这个例子:

假如现在有射击运动员甲和乙,甲乙射击靶心的概率为 θ^=0.9θ^=0.2 θ ^ 甲 = 0.9 , θ ^ 乙 = 0.2 ,如果现在有一组实验结果为

中 , 不 中 , 中 , 中 , 中 ,
问这次是谁射击的?
直观上的来看,非常大的概率是甲射击的,但是也有可能是乙走狗屎运了。那么该如何从概率的角度计算是谁射击的?
首先我们知道选择甲和乙的概率为 P()=P()=0.5 P ( 甲 ) = P ( 乙 ) = 0.5 (先验概率),本次实验记为E。通过贝叶斯公式
P(|E)=P(E|)P()P(E)=P(E|)P()P(E|)P()+P(E|)P()=(0.94×0.1)×0.5(0.94×0.1)×0.5+(0.24×0.8)×0.598% P ( 甲 | E ) = P ( E | 甲 ) P ( 甲 ) P ( E ) = P ( E | 甲 ) P ( 甲 ) P ( E | 甲 ) P ( 甲 ) + P ( E | 乙 ) P ( 乙 ) = ( 0.9 4 × 0.1 ) × 0.5 ( 0.9 4 × 0.1 ) × 0.5 + ( 0.2 4 × 0.8 ) × 0.5 ≈ 98 %

故本次实验有 98% 98 % 的可能是A射击的, 2% 2 % 的可能是B射击的。


有了上面的案例,我们再回到抛硬币的问题上,由贝叶斯公式:

P(A|E)=P(E|A)P(A)P(E) P ( A | E ) = P ( E | A ) P ( A ) P ( E )
A A 为选用硬币A, E E 为本次实验。而选择两个硬币的概率是相同的: P(A)=P(B)=12 P ( A ) = P ( B ) = 1 2
P(E|A)=0.65×0.450.0008,P(E|B)=0.55×0.550.001 P ( E | A ) = 0.6 5 × 0.4 5 ≈ 0.0008 , P ( E | B ) = 0.5 5 × 0.5 5 ≈ 0.001

P(A|E)=P(E|A)P(A)P(E)=P(E|A)P(A)P(E|A)P(A)+P(E|B)P(B)=(0.65×0.45)×12(0.65×0.45)×12+(0.55×0.55)×120.45 P ( A | E ) = P ( E | A ) P ( A ) P ( E ) = P ( E | A ) P ( A ) P ( E | A ) P ( A ) + P ( E | B ) P ( B ) = ( 0.6 5 × 0.4 5 ) × 1 2 ( 0.6 5 × 0.4 5 ) × 1 2 + ( 0.5 5 × 0.5 5 ) × 1 2 ≈ 0.45

0.45×10×122.2 0.45 × 10 × 1 2 ≈ 2.2 ,故第一次实验结果平均下来,有 2.2 2.2 个A硬币正面的可能。同理可得到多次实验的平均结果。

最后相加相除得到新的 A,B A , B 抛硬币正面估计值 θ^(1)A0.71,θ^(1)B0.58 θ ^ A ( 1 ) ≈ 0.71 , θ ^ B ( 1 ) ≈ 0.58 ,这是我们第一次迭代的值(这就是一次学习过程),照着这个流程迭代多次,得到最后的估测值。

上面我们计算出每次实验中是抛 A A 或抛 B B 的概率值就是隐变量.这个过程就是EM算法的简单案例。


形式化EM算法

未观测变量的学名是“隐变量”(latent variable).令 X X 表示已观测变量集, Z Z 表示隐变量集, Θ Θ 表示模型参数,欲对 Θ Θ 做极大似然估计,则应最大化对数似然

LL(Θ|X,Z)=lnP(X,Z|Θ) L L ( Θ | X , Z ) = l n P ( X , Z | Θ )
因为 Z Z 是隐变量,无法直接求解。 我们可通过对 Z Z 计算期望,来最大化已观测数据的对数“边际似然”
LL(Θ|X)=lnP(X|Θ)=lnZP(X,Z|Θ) L L ( Θ | X ) = l n P ( X | Θ ) = l n ∑ Z P ( X , Z | Θ )
EM算法是常用的估计参数隐变量的利器,基本思想是:
若参数 Θ Θ 已知,则可根据训练数据推断出最优隐变量 Z Z 的值( E E 步);再由推断出的最优隐变量 Z Z 对参数 Θ Θ 做极大似然估计( M M 步)。

以初始值 Θ0 Θ 0 为起点,对上式迭代执行以下步骤直到收敛:

  • 基于 Θt Θ t 推测隐变量 Z Z 的期望,记为 Zt Z t
  • 基于已观测变量 X X Zt Z t 对参数 Θ Θ 做极大似然估计,记为 Θt+1 Θ t + 1

这就是EM算法的原型。

进一步,若我们不是取 Z Z 的期望,而是基于 Θt Θ t 计算隐变量 Z Z 的概率分布 P(Z|X,Θt) P ( Z | X , Θ t )

则EM算法的两个步骤是:

  • E(Expectation)步:以当前参数 Θt Θ t 推断隐变量分布 P(Z|X,Θt) P ( Z | X , Θ t ) ,并计算对数似然 LL(Θ|X,Z) L L ( Θ | X , Z ) 关于 Z Z 的期望
    Q(Θ|Θt)=EZ|X,ΘtLL(Θ|X,Z) Q ( Θ | Θ t ) = E Z | X , Θ t L L ( Θ | X , Z )
  • M(Maximization)步:寻找参数最大化期望似然,即
    ,Θt+1=argmaxΘQ(Θ|Θt) , Θ t + 1 = arg ⁡ max Θ Q ( Θ | Θ t )

EM算法使用两个步骤交替计算:第一步是期望E步,利用当前估计的参数值来计算对数似然的期望值;第二步是最大化M步,寻找能使E步产生的似然期望最大化的参数值.得到的新值重新用于E步。重复上面的过程,直到收敛得到局部最优解。


如何使用EM算法求解高斯混合模型参数

定义的高斯混合分布如下:

PM(x)=i=1kαip(x|μi,Σi) P M ( x ) = ∑ i = 1 k α i p ( x | μ i , Σ i )
其中 p(x|μ,Σ) p ( x | μ , Σ ) 是高斯分布的概率密度函数。

假设样本训练集 D={x1,x2,...,xm} D = { x 1 , x 2 , . . . , x m } 由上述混合高斯分布产生的,令随机变量 zj{1,2,...,k} z j ∈ { 1 , 2 , . . . , k } 表示生成样本 xj x j 的高斯混合成分,其取值未知。显然, zj z j 的先验概率 P(zj=i) P ( z j = i ) 对应于 αi(i=1,2,...,k) α i ( i = 1 , 2 , . . . , k ) .根据贝叶斯定理, zj z j 的后验分布对应于

pM(zj=i|xj)=P(zj=i)pM(xj|zj=i)pM(xj)=αip(xj|μi,Σi)kl=1αlp(xj|μl,Σl) p M ( z j = i | x j ) = P ( z j = i ) · p M ( x j | z j = i ) p M ( x j ) = α i · p ( x j | μ i , Σ i ) ∑ l = 1 k α l p ( x j | μ l , Σ l )
换言之, pM(zj=i|xj) p M ( z j = i | x j ) 给出了样本 xj x j 由第 i i 个高斯混合成分生成的后验概率。为了方便叙述,将其简记为 γji(i=1,2,...,k) γ j i ( i = 1 , 2 , . . . , k ) .



Python实现EM算法

代码和数据集 :全部代码和数据我的github。

数据集

使用的是《机器学习》(西瓜书)西瓜数据集4.0。


Python实现手写EM算法

高斯混合聚类算法描述图

代码如下

 # coding:utf8
 # 高斯混合模型  使用EM算法解算
 # 数据集:《机器学习》--西瓜数据4.0   :文件watermelon4.txt
 import numpy as np
 import matplotlib.pyplot as plt

 # 预处理数据
 def loadData(filename):
     dataSet = []
     fr = open(filename)
     for line in fr.readlines():
         curLine = line.strip().split(' ')
         fltLine = list(map(float, curLine))
         dataSet.append(fltLine)
     return dataSet

 # 高斯分布的概率密度函数
 def prob(x, mu, sigma):
     n = np.shape(x)[1]
     expOn = float(-0.5 * (x - mu) * (sigma.I) * ((x - mu).T))
     divBy = pow(2 * np.pi, n / 2) * pow(np.linalg.det(sigma), 0.5)  # np.linalg.det 计算矩阵的行列式
     return pow(np.e, expOn) / divBy

 # EM算法
 def EM(dataMat, maxIter=50):
     m, n = np.shape(dataMat)
     # 1.初始化各高斯混合成分参数
     alpha = [1 / 3, 1 / 3, 1 / 3]   # 1.1初始化 alpha1=alpha2=alpha3=1/3
     mu = [dataMat[5, :], dataMat[21, :], dataMat[26, :]] # 1.2初始化 mu1=x6,mu2=x22,mu3=x27
     sigma = [np.mat([[0.1, 0], [0, 0.1]]) for x in range(3)]    # 1.3初始化协方差矩阵
     gamma = np.mat(np.zeros((m, 3)))
     for i in range(maxIter):
         for j in range(m):
             sumAlphaMulP = 0
             for k in range(3):
                 gamma[j, k] = alpha[k] * prob(dataMat[j, :], mu[k], sigma[k]) # 4.计算混合成分生成的后验概率,即gamma
                 sumAlphaMulP += gamma[j, k]
             for k in range(3):
                 gamma[j, k] /= sumAlphaMulP
         sumGamma = np.sum(gamma, axis=0)

         for k in range(3):
             mu[k] = np.mat(np.zeros((1, n)))
             sigma[k] = np.mat(np.zeros((n, n)))
             for j in range(m):
                 mu[k] += gamma[j, k] * dataMat[j, :]
             mu[k] /= sumGamma[0, k] #  7.计算新均值向量
             for j in range(m):
                 sigma[k] += gamma[j, k] * (dataMat[j, :] - mu[k]).T *(dataMat[j, :] - mu[k])
             sigma[k] /= sumGamma[0, k]  # 8. 计算新的协方差矩阵
             alpha[k] = sumGamma[0, k] / m   # 9. 计算新混合系数
             # print(mu)
     return gamma


 # init centroids with random samples
 def initCentroids(dataMat, k):
     numSamples, dim = dataMat.shape
     centroids = np.zeros((k, dim))
     for i in range(k):
         index = int(np.random.uniform(0, numSamples))
         centroids[i, :] = dataMat[index, :]
     return centroids


 def gaussianCluster(dataMat):
     m, n = np.shape(dataMat)
     centroids = initCentroids(dataMat, m)  ## step 1: init centroids
     clusterAssign = np.mat(np.zeros((m, 2)))
     gamma = EM(dataMat)
     for i in range(m):
         # amx返回矩阵最大值,argmax返回矩阵最大值所在下标
         clusterAssign[i, :] = np.argmax(gamma[i, :]), np.amax(gamma[i, :])  # 15.确定x的簇标记lambda
         ## step 4: update centroids
     for j in range(m):
         pointsInCluster = dataMat[np.nonzero(clusterAssign[:, 0].A == j)[0]]
         centroids[j, :] = np.mean(pointsInCluster, axis=0)  # 计算出均值向量
     return centroids, clusterAssign


 def showCluster(dataMat, k, centroids, clusterAssment):
     numSamples, dim = dataMat.shape
     if dim != 2:
         print("Sorry! I can not draw because the dimension of your data is not 2!")
         return 1

     mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', ', 'pr']
     if k > len(mark):
         print("Sorry! Your k is too large!")
         return 1

         # draw all samples
     for i in range(numSamples):
         markIndex = int(clusterAssment[i, 0])
         plt.plot(dataMat[i, 0], dataMat[i, 1], mark[markIndex])

     mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', ', 'pb']
     # draw the centroids
     for i in range(k):
         plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize=12)

     plt.show()

 if __name__=="__main__":
     dataMat = np.mat(loadData('watermelon4.txt'))
     centroids, clusterAssign = gaussianCluster(dataMat)
     print(clusterAssign)
     showCluster(dataMat, 3, centroids, clusterAssign)

输出图:

到30次迭代时,模型已经收敛了,故再运行到50轮时,均值向量基本没变。


使用scikit-learn包实现EM算法

函数原型

scikit提供了GaussianMixture类。构造方法为:

        def __init__(self, n_components=1, covariance_type='full', tol=1e-3,
                 reg_covar=1e-6, max_iter=100, n_init=1, init_params='kmeans',
                 weights_init=None, means_init=None, precisions_init=None,
                 random_state=None, warm_start=False,
                 verbose=0, verbose_interval=10):

参数介绍

参数 description
n_components int, defaults to 1.: 8
指定混合成分的数量.
max_iter int, default: 100
EM算法最大迭代次数
n_init int, default: 1
指定算法运行次数,算法最后会选择最佳的分类簇作为最终结果
covariance_type {‘full’, ‘tied’, ‘diag’, ‘spherical’} defaults to ‘full’.
指定协方差类型
- full: 每个分模型都有自己的协方差矩阵。
- tied:所有的分模型都共享一个协方差矩阵
- diag:每个分模型的协方差矩阵都是对角矩阵
- spherical:每个分模型的协方差矩阵都是标量值
reg_covar float, defaults to 0.
添加到协方差矩阵对角线上元素,确保所有协方差都是正数
init_params {{‘kmeans’, ‘random’}, defaults to ‘kmeans’.
指定初始化权重策略
tol float, default: 1e-3
指定判断算法收敛的阈值
weights_init array-like, shape (n_components, ), optional
;用于指定初始化权重
means_init array-like, shape (n_components, n_features), optional
指定初始化均值
precisions_init array-like, optional.
 用户提供的初始协方差矩阵逆矩阵
random_state integer or numpy.RandomState, optional
指定RandomState实例
verbose int, default 0
是否打印日志
warm_start bool, default to False.
上次训练的结果作为本次训练的初始条件

属性介绍

属性 description
weights_ array-like, shape (n_components,)
每个分模型的权重
means_ array-like, shape (n_components, n_features)
每个分模型的均值
covariances_ float
所有分模型的协方差

方法介绍

方法 description
fit(self, X, y=None) 训练模型
predict(self, X) 预测样本所属的簇
sample([n_samples, random_state]) 根据模型来随机生成一组样本

代码如下

# -*- coding: utf-8 -*-
#  使用EM算法解算GGM  EM算法采用scikit-learn包提供的api
#  数据集:《机器学习》--西瓜数据4.0   :文件watermelon4.txt

from sklearn import mixture
import matplotlib.pyplot as plt
import numpy as np


# 预处理数据
def loadData(filename):
    dataSet = []
    fr = open(filename)
    for line in fr.readlines():
        curLine = line.strip().split(' ')
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet


def test_GMM(dataMat, components=3,iter = 100,cov_type="full"):
    clst = mixture.GaussianMixture(n_components=n_components,max_iter=iter,covariance_type=cov_type)
    clst.fit(dataMat)
    predicted_labels =clst.predict(dataMat)
    return clst.means_,predicted_labels     # clst.means_返回均值



def showCluster(dataMat, k, centroids, clusterAssment):
    numSamples, dim = dataMat.shape
    if dim != 2:
        print("Sorry! I can not draw because the dimension of your data is not 2!")
        return 1

    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', ', 'pr']
    if k > len(mark):
        print("Sorry! Your k is too large!")
        return 1

        # draw all samples
    for i in range(numSamples):
        markIndex = int(clusterAssment[i])
        plt.plot(dataMat[i, 0], dataMat[i, 1], mark[markIndex])

    mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', ', 'pb']
    # draw the centroids
    for i in range(k):
        plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize=12)

    plt.show()


if __name__=="__main__":
    dataMat = np.mat(loadData('watermelon4.txt'))
    n_components = 3
    iter=100
    cov_types = ['spherical', 'tied', 'diag', 'full']
    centroids,labels = test_GMM(dataMat,n_components,iter,cov_types[3])
    showCluster(dataMat, n_components, centroids, labels)  # 这里labels维度改变了,注意修改showCluster方法

不同数目中心点数和不同迭代次数下:

不同协方差矩阵下



参考资料

MOOC-数据挖掘:理论与算法

你可能感兴趣的:(机器学习)