下麵是章節聚類的內容(其他內容參見全文目錄)
聚類是一個無監督學習問題,我們基於相似的特性將數據分組成多個子集。聚類通常用於探索性分析或者作為分層監督學習管道(每個簇訓練不同的分類或者回歸模型)的組件。
MLlib支持下麵的幾個模型:
- K均值(K-means)
- 高斯混合(Gaussian mixture)
- 冪迭代聚類(Power iteration clustering (PIC))
- 隱含狄利克雷分布(Latent Dirichlet allocation (LDA))
- 流式K均值(Streaming k-means)
K均值(K-means)
K均值(k-means)是最通用的聚類算法之一,該算法將數據點聚類為指定數量的簇(注:基本算法原理是隨機挑選N個中心點,每輪計算所有點到中心點的距離,並將點放到最近的中心,然後均值更新中心點,然後重複上述過程直至收斂,收斂的判斷依據是距離閾值)。MLLib的實現包含了 k-means++的並行計算變體,該算法也叫kmeans||。它有下列參數:
- k 需要聚簇的數量
- maxIterations 最大迭代次數
- initializationMode 指定初始化的模式,可以是隨機初始化也可以是k-means||初始化 (k-means||初始化不全是隨機選點,而是使用一個算法使選的點盡可能分散).
- runs 執行K均值聚簇算法的次數 (k-means不保證能找到全局最優解,同一數據集上執行多次的話,可以返回更好的聚簇結果)。
- initializationSteps 使用k-means|| 算法選初始點時最多迭代的次數.
- epsilon 判定k-means是否收斂的距離閾值(聚簇中心前後兩次的差值小於epsilon即達到收斂條件)
補充1:kmeans的損失函數。其中(x1, x2, …, xn)是點集,每個點是d維向量,S是聚類的k個簇,μi 是Si 中所有點的均值)。這個損失函數也叫WSSS( within set sum of square)
補充2:kmeans++方法:
kmeans++算法的主要工作體現在種子點的選擇上,基本原則是使得各個種子點之間的距離盡可能的大,但是又得排除噪聲的影響。 以下為基本思路:[1]
1、從輸入的數據點集合(要求有k個聚類)中隨機選擇一個點作為第一個聚類中心
2、對於數據集中的每一個點x,計算它與最近聚類中心(指已選擇的聚類中心)的距離D(x)
3、選擇一個新的數據點作為新的聚類中心,選擇的原則是:D(x)較大的點,被選取作為聚類中心的概率較大
4、重複2和3直到k個聚類中心被選出來
5、利用這k個初始的聚類中心來運行標準的k-means算法
下麵的示例可以使用PySpark Shell來測試。
在下麵的示例中,首先導入並解析數據,然後使用KMeans將數據聚為2類(期望的簇數量需要作為參數傳遞給算法)。然後計算了WSSSE(集合內平方誤差和)。我們可以通過增加k來降低這個錯誤評估指標。事實上,最優的k通常對應WSSSE曲線中的拐點。
from pyspark.mllib.clustering import KMeans
from numpy import array
from math import sqrt
# Load and parse the data
data = sc.textFile("data/mllib/kmeans_data.txt")
parsedData = data.map(lambda line: array([float(x) for x in line.split(' ')]))
# Build the model (cluster the data)
clusters = KMeans.train(parsedData, 2, maxIterations=10,
runs=10, initializationMode="random")
# Evaluate clustering by computing Within Set Sum of Squared Errors
def error(point):
center = clusters.centers[clusters.predict(point)]
return sqrt(sum([x**2 for x in (point - center)]))
WSSSE = parsedData.map(lambda point: error(point)).reduce(lambda x, y: x + y)
print("Within Set Sum of Squared Error = " + str(WSSSE))
高斯混合
高斯混合模型 表達的是一種混合分布,所有點都來自於k個高斯子分布中的一個,每個點都對應一個相應的概率。在MLlib的實現中,對於給定的樣本集,使用最大期望算法(EM)來引導最大似然模型。算法實現由下列參數:
- k 目標聚簇數量
- convergenceTol 兩次迭代損失(log-likelihood)變化的容忍度.
- maxIterations 收斂之前可以運行的最大迭代次數
- seed 隨機數的種子。
補充:
多維度(多分量)數據的高斯混合聚類原理:
目標函數是log似然函數(log-likelihood):
相關符號說明:
- πk : 第k個分布被選中的概率。
- uk: 第k個分布的均值向量(維度是d)。
- Σk: 第k個分布的協方差矩陣(d x d的矩陣)。
- N(xi |uk , Σk)是多分量高斯分布的概率密度函數。
1. 初始化:為πk , uk , Σk生成隨機初始值。(滿足約束:K個πk的和為1)
2. Expectation計算:估計樣本由每個高斯分布生成的概率:對於數據x它由k個分布生成的概率為(第二個等式是D維變量的高斯密度函數):
3. 最大化似然函數:在當前這一輪迭代中, 取值如下時,似然函數最大(這些公式經過一係列的數學推導得到(省略1000字)):
4. 重複2,3直至收斂。
示例
下麵的示例中,首先導入並解析數據,然後使用高斯混合 將數據聚為兩類。最後輸出混合模型的參數。
from pyspark.mllib.clustering import GaussianMixture
from numpy import array
# Load and parse the data
data = sc.textFile("data/mllib/gmm_data.txt")
parsedData = data.map(lambda line: array([float(x) for x in line.strip().split(' ')]))
# Build the model (cluster the data)
gmm = GaussianMixture.train(parsedData, 2)
# output parameters of model
for i in range(2):
print ("weight = ", gmm.weights[i], "mu = ", gmm.gaussians[i].mu,
"sigma = ", gmm.gaussians[i].sigma.toArray())
冪迭代聚類 (PIC)
對於圖的頂點聚類(頂點相似度作為邊的屬性)問題,冪迭代聚類(PIC)是高效並且易擴展的算法(參考: Lin and Cohen, Power Iteration Clustering)。MLlib包含了一個使用GraphX(MLlib)為基礎的實現。算法的輸入是RDD[srcID, dstID, similarity],輸出是每個頂點對應的聚類的模型。相似度(similarity)必須是非負值。PIC假設相似度的衡量是對稱的,也就是說在輸入數據中,(srcID, dstID)順序無關(例如:<1, 2, 0.1>, <2, 1, 0.1等價),但是隻能出現一次。輸入中沒有指定相似度的點對,相似度會置0。MLlib中的PIC實現具有下列參數:
- k: 聚簇的數量
- maxIterations: 最大迭代次數
- initializationMode: 初始化模式:默認值“random”,表示使用一個隨機向量作為頂點的聚類屬性;也可以是“degree”,表示使用歸一化的相似度和(作為頂點的聚類屬性)。
示例
下麵的代碼片段說明了如何使用MLlib中的PIC(這裏是Scala版,Python版後續才會實現)
PowerIterationClustering 實現了PIC算法。它的輸入是以RDD[srcId :Long, dstId: Long, similarity: Double]元組表示的關係矩陣。然後調用PowerIterationClustering.run並返回PowerIterationClusteringModel,它包含了計算出的類分配信息。
import org.apache.spark.mllib.clustering.PowerIterationClustering
import org.apache.spark.mllib.linalg.Vectors
val similarities: RDD[(Long, Long, Double)] = ...
val pic = new PowerIteartionClustering()
.setK(3)
.setMaxIterations(20)
val model = pic.run(similarities)
model.assignments.foreach { a =>
println(s"${a.id} -> ${a.cluster}")
}
隱含狄利克雷分布 (LDA)
隱含狄利克雷分布(LDA) 是一個主題模型,它能夠推理出一個文本文檔集合的主體。LDA可以認為是一個聚類算法,原因如下:
- 主題對應聚類中心,文檔對應數據集中的樣本(數據行)
- 主題和文檔都在一個特征空間中,其特征向量是詞頻向量。
- 跟使用傳統的距離來評估聚類不一樣的是,LDA使用評估方式是一個函數,該函數基於文檔如何生成的統計模型。
LDA以詞頻向量表示的文檔集合作為輸入。然後在最大似然函數上使用期望最大(EM)算法 來學習聚類。完成文檔擬合之後,LDA提供:
- Topics: 推斷出的主題,每個主體是單詞上的概率分布。
- Topic distributions for documents: 對訓練集中的每個文檔,LDA給了一個在主題上的概率分布。
LDA參數如下:
- k: 主題數量(或者說聚簇中心數量)
- maxIterations: EM算法的最大迭代次數。
- docConcentration: 文檔在主題上分布的先驗參數。當前必須大於1,值越大,推斷出的分布越平滑。
- topicConcentration: 主題在單詞上的先驗分布參數。當前必須大於1,值越大,推斷出的分布越平滑。
- checkpointInterval: 檢查點間隔。maxIterations很大的時候,檢查點可以幫助減少shuffle文件大小並且可以幫助故障恢複。
注意:當前在MLlib中,LDA是一個新特性,部分函數還沒有實現。特別是,目前還不支持新文檔的預測。另外也沒有Python的API。這些功能後續會添加進來。
示例(Scala)
下麵的例子中,首先導入詞頻向量表示的文檔預料,然後使用LDA 推測文檔的3個主題。最後,輸出主題在單詞上的概率分布。
import org.apache.spark.mllib.clustering.LDA
import org.apache.spark.mllib.linalg.Vectors
// Load and parse the data
val data = sc.textFile("data/mllib/sample_lda_data.txt")
val parsedData = data.map(s => Vectors.dense(s.trim.split(' ').map(_.toDouble)))
// Index documents with unique IDs
val corpus = parsedData.zipWithIndex.map(_.swap).cache()
// Cluster the documents into three topics using LDA
val ldaModel = new LDA().setK(3).run(corpus)
// Output topics. Each is a distribution over words (matching word count vectors)
println("Learned topics (as distributions over vocab of " + ldaModel.vocabSize + " words):")
val topics = ldaModel.topicsMatrix
for (topic <- Range(0, 3)) {
print("Topic " + topic + ":")
for (word <- Range(0, ldaModel.vocabSize)) { print(" " + topics(word, topic)); }
println()
}
流式K均值
當數據以流式到達,就需要動態預測分類,每當新數據到來時要更新模型。MLlib提供了流式k均值聚類,該方法使用參數來控製數據的衰減。這個算法使用mini-batch k均值更新規則的一種泛化版本。對於每一批數據,將所有點賦給最近的簇,計算新的簇中心,然後使用下麵的方法更新簇:
示例
下麵的例子(scala)說明了如果對流式數據預測分類。
首先包含需要的類。
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.clustering.StreamingKMeans
然後為訓練和測試分別創建輸入流。假設StreamingContext ssc已經創建好(參考Spark Streaming Programming Guide)。
val trainingData = ssc.textFileStream("/training/data/dir").map(Vectors.parse)
val testData = ssc.textFileStream("/testing/data/dir").map(LabeledPoint.parse)
創建以隨機數方式生成中心的模型,並指定聚類數量。
val numDimensions = 3
val numClusters = 2
val model = new StreamingKMeans()
.setK(numClusters)
.setDecayFactor(1.0)
.setRandomCenters(numDimensions, 0.0)
注冊訓練和測試數據流,並啟動任務。每當有新數據到達的時候,輸出預測的類。
model.trainOn(trainingData)
model.predictOnValues(testData.map(lp => (lp.label, lp.features))).print()
ssc.start()
ssc.awaitTermination()
當添加新文本文件的時候,聚類中心會被更新。訓練點格式:[x1, x2, x3], 測試點格式(y, [x1,x2,x3]),y是類型標記。任意時間有文本放到/training/data/dir下,模型將被更新。任意時間,文本放到/testing/data/dir下預測值就會輸出。對於新的數據,聚類中心會改變。
參考
[1] http://www.jb51.net/article/49395.htm
[2] http://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm
[3] http://en.wikipedia.org/wiki/Maximum_likelihood
[4] http://en.wikipedia.org/wiki/Power_iteration
[5] http://blog.csdn.net/abcjennifer/article/details/8198352