使用 Spark MLlib 做 K-means 聚类分析

来源:互联网 发布:蜗蜗网络董事长秀才 编辑:程序博客网 时间:2024/05/16 02:54

Spark 机器学习库介绍

Spark 机器学习库提供了常用机器学习算法的实现,包括聚类,分类,回归,协同过滤,维度缩减等。使用 Spark 机器学习库来做机器学习工作,可以说是非常的简单,通常只需要在对原始数据进行处理后,然后直接调用相应的 API 就可以实现。但是要想选择合适的算法,高效准确地对数据进行分析,您可能还需要深入了解下算法原理,以及相应 Spark MLlib API 实现的参数的意义。
需要提及的是,Spark 机器学习库从 1.2 版本以后被分为两个包,分别是:
spark.mllib
Spark MLlib 历史比较长了,1.0 以前的版本中已经包含了,提供的算法实现都是基于原始的 RDD,从学习角度上来讲,其实比较容易上手。如果您已经有机器学习方面的经验,那么您只需要熟悉下 MLlib 的 API 就可以开始数据分析工作了。想要基于这个包提供的工具构建完整并且复杂的机器学习流水线是比较困难的。
spark.ml
Spark ML Pipeline 从 Spark1.2 版本开始,目前已经从 Alpha 阶段毕业,成为可用并且较为稳定的新的机器学习库。ML Pipeline 弥补了原始 MLlib 库的不足,向用户提供了一个基于 DataFrame 的机器学习工作流式 API 套件,使用 ML Pipeline API,我们可以很方便的把数据处理,特征转换,正则化,以及多个机器学习算法联合起来,构建一个单一完整的机器学习流水线。显然,这种新的方式给我们提供了更灵活的方法,而且这也更符合机器学习过程的特点。
从官方文档来看,Spark ML Pipeline 虽然是被推荐的机器学习方式,但是并不会在短期内替代原始的 MLlib 库,因为 MLlib 已经包含了丰富稳定的算法实现,并且部分 ML Pipeline 实现基于 MLlib。而且就笔者看来,并不是所有的机器学习过程都需要被构建成一个流水线,有时候原始数据格式整齐且完整,而且使用单一的算法就能实现目标,我们就没有必要把事情复杂化,采用最简单且容易理解的方式才是正确的选择。
本文基于 Spark 1.5,向读者展示使用 MLlib API 进行聚类分析的过程。读者将会发现,使用 MLlib API 开发机器学习应用方式是比较简单的,相信本文可以使读者建立起信心并掌握基本方法,以便在后续的学习和工作中事半功倍。

K-means 聚类算法原理
聚类分析是一个无监督学习 (Unsupervised Learning) 过程, 一般是用来对数据对象按照其特征属性进行分组,经常被应用在客户分群,欺诈检测,图像分析等领域。K-means 应该是最有名并且最经常使用的聚类算法了,其原理比较容易理解,并且聚类效果良好,有着广泛的使用。
和诸多机器学习算法一样,K-means 算法也是一个迭代式的算法,其主要步骤如下:

第一步,选择 K 个点作为初始聚类中心。

第二步,计算其余所有点到聚类中心的距离,并把每个点划分到离它最近的聚类中心所在的聚类中去。在这里,衡量距离一般有多个函数可以选择,最常用的是欧几里得距离 (Euclidean Distance), 也叫欧式距离。公式如下:
这里写图片描述
其中 C 代表中心点,X 代表任意一个非中心点。

第三步,重新计算每个聚类中所有点的平均值,并将其作为新的聚类中心点。

最后,重复 (二),(三) 步的过程,直至聚类中心不再发生改变,或者算法达到预定的迭代次数,又或聚类中心的改变小于预先设定的阀值。
在实际应用中,K-means 算法有两个不得不面对并且克服的问题。

聚类个数 K 的选择。K 的选择是一个比较有学问和讲究的步骤,我们会在后文专门描述如何使用 Spark 提供的工具选择 K。

初始聚类中心点的选择。选择不同的聚类中心可能导致聚类结果的差异。
Spark MLlib K-means 算法的实现在初始聚类点的选择上,借鉴了一个叫 K-means||的类 K-means++ 实现。K-means++ 算法在初始点选择上遵循一个基本原则: 初始聚类中心点相互之间的距离应该尽可能的远。基本步骤如下:

第一步,从数据集 X 中随机选择一个点作为第一个初始点。
第二步,计算数据集中所有点与最新选择的中心点的距离 D(x)。
第三步,选择下一个中心点,使得
这里写图片描述
最大。
第四部,重复 (二),(三) 步过程,直到 K 个初始点选择完成。

MLlib 的 K-means 实现
Spark MLlib 中 K-means 算法的实现类 (KMeans.scala) 具有以下参数,具体如下。
下面这个是mlib中类的定义

