Spark ML - 聚类算法

Spark ML - 聚类算法

1.KMeans快速聚类

首先到UR需要的包:

1
2
import org.apache.spark.ml.clustering.{KMeans,KMeansModel}
import org.apache.spark.ml.linalg.Vectors

开启RDD的隐式转换:

1
import spark.implicits._

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

1
case class model_instance (features: org.apache.spark.ml.linalg.Vector)

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

1
2
3
4
5
val rawData = sc.textFile("file:///home/hduser/iris.data")
val df = rawData.map(
line =>
{ model_instance( Vectors.dense(line.split(",").filter(p => p.matches("\\d*(\\.?)\\d*"))
.map(_.toDouble)) )}).toDF()

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

​ 在得到数据后,我们即可通过ML包的固有流程:创建Estimator并调用其fit()方法来生成相应的Transformer对象,很显然,在这里KMeans类是Estimator,而用于保存训练后模型的KMeansModel类则属于Transformer

1
2
3
4
5
val kmeansmodel = new KMeans().
setK(3).
setFeaturesCol("features").
setPredictionCol("prediction").
fit(df)

​ 与MLlib版本类似,ML包下的KMeans方法也有Seed(随机数种子)、Tol(收敛阈值)、K(簇个数)、MaxIter(最大迭代次数)、initMode(初始化方式)、initStep(KMeans||方法的步数)等参数可供设置,和其他的ML框架算法一样,用户可以通过相应的setXXX()方法来进行设置,或以ParamMap的形式传入参数,这里为了简介期间,使用setXXX()方法设置了参数K,其余参数均采用默认值。

​ 与MLlib中的实现不同,KMeansModel作为一个Transformer,不再提供predict()样式的方法,而是提供了一致性的transform()方法,用于将存储在DataFrame中的给定数据集进行整体处理,生成带有预测簇标签的数据集:

1
val results = kmeansmodel.transform(df)

​ 为了方便观察,我们可以使用collect()方法,该方法将DataFrame中所有的数据组织成一个Array对象进行返回:

1
2
3
4
results.collect().foreach(
row => {
println( row(0) + " is predicted as cluster " + row(1))
})

也可以通过KMeansModel类自带的clusterCenters属性获取到模型的所有聚类中心情况:

1
2
3
4
kmeansmodel.clusterCenters.foreach(
center => {
println("Clustering Center:"+center)
})

​ 与MLlib下的实现相同,KMeansModel类也提供了计算 集合内误差平方和(Within Set Sum of Squared Error, WSSSE) 的方法来度量聚类的有效性,在真实K值未知的情况下,该值的变化可以作为选取合适K值的一个重要参考:

1
kmeansmodel.computeCost(df)

2.高斯混合模型(GMM)聚类算法

2.1 基本原理

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

img

,一个高斯混合模型的概率密度函数可以由K个多元高斯分布组合成的混合分布表示:

img

其中,

img

是以

img

为均值向量,

img

为协方差矩阵的多元高斯分布的概率密度函数,可以看出,高斯混合模型由K个不同的多元高斯分布共同组成,每一个分布被称为高斯混合模型中的一个 成分(Component), 而

img

为第i个多元高斯分布在混合模型中的 权重 ,且有

img

假设已有一个存在的高斯混合模型,那么,样本空间中的样本的生成过程即是:以

img

作为概率(实际上,权重可以直观理解成相应成分产生的样本占总样本的比例),选择出一个混合成分,根据该混合成分的概率密度函数,采样产生出相应的样本。

那么,利用GMM进行聚类的过程是利用GMM生成数据样本的“逆过程”:给定聚类簇数K,通过给定的数据集,以某一种 参数估计 的方法,推导出每一个混合成分的参数(即均值向量

img

、协方差矩阵

img

和权重

img

),每一个多元高斯分布成分即对应于聚类后的一个簇。高斯混合模型在训练时使用了极大似然估计法,最大化以下对数似然函数:

img

img

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

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

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

2.2 模型的训练与分析

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

1
2
import org.apache.spark.ml.clustering.{GaussianMixture,GaussianMixtureModel}
import org.apache.spark.ml.linalg.Vector

开启RDD的隐式转换:

1
import spark.implicits._

​ 我们仍采用Iris数据集进行实验。为了便于生成相应的DataFrame,这里定义一个名为model_instancecase class作为DataFrame每一行(一个数据样本)的数据类型。

1
case class model_instance (features: org.apache.spark.ml.linalg.Vector)

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

1
2
3
4
val rawData = sc.textFile("file:///home/hduser/iris.data")
val df = rawData.map(line =>
{ model_instance( Vectors.dense(line.split(",").filter(p => p.matches("\\d*(\\.?)\\d*"))
.map(_.toDouble)) )}).toDF()

​ 与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,其他参数取默认值。

1
2
3
4
val gm = new GaussianMixture().setK(3)
.setPredictionCol("Prediction")
.setProbabilityCol("Probability")
val gmm = gm.fit(df)

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

​ 调用transform()方法处理数据集之后,打印数据集,可以看到每一个样本的预测簇以及其概率分布向量

1
2
val result = gmm.transform(df)
result.show(150, false)

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

1
2
3
4
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))
}