首页 > 代码库 > kmeans

kmeans

如果是自己写kmeans的话,会怎么写呢?
首先kmeans的算法步骤是
随机选取k个点作为初始的簇心,接着计算各个点到各个簇心的距离,将最近的簇心作为该点的簇心。
接着对相同簇心的点做平均,得到下一个簇心
接着就是不停地迭代,知道收敛为止
那么哪些步骤可以并行计算呢?
这里主要有两部分计算量
第一部分是计算各个点到各个簇心的距离,并选取最短的簇心作为自己的簇心
第二部分是计算每个簇的均值从而获得下个迭代的簇心
目前想到的是:
比如有100w条数据,一共分成10个Partition,需要分成5个簇,那么首先将这个k个簇心分发到这10个Partition中,接着对每个Partition中的数据求到这5个簇心的最短簇心,接着利用reduceByKey计算下一个簇心(reduceByKey会首先计算各个Partition中相同的key值)
好吧,接下来看看spark中是怎么做的
首先KMeans调用了train方法:
  1. def train(
  2. data: RDD[Vector],
  3. k: Int,
  4. maxIterations: Int,
  5. runs: Int,
  6. initializationMode: String): KMeansModel = {
  7. new KMeans().setK(k)
  8. .setMaxIterations(maxIterations)
  9. .setRuns(runs)
  10. .setInitializationMode(initializationMode)
  11. .run(data)
  12. }
所以这里返回的是KMeansModel,这里主要设置了最大的迭代次数,设置簇数目,setRuns是设置并行数,
这里最重要的就是run方法了。
接下来看run
  1. def run(data: RDD[Vector]): KMeansModel = {
  2. if (data.getStorageLevel == StorageLevel.NONE) {
  3. logWarning("The input data is not directly cached, which may hurt performance if its"
  4. + " parent RDDs are also uncached.")
  5. }
  6. // Compute squared norms and cache them.
  7. //求2范数
  8. val norms = data.map(Vectors.norm(_, 2.0))
  9. norms.persist()
  10. //将向量和平方和zip起来
  11. val zippedData = data.zip(norms).map { case (v, norm) =>
  12. new VectorWithNorm(v, norm)
  13. }
  14. //这个是大头
  15. val model = runAlgorithm(zippedData)
  16. //原来还能主动unpersist的,涨姿势了
  17. norms.unpersist()
  18. // Warn at the end of the run as well, for increased visibility.
  19. if (data.getStorageLevel == StorageLevel.NONE) {
  20. logWarning("The input data was not directly cached, which may hurt performance if its"
  21. + " parent RDDs are also uncached.")
  22. }
  23. model
  24. }
这里解释下Vectors.norm(_,2.0)的作用
这里其实是在求2范数,怎么求范数呢?
技术分享这是个求P范数
所以这里的2范数其实就是各个维度的属性值平方和的开方
顺便看下norm的源码
  1. def norm(vector: Vector, p: Double): Double = {
  2. require(p >= 1.0, "To compute the p-norm of the vector, we require that you specify a p>=1. " +
  3. s"You specified p=$p.")
  4. val values = vector match {
  5. case DenseVector(vs) => vs
  6. case SparseVector(n, ids, vs) => vs
  7. case v => throw new IllegalArgumentException("Do not support vector type " + v.getClass)
  8. }
  9. val size = values.length
  10. if (p == 1) {
  11. var sum = 0.0
  12. var i = 0
  13. while (i < size) {
  14. sum += math.abs(values(i))
  15. i += 1
  16. }
  17. sum
  18. } else if (p == 2) {
  19. var sum = 0.0
  20. var i = 0
  21. while (i < size) {
  22. sum += values(i) * values(i)
  23. i += 1
  24. }
  25. math.sqrt(sum)
  26. } else if (p == Double.PositiveInfinity) {
  27. var max = 0.0
  28. var i = 0
  29. while (i < size) {
  30. val value = math.abs(values(i))
  31. if (value > max) max = value
  32. i += 1
  33. }
  34. max
  35. } else {
  36. var sum = 0.0
  37. var i = 0
  38. while (i < size) {
  39. sum += math.pow(math.abs(values(i)), p)
  40. i += 1
  41. }
  42. math.pow(sum, 1.0 / p)
  43. }
  44. }
额,似乎没啥好说的,一般来说用1,2,正无穷范数比较多,所以这里单独列出这三个来了。
接下来就主要分析runAlgorithm这个函数(话说这名字取得有点粗糙啊,你runKmeans都比这个好)
这个函数主要的工作就我上面说的那样,只是里面加了一些东西,不太理解。


kmeans