K-均值聚类算法对未标注数据分组(1)
1. K-均值聚類算法
優點:容易實現。
缺點:可能收斂到局部最小值,在大規模數據集上收斂較慢。
適用數據類型:數值型數據。
K-均值算法的工作流程是這樣的。首先,隨機確定&個初始點作為質心。然后將數據集中的每個點分配到一個簇中,具體來講,為每個點找距其最近的質心,并將其分配給該質心所對應的簇。這一步完成之后,每個簇的質心更新為該簇所有點的平均值。
上述過程的偽代碼表示如下:
創建k個點作為起始質心(經常是隨機選擇)
當任意一個點的簇分配結果發生改變時
對養據集中的每個數據點
對每個質心
計算質心與數據點之間的距離
將數據點分配到距其最近的簇
對每一個簇,計算簇中所有點的均值并將均值作為質心
k均值聚類算法:
# -*- coding: utf-8 -*- from numpy import *# K-均值聚類支持函數 def loadDataSet(fileName): dataMat = [] fr = open(fileName)for line in fr.readlines():curLine = line.strip().split('\t')fltLine = map(float,curLine) dataMat.append(fltLine)return dataMat# 計算兩個向量的歐式距離 def distEclud(vecA, vecB):return sqrt(sum(power(vecA - vecB, 2))) # 為給定數據集構建一個包含k個隨機質心的集合,是以每列的形式生成的 def randCent(dataSet, k):n = shape(dataSet)[1]centroids = mat(zeros((k,n))) for j in range(n): minJ = min(dataSet[:,j]) # 找到每一維的最小rangeJ = float(max(dataSet[:,j]) - minJ) # 每一維的最大和最小值之差centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) # 生成隨機值#print centroids[:,j]return centroids # 返回隨機質心,是和數據點相同的結構# k--均值聚類算法(計算質心--分配--重新計算) def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): # k是簇的數目m = shape(dataSet)[0] # 得到樣本的數目clusterAssment = mat(zeros((m,2))) # 創建矩陣來存儲每個點的簇分配結果# 第一列:記錄簇索引值,第二列:存儲誤差,歐式距離的平方centroids = createCent(dataSet, k) # 創建k個隨機質心clusterChanged = Truewhile clusterChanged: # 迭代使用while循環來實現clusterChanged = False for i in range(m): # 遍歷每個數據點,找到距離每個點最近的質心minDist = inf; minIndex = -1for j in range(k): # 尋找最近的質心distJI = distMeas(centroids[j,:],dataSet[i,:])if distJI < minDist:minDist = distJI; minIndex = jif clusterAssment[i,0] != minIndex: # 更新停止的條件clusterChanged = TrueclusterAssment[i,:] = minIndex,minDist**2 # minDist**2就去掉了根號 for cent in range(k): # 更新質心的位置ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] centroids[cent,:] = mean(ptsInClust, axis=0) # 然后計算均值,axis=0:沿列方向 print 'centroids:',centroidsreturn centroids, clusterAssment # 返回簇和每個簇的誤差值,誤差值是當前點到該簇的質心的距離# 主函數 datMat=mat(loadDataSet('testSet.txt')) rand_centroids=randCent(datMat, 2) print 'Random centroids:',rand_centroids myCentroids,clustAssing=kMeans(datMat,4) print 'myCentroids:',myCentroids #centList,myNewAssments=biKmeans(datMat, 3)運行結果:
Random centroids: [[ 0.47004012 -0.70046073][ 4.59776536 2.61143197]] centroids: [[-3.38237045 -2.9473363 ][-2.46154315 2.78737555][ 2.80293085 -2.7315146 ][ 2.6265299 3.10868015]] myCentroids: [[-3.38237045 -2.9473363 ][-2.46154315 2.78737555][ 2.80293085 -2.7315146 ][ 2.6265299 3.10868015]]上面的結果可以看出得到了4個質心
2. 二分k-均值算法
在K-均值聚類中簇的數目k是一個用戶預先定義的參數,那么用戶如何才能知道乂的選擇是否正確?如何才能知道生成的簇比較好呢?在包含簇分配結果的矩陣中保存著每個點的誤差,即該點到簇質心的距離平方值。
使用后處理來提高聚類性能:
- K-均值算法收斂到了局部最小值,而非全局最小值(局部最小值指結果還可以但并非最好結果,全局最小值是可能的最好結果)。
- 一種用于度量聚類效果的指標是SSE(Sum of Squared Error,誤差平方和。SSE值越小表示數據點越接近于它們的質心,聚類效果也越好。因為對誤差取了平方,因此更加重視那些遠離中心的點。一種肯定可以降低SSE值的方法是增加簇的個數,但這違背了聚類的目標。聚類的目標是在保持族數目不變的情況下提高簇的質量。那么如何進行改進?可以對生成的簇進行后處理,一種方法是將具有最大SSE值的簇劃分成兩個簇。具體實現時可以將最大簇包含的點過濾出來并在這些點上運行尺-均值算法,其中的K設為2。
為克服k-均值算法收斂于局部最小值的問題:
- 二分k-均值(bisecting K-means)算法,該算法首先將所有點作為一個簇,然后將該簇一分為二。之后選擇其中一個簇繼續進行劃分,選擇哪一個簇進行劃分取決于對”其劃分是否可以最大程度降低SSE的值。上述基于SSE的劃分過程不斷重復,直到得到用戶指定的簇數目為止。
- 另一種做法是選擇SSE最大的簇進行劃分,直到簇數目達到用戶指定的數目為止。這個做法聽起來并不難實現。
下面就來看一下該算法的實際效果。
# -*- coding: utf-8 -*- from numpy import *# K-均值聚類支持函數 def loadDataSet(fileName): dataMat = [] fr = open(fileName)for line in fr.readlines():curLine = line.strip().split('\t')fltLine = map(float,curLine) dataMat.append(fltLine)return dataMat# 計算兩個向量的歐式距離 def distEclud(vecA, vecB):return sqrt(sum(power(vecA - vecB, 2))) # 為給定數據集構建一個包含k個隨機質心的集合,是以每列的形式生成的 def randCent(dataSet, k):n = shape(dataSet)[1]centroids = mat(zeros((k,n))) for j in range(n): minJ = min(dataSet[:,j]) # 找到每一維的最小rangeJ = float(max(dataSet[:,j]) - minJ) # 每一維的最大和最小值之差centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) # 生成隨機值#print centroids[:,j]return centroids # 返回隨機質心,是和數據點相同的結構# k--均值聚類算法(計算質心--分配--重新計算) def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): # k是簇的數目m = shape(dataSet)[0] # 得到樣本的數目clusterAssment = mat(zeros((m,2))) # 創建矩陣來存儲每個點的簇分配結果# 第一列:記錄簇索引值,第二列:存儲誤差,歐式距離的平方centroids = createCent(dataSet, k) # 創建k個隨機質心clusterChanged = Truewhile clusterChanged: # 迭代使用while循環來實現clusterChanged = False for i in range(m): # 遍歷每個數據點,找到距離每個點最近的質心minDist = inf; minIndex = -1for j in range(k): # 尋找最近的質心distJI = distMeas(centroids[j,:],dataSet[i,:])if distJI < minDist:minDist = distJI; minIndex = jif clusterAssment[i,0] != minIndex: # 更新停止的條件clusterChanged = TrueclusterAssment[i,:] = minIndex,minDist**2 # minDist**2就去掉了根號 for cent in range(k): # 更新質心的位置ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] centroids[cent,:] = mean(ptsInClust, axis=0) # 然后計算均值,axis=0:沿列方向 print 'centroids:',centroidsreturn centroids, clusterAssment # 返回簇和每個簇的誤差值,誤差值是當前點到該簇的質心的距離# 二分k--均值聚類算法 def biKmeans(dataSet, k, distMeas=distEclud):m = shape(dataSet)[0] clusterAssment = mat(zeros((m,2))) # 存儲數據集中每個點的簇分配結果及平方誤差centroid0 = mean(dataSet, axis=0).tolist()[0] # 計算整個數據集的質心:1*2的向量centList =[centroid0] # []的意思是使用一個列表保存所有的質心,簇列表,[]的作用很大for j in range(m): # 遍歷所有的數據點,計算到初始質心的誤差值,存儲在第1列clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2while (len(centList) < k): # 不斷對簇進行劃分,直到klowestSSE = inf # 初始化SSE為無窮大for i in range(len(centList)): # 遍歷每一個簇print 'i:',i # 數組過濾得到所有的類別簇等于i的數據集ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]# 得到2個簇和每個簇的誤差,centroidMat:簇矩陣 splitClustAss:[索引值,誤差]centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) # centroidMat是矩陣sseSplit = sum(splitClustAss[:,1]) # 求二分k劃分后所有數據點的誤差和 # 數組過濾得到整個數據點集的簇中不等于i的點集print nonzero(clusterAssment[:,0].A!=i)[0]sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])# 所有剩余數據集的誤差之和print "sseSplit and notSplit: ",sseSplit,',',sseNotSplitif (sseSplit + sseNotSplit) < lowestSSE: # 劃分后的誤差和小于當前的誤差,本次劃分被保存print 'here..........'bestCentToSplit = i # i代表簇數bestNewCents = centroidMat # 保存簇矩陣print 'bestNewCents',bestNewCentsbestClustAss = splitClustAss.copy() # 拷貝所有數據點的簇索引和誤差lowestSSE = sseSplit + sseNotSplit # 保存當前誤差和# centList是原劃分的簇向量,bestCentToSplit是i值print 'len(centList) and bestCentToSplit ',len(centList),',',bestCentToSplit# 數組過濾得到的是新劃分的簇類別是1的數據集的類別簇重新劃為新的類別值為最大的類別數bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) # 數組過濾得到的是新劃分的簇類別是0的數據集的類別簇重新劃為新的類別值為ibestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplitprint 'the bestCentToSplit is: ',bestCentToSplit # 代表的是劃分的簇個數-1print 'the len of bestClustAss is: ', len(bestClustAss) # 數據簇的數據點個數# 新劃分簇矩陣的第0簇向量新增到當前的簇列表中centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0] print 'centList[bestCentToSplit]:',centList[bestCentToSplit]# 新劃分簇矩陣的第1簇向量添加到當前的簇列表中centList.append(bestNewCents[1,:].tolist()[0]) # centList是列表的格式print 'centList',centList# 數組過濾得到所有數據集中簇類別是新簇的數據點clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAssreturn mat(centList), clusterAssment # 返回質心列表和簇分配結果# 主函數 datMat=mat(loadDataSet('testSet.txt')) centList,myNewAssments=biKmeans(datMat, 3)運行結果:
i: 0 centroids: [[-1.12630774 1.13042276][ 2.59258145 -2.78274655]] [] sseSplit and notSplit: 1000.74868136 , 0.0 here.......... bestNewCents [[-1.12630774 1.13042276][ 2.59258145 -2.78274655]] len(centList) and bestCentToSplit 1 , 0 the bestCentToSplit is: 0 the len of bestClustAss is: 80 centList[bestCentToSplit]: [-1.1263077413793101, 1.13042275862069] centList [[-1.1263077413793101, 1.13042275862069], [2.592581454545454, -2.782746545454545]] i: 0 centroids: [[ 1.88660209 3.23434291][-3.10621991 -0.25215334]] [ 2 6 10 ..., 72 74 78] sseSplit and notSplit: 390.512447704 , 95.5368926046 here.......... bestNewCents [[ 1.88660209 3.23434291][-3.10621991 -0.25215334]] i: 1 centroids: [[ 4.5110462 -1.0349174 ][ 2.02832712 -3.29681394]] [ 0 1 3 ..., 76 77 79] sseSplit and notSplit: 51.9548039494 , 905.211788759 len(centList) and bestCentToSplit 2 , 0 the bestCentToSplit is: 0 the len of bestClustAss is: 58 centList[bestCentToSplit]: [1.8866020869565217, 3.2343429130434784] centList [[1.8866020869565217, 3.2343429130434784], [2.592581454545454, -2.782746545454545], [-3.1062199142857145, -0.2521533428571429]]可以看出算法運行的中間過程,其中最容易迷惑的地方就是數組過濾器。可參考:
tolist()的用法
nonzero()
要注意的幾點:
(1)centList =[centroid0] # []的意思是使用一個列表保存所有的質心,簇列表,[]的作用很大
In [12]: mean(datMat, axis=0).tolist()[0] Out[12]: [-0.10361321250000004, 0.05430119999999998]In [13]: centroid0=mean(datMat, axis=0).tolist()[0]In [14]: centList =[centroid0]In [15]: centList Out[15]: [[-0.10361321250000004, 0.05430119999999998]](2)dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]
>>> from numpy import * >>> clusterAssment = mat(zeros((5,2))) >>> clusterAssment[:,0].A==0 array([[ True],[ True],[ True],[ True],[ True]], dtype=bool) >>> clusterAssment[:,0]==0 matrix([[ True],[ True],[ True],[ True],[ True]], dtype=bool) >>> clusterAssment[:,0].A==1 array([[False],[False],[False],[False],[False]], dtype=bool) >>> clusterAssment matrix([[ 0., 0.],[ 0., 0.],[ 0., 0.],[ 0., 0.],[ 0., 0.]]) >>> mm=transpose(nonzero(clusterAssment[:,0]==0)) >>> mm array([[0, 0],[1, 0],[2, 0],[3, 0],[4, 0]], dtype=int64) >>> nonzero(clusterAssment[:,0].A==0)[0] array([0, 1, 2, 3, 4], dtype=int64) >>> transpose(nonzero(clusterAssment[:,0].A==0)) array([[0, 0],[1, 0],[2, 0],[3, 0],[4, 0]], dtype=int64) >>> nonzero(clusterAssment[:,0].A==0) (array([0, 1, 2, 3, 4], dtype=int64), array([0, 0, 0, 0, 0], dtype=int64)) >>> dataSet=mat([[1,2],[2,3],[3,4],[4,5],[5,6]]) >>> dataSet[nonzero(clusterAssment[:,0].A==0)[0],:] matrix([[1, 2],[2, 3],[3, 4],[4, 5],[5, 6]]) >>>(3)bestNewCents[0,:].tolist()[0]
In [17]:a=mat([[1,2],[2,3]])In [18]:a Out[18]: matrix([[1, 2],[2, 3]])In [19]:a[0,:] Out[19]: matrix([[1, 2]])In [20]:a[0,:].tolist() Out[20]: [[1, 2]]In [21]:a[0,:].tolist()[0] Out[21]: [1, 2] 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的K-均值聚类算法对未标注数据分组(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 树回归--python Tkinter库
- 下一篇: 纯黑猫咪(纯黑猫)