K-Means聚類算法

聚類分析是在數據中發現數據對象之間的關係,將數據進行分組,組內的類似性越大,組間的差異越大,則聚類效果越好。python

不一樣的簇類型

聚類旨在發現有用的對象簇,在現實中咱們用到不少的簇的類型,使用不一樣的簇類型劃分數據的結果是不一樣的,以下的幾種簇類型。算法

這裏寫圖片描述

明顯分離的

能夠看到(a)中不一樣組中任意兩點之間的距離都大於組內任意兩點之間的距離,明顯分離的簇不必定是球形的,能夠具備任意的形狀。數組

基於原型的

簇是對象的集合,其中每一個對象到定義該簇的原型的距離比其餘簇的原型距離更近,如(b)所示的原型即爲中心點,在一個簇中的數據到其中心點比到另外一個簇的中心點更近。這是一種常見的基於中心的簇,最經常使用的K-Means就是這樣的一種簇類型。 
這樣的簇趨向於球形。微信

基於密度的

簇是對象的密度區域,(d)所示的是基於密度的簇,當簇不規則或相互盤繞,而且有早上和離羣點事,經常使用基於密度的簇定義。app

關於更多的簇介紹參考《數據挖掘導論》。dom

基本的聚類分析算法

1. K均值: 
基於原型的、劃分的距離技術,它試圖發現用戶指定個數(K)的簇。函數

2. 凝聚的層次距離: 
思想是開始時,每一個點都做爲一個單點簇,而後,重複的合併兩個最靠近的簇,直到嘗試單個、包含全部點的簇。優化

3. DBSCAN: 
一種基於密度的劃分距離的算法,簇的個數有算法自動的肯定,低密度中的點被視爲噪聲而忽略,所以其不產生徹底聚類。ui

距離量度

不一樣的距離量度會對距離的結果產生影響,常見的距離量度以下所示:lua

這裏寫圖片描述

這裏寫圖片描述

K-Means算法

下面介紹K均值算法:

優勢:易於實現 
缺點:可能收斂於局部最小值,在大規模數據收斂慢

算法思想較爲簡單以下所示:

選擇K個點做爲初始質心  
repeat 將每一個點指派到最近的質心,造成K個簇 從新計算每一個簇的質心 until 簇不發生變化或達到最大迭代次數 

這裏的從新計算每一個簇的質心,如何計算的是根據目標函數得來的,所以在開始時咱們要考慮距離度量和目標函數。

考慮歐幾里得距離的數據,使用偏差平方和(Sum of the Squared Error,SSE)做爲聚類的目標函數,兩次運行K均值產生的兩個不一樣的簇集,咱們更喜歡SSE最小的那個。

這裏寫圖片描述

k表示k個聚類中心,ci表示第幾個中心,dist表示的是歐幾里得距離。 
這裏有一個問題就是爲何,咱們更新質心是讓全部的點的平均值,這裏就是SSE所決定的。

這裏寫圖片描述

下面用Python進行實現

# dataSet樣本點,k 簇的個數 # disMeas距離量度,默認爲歐幾里得距離 # createCent,初始點的選取 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): m = shape(dataSet)[0] #樣本數 clusterAssment = mat(zeros((m,2))) #m*2的矩陣 centroids = createCent(dataSet, k) #初始化k箇中心 clusterChanged = True while clusterChanged: #當聚類再也不變化 clusterChanged = False for i in range(m): minDist = inf; minIndex = -1 for j in range(k): #找到最近的質心 distJI = distMeas(centroids[j,:],dataSet[i,:]) if distJI < minDist: minDist = distJI; minIndex = j if clusterAssment[i,0] != minIndex: clusterChanged = True # 第1列爲所屬質心,第2列爲距離 clusterAssment[i,:] = minIndex,minDist**2 print centroids # 更改質心位置 for cent in range(k): ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] centroids[cent,:] = mean(ptsInClust, axis=0) return centroids, clusterAssment

重點理解一下:

for cent in range(k): ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] centroids[cent,:] = mean(ptsInClust, axis=0) 

循環每個質心,找到屬於當前質心的全部點,而後根據這些點去更新當前的質心。 
nonzero()返回的是一個二維的數組,其表示非0的元素位置。

>>> from numpy import *
>>> a=array([[1,0,0],[0,1,2],[2,0,0]]) >>> a array([[1, 0, 0], [0, 1, 2], [2, 0, 0]]) >>> nonzero(a) (array([0, 1, 1, 2]), array([0, 1, 2, 0]))

表示第[0,0],[1,1] … 位非零元素。第一個數組爲行,第二個數組爲列,二者進行組合獲得的。

ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] 
所以首先先比較clusterAssment[:,0].A==cent的真假,若是爲真則記錄了他所在的行,所以在用切片進行取值。

一些輔助的函數:

def loadDataSet(fileName): #general function to parse tab -delimited floats dataMat = [] #assume last column is target value fr = open(fileName) for line in fr.readlines(): curLine = line.strip().split('\t') fltLine = map(float,curLine) #map all elements to float() dataMat.append(fltLine) return dataMat def distEclud(vecA, vecB): return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB) def randCent(dataSet, k): n = shape(dataSet)[1] centroids = mat(zeros((k,n)))#create centroid mat for j in range(n):#create random cluster centers, within bounds of each dimension minJ = min(dataSet[:,j]) rangeJ = float(max(dataSet[:,j]) - minJ) centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) return centroids

運行和結果

將上述代碼寫到kMeans.py中,而後打開python交互端。

>>> from numpy import *
>>> import kMeans
>>> dat=mat(kMeans.loadDataSet('testSet.txt')) #讀入數據 >>> center,clust=kMeans.kMeans(dat,4) [[ 0.90796996 5.05836784] [-2.88425582 0.01687006] [-3.3447423 -1.01730512] [-0.32810867 0.48063528]] [[ 1.90508653 3.530091 ] [-3.00984169 2.66771831] [-3.38237045 -2.9473363 ] [ 2.22463036 -1.37361589]] [[ 2.54391447 3.21299611] [-2.46154315 2.78737555] [-3.38237045 -2.9473363 ] [ 2.8692781 -2.54779119]] [[ 2.6265299 3.10868015] [-2.46154315 2.78737555] [-3.38237045 -2.9473363 ] [ 2.80293085 -2.7315146 ]] # 做圖 >>>kMeans(dat,center)

這裏寫圖片描述

繪圖的程序以下:

def draw(data,center): length=len(center) fig=plt.figure # 繪製原始數據的散點圖 plt.scatter(data[:,0],data[:,1],s=25,alpha=0.4) # 繪製簇的質心點 for i in range(length): plt.annotate('center',xy=(center[i,0],center[i,1]),xytext=\ (center[i,0]+1,center[i,1]+1),arrowprops=dict(facecolor='red')) plt.show()

K-Means算法的缺陷

k均值算法很是簡單且使用普遍,可是其有主要的兩個缺陷: 
1. K值須要預先給定,屬於預先知識,不少狀況下K值的估計是很是困難的,對於像計算所有微信用戶的交往圈這樣的場景就徹底的沒辦法用K-Means進行。對於能夠肯定K值不會太大但不明確精確的K值的場景,能夠進行迭代運算,而後找出Cost Function最小時所對應的K值,這個值每每能較好的描述有多少個簇類。 
2. K-Means算法對初始選取的聚類中心點是敏感的,不一樣的隨機種子點獲得的聚類結果徹底不一樣 
3. K均值算法並非很全部的數據類型。它不能處理非球形簇、不一樣尺寸和不一樣密度的簇,銀冠指定足夠大的簇的個數是他一般能夠發現純子簇。 
4. 對離羣點的數據進行聚類時,K均值也有問題,這種狀況下,離羣點檢測和刪除有很大的幫助。

下面對初始質心的選擇進行討論:

拙劣的初始質心

當初始質心是隨機的進行初始化的時候,K均值的每次運行將會產生不一樣的SSE,並且隨機的選擇初始質心結果可能很糟糕,可能只能獲得局部的最優解,而沒法獲得全局的最優解。以下圖所示:

這裏寫圖片描述
能夠看到程序迭代了4次終止,其獲得了局部的最優解,顯然咱們能夠看到其不是全局最優的,咱們仍然能夠找到一個更小的SSE的聚類。

隨機初始化的侷限

你可能會想到:屢次運行,每次使用一組不一樣的隨機初始質心,而後選擇一個具備最小的SSE的簇集。該策略很是的簡單,可是效果可能不是很好,這取決於數據集合尋找的簇的個數。

關於更多,參考《數據挖掘導論》

K-Means優化算法

