聚類是一種無監督的學習,它將類似的對象歸到同一個簇中。它有點像全自動分類。聚類方法幾乎能夠應用於全部對象,簇內的對象越類似,聚類的效果越好
簇識別給出聚類結果的含義。假定有一些數據,如今將類似數據歸到一塊兒,簇識別會告訴咱們這些簇到底都是些什麼。聚類與分類的最大不一樣在於,分類的目標事先巳知,而聚類則不同。由於其產生的結果與分類相同,而只是類別沒有預先定義,聚類有時也被稱爲無監督分類(unsupervised classification )。
聚類分析試圖將類似對象歸人同一簇,將不類似對象歸到不一樣簇。類似這一律念取決於所選擇的類似度計算方法python
K-均值聚類
優勢:容易實現。
缺點:可能收斂到局部最小值,在大規模數據集上收斂較慢。
適用數據類型:數值型數據。git
K-均值是發現給定數據集的k個簇的算法。簇個數k是用戶給定的,每個簇經過其質心( centroid) , 即簇中全部點的中心來描述。
K-均值算法的工做流程是這樣的。首先,隨機肯定k個初始點做爲質心。而後將數據集中的每一個點分配到一個簇中,具體來說,爲每一個點找距其最近的質心,並將其分配給該質心所對應的簇。這一步完成以後,每一個簇的質心更新爲該簇全部點的平均值。算法
上述過程的僞代碼表示以下:
建立k個點做爲起始質心(常常是隨機選擇)
當任意一個點的簇分配結果發生改變時
對數據集中的每一個數據點
對每一個質心
計算質心與數據點之間的距離
將數據點分配到距其最近的簇
對每個簇,計算簇中全部點的均值並將均值做爲質心json
K-均值聚類的通常流程
(1)收集數據:使用任意方法。
⑵準備數據:須要數值型數據來計算距離,也能夠將標稱型數據映射爲二值型數據再用於距離計算。
(3)分析數據:使用任意方法。
(4)訓練算法:不適用於無監督學習,即無監督學習沒有訓練過程。
(5)測試算法:應用聚類算法、觀察結果。可使用量化的偏差指標如偏差平方和(後面會介紹)來評價算法的結果。
(6)使用算法:能夠用於所但願的任何應用。一般狀況下,簇質心能夠表明整個簇的數據來作出決策。api
K-均值聚類支持函數(即完成K均值聚類的一些輔助函數),代碼以下:數組
from numpy import * #general function to parse tab -delimited floats #assume last column is target value def loadDataSet(fileName): dataMat = [] fr = open(fileName) for line in fr.readlines(): curLine = line.strip().split('\t') #筆者使用的是python3,須要將map映射後的結果轉化爲list #map all elements to float() fltLine = list(map(float,curLine)) dataMat.append(fltLine) return dataMat #樣本距離計算函數 def distEclud(vecA, vecB): return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB) #建立簇中心矩陣,初始化爲k個在數據集的邊界內隨機分佈的簇中心 def randCent(dataSet, k): n = shape(dataSet)[1] #create centroid mat centroids = mat(zeros((k,n))) #create random cluster centers, within bounds of each dimension for j in range(n): #求出數據集中第j列的最小值(即第j個特徵) minJ = min(dataSet[:,j]) #用第j個特徵最大值減去最小值得出特徵值範圍 rangeJ = float(max(dataSet[:,j]) - minJ) #建立簇矩陣的第J列,random.rand(k,1)表示產生(10,1)維的矩陣,其中每行值都爲0-1中的隨機值 #能夠這樣理解,每一個centroid矩陣每列的值都在數據集對應特徵的範圍內,那麼k個簇中心天然也都在數據集範圍內 centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) return centroids
測試截圖以下:
markdown
K -均值聚類算法,代碼以下:app
#distMeas爲距離計算函數 #createCent爲初始化隨機簇心函數 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): m = shape(dataSet)[0] #create mat to assign data points to a centroid, also holds SE of each point #建立一個(m,2)維矩陣,第一列存儲每一個樣本對應的簇心,第二列存儲樣本到簇心的距離 clusterAssment = mat(zeros((m,2))) #用createCent()函數初始化簇心矩陣 centroids = createCent(dataSet, k) #保存迭代中clusterAssment是否更新的狀態,若是未更新,那麼退出迭代,表示收斂 #若是更新,那麼繼續迭代,直到收斂 clusterChanged = True while clusterChanged: clusterChanged = False #for each data point assign it to the closest centroid #對每一個樣本找出離樣本最近的簇心 for i in range(m): #minDist保存最小距離 #minIndex保存最小距離對應的簇心 minDist = inf; minIndex = -1 #遍歷簇心,找出離i樣本最近的簇心 for j in range(k): distJI = distMeas(centroids[j,:],dataSet[i,:]) if distJI < minDist: minDist = distJI; minIndex = j #若是clusterAssment更新,表示對應樣本的簇心發生變化,那麼繼續迭代 if clusterAssment[i,0] != minIndex: clusterChanged = True #更新clusterAssment,樣本到簇心的距離 clusterAssment[i,:] = minIndex,minDist**2 print(centroids) #遍歷簇心,更新簇心爲對應簇中全部樣本的均值 for cent in range(k):#recalculate centroids #利用數組過濾找出簇心對應的簇(數組過濾真是好東西!) ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]#get all the point in this cluster #對簇求均值,賦給對應的centroids簇心 centroids[cent,:] = mean(ptsInClust, axis=0) #assign centroid to mean return centroids, clusterAssment
代碼測試截圖以下:
繪製測試截圖:
paint函數爲筆者寫的繪圖函數:dom
def paint(xArr,yArr,xArr1,yArr1): fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(xArr,yArr,c='blue') ax.scatter(xArr1,yArr1,c='red') plt.show()
效果以下(其中紅色的點爲簇心):
能夠看到,通過3次迭代以後K-均值算法收斂函數
考慮圖10-2中的聚類結果,這是在一個包含三個簇的數據集上運行K-均值算法以後的結果,可是點的簇分配結果值沒有那麼準確。K-均值算法收斂但聚類效果較差的緣由是,K-均值算法收斂到了局部最小值,而非全局最小值(局部最小值指結果還能夠但並不是最好結果,全局最小值是可能的最好結果)。
一種用於度量聚類效果的指標是SSE(Sum of Squared Error,偏差平方和),對應clusterAssment矩陣的第一列之和。SSE值越小表示數據點越接近於它們的質心,聚類效果也越好。由於對偏差取了平方,所以更加劇視那些遠離中心的點。一種確定能夠下降SSE值的方法是增長簇的個數,但這違背了聚類的目標。聚類的目標是在保持簇數目不變的狀況下提升簇的質量。
那麼如何對結果進行改進?你能夠對生成的簇進行後處理,一種方法是將具備最大SSE值的簇劃分紅兩個簇。具體實現時能夠將最大簇包含的點過濾出來並在這些點上運行K-均值聚類算法,其中的K爲2。
爲了保持簇總數不變,能夠將某兩個簇進行合併。從圖10-2中很明顯就能夠看出,應該將圖下部兩個出錯的簇質心進行合併。能夠很容易對二維數據上的聚類進行可視化,可是若是遇到40維的數據應該如何去作?
有兩種能夠量化的辦法:合併最近的質心,或者合併兩個使得SSE增幅最小的質心。第一種思路經過計算全部質心之間的距離,而後合併距離最近的兩個點來實現。第二種方法須要合併兩個簇而後計算總SSE值。必須在全部可能的兩個簇上重複上述處理過程,直到找到合併最佳的兩個簇爲止。接下來將討論利用上述簇劃分技術獲得更好的聚類結果的方法。
爲克服K-均值算法收斂於局部最小值的問題,有人提出了另外一個稱爲二分K均值(bisectingK-means)的算法, 該算法首先將全部點做爲一個簇,而後將該簇一分爲二。以後選擇其中一個簇繼續進行劃分,選擇哪個簇進行劃分取決於對其劃分是否能夠最大程度下降SSE的值。上述基於SSE的劃分過程不斷重複,直到獲得用戶指定的簇數目爲止。
二分K-均值算法的僞代碼形式以下:
將全部點當作一個簇
當簇數目小於k時
對於每個簇
計算總偏差
在給定的簇上面進行K-均值聚類(k=2)
計算將該簇一分爲二以後的總偏差
選擇使得偏差最小的那個簇進行劃分操做
另外一種作法是選擇SSE最大的簇進行劃分,直到簇數目達到用戶指定的數目爲止。這個作法聽起來並不難實現。下面就來看一下該算法的實際效果。
二分K均值聚類算法,代碼以下:
#distMeas爲距離計算函數 def biKmeans(dataSet, k, distMeas=distEclud): m = shape(dataSet)[0] #(m,2)維矩陣,第一列保存樣本所屬簇,第二列保存樣本到簇中心的距離 clusterAssment = mat(zeros((m,2))) #取數據集特徵均值做爲初始簇中心 centroid0 = mean(dataSet, axis=0).tolist()[0] #centList保存簇中心數組,初始化爲一個簇中心 #create a list with one centroid centList =[centroid0] #calc initial Error for j in range(m): clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2 #迭代,直到簇中心集合長度達到k while (len(centList) < k): #初始化最小偏差 lowestSSE = inf #迭代簇中心集合,找出找出分簇後總偏差最小的那個簇進行分解 for i in range(len(centList)): #get the data points currently in cluster i #獲取屬於i簇的數據集樣本 ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] #對該簇進行k均值聚類 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) #獲取該簇分類後的偏差和 sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum #獲取不屬於該簇的樣本集合的偏差和,注意矩陣過濾中用的是!=i sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) #打印該簇分類後的偏差和和不屬於該簇的樣本集合的偏差和 print("sseSplit, and notSplit: ",sseSplit,sseNotSplit) #兩偏差和相加即爲分簇後整個樣本集合的偏差和,找出簇中心集合中能讓分簇後偏差和最小的簇中心,保存最佳簇中心(bestCentToSplit),最佳分簇中心集合(bestNewCents),以及分簇數據集中樣本對應簇中心及距離集合(bestClustAss),最小偏差(lowestSSE) if (sseSplit + sseNotSplit) < lowestSSE: bestCentToSplit = i bestNewCents = centroidMat bestClustAss = splitClustAss.copy() lowestSSE = sseSplit + sseNotSplit #更新用K-means獲取的簇中心集合,將簇中心換爲len(centList)和bestCentToSplit,以便以後調整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 print('the bestCentToSplit is: ',bestCentToSplit) print('the len of bestClustAss is: ', len(bestClustAss)) #更新簇中心集合,注意與bestClustAss矩陣是一一對應的 centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]#replace a centroid with two best centroids centList.append(bestNewCents[1,:].tolist()[0]) #reassign new clusters, and SSE clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss return mat(centList), clusterAssment
二分K值最重要的是記住要將最佳分簇集合與clusterAssment一一對應
測試代碼以下:
datMat3 = mat(loadDataSet('testSet2.txt')) centList,myNewAssments = biKmeans(datMat3,3) print(centList) xArr = datMat3[:,0].flatten().A[0] yArr = datMat3[:,1].flatten().A[0] xArr1 = centList[:,0].flatten().A[0] yArr1 = centList[:,1].flatten().A[0] #paint爲筆者本身寫的繪圖函數 paint(xArr,yArr,xArr1,yArr1) def paint(xArr,yArr,xArr1,yArr1): fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(xArr,yArr,c='blue') ax.scatter(xArr1,yArr1,c='red') plt.show()
測試截圖以下:
上述函數能夠運行屢次,聚類會收斂到全局最小值,而原始的別的!!3 ()函數偶爾會陷人局部最小值。
假若有這樣一種狀況:你的朋友Drew但願你帶他去城裏慶祝他的生日。因爲其餘一些朋友也會過來,因此須要你提供一個你們均可行的計劃。Drew給了你一些他但願去的地址。這個地址列表很長,有70個位置。我把這個列表保存在文件portland-Clubs.txt中,該文件和源代碼一塊兒打包。這些地址其實都在俄勒岡州的波特蘭地區。
也就是說,一夜要去70個地方!你要決定一個將這些地方進行聚類的最佳策略,這樣就能夠安排交通工具抵達這些簇的質心,而後步行到每一個簇內地址。Drew的清單中雖然給出了地址,可是並無給出這些地址之間的距離遠近信息。所以,你要獲得每一個地址的緯度和經度,而後對這些地址進行聚類以安排你的行程。
示例:對於地理數據應用二分K-均值算法
(1)收集數據:使用Yahoo!PlaceFinder API收集數據
(2)準備數據:只保留經緯度信息
(3)分析數據:使用Matplotlib來構建一個二維數據圖,其中包含簇與地圖
(4)訓練算法:訓練不適用無監督學習
(5)測試算法:使用10.4節中的biKmeans( )函教
(6)使用算法| 最後的輸出是包含簇及簇中心的地圖
Yahoo! PlaceFinderAPI,代碼以下:
import urllib import json def geoGrab(stAddress, city): #create a dict and constants for the goecoder apiStem = 'http://where.yahooapis.com/geocode?' #請求參數字典 params = {} params['flags'] = 'J'#JSON return type params['appid'] = 'aaa0VN6k' params['location'] = '%s %s' % (stAddress, city) #url編碼請求參數,化爲x1=xx&x2=xx形式 url_params = urllib.urlencode(params) #print url_params yahooApi = apiStem + url_params print(yahooApi) #請求api c=urllib.urlopen(yahooApi) #獲取json格式的數據 return json.loads(c.read()) from time import sleep def massPlaceFind(fileName): fw = open('places.txt', 'w') #對文件中的每一個樣本調用geoGrab()獲取json數據,解析後寫入源文件 for line in open(fileName).readlines(): line = line.strip() lineArr = line.split('\t') retDict = geoGrab(lineArr[1], lineArr[2]) if retDict['ResultSet']['Error'] == 0: lat = float(retDict['ResultSet']['Results'][0]['latitude']) lng = float(retDict['ResultSet']['Results'][0]['longitude']) print("%s\t%f\t%f" % (lineArr[0], lat, lng)) fw.write('%s\t%f\t%f\n' % (line, lat, lng)) else: print("error fetching") sleep(1) fw.close()
測試代碼以下:
geoResults = geoGrab('1 VA Center', 'Augusta, ME') print(geoResults)
因爲主要不是爲了調用YahooAPI,所以筆者沒有實際調用API獲取數據,理解這個過程就能夠了,首先獲取數據,而後調用二分K均值聚類對地址聚類分析。
10.4.2 對地理座標進行聚類
這個例子中要聚類的俱樂部給出的信息爲經度和維度,但這些信息對於距離計算還不夠。在北極附近每走幾米的經度變化可能達到數10度 ;而在赤道附近走相同的距離,帶來的經度變化可能只是零點幾。可使用球面餘弦定理來計算兩個經緯度之間的距離
球面距離計算及簇繪圖函數,代碼以下:
#利用球面餘弦定理計算指定(經度,緯度)兩點的距離 def distSLC(vecA, vecB):#Spherical Law of Cosines a = sin(vecA[0,1]*pi/180) * sin(vecB[0,1]*pi/180) b = cos(vecA[0,1]*pi/180) * cos(vecB[0,1]*pi/180) * \ cos(pi * (vecB[0,0]-vecA[0,0]) /180) return arccos(a + b)*6371.0 #pi is imported with numpy import matplotlib import matplotlib.pyplot as plt def clusterClubs(numClust=5): datList = [] #讀取數據集,存儲在datList中 for line in open('places.txt').readlines(): lineArr = line.split('\t') datList.append([float(lineArr[4]), float(lineArr[3])]) datMat = mat(datList) #調用二分K聚類獲取簇中心集合以及clustAssing矩陣 myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC) fig = plt.figure() rect=[0.1,0.1,0.8,0.8] scatterMarkers=['s', 'o', '^', '8', 'p', \ 'd', 'v', 'h', '>', '<'] axprops = dict(xticks=[], yticks=[]) ax0=fig.add_axes(rect, label='ax0', **axprops) imgP = plt.imread('Portland.png') ax0.imshow(imgP) ax1=fig.add_axes(rect, label='ax1', frameon=False) #迭代簇集合,根據不一樣的marker畫出對應的簇 for i in range(numClust): ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A==i)[0],:] markerStyle = scatterMarkers[i % len(scatterMarkers)] ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0], ptsInCurrCluster[:,1].flatten().A[0], marker=markerStyle, s=90) #畫出全部簇中心 ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300) plt.show()
測試代碼以下:
kMeans.clusterClubs(5)
測試截圖以下:
聚類是一種無監督的學習方法。所謂無監督學習是指事先並不知道要尋找的內容,即沒有目標變量。聚類將數據點歸到多個簇中,其中類似數據點處於同一簇,而不類似數據點處於不一樣簇中。聚類中可使用多種不一樣的方法來計算類似度。
一種普遍使用的聚類算法是K-均值算法,其中K是用戶指定的要建立的簇的數目。K-均值聚類算法以K個隨機質心開始。算法會計算每一個點到質心的距離。每一個點會被分配到距其最近的簇質心,而後緊接着基於新分配到簇的點更新簇質心。以上過程重複數次,直到簇質心再也不改變。這個簡單的算法很是有效可是也容易受到初始簇質心的影響。爲了得到更好的聚類效果,可使用另外一種稱爲二分K-均值的聚類算法。二分K-均值算法首先將全部點做爲一個簇,而後使用K-均值算法(K = 2 ) 對其劃分。下一次迭代時,選擇有最大偏差的簇進行劃分。該過程重複直到K個簇建立成功爲止。二分K-均值的聚類效果要好於K-均值算法。 K-均值算法以及變形的K-均值算法並不是僅有的聚類算法, 另外稱爲層次聚類的方法也被普遍使用