Spark集羣下的K-Means算法
引言
1.1背景
由於剛剛開始學習Spark平臺,希望通過學習基礎的Spark機器學習算法的使用來對Spark平臺以及Scala語言進行一個簡單的瞭解和使用。在這裏我首先以最常見的機器學習的K-Means聚類算法。後期希望能夠在Spark上實現AHP算法。
1.2編寫目的
在學習過程中,發現了有許多介紹K-Means算法原理博客和文章,也有許多關於K-Means的代碼(其中包括有C、C++、Java、Scal
a等等),但是從項目的構建,數據的選取到最後的聚類結果,很少有對整個運行過程有一個系統的介紹。在這裏我避開K-Means
原理的介紹,重點闡述一下整個Spark環境上如何運行Scala的K-Means算法。
1.3參考資料
在這篇博客的編寫過程中,主要參考了《kmeans算法詳解與spark實戰》,《Spark下的K-Means算法》,感謝他們。
在項目的運行中,採用的也是博客中推薦的數據集《Wholesale customers Data Set 》
K-Means實現簡介
2.1完整代碼
我們採用上述註明的參考博客中的代碼進行運行,具體的代碼如下所示:
package KMeansTest
import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.{SparkConf, SparkContext}
object Kmeans {
def main(args: Array[String]): Unit = {
val conf =new SparkConf()
val sc = new SparkContext(conf)
val rawTrainingData = sc.textFile("file:///usr/local/spark/data_train")
val parsedTrainingData =rawTrainingData.map{
line=>
Vectors.dense(line.split(',').map(_.toDouble))
}.cache()
val numClusters = 8
val numIterations = 30
val runTimes = 3
var clusterIndex: Int = 0
val clusters:KMeansModel= KMeans.train(parsedTrainingData,numClusters,numIterations,runTimes)
println("cluster Number:"+clusters.clusterCenters.length)
println("Cluster centers Information Overview")
clusters.clusterCenters.foreach(
x => {
println("Center Point of Cluster" + clusterIndex + ":")
println(x)
clusterIndex +=1
})
val rawTestData = sc.textFile("file:///usr/local/spark/data_test")
val parsedTestData =rawTestData.map{
line=>
Vectors.dense(line.split(',').map(_.toDouble))
}
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")
}
}
2.2依賴分析
在上述代碼中,程序的依賴如下所示:
import org.apache.spark.{SparkContext, SparkConf}
import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}
import org.apache.spark.mllib.linalg.Vectors
其中調用不同的庫的作用分別如下:
(1)SparkContext是Spark Driver的核心,用於連接Spark集羣,創建RDD、累加器、廣播變量等等。
(2)SparkConf爲SparkContext的組件,是SparkContext的配置類,配置信息以鍵值對的形式存在。
(3)mllib.clustering是Spark Mlib庫中提供的用於做聚類的依賴。
(4)mllib.linalg.Vectors是分別用來保存MLlib的本地向量的,其中包含Dense和Sparse兩種分別用力來保存稠密向量和稀疏向量。
2.3屬性設置
val conf = new SparkConf().setAppName("K-Means Clustering")
val sc = new SparkContext(conf)
在上述代碼中,我們通過SparkConf()設置程序的參數,並傳遞到SparkContext中。
2.4讀取並分析數據
sc.textFile("file:///usr/local/spark/data_train")
val parsedTrainingData =rawTrainingData.map{
line=>
Vectors.dense(line.split(',').map(_.toDouble))
}.cache()
在上述代碼,中我們通過textFile讀取聚類文件中的數據,並將數據處理轉化成double類型,在數據集中數據之間通過‘,’隔開。
由於我們在UCI上下載到的數據集合中第一列是每一行屬性代表的含義,我們通過isColumnNameLine(_)來進行判斷。判斷代碼如下:
private def isColumnNameLine(line: String): Boolean = {
if (line != null && line.contains("Channel")) true
else false
}
如果在該行中存在“Channel”子串,我們則不對該行進行處理。
2.5進行聚類訓練
val numClusters = 8
val numIterations = 30
val runTimes = 3
var clusterIndex: Int = 0
val clusters: KMeansModel =KMeans.train(parsedTrainingData, numClusters, numIterations, runTimes)
println("Cluster Number:" + clusters.clusterCenters.length)
println("Cluster Centers Information Overview:")
clusters.clusterCenters.foreach(
x => {
println("Center Point of Cluster " + clusterIndex + ":")
println(x)
clusterIndex += 1
})
根據上述代碼可知,首先我們設置具體的聚類參數:
val numClusters = 8
val numIterations = 30
val runTimes = 3
var clusterIndex: Int = 0
其中:numClusters = 8表示一共聚成了多少個簇,val numIterations = 30表示迭代的次數,val runTimes =
3表示運行的時間,var clusterIndex: Int = 0用來記錄簇的索引。
在設置完聚類參數後,我們通過具體的代碼利用KMeans.train()傳入參數對數據進行聚類分析,並返回聚類模型,輸出每一
個簇的中心點。
val clusters: KMeansModel =KMeans.train(parsedTrainingData, numClusters, numIterations, runTimes)
println("Cluster Number:" + clusters.clusterCenters.length)
println("Cluster Centers Information Overview:")
clusters.clusterCenters.foreach(
x => {
println("Center Point of Cluster " + clusterIndex + ":")
println(x)
clusterIndex += 1
})
2.6結果顯示
在聚類完成後,我們再次讀取數據集合中的數據信息,通過數據集合中的數據信息,並通過predict來預測每一個數據對象所
屬的簇。具體的代碼如下所示:
val rawTestData = sc.textFile("file:///usr/local/spark/data_train")
val parsedTestData =rawTestData.map{
line=>
Vectors.dense(line.split(',').map(_.toDouble))
}.cache()
parsedTestData.collect().foreach(testDataLine => {
val predictedClusterIndex:
Int = clusters.predict(testDataLine)
println("The data " + testDataLine.toString + " belongs to cluster " +
predictedClusterIndex)
})
數據集格式如下:
Channel | Region | Fresh | Milk | Grocery | Frozen | Detergents_Paper | Delicassen |
---|---|---|---|---|---|---|---|
2 | 3 | 12669 | 9656 | 7561 | 214 | 2674 | 1338 |
2 | 3 | 7057 | 9810 | 9568 | 1762 | 3293 | 1776 |
2 | 3 | 6353 | 8808 | 7684 | 2405 | 3516 | |
1 | 3 | 13265 | 1196 | 4221 | 6404 | 507 | 1788 |
2 | 3 | 22615 | 5410 | 7198 | 3915 | 1777 | 5185 |
根據UCI上對數據的介紹和上圖可以知道,該數據集合爲8維的數據集,其中對應的不同屬性分別爲:
Fresh:表示在新鮮產品的年度支出上。
Milk:表示一年內在奶制產品上的消費。
GROCERY:表示一年在零食上的消費。
FROZEN:表示一年在冷凍食品上的消費
DETERGENTS_PAPER:表示一年在洗滌用品和紙上的消費。
DELICATESSEN:表示一年在熟食上的消費。
CHANNEL:表示購買的渠道:分別爲團購和零售購買。
REGION:表示顧客所屬的地區,分別爲Lisnon, Oporto or Other
注意:在運行過程中,下載的文件爲.csv文件,我們只需要把後綴名去掉,則文件中每一個數據對象的屬性之間用‘,’隔開。
項目構建
3.1 Maven 構建項目
項目的IDE使用的是IDEA 2017,項目依賴管理採用的是Maven(當然也可以採用SBT,一樣的)。具體的建立過程就不在詳細敘述,不懂可以自行百度(很多博客都有寫)
3.2 導入spark的jar包
在項目中加入spark-assembly-1.6.1-hadoop2.6.0.jar包,編寫程序是會用到。
該jar的路徑位於Spark 解壓目錄下的lib文件夾,如下:
3.3 將項目編譯並生成Jar包
在IDEA 中依次打開File—>Project Structure—>Artifacts, 點擊“+”號,選擇需要生成JAR包的項目和main函數。
在IDEA 中點擊Build—>Build Artifacts—>build .如下面的圖中所示,生成的jar包放在項目文件下的\out目錄中。
3.4 在Spark平臺上運行KMeans算法Jar包
啓動Spark平臺,本文中使用的集羣方式爲Spark on YARN,將相應的訓練數據和測試數據copy到對應的路徑中。Spark submit 提交的格式如下:
/usr/local/spark/bin/spark-submit --class KMeansTest.Kmeans /home/hadoop/Downloads/dl.jar
3.5 聚類結果
本文的數據量較小,運算時間比較快,結果如下: