大数据进阶之算法——KMeans聚类算法
首先说一下分类和聚类的区别:分类:分类其实就是从特定的数据中挖掘模式,做出相对应的判断。例如对班级的学生进性性别的分类,我事先已经知道只有男性和女性两个分类。聚类:聚类的目的也是将数据分类,但是在事前不知道按照何种标准去分类,完全是靠算法自己来判别各条数据的相似性,相似的就放在一起。聚类和分类最大的不同在于:分类的目标是事先已知的,而聚类则完全不一样,举类事先不知道分类标准是什么,完全靠算法自己去
首先说一下分类和聚类的区别:
分类:
分类其实就是从特定的数据中挖掘模式,做出相对应的判断。例如对班级的学生进性性别的分类,我事先已经知道只有男性和女性两个分类。
聚类:
聚类的目的也是将数据分类,但是在事前不知道按照何种标准去分类,完全是靠算法自己来判别各条数据的相似性,相似的就放在一起。
聚类和分类最大的不同在于:
分类的目标是事先已知的,而聚类则完全不一样,举类事先不知道分类标准是什么,完全靠算法自己去判别。
KMeans
KMeans算法是聚类中最常用最普遍的一种算法,该算法最大的特点就是简单,易于理解,运算速度快。
- 首先输入一个合适的k值,即希望将数据集分成k个分组。
- 从数据集中随机选择k个数据点作为质心
- 对集合中的每个点,计算与质心的距离(欧式距离法),离哪个质心最近,就属于哪个分组。
- 这时从数据集中重新选择一个新的质心。
- 如果新的质心和以前的质心距离小于一定的阈值,可以认为我们进行的聚类算法已经达到期望的结果,算法终止。
- 如果距离差距过大,需要继续迭代
案例:
- 首先我们假设图中的绿点为每个小区的分布图。要在这些小区中开超市。
- 我们自己随机将这些小区分成3组,图中的红色三角形为超市的地点位置。
- 那么分别以这三个质心为中心,将周边的小区分成三个部分,我随机画的,意思看懂就行。
- 我们在这三个部分中分别重新计算质心位置。
- 我们可以发现上图中的质心位置被重新调整之后,聚类的结果也发生了变化。之前分错类的点也重新进行了调整。现在的聚类结果离我们的理想结果越来越近了。但是还没达到最佳的聚类效果。我们需要不停的迭代更换质心的位置,直到质心的位置不再有很大的变化或者新老质心的距离差距小于阈值再或者达到迭代次数的阈值,聚类结束。
项目代码:
以下是我项目中的KMeans算法代码
设置不同的质心的数量来找出一个最合适的质心数量作为K值
// 使用kmeans算法进行分组
// 计算根据不同的质心点计算所有的距离
// 记录不同质心点距离的集合
val distList = ListBuffer[Double]()
for (i <-2 to 35){
val kms = new KMeans().setFeaturesCol("feature").setK(i)
val model = kms.fit(resdf)
distList.append(model.computeCost(resdf))
}
注意:
SparkMLlib在KMeansModel里实现了computeCost方法,这个方法通过计算数据集中所有的点到最近中心点的平方和来衡量聚类的效果。一般来说,同样的迭代次数,这个cost值越小,说明聚类的效果越好。但在实际使用过程中,必须还要考虑聚类结果的可解释性,不能一味地选择cost值最小的那个k。比如我们如果考虑极限情况,如果数据集有n个点,如果令k=n,每个点都是聚类中心,每个类都只有一个点,此时cost值最小为0。但是这样的聚类结果显然是没有实际意义的。
调用java的jfreechart画图工具来画出质心个数和距离之间的关系图
class LineGraph(appName:String) extends ApplicationFrame(appName){
def this(appName:String,title:String,list:ListBuffer[Double]){
this(appName)
val lineChart = ChartFactory.createLineChart(title,
"质点", "距离", createDataset(list),
PlotOrientation.VERTICAL, true, true, false)
// 设置窗口字体
val font = new Font("黑体",Font.BOLD,20)
lineChart.getTitle.setFont(font) //设置标题
lineChart.getLegend.setItemFont(font) //设置标签字体
lineChart.getCategoryPlot.getDomainAxis.setLabelFont(font) //设置x轴标签字体
lineChart.getCategoryPlot.getRangeAxis.setLabelFont(font) //设置Y轴标签字体
val chartPanel = new ChartPanel(lineChart)
chartPanel.setPreferredSize(new java.awt.Dimension(1200, 1000))
setContentPane(chartPanel)
}
def createDataset(list:ListBuffer[Double]) = {
val dataset = new DefaultCategoryDataset();
var point = 2;
for(dst<- list){
dataset.addValue(dst,"dist",point+"")
point+=1
}
dataset;
}
}
//distList为上面for循环计算出来的不同质心个数距离的平均值
val chart = new LineGraph("app","KMeans质心距离",distList)
chart.pack()
RefineryUtilities.centerFrameOnScreen(chart)
chart.setVisible(true)
最终得到一张折线图
(运行时间很长,因为不同的质心个数都要计算每个点到各个质心的距离,最后再求均值,很耗时间,大概要半个小时左右,看电脑性能的)
我们最终选择30作为K值,因为在30以后逐渐趋于平缓,没有过大的变化了
val kms = new KMeans().setFeaturesCol("feature").setK(30)
val user_group_tab = kms.fit(resdf).transform(resdf)
.withColumnRenamed("prediction","groups").drop("feature").show()
最终分群结果:
更多推荐
所有评论(0)