爲了克服K-Means算法收斂於局部最小值的問題,提出了一種二分K-均值(bisecting K-means)

bisecting K-means

算法的僞代碼以下:

將全部的點當作是一個簇
當簇小於數目k時
    對於每個簇
        計算總偏差
        在給定的簇上進行K-均值聚類,k值爲2 計算將該簇劃分紅兩個簇後總偏差 選擇是的偏差最小的那個簇進行劃分

完整的Python代碼以下:

def biKmeans(dataSet, k, distMeas=distEclud): m = shape(dataSet)[0] # 這裏第一列爲類別,第二列爲SSE clusterAssment = mat(zeros((m,2))) # 當作一個簇是的質心 centroid0 = mean(dataSet, axis=0).tolist()[0] centList =[centroid0] #create a list with one centroid for j in range(m): #計算只有一個簇是的偏差 clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2 # 核心代碼 while (len(centList) < k): lowestSSE = inf # 對於每個質心,嘗試的進行劃分 for i in range(len(centList)): # 獲得屬於該質心的數據 ptsInCurrCluster =\ dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] # 對該質心劃分紅兩類 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) # 計算該簇劃分後的SSE sseSplit = sum(splitClustAss[:,1]) # 沒有參與劃分的簇的SSE sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) print "sseSplit, and notSplit: ",sseSplit,sseNotSplit # 尋找最小的SSE進行劃分 # 即對哪個簇進行劃分後SSE最小 if (sseSplit + sseNotSplit) < lowestSSE: bestCentToSplit = i bestNewCents = centroidMat bestClustAss = splitClustAss.copy() lowestSSE = sseSplit + sseNotSplit # 較難理解的部分 bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit print 'the bestCentToSplit is: ',bestCentToSplit print 'the len of bestClustAss is: ', len(bestClustAss) centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids centList.append(bestNewCents[1,:].tolist()[0]) clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE return mat(centList), clusterAssment

 

下面對最後的代碼進行解析:

 bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever  bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit

這裏是更改其所屬的類別,其中bestClustAss = splitClustAss.copy()是進行k-means後所返回的矩陣,其中第一列爲類別,第二列爲SSE值,由於當k=2是k-means返回的是類別0,1兩類,所以這裏講類別爲1的更改成其質心的長度,而類別爲0的返回的是該簇原先的類別。

舉個例子: 
例如:目前劃分紅了0,1兩個簇,而要求劃分紅3個簇,則在算法進行時,假設對1進行劃分獲得的SSE最小,則將1劃分紅了2個簇,其返回值爲0,1兩個簇,將返回爲1的簇改爲2,返回爲0的簇改爲1,所以如今就有0,1,2三個簇了。

centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids centList.append(bestNewCents[1,:].tolist()[0]) clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE

其中bestNewCents是k-means的返回簇中心的值,其有兩個值,分別是第一個簇,和第二個簇的座標(k=2),這裏將第一個座標賦值給 centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0],將另外一個座標添加到centList中 centList.append(bestNewCents[1,:].tolist()[0])

運行與結果

>>> from numpy import * >>> import kMeans >>> dat = mat(kMeans.loadDataSet('testSet2.txt')) >>> cent,assment=kMeans.biKmeans(dat,3) sseSplit, and notSplit: 570.722757425 0.0 the bestCentToSplit is: 0 the len of bestClustAss is: 60 sseSplit, and notSplit: 68.6865481262 38.0629506357 sseSplit, and notSplit: 22.9717718963 532.659806789 the bestCentToSplit is: 0 the len of bestClustAss is: 40

 

能夠看到進行了兩次的劃分,第一次最好的劃分是在0簇,第二次劃分是在1簇。 
可視化以下圖所示:

這裏寫圖片描述

Mini Batch k-Means

在原始的K-means算法中,每一次的劃分全部的樣本都要參與運算,若是數據量很是大的話,這個時間是很是高的,所以有了一種分批處理的改進算法。 使用Mini Batch(分批處理)的方法對數據點之間的距離進行計算。 Mini Batch的好處:沒必要使用全部的數據樣本,而是從不一樣類別的樣本中抽取一部分樣原本表明各自類型進行計算。n 因爲計算樣本量少,因此會相應的減小運行時間n 但另外一方面抽樣也必然會帶來準確度的降低。

相關文章
相關標籤/搜索