聚类算法之K-Means算法Spark实践

Posted 计算姬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聚类算法之K-Means算法Spark实践相关的知识,希望对你有一定的参考价值。


聚类算法常被用于数据量非常大的应用中。我们都知道,机器学习算法大体分为三类:监督学习(supervised learning)、无监督学习(unsupervised learning)和半监督学习(semi-supervised learning)。监督学习是指我们利用带有类别属性标注的数据去训练、学习,用于预测未知数据的类别属性。例如,根据用户之前的购物行为去预测用户是否会购买某一商品。常用的算法有决策树,支持向量机SVM,朴素贝叶斯分类器,K-近邻算法KNN,线性回归和逻辑回归等,相信大家在学习机器学习算法的过程中已经耳熟能详。无监督学习是指在无人工干预的情况下将数据按照相似程度划分,而聚类算法就是非常典型的无监督学习方法,通常要处理的数据没有标签信息,可以通过计算数据之间的相似性来自动划分类别。


K-Means算法


这里要介绍的K-Means算法是聚类分析中最基础的一个聚类算法,算法的思想是初始随机给定K个簇中心,按照距离最近原则把待分类的样本点分到各个簇,然后按平均法重新计算各个簇的质心,从而确定新的簇心,迭代计算,直到簇心的移动距离小于某个给定的误差值。使用算法描述语言,只要四个步骤:


  1. 任意选择K个点作为初始聚类中心;

  2. 计算每个样本点到聚类中心的距离,将每个样本点划分到离该点最近的聚类中去;

  3. 计算每个聚类中所有点的坐标平均值,并将这个平均值作为新的聚类中心;

  4. 反复执行2、3,直到聚类中心的移动小于某误差值或者聚类次数达到要求为止。


这里计算距离的方法通常是计算欧几里得距离,假设中心点center是(x1, y1),需要计算的样本点point是(x2, y2):




另外,给出损失函数(Cost Function),每一次选取好新的中心点,我们就要计算一下当前选好的中心点损失为多少,这个损失代表着偏移量,越大说明当前聚类的效果越差,计算公式称为(Within-Cluster Sum of Squares, WCSS):


聚类算法之K-Means算法Spark实践


其中,xi表示某一对象,ck表示该对象所属类别的中心点。整个式子的含义就是对各个类别下的对象,求对象与中心点的欧式距离的平方,把所有的平方求和就是L(C)。


测试数据:


0.0 0.0

0.1 0.1

0.2 0.2

3.0 3.0

3.1 3.1

3.2 3.2

9.0 9.0

9.1 9.1

9.2 9.2


Scala版KMeans代码示例:


import org.apache.spark.mllib.clustering.KMeans

import org.apache.spark.mllib.linalg.Vectors

import org.apache.spark.{SparkConf, SparkContext}


object KMeansTest {

    def main(args: Array[String]) {

        val conf = new SparkConf()

        val sc = new SparkContext(conf)

    

        val data =sc.textFile(args(0))

        val parsedData =data.map(s => Vectors.dense(s.split(' ').map(_.trim.toDouble))).cache()

        

        //设置簇的个数为3

        val numClusters =3

        //迭代20次

        val numIterations= 20

        //运行10次,选出最优解

        val runs=10

        //设置初始K选取方式为k-means++

        val initMode = "k-means||"

        val clusters = new KMeans().

        setInitializationMode(initMode).

        setK(numClusters).

        setMaxIterations(numIterations).

        run(parsedData)

        

        //打印出测试数据属于哪个簇

        println(parsedData.map(v=> v.toString() + " belong to cluster :" +clusters.predict(v)).collect().mkString(" "))

        

        // Evaluateclustering by computing Within Set Sum of Squared Errors

        val WSSSE = clusters.computeCost(parsedData)

        println("WithinSet Sum of Squared Errors = " + WSSSE)

        

        val a21 =clusters.predict(Vectors.dense(1.2,1.3))

        val a22 =clusters.predict(Vectors.dense(4.1,4.2))

        

        //打印出中心点

        println("Clustercenters:")

        for (center <-clusters.clusterCenters) {

          println(" "+ center)

        }

        

        println("Prediction of (1.2,1.3)-->"+a21)

        println("Prediction of (4.1,4.2)-->"+a22)

  }

}