@Since("0.8.0")class KMeans private (    private var k: Int,    private var maxIterations: Int,    private var runs: Int,    private var initializationMode: String,    private var initializationSteps: Int,    private var epsilon: Double,    private var seed: Long) 

mlib构造函数的定义

def this() = this(2, 20, 1, KMeans.K_MEANS_PARALLEL, 5, 1e-4, Utils.random.nextLong())

参数的含义解释如下:
k 表示期望的聚类的个数。
1、maxInterations 表示方法单次运行最大的迭代次数。
2、runs 表示算法被运行的次数。K-means 算法不保证能返回全局最优的聚类结果,所以在目标数据集上多次跑 K-means 算法,有助于返回最佳聚类结果。
3、initializationMode 表示初始聚类中心点的选择方式, 目前支持随机选择或者 K-means||方式。默认是 K-means||。
4、initializationSteps表示 K-means||方法中的部数。
5、epsilon 表示 K-means 算法迭代收敛的阀值。
6、seed 表示集群初始化时的随机种子。

通常应用时,我们都会先调用 KMeans.train 方法对数据集进行聚类训练,这个方法会返回 KMeansModel 类实例,然后我们也可以使用 KMeansModel.predict 方法对新的数据点进行所属聚类的预测,这是非常实用的功能。

KMeans.train 方法有很多重载方法,这里我们选择参数最全的一个展示。
下面是train方法比较全的一个

@Since("1.3.0")  def train(      data: RDD[Vector],      k: Int,      maxIterations: Int,      runs: Int,      initializationMode: String,      seed: Long): KMeansModel = {    new KMeans().setK(k)      .setMaxIterations(maxIterations)      .setRuns(runs)      .setInitializationMode(initializationMode)      .setSeed(seed)      .run(data)  }

KMeansModel.predict 方法接受不同的参数,可以是向量,或者 RDD,返回是入参所属的聚类的索引号。@Since(“0.8.0”)
def predict(point: Vector): Int = {
KMeans.findClosest(clusterCentersWithNorm, new VectorWithNorm(point))._1
}

/**
* Maps given points to their cluster indices.
*/
@Since(“1.0.0”)
def predict(points: RDD[Vector]): RDD[Int] = {
val centersWithNorm = clusterCentersWithNorm
val bcCentersWithNorm = points.context.broadcast(centersWithNorm)
points.map(p => KMeans.findClosest(bcCentersWithNorm.value, new VectorWithNorm(p))._1)
}
聚类测试数据集简介
我训练和测试过程采用的数据来自于UCI Machine Learning Repository的销售数据
UCI 是一个关于机器学习测试数据的下载中心站点,里面包含了适用于做聚类,分群,回归等各种机器学习问题的数据集。

为了方便验证,我们将这个销售数据转换成了2份aa为训练数据,bb为测试数据

消费数

Channel,Region,Fresh,Milk,Grocery,Frozen,Detergents_Paper,Delicassen2,3,12669,9656,7561,214,2674,13382,3,7057,9810,9568,1762,3293,17762,3,6353,8808,7684,2405,3516,78441,3,13265,1196,4221,6404,507,17882,3,22615,5410,7198,3915,1777,51852,3,9413,8259,5126,666,1795,14512,3,12126,3199,6975,480,3140,5452,3,7579,4956,9426,1669,3321,25661,3,5963,3648,6192,425,1716,7502,3,6006,11093,18881,1159,7425,20982,3,3366,5403,12974,4400,5977,17442,3,13146,1124,4523,1420,549,4972,3,31714,12319,11757,287,3881,2931

案例分析和编码实现

本例中,我们将根据目标客户的消费数据,将每一列视为一个特征指标,对数据集进行聚类分析。代码实现步骤如下

