简介

K-means算法是集简单和经典于一身的基于距离的聚类算法,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为类簇是由距离靠近的对象组成的,因为把得到紧凑且独立的簇作为最终目标。


算法

核心思想

通过迭代寻找k个类簇的一种划分方案,使得用这k个类簇的均值来代表相应各类样本时所得的总体误差最小。

k个聚类具有以下特点:各聚类本身尽可能的紧凑,而各聚类之间尽可能的分开。k-means算法的基础是最小误差平方和准则


步骤

将样本聚类成k个簇,其中k是用户给定的,其求解过程非常直观简单

1.随机选取k个聚类质心点

2.重复下面过程直到收敛

机器学习系列--kmeans分类算法_数据

对于每一个样例 i,计算其应该属于的类


机器学习系列--kmeans分类算法_聚类_02

对于每一个类 j,重新计算该类的质心


伪代码

1.创建k个点作为初始的质心点(随机选择)

2.当任意一个点的簇分配结果发生改变时

对数据集中的每一个数据点

              对每一个质心

                     计算质心与数据点的距离

              将数据点分配到距离最近的簇(分组)

对每一个簇,计算簇中所有点的均值,并将均值作为质心 (新的均值等于原均值时候跳出迭代输出划分)


特点

各聚类本身尽可能的紧凑,而各聚类之间尽可能的分开

 k-means算法的基础是最小误差平方和准则

机器学习系列--kmeans分类算法_数据_03

μc(i)表示第i个聚类的均值

各类簇内的样本越相似,其与该类均值间的误差平方越小,对所有类所得到的误差平方求和,即可验证分为k类时,各聚类是否是最优的。


代码

spark2

Scala2.11

1.原生sparkcore

object KmeansTest {

    val k=2  //类个数

    val dim=2   //数据集维度

    val shold=0.0000000001  //阀值用于判断聚类中心偏移量

    val centers=new Array[Vector[Double]](k)    //聚类中心点(迭代更新)

    /**

      * 数据

      * 1.658985, 4.285136

      * -3.453687, 3.424321

      * 4.838138, -1.151539

      * -5.379713, -3.362104

      *

      * @param sc

      * @return

      */

    def loadDataSet(sc:SparkContext): Array[Vector[Double]] ={

        val file = sc.textFile("")

        val res=file.map(t=>{

            val value=t.split(" ").map(x=>{x.toDouble})

            var vector = Vector[Double]()

            for(i <- 0 until dim)

                vector ++= Vector(value(i))

            vector

        }).collect()

        res

    }

    /**

      *

      * 随机初始化聚类中心

      * k个聚类中心

      * 初始化中心点如下:

        3

        Vector(-5.379713, -3.362104)

       初始化中心点如下:

        4

        Vector(0.972564, 2.924086)

      * */

    def initialCenters(points:Array[Vector[Double]]): Unit ={

        val pointsNum=points.length

        val random = new Random()

        var index=0

        var flag=true

        var temp=0

        val array=new ListBuffer[Int]

        while(index < k){

            val temp: Int = random.nextInt(pointsNum)

            flag=true

            if(array.contains(temp)){

                flag=false

            }else{

                if(flag){

                    array.append(temp)

                    index+=1

                }

            }

        }

        for(i <- centers.indices){

            centers(i)=points(array(i))

            println("初始化中心点如下:")

            println(array(i))

            println(centers(i))

        }

    }

    /**

      * 迭代做聚类

      * @param points 随机下标

      * @param centers 中心点

      */

    def kmeans(points:Array[Vector[Double]],centers:Array[Vector[Double]]): Unit  = {

        var bool = true

        var newCenters = Array[Vector[Double]]()

        var move = 0.0

        var currentCost = 0.0 //当前的代价函数值

        var newCost = 0.0

        //根据每个样本点最近的聚类中心进行groupBy分组,最后得到的cluster是Map[Vector[Double],Array[Vector[Double]]]

        //Map中的key就是聚类中心,value就是依赖于该聚类中心的点集

        while(bool){//迭代更新聚类中心,直到最优

            move = 0.0

            //

            currentCost = computeCost(points,centers)

            val cluster = points.groupBy(v => closestCenter(centers,v))//聚类中心

            newCenters =

              centers.map(oldCenter => {

                  cluster.get(oldCenter) match {//找到该聚类中心所拥有的点集

                      case Some(pointsInThisCluster) =>

                          //均值作为新的聚类中心

                          vectorDivide(pointsInThisCluster.reduceLeft((v1,v2) => vectorAdd(v1,v2)),pointsInThisCluster.length)

                      case None => oldCenter

                  }

              })

            for(i <- centers.indices){

                //move += math.sqrt(vectorDis(newCenters(i),centers(i)))

                centers(i) = newCenters(i)

            }

            println("新的代价函数值:" + newCost)

            if(math.sqrt(vectorDis(Vector(currentCost),Vector(newCost))) < shold)

                bool = false

            newCost = computeCost(points,centers)//新的代价函数值

            println("当前代价函数值:" + currentCost)

        }//while-end

        println("寻找到的最优中心点如下:")

        for(i <- centers.indices){

            println(centers(i))

        }

    }

    /**

      * 输出聚类结果

      * @param points

      * @param centers

      */

    def printResult(points:Array[Vector[Double]],centers:Array[Vector[Double]]): Unit  = {

        //将每个点的聚类中心用centers中的下标表示,属于同一类的点拥有相同的下标

        val pointsNum = points.length

        val pointsLabel = new Array[Int](pointsNum)

        var closetCenter = Vector[Double]()

        println("聚类结果如下:")

        for(i <- 0 until pointsNum){

            closetCenter = centers.reduceLeft((c1,c2) => if (vectorDis(c1,points(i)) < vectorDis(c2,points(i))) c1 else c2)

            pointsLabel(i) = centers.indexOf(closetCenter)

            println(points(i) + "-----------" + pointsLabel(i))

        }

    }

    /**

      * 找到某样本点所属的聚类中心

      * @param centers

      * @param v

      * @return

      */

    def closestCenter(centers:Array[Vector[Double]],v:Vector[Double]):Vector[Double] = {

        centers.reduceLeft((c1,c2) =>

            if(vectorDis(c1,v) < vectorDis(c2,v)) c1 else c2

        )

    }

    /**

      * 计算代价函数(每个样本点到聚类中心的距离之和不再有很大变化)

      * @param points

      * @param centers

      * @return

      */

    def computeCost(points:Array[Vector[Double]],centers:Array[Vector[Double]]):Double = {

        //cluster:Map[Vector[Double],Array[Vector[Double]]

        //类分组

        val cluster = points.groupBy(v => closestCenter(centers,v))

        //欧式距离

        var costSum = 0.0

        for(i <- centers.indices){

            println(cluster.get(centers(i)).toBuffer)

            cluster.get(centers(i)) match{

                case Some(subSets) =>

                    for(j <- subSets.indices){

                        costSum += (vectorDis(centers(i),subSets(j)) * vectorDis(centers(i),subSets(j)))

                    }

                case None => costSum = costSum

            }

        }

        costSum

    }

    //--------------------------自定义向量间的运算-----------------------------

    //--------------------------向量间的欧式距离-----------------------------

    def vectorDis(v1: Vector[Double], v2: Vector[Double]):Double = {

        var distance = 0.0

        for(i <- v1.indices){

            distance += (v1(i) - v2(i)) * (v1(i) - v2(i))

        }

        distance = math.sqrt(distance)

        distance

    }

    //--------------------------向量加法-----------------------------

    def vectorAdd(v1:Vector[Double],v2:Vector[Double]):Vector[Double] = {

        var v3 = v1

        for(i <-  v1.indices){

            v3 = v3.updated(i,v1(i) + v2(i))

        }

        v3

    }

    //--------------------------向量除法-----------------------------

    def vectorDivide(v:Vector[Double],num:Int):Vector[Double] = {

        var r = v

        for(i <- v.indices){

            r = r.updated(i,r(i) / num)

        }

        r

    }

    def main(args: Array[String]): Unit = {

        val sparkConf = new SparkConf().setMaster("local[2]").setAppName("KmeansTest")

        val sc=new SparkContext(sparkConf)

        val rows=loadDataSet(sc)

        //ArrayBuffer(Vector(1.658985, 4.285136), Vector(-3.453687, 3.424321),

        // Vector(4.838138, -1.151539), Vector(-5.379713, -3.362104))

        initialCenters(rows)

        /**

          * 寻找到的最优中心点如下:

            Vector(-2.3914716666666664, 1.4491176666666667)

            Vector(4.838138, -1.151539)

            聚类结果如下:

            Vector(1.658985, 4.285136)-----------0

            Vector(-3.453687, 3.424321)-----------0

            Vector(4.838138, -1.151539)-----------1

            Vector(-5.379713, -3.362104)-----------0

          */

        kmeans(rows,centers)

        printResult(rows,centers)

    }

}


2.sparkmllib

object KmeansTest2 {

  def main(args: Array[String]): Unit = {

    val sparkConf=new SparkConf().setAppName("KmeansTest2").setMaster("local[2]")

    val sc=new SparkContext(sparkConf)

    val data=sc.textFile("")

    val parsedData=data.map(s=>Vectors.dense(s.split(" ").map(_.toDouble)))

    val numClusters=2

    val numIterations=30

    val model=KMeans.train(parsedData,numClusters,numIterations)

    // 数据模型的中心点

    println("Cluster centres:")

    for(c <- model.clusterCenters) {

      println("  " + c.toString)

    }

    // 使用误差平方之和来评估数据模型

    val cost = model.computeCost(parsedData)

    println("Within Set Sum of Squared Errors = " + cost)

    // 使用模型测试单点数据

    /*println("Vectors 7.3 1.5 10.9 is belong to cluster:" + model.predict(Vectors.dense("1.5 10.9".split(" ")

      .map(_.toDouble))))

    println("Vectors 4.2 11.2 2.7 is belong to cluster:" + model.predict(Vectors.dense("11.2 2.7".split(" ")

      .map(_.toDouble))))

    println("Vectors 18.0 4.5 3.8 is belong to cluster:" + model.predict(Vectors.dense("14.5 73.8".split(" ")

      .map(_.toDouble))))*/

    // 返回数据集和结果

    val result = data.map {

      line =>

        val linevectore = Vectors.dense(line.split(" ").map(_.toDouble))

        val prediction = model.predict(linevectore)

        line + " " + prediction

    }.collect.foreach(println)

    sc.stop

  }

}