提交spark执行(standalone):


spark-submit --class KMeansTest --master local[4] KMeansTest.jar ../data/kmeans_data.txt


结果:


[0.0,0.0] belong to cluster :0

[0.1,0.1] belong to cluster :0

[0.2,0.2] belong to cluster :0

[3.0,3.0] belong to cluster :2

[3.1,3.1] belong to cluster :2

[3.2,3.2] belong to cluster :2

[9.0,9.0] belong to cluster :1

[9.1,9.1] belong to cluster :1

[9.2,9.2] belong to cluster :1


WithinSet Sum of Squared Errors = 0.12000000000007648

Clustercenters:

 [0.1,0.1]

 [9.1,9.1]

 [3.1,3.1]


Prediction of (1.2,1.3)-->0

Prediction of (4.1,4.2)-->2


K-Means算法有两个重大缺陷:


  • K-Means属于无监督学习,最大的特别和优势在于模型的建立不需要训练数据。在日常工作中,很多情况下没有办法事先获取到有效的训练数据,这时采用K-Means是一个不错的选择。K值是预先给定的,属于预先知识,很多情况下K值的估计是非常困难的,对于像计算全部微信用户的交往圈这样的场景就完全的没办法用K-Means进行。对于可以确定K值不会太大但不明确精确的K值的场景,可以进行迭代运算,然后找出Cost Function最小时所对应的K值,这个值往往能较好的描述有多少个簇类。

  • K-Means算法对初始选取的聚类中心点是敏感的,不同的随机种子点得到的聚类结果完全不同。可以用K-Means++算法来解决这个问题。


K-Means++算法


K-Means++算法选择初始聚类中心的思想是:初始的聚类中心之间的相互距离要尽可能远。算法步骤如下:


  1. 随机挑选一个点作为第一个聚类中心;

  2. 对于每一个点x,计算和其最近的一个聚类中心的距离D(x),将所有距离求和得到Sum(D(x));

  3. 然后,再取一个随机值,用权重的方式来取计算下一个“种子点”。这个算法的思想是,先取一个能落在Sum(D(x))中的随机值Random,然后用Random -= D(x),直到其<=0,此时的点就是下一个“种子点”(其思想是,D(x)较大的点,被选取作为聚类中心的概率较大);

  4. 重复2和3,直到K个聚类中心被选出来;

  5. 利用这K个初始聚类中心进行K-Means算法。


Spark的MLlib库中也支持K-Means++初始化方法,只需增加初始模式设置:


val initMode = "k-means||"

val clusters = new KMeans().

        setInitializationMode(initMode).

        setK(numClusters).

        setMaxIterations(numIterations).

        run(parsedData)


在构成圆形的5000个随机样本点上,设置7个簇,分别使用K-Means算法和K-Means++算法的聚类对比图如下:

聚类算法之K-Means算法Spark实践
Fig 1. K-Means聚类结果


Fig 2. K-Means++聚类结果


可以看出使用K-Means++算法优化初始聚类中心可以使得划分的类别较好。


当然,如果想彻底不用设置K值也是有很多算法来支持的,从聚类算法衍生出一派的社区发现(Community Detection)算法,在社区发现算法中,点表示网络中的成员,边代表成员之间由于共同的兴趣而构成的相似性连接。聚类和社区发现算法之间最大的不同在于,社区发现中的点被网络连接到了一起,而聚类中的数据点并没有构成网络。关于社区发现算法,这是我的博士研究课题,有空再表。


参考资料:(本文算法请戳阅读原文)

  1. 《Spark MLlib机器学习-算法、源码及实战详解》黄美灵著

  2. http://sobuhu.com/ml/2012/11/25/kmeans-python.html

  3. http://coolshell.cn/articles/7779.html

  4. http://rosettacode.org/wiki/K-means%2B%2B_clustering



六一儿童节快乐!


以上是关于聚类算法之K-Means算法Spark实践的主要内容,如果未能解决你的问题,请参考以下文章

[机器学习]二分k-means算法详解

Canopy聚类算法

ML: 聚类算法R包-K中心点聚类

推荐|数据科学家需要了解的5大聚类算法

K-Means算法的Python实现

Spark2.0机器学习系列之8: 聚类分析(K-Means,Bisecting K-Means,LDA,高斯混合模型)