spark ml聚类算法
一、K-means
原理
1.核心思想
簇内距离最小,簇间距离最大
此处添加公式
2.流程
- 在初始样本集中选择K个点做为初始质心,其选择可随机选择,最好相差较大一些。
- 计算每一个非质心样本到个质心的距离,根据最小距离原则进行样本类别划分。
- 分类后计算各类新质心。
- 重复步骤2,3,直至个类别质心变化少于某阈值或样本划分变化少于某阈值。
3. 注意点
初始值选取可参考:先选择距离最大的一对样本点,第三个选择到两点间距离和最大的样本点,以此类推。
选取公式:
聚类评判标准SSE,计算最终所有样本归属类别与归属类质心的距离平方之和。由公式可知,当每个样本均划为一类时,其SSE为0
评判公式
K值选取:当无法提前知道初始簇类时,需要通过试验去确定,同常K在2~10之间,观察K的变化引起的SSE的变化趋势,选择一个拐点(SSE下降趋于平缓)对应的簇类作为初始K。
优点
- 实现简单
- 运行速度快
- 非常成熟
缺点
- 我们必须提前知道数据有多少类/组。
- 容易受异常值,噪声的影响
- 初始化质心的选择影响聚类效果与运行时间
- 如果各隐含类别的数据不平衡,比如各隐含类别的数据量严重失衡,或者各隐含类别的方差不同,则聚类效果不佳。
spark实现
选取了官方文档中的例子,给出了大致的框架,然对其中的诸多细节未加展示,接下来做进一步的解读。
import org.apache.spark.ml.clustering.KMeans
import org.apache.spark.ml.evaluation.ClusteringEvaluator
// $example off$
import org.apache.spark.sql.SparkSession
/**
1. An example demonstrating k-means clustering.
2. Run with
3. {{{
4. bin/run-example ml.KMeansExample
5. }}}
*/
object KMeansExample {
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.appName(s"${this.getClass.getSimpleName}")
.getOrCreate()
// $example on$
// Loads data.
val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")
// Trains a k-means model.
val kmeans = new KMeans().setK(2).setSeed(1L)
val model = kmeans.fit(dataset)
// Make predictions
val predictions = model.transform(dataset)
// Evaluate clustering by computing Silhouette score
val evaluator = new ClusteringEvaluator()
val silhouette = evaluator.evaluate(predictions)
println(s"Silhouette with squared euclidean distance = $silhouette")
// Shows the result.
println("Cluster Centers: ")
model.clusterCenters.foreach(println)
// $example off$
spark.stop()
}
}
模型参数解读
损失值计算:样本点到最近质心的距离平方之和
val algo = new MLlibKMeans()
.setK($(k)) //初始化类数
.setInitializationMode($(initMode)) //初始化质心的模式,随机选取或者k-means++的方式:This can be either "random" to choose random points as initial cluster centers, or "k-means||" to use a parallel variant of k-means++
.setInitializationSteps($(initSteps))//Param for the number of steps for the k-means|| initialization mode. This is an advanced setting -- the default of 2 is almost always enough. Must be > 0. Default: 2.
.setMaxIterations($(maxIter)) //最大迭代次数
.setSeed($(seed))//随机种子
.setEpsilon($(tol))//判断收敛的阈值,默认1e-4
K-means变种
初始值优化k-means++
K-Means++的对于初始化质心的优化策略也很简单,如下:
a) 从输入的数据点集合中随机选择一个点作为第一个聚类中心μ1
b) 对于数据集中的每一个点xi,计算它与已选择的聚类中心中最近聚类中心的距离D(xi)=argmin||xi−μr||22 r=1,2,...kselected
c) 选择一个新的数据点作为新的聚类中心,选择的原则是:D(x)较大的点,被选取作为聚类中心的概率较大
d) 重复b和c直到选择出k个聚类质心
e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法
这个在spark的org.apache.spark.ml.clustering.KMeans 中已包含,在初始化参数上进行选择即可。
初始值优化BisectingKMeans
主要是为了改进k-means算法随机选择初始质心的随机性造成聚类结果不确定性的问题,而Bisecting k-means算法受随机选择初始质心的影响比较小
Bisecting k-means聚类算法的基本思想是,通过引入局部二分试验,每次试验都通过二分具有最大SSE值的一个簇,二分这个簇以后得到的2个子簇,选择2个子簇的总SSE最小的划分方法,这样能够保证每次二分得到的2个簇是比较优的(也可能是最优的),也就是这2个簇的划分可能是局部最优的,取决于试验的次数。
Bisecting k-means聚类算法的具体执行过程,描述如下所示:
1、初始时,将待聚类数据集D作为一个簇C0,即C={C0},输入参数为:二分试验次数m、k-means聚类的基本参数;
2、取C中具有最大SSE的簇Cp,进行二分试验m次:调用k-means聚类算法,取k=2,将Cp分为2个簇:Ci1、Ci2,一共得到m个二分结果集合B={B1,B2,…,Bm},其中,Bi={Ci1,Ci2},这里Ci1和Ci2为每一次二分试验得到的2个簇;
3、计算上一步二分结果集合B中,每一个划分方法得到的2个簇的总SSE值,选择具有最小总SSE的二分方法得到的结果:Bj={Cj1,Cj2},并将簇Cj1、Cj2加入到集合C,并将Cp从C中移除;
4、重复步骤2和3,直到得到k个簇,即集合C中有k个簇。
这个在spark的org.apache.spark.ml.clustering.BisectingKMeans 中。
距离计算优化elkan K-Means
elkan K-Means利用了两边之和大于等于第三边,以及两边之差小于第三边的三角形性质,来减少距离的计算。
第一种规律是对于一个样本点x和两个质心μj1,μj2。如果我们预先计算出了这两个质心之间的距离D(j1,j2),则如果计算发现2D(x,j1)≤D(j1,j2),我们立即就可以知道D(x,j1)≤D(x,j2)。此时我们不需要再计算D(x,j2),也就是说省了一步距离计算。
第二种规律是对于一个样本点x和两个质心μj1,μj2。我们可以得到D(x,j2)≥max{0,D(x,j1)−D(j1,j2)}。这个从三角形的性质也很容易得到。
利用上边的两个规律,elkan K-Means比起传统的K-Means迭代速度有很大的提高。但是如果我们的样本的特征是稀疏的,有缺失值的话,这个方法就不使用了,此时某些距离无法计算,则不能使用该算法。
大样本优化Mini Batch K-Means
在Mini Batch K-Means中,我们会选择一个合适的批样本大小batch size,我们仅仅用batch size个样本来做K-Means聚类。那么这batch size个样本怎么来的?一般是通过无放回的随机采样得到的。
二、高斯混合模型GaussianMixtureModel
原理
1.核心思想
使用混合高斯函数去拟合数据分布,某一个样本在各高斯函数成分下的概率有不同,在类别判定中通常将样本归于最大概率的高斯函数成分中。
高斯混合公式如下
P
m
(
x
)
=
∑
i
=
1
k
α
i
∗
p
(
x
∣
μ
i
,
W
i
)
−
−
−
−
(
1
)
Pm(x) = \sum_{i=1}^k \alpha_i*p(x|\mu_i,W_i) \quad ----(1)
Pm(x)=i=1∑kαi∗p(x∣μi,Wi)−−−−(1)
该函数由K个混合成分组成,每个混合成分对应一个高斯分布,其中
μ
i
,
W
i
\mu_i,W_i
μi,Wi是该高斯分布的参数均值与协方差,
α
i
\alpha_i
αi是混合系数,
∑
i
=
1
k
α
i
=
1
\sum_{i=1}^k \alpha_i=1
∑i=1kαi=1。
若随机变量
z
j
∈
{
1
,
2
,
.
.
.
,
k
}
z_j\in \{1,2,...,k\}
zj∈{1,2,...,k}表示生成样本
x
j
x_j
xj的高斯混合成分,则
z
j
z_j
zj的先验概率
P
(
z
j
=
i
)
P(z_j=i)
P(zj=i)等于
α
i
(
i
=
1
,
2
,
.
.
.
,
k
)
\alpha_i(i=1,2,...,k)
αi(i=1,2,...,k),
z
j
z_j
zj的后验概率为:
P
m
(
z
j
=
i
∣
x
j
)
=
P
(
z
j
=
i
)
∗
P
(
x
j
∣
z
j
=
i
)
P
m
(
x
j
)
=
α
i
∗
p
(
x
j
∣
μ
i
,
W
i
)
∑
l
=
1
k
α
l
∗
p
(
x
j
∣
μ
l
,
W
l
)
−
−
−
−
(
2
)
Pm(z_j=i|x_j)=\frac{P(z_j=i)*P(x_j|z_j=i)}{Pm(x_j)}\\ \quad=\frac{\alpha_i*p(x_j|\mu_i,W_i)}{\sum_{l=1}^k\alpha_l*p(x_j|\mu_l,W_l)}\quad----(2)
Pm(zj=i∣xj)=Pm(xj)P(zj=i)∗P(xj∣zj=i)=∑l=1kαl∗p(xj∣μl,Wl)αi∗p(xj∣μi,Wi)−−−−(2)
公式2的后验概率简计为
γ
j
i
\gamma_{ji}
γji,表明是
x
j
x_j
xj在第i个成分上的后验概率。
给定训练集
D
=
{
x
1
,
x
2
,
.
.
.
,
x
m
}
D=\{x_1,x_2,...,x_m\}
D={x1,x2,...,xm},通过极大似然估计结合迭代优化求解,可得参数
{
(
α
i
,
μ
i
,
W
i
)
}
\{(\alpha_i,\mu_i,W_i)\}
{(αi,μi,Wi)}的解:
α
i
=
∑
j
=
1
m
γ
j
i
m
−
−
−
−
(
3
)
μ
i
=
∑
j
=
1
m
γ
j
i
x
j
∑
j
=
1
m
γ
j
i
−
−
−
−
(
4
)
W
i
=
∑
j
=
1
m
γ
j
i
(
x
j
−
μ
i
)
(
x
j
−
μ
i
)
T
∑
j
=
1
m
γ
j
i
−
−
−
−
(
5
)
\alpha_i=\frac{\sum_{j=1}^m\gamma_{ji}}{m} \quad----(3)\\\mu_i=\frac{\sum_{j=1}^m \gamma_{ji}x_j}{\sum_{j=1}^m\gamma_{ji}}\quad ----(4)\\W_i=\frac{\sum_{j=1}^m\gamma_{ji}(x_j-\mu_i)(x_j-\mu_i)^T}{\sum_{j=1}^m\gamma_{ji}}\quad ----(5)
αi=m∑j=1mγji−−−−(3)μi=∑j=1mγji∑j=1mγjixj−−−−(4)Wi=∑j=1mγji∑j=1mγji(xj−μi)(xj−μi)T−−−−(5)
由此可以进行迭代更新,直至收敛得到最终的高斯混合函数与每个样本最终各成分取值,再对样本从属进行划分。
2.流程
1、根据训练集D与K簇类,初始化
α
i
=
1
/
k
,
μ
i
=
x
j
,
W
i
=
E
\alpha_i=1/k,\mu_i=x_j,W_i=E
αi=1/k,μi=xj,Wi=E,其中E表示单位矩阵。
2、按公式(2)与公式(1)计算得到后验概率
γ
j
i
\gamma_{ji}
γji。
3、按公式(3)(4)(5)更新
α
i
,
μ
,
W
i
\alpha_i,\mu_,W_i
αi,μ,Wi。
4、重复步骤2与3直到满足停止条件(如迭代次数,SSE变化阈值)。
5、根据最终的参数得到样本的,每一个混合成分的取值。
6、根据取最大的原理对样本进行类别划分。
优缺点
优点: 结果用概率表示,更具可视化并且可以根据这些概率在某个感兴趣的区域重新拟合预测。
缺点: 需要使用完整的样本信息进行预测、在高维空间失去有效性。
spark实现
import org.apache.spark.ml.clustering.GaussianMixture
// $example off$
import org.apache.spark.sql.SparkSession
/**
* An example demonstrating Gaussian Mixture Model (GMM).
* Run with
* {{{
* bin/run-example ml.GaussianMixtureExample
* }}}
*/
object GaussianMixtureExample {
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.appName(s"${this.getClass.getSimpleName}")
.getOrCreate()
// $example on$
// Loads data
val dataset = spark.read.format("libsvm").load("data/mllib/sample_kmeans_data.txt")
// Trains Gaussian Mixture Model
val gmm = new GaussianMixture()
.setK(2)
val model = gmm.fit(dataset)
// output parameters of mixture model model
for (i <- 0 until model.getK) {
println(s"Gaussian $i:\nweight=${model.weights(i)}\n" +
s"mu=${model.gaussians(i).mean}\nsigma=\n${model.gaussians(i).cov}\n")
}
// $example off$
spark.stop()
}
}