package com.cn.paicimport org.apache.spark.{SparkContext, SparkConf}import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}import org.apache.spark.mllib.linalg.Vectors/** * @author XULU921 */object KMeansClustering {def main (args: Array[String]) {  //需要输入5个参数  //训练数据集文本路径  //测试数据集文本路径  //聚类的个数  //K-means算法的迭代次数  //K-means算法run的次数  if(args.length<5){    println("Usage:KMeansClustering trainingDataFilePath testDataFilePath numClustersnumIterations runTimes")    sys.exit(1);  }  //设置spark application的名字  val conf =new SparkConf().setAppName("Spark MLlib Exercise:K-Means Clustering");  //启动sparkcontext实例  val sc=new SparkContext(conf)  val rawTrainingData = sc.textFile(args(0))  //加载训练数据集,这里需要将数据转换成vectors集合  val parsedTrainingData =rawTrainingData.filter(!isColumnNameLine(_)).map(line => {   Vectors.dense(line.split(",").map(_.trim).filter(!"".equals(_)).map(_.toDouble)) }).cache()   //获取聚类的值  val numClusters = args(2).toInt  //获取算法迭代的次数  val numIterations = args(3).toInt  //获取算法运行的次数  val runTimes =args(4).toInt    var clusterIndex:Int = 0  //通过Kmeans的成员函数train来构造一个KMeansModel实例   val clusters:KMeansModel =KMeans.train(parsedTrainingData, numClusters, numIterations,runTimes)   println("Cluster Number:" + clusters.clusterCenters.length)   println("Cluster Centers Information Overview:")   //遍历打印 k值   clusters.clusterCenters.foreach(x => {   println("Center Point of Cluster " + clusterIndex + ":")   println(x)   clusterIndex += 1 })   //读取测试样本的数据   val rawTestData = sc.textFile(args(1))   //加载测试样本的数据,需要转换成vectors集合   val parsedTestData=rawTestData.map(line =>{   Vectors.dense(line.split(",").map(_.trim).filter(!"".equals(_)).map(_.toDouble))})   //处理测试数据,predict返回的是这个数据属于哪个类。   parsedTestData.collect().foreach(testDataLine => { val predictedClusterIndex:     Int = clusters.predict(testDataLine)   println("The data " + testDataLine.toString + " belongs to cluster " +predictedClusterIndex) })   println("Spark MLlib K-means clustering test finished.")}private def isColumnNameLine(line:String):Boolean = {  if (line != null &&line.contains("Channel")) true else false   }}

该示例程序接受五个入参,分别是
1、训练数据集文件路径
2、测试数据集文件路径
3、聚类的个数
4、K-means 算法的迭代次数
5、K-means 算法 run 的次数

运行示例程序
先上传数据到hdfs上

[root@SZB-L0038787 data]# hadoop fs -ls -R /mlib/drwxr-xr-x   - root supergroup          0 2017-07-28 18:24 /mlib/data-rw-r--r--   3 root supergroup      15021 2017-07-28 18:24 /mlib/data/aadrwxr-xr-x   - root supergroup          0 2017-07-28 21:23 /mlib/out-rw-r--r--   3 root supergroup        312 2017-07-28 21:23 /mlib/out/bb

提交程序

./spark-submit --class com.cn.paic.KMeansClustering --master spark://10.20.23.38:7077 /data/spark.jar hdfs://10.20.23.29:9000/mlib/data/aa hdfs://10.20.23.29:9000/mlib/out/bb 5 30 3

训练得到模型的结果

Cluster Number:5Cluster Centers Information Overview:Center Point of Cluster 0:[1.0869565217391304,2.6956521739130435,49296.086956521736,4983.782608695652,5590.304347826087,8285.782608695652,962.2608695652174,2543.695652173913]Center Point of Cluster 1:[1.201923076923077,2.5480769230769234,21200.057692307695,3886.423076923077,5138.932692307692,4119.8557692307695,1131.519230769231,1690.3365384615386]Center Point of Cluster 2:[1.9113924050632911,2.5063291139240507,4667.5822784810125,11639.101265822785,18289.78481012658,1525.240506329114,8060.316455696202,1594.4556962025317]Center Point of Cluster 3:[1.169642857142857,2.53125,6072.049107142857,3292.9464285714284,4122.933035714285,2451.991071428571,1224.5,996.6428571428571]Center Point of Cluster 4:[1.9000000000000001,2.7,21263.7,37443.3,46710.600000000006,6287.200000000001,21699.4,8743.300000000001]

测试数据得到的分类

The data [2.0,3.0,12669.0,9656.0,7561.0,214.0,2674.0,1338.0] belongs to cluster 3The data [2.0,3.0,7057.0,9810.0,9568.0,1762.0,3293.0,1776.0] belongs to cluster 3The data [2.0,3.0,6353.0,8808.0,7684.0,2405.0,3516.0,7844.0] belongs to cluster 3The data [1.0,3.0,13265.0,1196.0,4221.0,6404.0,507.0,1788.0] belongs to cluster 3The data [2.0,3.0,22615.0,5410.0,7198.0,3915.0,1777.0,5185.0] belongs to cluster 1The data [2.0,3.0,9413.0,8259.0,5126.0,666.0,1795.0,1451.0] belongs to cluster 3The data [2.0,3.0,12126.0,3199.0,6975.0,480.0,3140.0,545.0] belongs to cluster 3The data [2.0,3.0,7579.0,4956.0,9426.0,1669.0,3321.0,2566.0] belongs to cluster 3The data [1.0,3.0,5963.0,3648.0,6192.0,425.0,1716.0,750.0] belongs to cluster 3Spark MLlib K-means clustering test finished.

参考文献:https://www.ibm.com/developerworks/cn/opensource/os-cn-spark-practice4/

原创粉丝点击