这篇代码是KNN的优化,代码就是
flatMap里面的结构转换
和aggregate的应用感觉有点难理解
但是思想简单
首先要知道这个优化是基于 有个大数据的思想:分而治之
求出一份数据最大的前一百个数 如果数据太多,内存太小,则将数据切割成多份
每份求出前一百 然后这多份的前一百再求出真正的前一百
TODO 优化的地方
使用 广播变量进行广播
2、没有跟上一个代码一样嵌套两层循环 比如y在外面 x在里面
y {
x {
结果:(x, y)
}
}
不是像上面那样了 而是成了 Array[(String, (Double, String))] 这样的格式
这次是y的RDD在外面 x这个array在里面了 它的所有位置点在里面进行匹配
Array[(x1, (dis, y.label))] 即每个未知点与当前flatMap里面的已知点的距离和已知点的标签 组成的数组
注意使用了flatMap 所以将数组去掉了 在RDD里面是一个个的[(String, (Double, String))]
这样就得到了想要的 所有的未知点与已知点两两匹配得到的每一条数据
2、有着多个数组的RDD进行aggregateByKey 这个key就是未知点 ok这就是分组了 然后再进行计算
- 有着多个数组的RDD进行aggregateByKey有两个括号3个参数, 这都是对value的操作 即(Double, String)
就是指距离和标签 先是初始值 然后是分区内的计算 然后是分区间的计算
初始值:不可变的TreeSet,TreeSet可以自动排序但缺点是不能存重复值,这里先忽略
(Double, String) 将距离放在前边的原因是要安装key距离排序 这样调换一下就不用手动写排序实现类了
不可变是为了线程安全 不可变是指对象不可变
分区内2个参数(buffer, value: (Double, String))先是buffer初始数组
然后是每一条数据的value值 简单每条都加进buffer即可
因为放进TreeSet的都是自动排好序的 所以始终拿前K个数据 这样不用携带大量数据 省存储还排序快
分区间也是2个参数 (buffer1, buffer2) 这样就是两个buffer相加合在一起了,还是一直保持只取前K个
正好TreeSet默认是升序 我们需要的也是距离未知点最近的 ok
这样得出的结果就是每个未知点与距离最近的前9个已知点的distance和label
就是这样似的RDD[(String, TreeSet[(Double, String)])]
然后value就不需要distance了,map一下,做9个label的WordCount即可 然后取出最多的label
即为KNN推测结果
package IrisKNN.teacher
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.immutable
import scala.collection.immutable.TreeSet
/**
* Created by Shi shuai RollerQing on 2019/12/30 19:26
*/
object KNNDemo2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getCanonicalName)
val sc = new SparkContext(conf)
val K: Int = 9
val data: RDD[String] = sc.textFile("C:\\Users\\HP\\IdeaProjects\\sparkCore\\data\\iris.dat")
val dataRDD: RDD[LabeledPoint] = data.map(line => {
val arr: Array[String] = line.split(",")
if (arr.length == 5) LabeledPoint(arr.last, arr.init.map(_.toDouble))
else LabeledPoint("", arr.map(_.toDouble))
})
val sampleRDD: RDD[LabeledPoint] = dataRDD.filter(_.label != "")
val testData: Array[Array[Double]] = dataRDD.filter(_.label == "").map(_.point).collect()
val testDataBC: Broadcast[Array[Array[Double]]] = sc.broadcast(testData)
val unKnownAndDisAndLabelRDD: RDD[(String, (Double, String))] = sampleRDD.flatMap { rddPoint =>
val points: Array[Array[Double]] = testDataBC.value
// map后的point也是array 所以需要换成String
//然后注意将dis放在前边 label放在后面 方便后续TreeSet根据dis排序
val unKnownAndDisAndLabel: Array[(String, (Double, String))] = points.map(point => (point.mkString(","), (getDistance(point, rddPoint.point), rddPoint.label)))
unKnownAndDisAndLabel
}
val LabeledRDD: RDD[(String, TreeSet[(Double, String)])] = unKnownAndDisAndLabelRDD.aggregateByKey(immutable.TreeSet[(Double, String)]())(
(buffer, value: (Double, String)) => {
val newBuffer = buffer + value
newBuffer.take(K)
},
(buffer1, buffer2) => (buffer1 ++ buffer2).take(K)
)
LabeledRDD.map{case (key, buffer) =>
val labelSorted: List[String] = buffer.toList.map(_._2).groupBy(x => x).mapValues(_.length).toList.sortBy(_._2).reverse.take(1).map(_._1)
(key, labelSorted.toString())
}.foreach(println)
sc.stop()
}
def getDistance(x: Array[Double], y: Array[Double]) = {
import scala.math._
sqrt(x.zip(y).map(t => pow(t._1 - t._2, 2.0)).sum)
}
}
这么写不知道为什么错了。。。我认为是两个
结果没有错误 但是原来的顺序都乱了 可以在getDistance那个地方 不倒换dis和label的位置 然后实现TreeSet的一个自定义排序 本代码就不改了 就是加个实现类的事 写在下一篇的Demo里面吧