Spark 2.1.0 入门:高斯混合模型(GMM)聚类算法

大数据技术原理与应用

【版权声明】博客内容由厦门大学数据库实验室拥有版权,未经允许,请勿转载!

[返回Spark教程首页]

高斯混合模型(Gaussian Mixture Model, GMM) 是一种概率式的聚类方法,属于生成式模型,它假设所有的数据样本都是由某一个给定参数的 多元高斯分布 所生成的。具体地,给定类个数K,对于给定样本空间中的样本 \(\mathbf{x}\) ,一个高斯混合模型的概率密度函数可以由K个多元高斯分布组合成的混合分布表示:

\(p(\mathbf{x}) = \sum_{i=1}^{K}w_i\cdot p(\mathbf{x}|\mathbf{\mu}_i,\Sigma_i)\)

其中, \(p(\mathbf{x}|\mathbf{\mu},\Sigma)\) 是以 \(\mathbf{\mu}\) 为均值向量, \(\mathbf{\Sigma}\) 为协方差矩阵的多元高斯分布的概率密度函数,可以看出,高斯混合模型由K个不同的多元高斯分布共同组成,每一个分布被称为高斯混合模型中的一个 成分(Component), 而 \(w_i\) 为第i个多元高斯分布在混合模型中的 权重 ,且有 \(\Sigma_{i=1}^{K}w_i = 1\)

假设已有一个存在的高斯混合模型,那么,样本空间中的样本的生成过程即是:以 \(w_1, w_2, ... w_K\) 作为概率(实际上,权重可以直观理解成相应成分产生的样本占总样本的比例),选择出一个混合成分,根据该混合成分的概率密度函数,采样产生出相应的样本。

那么,利用GMM进行聚类的过程是利用GMM生成数据样本的“逆过程”:给定聚类簇数K,通过给定的数据集,以某一种 参数估计 的方法,推导出每一个混合成分的参数(即均值向量 \(\mathbf{\mu}\) 、协方差矩阵 \(\mathbf{\Sigma}\) 和权重 \(w\) ),每一个多元高斯分布成分即对应于聚类后的一个簇。高斯混合模型在训练时使用了极大似然估计法,最大化以下对数似然函数:
\(L = \log{\prod_{j=1}^{m}{p(\mathbf{x})}}\)
\(L =\sum_{j=1}^{m}\log{(\sum_{i=1}^{K}{w_i\cdot p(\mathbf{x}|\mathbf{\mu}_i,\Sigma_i)})}\)

显然,该优化式无法直接通过解析方式求得解,故可采用 期望-最大化(Expectation-Maximization, EM) 方法求解,具体过程如下(为了简洁,这里省去了具体的数学表达式,详细可见wikipedia):

1.根据给定的K值,初始化K个多元高斯分布以及其权重;
2.根据贝叶斯定理,估计每个样本由每个成分生成的后验概率;(EM方法中的E步)
3.根据均值,协方差的定义以及2步求出的后验概率,更新均值向量、协方差矩阵和权重;(EM方法的M步)
重复2~3步,直到似然函数增加值已小于收敛阈值,或达到最大迭代次数

当参数估计过程完成后,对于每一个样本点,根据贝叶斯定理计算出其属于每一个簇的后验概率,并将样本划分到后验概率最大的簇上去。相对于KMeans等直接给出样本点的簇划分的聚类方法,GMM这种给出样本点属于每个簇的概率的聚类方法,被称为 软聚类(Soft Clustering / Soft Assignment)

模型的训练与分析

Spark的ML库提供的高斯混合模型都在org.apache.spark.ml.clustering包下,和其他的聚类方法类似,其具体实现分为两个类:用于抽象GMM的超参数并进行训练的GaussianMixture类(Estimator)和训练后的模型GaussianMixtureModel类(Transformer),在使用前,引入需要的包:

import org.apache.spark.ml.clustering.{GaussianMixture,GaussianMixtureModel}
import org.apache.spark.ml.linalg.Vectors

开启RDD的隐式转换:

import spark.implicits._

下文中,我们默认名为sparkSparkSession已经创建。
与其他教程相同,本文亦使用模式识别领域广泛使用的UCI数据集中的鸢尾花数据Iris进行实验,它可以在iris获取,Iris数据的样本容量为150,有四个实数值的特征,分别代表花朵四个部位的尺寸,以及该样本对应鸢尾花的亚种类型(共有3种亚种类型)
,如下所示:

5.1,3.5,1.4,0.2,setosa
...
5.4,3.0,4.5,1.5,versicolor
...
7.1,3.0,5.9,2.1,virginica
...

为了便于生成相应的DataFrame,这里定义一个名为model_instancecase class作为DataFrame每一行(一个数据样本)的数据类型。

scala> case class model_instance (features: Vector)
defined class model_instance

在定义数据类型完成后,即可将数据读入RDD[model_instance]的结构中,并通过RDD的隐式转换.toDF()方法完成RDDDataFrame的转换:

scala> val rawData = sc.textFile("file:///usr/local/spark/iris.txt")
rawData: org.apache.spark.rdd.RDD[String] = iris.csv MapPartitionsRDD[48] at textFile at <console>:33

