4.1 k-means算法簡介 聚類分析,作為機器學習領域中的一種無監督學習方法,在數據探索與知識發現過程中扮演著舉足輕重的角色。它能夠在沒有先驗知識或標簽信息的情況下,通過挖掘數據中的內在結構和規律,將數據對象自動劃分為多個類別或簇。每個簇內的對象具有高度的相似性,而不同簇間的對象則表現出明顯的差異性。 在眾多聚類算法中,K-means算法因其簡單高效而備受青睞。K-means算法的基本思想是:通過迭代的方式,將數據劃分為K個不同的簇,并使得每個數據點與其所屬簇的質心(或稱為中心點、均值點)之間的距離之和最小。 K-means算法的優點在于其直觀易懂、計算速度快且易于實現。然而,它也存在一些局限性,如對初始簇質心的選擇敏感、可能陷入局部最優解以及需要預先設定聚類數K等。因此,在實際應用中,我們需要根據具體的問題和數據特點來選擇合適的聚類算法,并可能需要對算法進行優化或改進以適應特定的需求。 4.2 算法的基本原理 (1)類和分類 類指的是具有相似性的集合。而分類是根據給定的已知類別的樣本,訓練出來一個模型,使該模型能夠對未知類別的樣本進行分類。故分類屬于有監督學習。有監督學習是指提前由人工對訓練數據做好了分類,訓練樣本同時包含有特征和類別信息。有監督學習類似于先學習帶有標準答案的復習題,學習到知識規律以后,再去參加考試。 (2)聚類 對于一些給定的樣本,不知道樣本之間的關系,不知道他們是否屬于同一類,也不知道到底可以分為多少類。此時,通過聚類把未知類別的數據,分為多個類別。聚類目的在于把相似的東西聚在一起,因此聚類屬于無監督學習。 (3)K-均值聚類思想 K-均值(K-Means)是發現給定數據集的K個簇的算法。簇個數K是用戶給定的,每一個簇通過其質心(即簇中所有點的中心)來描述。其核心思想是將數據集中的n個對象劃分為K個聚類,使得每個對象到其所屬聚類的中心(或稱為均值點、質心)的距離之和最小。這里所說的距離通常指的是歐氏距離,但也可以是其他類型的距離度量。通過迭代的方式不斷優化聚類結果,使得每個聚類內的對象盡可能緊密,而不同聚類間的對象則盡可能分開。 (4)算法步驟 K-means算法作為一種強大的無監督學習工具,具有簡單易懂、計算效率高和易于實現等優點在多個領域有著廣泛的應用,下面我們將詳細探討K-means算法的實現步驟。K-means算法的執行過程通常包括以下四個步驟。 a.初始化,選擇K個初始聚類中心。在算法開始時,需要隨機選擇K個數據點作為初始的聚類中心。這些初始聚類中心的選擇對最終的聚類結果有一定的影響,因此在實際應用中,通常會采用一些啟發式的方法來選擇較好的初始聚類中心,如K-means++算法。 b.分配,將每個數據點分配給最近的聚類中心。對于數據集中的每個數據點,計算其與每個聚類中心的距離,并將其分配給距離最近的聚類中心。這一步通常使用歐氏距離作為距離度量,計算公式如下(1)。其中,x是數據點,ci是第i個聚類中心,d是數據的維度,xj和cij分別是x和ci在第j維上的值。 c.更新,重新計算每個聚類的中心。對于每個聚類,重新計算其聚類中心,新的聚類中心是該聚類內所有數據點的均值,計算公式如下(2)。其中,Si是第i個聚類的數據點集合,|Si|是該集合中數據點的數量。 d.迭代,重復分配和更新步驟,直到滿足終止條件。重復執行分配和更新步驟,直到滿足某種終止條件。常見的終止條件包括,聚類中心不再發生顯著變化:即新的聚類中心與舊的聚類中心之間的距離小于某個預設的閾值。達到最大迭代次數:為了避免算法陷入無限循環,通常會設置一個最大迭代次數作為終止條件。在迭代過程中,算法會不斷優化聚類結果,使得每個聚類內的對象更加緊密,而不同聚類間的對象更加分散。最終,當滿足終止條件時,算法停止迭代并輸出最終的聚類結果。 4.3 算法實例 K-means算法作為一種強大的無監督學習工具,在多個領域有著廣泛的應用。本實驗以對俄勒岡州的波特蘭地區地圖上的點進行聚類為例,基于k-means算法實現,將這些地方進行聚類的最佳策略,這樣就可以安排交通工具抵達這些簇的質心,然后步行到每個內地址。實例操作可分為以下五個過程。
通過調用geoGrab函數來實現收集數據,geoGrab函數的作用是收集數據,通過調用雅虎地理編碼 API(Yahoo Geocoding API),將輸入的街道地址和城市名稱轉換為對應的地理坐標(經緯度等位置信息),返回 API 響應的 JSON 格式數據,包含地址對應的地理編碼信息(如坐標、地址匹配度等)。 相關代碼如下: def geoGrab(stAddress, city): ????apiStem = 'http://where.yahooapis.com/geocode?' ?#create a dict and constants for the goecoder ????params = {} ????params['flags'] = 'J'#JSON return type ????params['appid'] = 'aaa0VN6k' ????params['location'] = '%s %s' % (stAddress, city) ????url_params = urllib.parse.urlencode(params) ????yahooApi = apiStem + url_params ?????#print url_params ????print (yahooApi) ????c=urllib.parse.urlopen(yahooApi) ????return json.loads(c.read())
準備數據中,只保留經緯度信息。我們可以通過調用massPlaceFind函數。該函數其作用是批量處理地址數據,通過調用geoGrab函數獲取地理坐標(經緯度),并將結果寫入文件。該函數適用于批量處理地址數據,生成帶地理坐標的數據集,常用于地理信息系統(GIS)、數據分析或地圖可視化等場景。 相關代碼如下: def massPlaceFind(fileName): ????fw = open('places.txt', 'w') ????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()
使用Matplotlib來構建一個二維數據圖,其中包含簇與地圖。我們通過調用clusterClubs函數來實現。clusterClubs函數,其核心功能是對地點的經緯度數據進行聚類分析,并在地圖背景上可視化聚類結果。該函數適用于地理數據的聚類分析與可視化,常用于城市規劃、商業選址、軌跡分析等場景,幫助快速識別數據中的空間分布模式。 圖1 調用函數 圖2 二維數據圖 相關代碼如下: def clusterClubs(numClust=5): ????datList = [] ????for line in open('places.txt').readlines(): ????????lineArr = line.split('\t') ????????datList.append([float(lineArr[4]), float(lineArr[3])]) ????datMat = matrix(datList) ????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) ????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()
通過調用biKmeans()函數來測試算法。該函數實現了二分K均值算法(Bisecting K-Means),用于對數據集進行聚類分析。其核心作用是通過 “逐步分裂簇” 的策略,將數據集劃分為指定數量的簇(k個)。 圖3 測試算法 相關代碼如下: def biKmeans(dataSet, k, distMeas=distEclud): ????m = shape(dataSet)[0] ????clusterAssment = matrix(zeros((m,2))) ????centroid0 = mean(dataSet, axis=0).tolist()[0] ????centList =[centroid0] #create a list with one centroid ????for j in range(m):#calc initial Error ????????clusterAssment[j,1] = distMeas(matrix(centroid0), dataSet[j,:])**2 ????while (len(centList) < k): ????????lowestSSE = inf ????????for i in range(len(centList)): ????????????ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#get the data points currently in cluster i ????????????centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) ????????????sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum ????????????sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) ????????????print("sseSplit, and notSplit: ",sseSplit,sseNotSplit) ????????????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 matrix(centList), clusterAssment
我們通過使用clusterClubs函數來輸出包含簇及簇中心的地圖。clusterClubs函數,其核心功能是對地點的經緯度數據進行聚類分析,并在地圖背景上可視化聚類結果。操作過程與(3)分析數據相同。 圖4 簇及簇中心圖 相關代碼如下: def clusterClubs(numClust=5): ????datList = [] ????for line in open('places.txt').readlines(): ????????lineArr = line.split('\t') ????????datList.append([float(lineArr[4]), float(lineArr[3])]) ????datMat = matrix(datList) ????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) ????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() |