【版权声明】博客内容由厦门大学数据库实验室拥有版权,未经允许,请勿转载!
[返回Spark教程首页]
推荐纸质教材:林子雨、郑海山、赖永炫编著《Spark编程基础(Python版)》
高斯混合模型(Gaussian Mixture Model, GMM) 是一种概率式的聚类方法,属于生成式模型,它假设所有的数据样本都是由某一个给定参数的 多元高斯分布 所生成的。具体地,给定类个数K
,对于给定样本空间中的样本
,一个高斯混合模型的概率密度函数可以由K个多元高斯分布组合成的混合分布表示:
其中,
是以
为均值向量,
为协方差矩阵的多元高斯分布的概率密度函数,可以看出,高斯混合模型由K个不同的多元高斯分布共同组成,每一个分布被称为高斯混合模型中的一个 成分(Component), 而
为第i
个多元高斯分布在混合模型中的 权重 ,且有
。
假设已有一个存在的高斯混合模型,那么,样本空间中的样本的生成过程即是:以
作为概率(实际上,权重可以直观理解成相应成分产生的样本占总样本的比例),选择出一个混合成分,根据该混合成分的概率密度函数,采样产生出相应的样本。
那么,利用GMM进行聚类的过程是利用GMM生成数据样本的“逆过程”:给定聚类簇数K
,通过给定的数据集,以某一种 参数估计 的方法,推导出每一个混合成分的参数(即均值向量
、协方差矩阵
和权重
),每一个多元高斯分布成分即对应于聚类后的一个簇。高斯混合模型在训练时使用了极大似然估计法,最大化以下对数似然函数:
显然,该优化式无法直接通过解析方式求得解,故可采用 期望-最大化(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
),在使用前,引入需要的包:
from pyspark.sql import Row
from pyspark.ml.clustering import GaussianMixture, GaussianMixtureModel
from pyspark.ml.linalg import Vectors
与其他教程相同,本文亦使用模式识别领域广泛使用的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
,我们定义一个函数,来生成我们想要的数据。
def f(x):
rel = {}
rel['features'] = Vectors.dense(float(x[0]),float(x[1]),float(x[2]),float(x[3]))
return rel
在定义数据类型完成后,即可将数据读入RDD[model_instance]
的结构中,并通过RDD
的隐式转换.toDF()
方法完成RDD
到DataFrame
的转换:
df = sc.textFile("file:///usr/local/spark/iris.txt").map(lambda line: line.split(',')).map(lambda p: Row(**f(p))).toDF()
可以通过创建一个GaussianMixture
类,设置相应的超参数,并调用fit(..)
方法来训练一个GMM模型GaussianMixtureModel
,在该方法调用前需要设置一系列超参数,如下表所示:
| 参数 | 含义 |
| ------- | :----------------: |
| K | 聚类数目,默认为2 |
| maxIter | 最大迭代次数,默认为100 |
| seed | 随机数种子,默认为随机Long值 |
| Tol | 对数似然函数收敛阈值,默认为0.01 |
其中,每一个超参数均可通过名为setXXX(...)
(如maxIterations即为setMaxIterations()
)的方法进行设置。
这里,我们建立一个简单的GaussianMixture
对象,设定其聚类数目为3,其他参数取默认值。
gm = GaussianMixture().setK(3).setPredictionCol("Prediction").setProbabilityCol("Probability")
gmm = gm.fit(df)
和KMeans
等硬聚类方法不同的是,除了可以得到对样本的聚簇归属预测外,还可以得到样本属于各个聚簇的概率(这里我们存在"Probability"列中)。
调用transform()
方法处理数据集之后,打印数据集,可以看到每一个样本的预测簇以及其概率分布向量(这里为了明晰起见,省略了大部分行,只选择三行):
result = gmm.transform(df)
result.show(150, False)
+-----------------+----------+------------------------------------------------------------------+
|features |Prediction|Probability |
+-----------------+----------+------------------------------------------------------------------+
|[5.1,3.5,1.4,0.2]|2 |[7.6734208374482E-12,3.834611512428377E-17,0.9999999999923265] |
|[4.9,3.0,1.4,0.2]|2 |[3.166798629239744E-8,7.627279694305193E-17,0.9999999683320137] |
|[4.7,3.2,1.3,0.2]|2 |[2.4270047914398485E-9,6.829198934603355E-17,0.9999999975729952] |
|[4.6,3.1,1.5,0.2]|2 |[6.358110927310705E-8,6.742515135411673E-17,0.9999999364188907] |
|[5.0,3.6,1.4,0.2]|2 |[2.76196999866254E-12,5.209131973934126E-17,0.9999999999972379] |
|[5.4,3.9,1.7,0.4]|2 |[4.423526830721967E-13,3.661673865920354E-16,0.9999999999995574] |
|[4.6,3.4,1.4,0.3]|2 |[1.996253995900862E-9,2.8082283131963835E-16,0.9999999980037457] |
|[5.0,3.4,1.5,0.2]|2 |[1.5177480903927398E-10,3.3953493957830917E-17,0.9999999998482252]|
|[4.4,2.9,1.4,0.2]|2 |[8.259607931046402E-7,1.383366216513306E-16,0.9999991740392067] |
得到模型后,即可查看模型的相关参数,与KMeans方法不同,GMM不直接给出聚类中心,而是给出各个混合成分(多元高斯分布)的参数。在ML的实现中,GMM的每一个混合成分都使用一个MultivariateGaussian
类(位于org.apache.spark.ml.stat.distribution
包)来存储,我们可以使用GaussianMixtureModel
类的weights
成员获取到各个混合成分的权重,使用gaussians
成员来获取到各个混合成分的参数(均值向量和协方差矩阵):
for i in range(3):
print("Component "+str(i)+" : weight is "+str(gmm.weights[i])+"\n mu vector is "+str( gmm.gaussiansDF.select('mean').head())+" \n sigma matrix is "+ str(gmm.gaussiansDF.select('cov').head()))
...
Component 0 : weight is 0.6537361109963744
mu vector is Row(mean=DenseVector([6.2591, 2.8764, 4.9057, 1.6784]))
sigma matrix is Row(cov=DenseMatrix(4, 4, [0.4414, 0.1249, 0.4524, 0.1676, 0.1249, 0.1103, 0.1469, 0.081, 0.4524, 0.1469, 0.6722, 0.2871, 0.1676, 0.081, 0.2871, 0.1806], False))
Component 1 : weight is 0.03291269344724756
mu vector is Row(mean=DenseVector([6.2591, 2.8764, 4.9057, 1.6784]))
sigma matrix is Row(cov=DenseMatrix(4, 4, [0.4414, 0.1249, 0.4524, 0.1676, 0.1249, 0.1103, 0.1469, 0.081, 0.4524, 0.1469, 0.6722, 0.2871, 0.1676, 0.081, 0.2871, 0.1806], False))
Component 2 : weight is 0.31335119555637797
mu vector is Row(mean=DenseVector([6.2591, 2.8764, 4.9057, 1.6784]))
sigma matrix is Row(cov=DenseMatrix(4, 4, [0.4414, 0.1249, 0.4524, 0.1676, 0.1249, 0.1103, 0.1469, 0.081, 0.4524, 0.1469, 0.6722, 0.2871, 0.1676, 0.081, 0.2871, 0.1806], False))