scala> val df = rawData.map(line =>
     | { model_instance( Vectors.dense(line.split(",").filter(p => p.matches("\\d*(\\.?)\\d*"))
     | .map(_.toDouble)) )}).toDF()
df: org.apache.spark.sql.DataFrame = [features: vector]

与MLlib版的教程类似,我们使用了filter算子,过滤掉类标签,正则表达式\\d*(\\.?)\\d*可以用于匹配实数类型的数字,\\d*使用了*限定符,表示匹配0次或多次的数字字符,\\.?使用了?限定符,表示匹配0次或1次的小数点。

可以通过创建一个GaussianMixture类,设置相应的超参数,并调用fit(..)方法来训练一个GMM模型GaussianMixtureModel,在该方法调用前需要设置一系列超参数,如下表所示:
| 参数 | 含义 |
| ------- | :----------------: |
| K | 聚类数目,默认为2 |
| maxIter | 最大迭代次数,默认为100 |
| seed | 随机数种子,默认为随机Long值 |
| Tol | 对数似然函数收敛阈值,默认为0.01 |
其中,每一个超参数均可通过名为setXXX(...)(如maxIterations即为setMaxIterations())的方法进行设置。

这里,我们建立一个简单的GaussianMixture对象,设定其聚类数目为3,其他参数取默认值。

scala> val gm = new GaussianMixture().setK(3)
 |              .setPredictionCol("Prediction")
 |              .setProbabilityCol("Probability")
gm: org.apache.spark.ml.clustering.GaussianMixture = GaussianMixture_53916e2247ae
scala> val gmm = gm.fit(df)
gmm: org.apache.spark.ml.clustering.GaussianMixtureModel = GaussianMixture_53916e2247ae

KMeans等硬聚类方法不同的是,除了可以得到对样本的聚簇归属预测外,还可以得到样本属于各个聚簇的概率(这里我们存在"Probability"列中)。

调用transform()方法处理数据集之后,打印数据集,可以看到每一个样本的预测簇以及其概率分布向量(这里为了明晰起见,省略了大部分行,只选择三行):

scala> val result = gmm.transform(df)
scala> result.show(150, false)                                                                        
+-----------------+----------+------------------------------------------------------------------+     
|features         |Prediction|Probability                                                       |     
+-----------------+----------+------------------------------------------------------------------+     
|[5.1,3.5,1.4,0.2]|0         |[0.9999999999999951,4.682229962936943E-17,4.868372929920407E-15]  |     
|.................|..        |................................................................  |        
|[5.6,2.8,4.9,2.0]|1         |[8.920203149708086E-16,0.5988576194515217,0.4011423805484774]     |     
|.................|..        |................................................................  |     
|[6.3,2.7,4.9,1.8]|2         |[5.703158630226758E-16,0.022033640207248576,0.9779663597927509]   |        
+-----------------+----------+------------------------------------------------------------------+     

得到模型后,即可查看模型的相关参数,与KMeans方法不同,GMM不直接给出聚类中心,而是给出各个混合成分(多元高斯分布)的参数。在ML的实现中,GMM的每一个混合成分都使用一个MultivariateGaussian类(位于org.apache.spark.ml.stat.distribution包)来存储,我们可以使用GaussianMixtureModel类的weights成员获取到各个混合成分的权重,使用gaussians成员来获取到各个混合成分的参数(均值向量和协方差矩阵):

scala> for (i <- 0 until gmm.getK) {
     | println("Component %d : weight is %f \n mu vector is %s \n sigma matrix is %s" format
     | (i, gmm.weights(i), gmm.gaussians(i).mean, gmm.gaussians(i).cov))
     | }
Component 0 : weight is 0.333333
 mu vector is [5.006000336585284,3.41800074359835,1.4640001090120234,0.2439999627867791]
 sigma matrix is 0.12176391071215485  0.09829168918600302   0.01581595534223468   0.01033602571352466
0.09829168918600302  0.14227526345684152   0.011447885703674401  0.01120804907975396
0.01581595534223468  0.011447885703674401  0.02950400173292353   0.005584009823879005
0.01033602571352466  0.01120804907975396   0.005584009823879005  0.01126400540784641
Component 1 : weight is 0.158358
 mu vector is [6.683368733405807,2.86961545411428,5.6462886220107515,2.005673427136211]
 sigma matrix is 0.49328013505428253   0.050374713498113975  0.3573203540815462    0.050018569392196975
0.050374713498113975  0.04009423452907058   0.00416971505937197   0.02000523766170409
0.3573203540815462    0.00416971505937197   0.33772537665488306   0.017006917604832562
0.050018569392196975  0.02000523766170409   0.017006917604832562  0.06935869650451881
Component 2 : weight is 0.508309
 mu vector is [6.130726266791161,2.872742630634873,4.675369349848198,1.5732931362538298]
 sigma matrix is 0.34423978263401117  0.14332952213838432  0.3498831855148551  0.1447023418962832
0.14332952213838432  0.13127254135549662  0.1848327285944271  0.09799971374720898
0.3498831855148551   0.1848327285944271   0.5558476131836437  0.2698797562441122
0.1447023418962832   0.09799971374720898  0.2698797562441122  0.16825697031717957



子雨大数据之Spark入门
扫一扫访问本博客