上一節:Spark數據挖掘-基於 K 均值聚類的網絡流量異常檢測(1): 數據探索模型初步實驗算法
經過上一節的介紹,已經對數據集長什麼樣子,模型如何工做的有了一個基本的瞭解,本節重點就是探討如何優化 K-means 聚類模型。微信
首先探討的第一個問題是 K-means 的類別 K 該如何肯定?爲了回答這個問題,須要先回答下面的問題:網絡
第一個想到的答案就是:當K肯定下來以後,模型獲得K個類中心,每一個樣本也歸屬到本身的類,那麼每一個樣本距離類中心的距離也是知道的,將全部樣本距離類中心的距離相加, 這個總距離數值越小越好(固然看總距離的平均值也是同樣的,由於樣本數量是相同的)。
這彷佛頗有道理,可是細細一想就發現,這個有點不靠譜,爲何?當你的類別數目等於樣本數量的時候每個樣本都是類中心,那這個距離相加爲0,是否是最小的?也就是說 這個總距離會隨着類個數增長而減小。那這個K值如何取?
很簡單,取總距離降低拐點處的 K 值。由於總距離隨着 K 值的增長而減小可是減小的幅度不是每次都會很大,總會有一個K值以後,距離降低趨於平緩,這個點就是拐點。
這個思路其實和主成份分析找主成份的思路是一致的:也是找碎石圖中的拐點。
下面將會以實戰的方式,來具體求出這個最佳的 K 值,具體含義代碼都有相應註釋:機器學習
/** * 歐幾里德距離計算 * zip 先將兩個相同長度的向量按照對應的索引位置合爲一個 * 計算(a - b)的 平方和在求根 * @param a 向量a * @param b 向量b * @return */ def distance(a: Vector, b: Vector): Double = { val s = a.toArray.zip(b.toArray).map(p => p._1 - p._2) .map(d => d * d).sum Math.sqrt(s) } /** * 計算每個數據點離類中心的距離 * @param datum * @param model */ def distToCentroid(datum: Vector, model: KMeansModel): Double = { val cluster = model.predict(datum) val centroid = model.clusterCenters(cluster) distance(datum, centroid) } /** * 從新指定聚類的個數從新聚類 * @param data 訓練樣本 * @param k 聚類類別個數 */ def clusteringScore(data: RDD[Vector], k: Int): Double = { val kmeans = new KMeans() kmeans.setK(k) val model = kmeans.run(data) //計算每個點離中心點的距離 data.map { datum => distToCentroid(datum, model) }.mean() } //驅動代碼:對不一樣K值計算總距離平均值 (5 to 40 by 5).map(k => (k, clusteringScore(data, k))).sortBy(_._2).foreach(println)
上面的結果每次運行都有可能不同,由於 K-means 算法是隨機初始化類中心的,下面是某次運行的結果:分佈式
(5,1938.858341805931) (10,1689.4950178959496) (15,1381.315620528147) (20,1318.256644582388) (25,932.0599419255919) (30,594.2334547238697) (35,829.5361226176625) (40,424.83023056838846)
從上面的結果中能夠發現,隨着 K 值的增長,總距離確實是在減小,可是 35 這個 K 值對應的距離卻比 30 對應的距離大,這是爲何? 其實 K 均值聚類算法並不會嘗試去找到全局最優,它還受到其餘參數的影響,請看下面的分析。ide
實際上 Spark 實現的是 K-means|| 初始質心選擇算法。 K-means++ and K-means|| 都是儘量選擇 多樣的分離的初始類中心的算法的變體,目的都是爲了獲得更加可靠的好的聚類結果。可是它們裏面仍是存在 隨機的因素,致使得不到全局最優解,而在某個局部最優解就提早中止了計算。固然能夠調整其餘參數使得小狗狗更好, 下面分佈介紹另外幾個重要的參數:學習
下面從新調整參數,而且增長測試的次數,爲了增長測試速度,每個參數能夠並行啓動,而不是等另外一個結束才啓動, 這就是分佈式上的分佈式,下面直接給出代碼:測試
/** * 從新指定聚類的個數從新聚類 * @param data 訓練樣本 * @param k 聚類類別個數 */ def clusteringScore(data: RDD[Vector], k: Int): Double = { val kmeans = new KMeans() kmeans.setRuns(10) kmeans.setEpsilon(1.0e-6) kmeans.setK(k) val model = kmeans.run(data) data.map { datum => distToCentroid(datum, model) }.mean() } //驅動代碼 (30 to 100 by 10).par.map(k => (k, clusteringScore(data, k))) .toList.sortBy(_._2).foreach(println)
K-means 算法原理大數據
歡迎關注本人微信公衆號,會定時發送關於大數據、機器學習、Java、Linux 等技術的學習文章,並且是一個系列一個系列的發佈,無任何廣告,純屬我的興趣。
優化