[置顶] Spark canopy算法

canopy算法 概念

与传统的聚类算法(比如K-means)不同,Canopy聚类最大的特点是不需要事先指定k值(即clustering的个数),因此具有很大的实际应用价值。与其他聚类算法相比,Canopy聚类虽然精度较低,但其在速度上有很大优势,因此可以使用Canopy聚类先对数据进行“粗”聚类,得到k值后再使用K-means进行进一步“细”聚类。这种Canopy+K-means的混合聚类方式分为以下两步:

Step1、聚类最耗费计算的地方是计算对象相似性的时候,Canopy聚类在第一阶段选择简单、计算代价较低的方法计算对象相似性,将相似的对象放在一个子集中,这个子集被叫做Canopy ,通过一系列计算得到若干Canopy,Canopy之间可以是重叠的,但不会存在某个对象不属于任何Canopy的情况,可以把这一阶段看做数据预处理;

Step2、在各个Canopy 内使用传统的聚类方法(如K-means),不属于同一Canopy 的对象之间不进行相似性计算。
从这个方法起码可以看出两点好处:首先,Canopy 不要太大且Canopy 之间重叠的不要太多的话会大大减少后续需要计算相似性的对象的个数;其次,类似于K-means这样的聚类方法是需要人为指出K的值的,通过Stage1得到的Canopy 个数完全可以作为这个K值,一定程度上减少了选择K的盲目性。

二、聚类精度

对传统聚类来说,例如K-means、Expectation-Maximization、Greedy Agglomerative Clustering,某个对象与Cluster的相似性是该点到Cluster中心的距离,那么聚类精度能够被很好保证的条件是:

  对于每个Cluster都存在一个Canopy,它包含所有属于这个Cluster的元素。

  如果这种相似性的度量为当前点与某个Cluster中离的最近的点的距离,那么聚类精度能够被很好保证的条件是:

  对于每个Cluster都存在若干个Canopy,这些Canopy之间由Cluster中的元素连接(重叠的部分包含Cluster中的元素)。

  数据集的Canopy划分完成后,类似于下图:

[置顶] Spark canopy算法_第1张图片

三、Canopy算法流程

(1)、将数据集向量化得到一个list后放入内存,选择两个距离阈值:T1和T2,其中T1 > T2,对应上图,实线圈为T1,虚线圈为T2,T1和T2的值可以用交叉校验来确定;

  (2)、从list中任取一点P,用低计算成本方法快速计算点P与所有Canopy之间的距离(如果当前不存在Canopy,则把点P作为一个Canopy),如果点P与某个Canopy距离在T1以内,则将点P加入到这个Canopy;

  (3)、如果点P曾经与某个Canopy的距离在T2以内,则需要把点P从list中删除,这一步是认为点P此时与这个Canopy已经够近了,因此它不可以再做其它Canopy的中心了;

  (4)、重复步骤2、3,直到list为空结束。
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import scala.collection.mutable.HashSet
import spark.SparkContext

object Canopy {

  def main(args: Array[String]): Unit = {
    val master = args(0)
    val input = args(1)
    val slices = args(2).toInt
    val output = args(3)
    val t1 = args(4).toDouble
    val t2 = args(5).toDouble

    val lable_separator = "\001"
    val vector_separator = ","

    val log = LoggerFactory.getLogger("Canopy")
    val sc = new SparkContext(master, "Canopy", null, Nil)
    try {
      val pairs = sc.textFile(input).map { line =>
        val pair = line.split(lable_separator)
        (pair(0), pair(1).split(vector_separator).map(_.toDouble))
      }
      val map_centers = new HashSet[(String, Array[Double])]
      val raw_center_pairs = pairs.map(v =>
        (v._1, canopy_(v, map_centers, t2))).filter(a => a._2 != null).collect().toList

      val center_pairs = new HashSet[(String, Array[Double])]

      for (i <- 0 until raw_center_pairs.size) {
        canopy_(raw_center_pairs(i)._2, center_pairs, t2)
      }
      sc.makeRDD(center_pairs.toList, 1).map { pair =>
        pair._1 + pair._2.mkString(",")
      }.saveAsTextFile(output)
    } catch {
      case e: Exception =>
        log.info(e.getStackTrace().mkString("\n"))
    }
    sc.stop()
  }

  def measure(v1: Array[Double], v2: Array[Double]): Double = {
    var distance = 0.0
    val aa = if (v1.length < v2.length)
      v1.length
    else
      v2.length
    for (i <- 0 until aa) {
      distance += scala.Math.pow((v1(i) - v2(i)), 2)
    }
    distance
  }

  def canopy_(p0: (String, Array[Double]), pair: HashSet[(String, Array[Double])], t2: Double): (String, Array[Double]) = {
    if (!pair.exists(p => measure(p._2, p0._2) < t2)) {
      pair += p0
      p0
    } else {
      null
    }
  }
}


参考
程序参考

你可能感兴趣的:(算法,spark,CANOPY)