前言python
在前面的文章中,涉及到的機器學習算法均爲監督學習算法。算法
所謂監督學習,就是有訓練過程的學習。再確切點,就是有 "分類標籤集" 的學習。性能優化
如今開始,將進入到非監督學習領域。從經典的聚類問題展開討論。所謂聚類,就是事先並不知道具體分類方案的分類 (容許知道分類個數)。數據結構
本文將介紹一個最爲經典的聚類算法 - K-Means 聚類算法以及它的兩種實現。app
現實中的聚類分析問題 - 總統大選dom
假設 M 國又開始全民選舉總統了,目前 Mr.OBM 的投票率爲48%(投票數佔全部選民人數的百分比),而 Mr.MKN 的爲47%,而剩下的一部分出於【種種緣由】沒有投票。機器學習
作爲其中某個陣營的人,天然是但願可以儘量的爭取到這些剩餘的票 -由於這徹底可能影響最終選舉結果。函數
然而,你不可能爭取到這些人的全部投票,由於你知足某個羣體的人,也許就傷害到了另外一羣人的利益。性能
一個很不錯的想法是將這些人分爲 K 個羣體,而後主要對其中人數最多的幾個羣體作工做。這就須要使用到聚類的策略了。學習
聚類策略是蒐集剩餘選民的用戶信息(各類滿意/不滿意的信息),將這些信息輸入進聚類算法,而後對聚類結果中人數最多的簇的選民作思想工做。
可能你會發現某個簇的選民都是一個社區的,一個宗教信仰的,或者具備某些共性。這樣就方便各類各樣的拉票活動了。
K-Means 聚類算法
K,指的是它能夠發現 K 個簇;Means,指的是簇中心採用簇所含的值的均值來計算。
下面先給出僞代碼:
1 建立 k 個點做爲起始質心 (隨機選擇): 2 當任意一個點的簇分配結果發生改變的時候: 3 對數據集中的每一個數據點: 4 對每一個質心: 5 計算質心與數據點之間的距離 6 將數據點分配到距其最近的簇 7 對每個簇: 8 求出均值並將其更新爲質心
而後是一個具體實現Python程序:
1 #!/usr/bin/env python 2 # -*- coding:UTF-8 -*- 3 4 ''' 5 Created on 2015-01-05 6 7 @author: fangmeng 8 ''' 9 10 from numpy import * 11 12 #================================== 13 # 輸入: 14 # fileName: 數據文件名(含路徑) 15 # 輸出: 16 # dataMat: 數據集 17 #================================== 18 def loadDataSet(fileName): 19 '載入數據文件' 20 21 dataMat = [] 22 fr = open(fileName) 23 for line in fr.readlines(): 24 curLine = line.strip().split('\t') 25 fltLine = map(float,curLine) 26 dataMat.append(fltLine) 27 return dataMat 28 29 #================================================== 30 # 輸入: 31 # vecA: 樣本a 32 # vecB: 樣本b 33 # 輸出: 34 # sqrt(sum(power(vecA - vecB, 2))): 樣本距離 35 #================================================== 36 def distEclud(vecA, vecB): 37 '計算樣本距離' 38 39 return sqrt(sum(power(vecA - vecB, 2))) 40 41 #=========================================== 42 # 輸入: 43 # dataSet: 數據集 44 # k: 簇個數 45 # 輸出: 46 # centroids: 簇劃分集合(每一個元素爲簇質心) 47 #=========================================== 48 def randCent(dataSet, k): 49 '隨機初始化質心' 50 51 n = shape(dataSet)[1] 52 centroids = mat(zeros((k,n)))#create centroid mat 53 for j in range(n):#create random cluster centers, within bounds of each dimension 54 minJ = min(dataSet[:,j]) 55 rangeJ = float(max(dataSet[:,j]) - minJ) 56 centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) 57 return centroids 58 59 #=========================================== 60 # 輸入: 61 # dataSet: 數據集 62 # k: 簇個數 63 # distMeas: 距離生成器 64 # createCent: 質心生成器 65 # 輸出: 66 # centroids: 簇劃分集合(每一個元素爲簇質心) 67 # clusterAssment: 聚類結果 68 #=========================================== 69 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): 70 'K-Means基本實現' 71 72 m = shape(dataSet)[0] 73 # 簇分配結果矩陣。一列爲簇分類結果,一列爲偏差。 74 clusterAssment = mat(zeros((m,2))) 75 # 建立原始質心集 76 centroids = createCent(dataSet, k) 77 # 簇更改標記 78 clusterChanged = True 79 80 while clusterChanged: 81 clusterChanged = False 82 83 # 每一個樣本點加入其最近的簇。 84 for i in range(m): 85 minDist = inf; minIndex = -1 86 for j in range(k): 87 distJI = distMeas(centroids[j,:],dataSet[i,:]) 88 if distJI < minDist: 89 minDist = distJI; minIndex = j 90 if clusterAssment[i,0] != minIndex: clusterChanged = True 91 clusterAssment[i,:] = minIndex,minDist**2 92 93 # 更新簇 94 for cent in range(k):#recalculate centroids 95 ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] 96 centroids[cent,:] = mean(ptsInClust, axis=0) 97 98 return centroids, clusterAssment 99 100 def main(): 101 'k-Means聚類操做展現' 102 103 datMat = mat(loadDataSet('/home/fangmeng/testSet.txt')) 104 myCentroids, clustAssing = kMeans(datMat, 4) 105 106 #print myCentroids 107 print clustAssing 108 109 if __name__ == "__main__": 110 main()
測試結果:
K-Means性能優化
主要有兩種方式:
1. 分解最大SSE (偏差平方和)的簇
PS:直接在簇內執行一次 k=2 的 K-Means 聚類便可。
2. 合併距離最小的簇 或者 合併SSE增幅最小的兩個簇
基於這兩種最基本優化策略,有一種更爲科學的聚類算法 - 二分K-Means算法,下面進行詳細介紹。
二分K-Means算法
該算法大體思路爲:首先將全部的點做爲一個簇,而後將該簇一分爲二。以後選擇其中一個簇繼續劃分。
選擇方法天然是選擇SSE增長更小的那個方式。
如此不斷 "裂變",直到獲得用戶指定數目的簇。
僞代碼:
將全部點視爲一個簇: 當簇數目小於k時: 對於每個簇: 計算SSE 在給定的簇上面進行 k=2 的K-Means聚類 計算將簇一分爲二後的SSE 選擇使得偏差最小的那個簇進行劃分操做
具體實現函數:
1 #====================================== 2 # 輸入: 3 # dataSet: 數據集 4 # k: 簇個數 5 # distMeas: 距離生成器 6 # 輸出: 7 # mat(centList): 簇劃分集合(每一個元素爲簇質心) 8 # clusterAssment: 聚類結果 9 #====================================== 10 def biKmeans(dataSet, k, distMeas=distEclud): 11 '二分K-Means聚類算法' 12 13 m = shape(dataSet)[0] 14 # 聚類結果數據結構 15 clusterAssment = mat(zeros((m,2))) 16 # 原始質心 17 centroid0 = mean(dataSet, axis=0).tolist()[0] 18 centList =[centroid0] 19 20 # 統計原始SSE 21 for j in range(m): 22 clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2 23 24 # 循環執行直到獲得k個簇 25 while (len(centList) < k): 26 # 最小SSE 27 lowestSSE = inf 28 # 找到最適合分裂的簇進行分裂 29 for i in range(len(centList)): 30 ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] 31 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) 32 sseSplit = sum(splitClustAss[:,1]) 33 sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) 34 35 if (sseSplit + sseNotSplit) < lowestSSE: 36 bestCentToSplit = i 37 bestNewCents = centroidMat 38 bestClustAss = splitClustAss.copy() 39 lowestSSE = sseSplit + sseNotSplit 40 41 # 本次劃分信息 42 bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) 43 bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit 44 45 # 更新簇集 46 centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0] 47 centList.append(bestNewCents[1,:].tolist()[0]) 48 # 更新聚類結果集 49 clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss 50 51 return mat(centList), clusterAssment
測試結果:
小結
1. KMeans的用途很普遍,再舉個例子吧:好比你計劃要去中國100個城市旅遊,那麼如何規劃路線呢?
---> 能夠採用聚類的方法,將這些城市聚到幾個簇裏面,而後一個 」簇"一個 "簇" 的進行遊玩。質心就至關於機場,偏差平方和就至關於遊玩城市到質心的距離 :)
2. KMeans算法是很經常使用的聚類算法,然而,這裏也要提一提它的缺點 - k值選取很難。這個話題也產生了不少研究,文章。有興趣的讀者能夠進一步研究。