K-means学习笔记及简易代码实现
寫前思考
目前的幾個問題,如何找到簇的中心點。
如果要找簇的中心點,只需要求出那個簇所在地點的均值,然后將其賦值給新的中心點就可以了。
如何增加或者減少k的值(如何選擇k值)
這還是一個沒有解決的問題,現在我要去百度下嘿嘿。可以是由sse方法,找到數據變化的點
沒錯,現在可以寫程序了。
什么是K-means
k均值聚類算法(k-means clustering algorithm)是一種迭代求解的聚類分析算法,其步驟是,預將數據分為K組,則隨機選取K個對象作為初始的聚類中心,然后計算每個對象與各個種子聚類中心之間的距離,把每個對象分配給距離它最近的聚類中心。聚類中心以及分配給它們的對象就代表一個聚類。每分配一個樣本,聚類的聚類中心會根據聚類中現有的對象被重新計算。這個過程將不斷重復直到滿足某個終止條件。終止條件可以是沒有(或最小數目)對象被重新分配給不同的聚類,沒有(或最小數目)聚類中心再發生變化,誤差平方和局部最小。
分析k-means的算法流程
主要函數的編寫
首先,隨機產生k個聚類中心,這里我們可以使用np的函數。
但是,我們要先得出這個數據集的邊界,也就是x,y的最大最小值。
步驟: 首先讀取數據集,然后使用np.max方法求最大最小值。然后生成一個(0,1)的矩陣。
之后使用生成的矩陣乘增量+初始值的方式。可能這個方式有點原始,有好的辦法之后,會更新。
下面這個函數是劃分樣本到最近的聚類中心:
def findClosestCentroids(data,centroids):cluster_indexs=[]for i in range(len(data)):diff=data[i]-centroids#這樣減法會生成K行,這樣一下子就能算出來 一個樣本到k個聚類中心的坐標差# 下面求歐氏距離dist=0for j in range(len(diff[0])):dist+=diff[:,j]**2 # 求x,和y的平方和min_index=np.argmin(dist) #然后找到距離最小的值 并將其標注為簇編號cluster_indexs.append(min_index)return np.array(cluster_indexs) # 返回對應索引的簇編號使用上述函數,我們就可以計算出,每個樣本距離最近的聚類中心,接下來我們需要找出這個簇的真實中心,怎么找呢?
我們只需要找這個簇中樣本的平均坐標就行了,同時np.mean()方法可以提供很好的幫助。
此外可以使用一點小技巧劃分數據集。Datas[clustering==i]先舉個例子。
如果數據集為datas=[[1,2],[1,3],[1,4]],那么datas[True ,False,True]=[[1,2],[1,4]] 所以我們可以用i對比整個簇編號列表。就會得到一個布爾型的列表,如果 和i相同 就會顯示True 否則就是False 。這樣就一步取出了所有的屬于i的樣本,然后在使用numpy.mean()方法求均值。
然后就完成了步驟1-3的主要函數的編寫,下面只要重復這些步驟,就可以求出來。
下面開始編輯主函數
現在,主函數已經結束了,但是我們還不知道最優的k,這就是我開頭說的第二個難題:
我們可以使用誤差平方和( sum of squared errors)SSE。
手肘法求k值
手肘法的核心思想:隨著聚類數k的增大,樣本劃分會更加精細,每個簇的聚合程度會逐漸提高,那么誤差平方和SSE自然會逐漸變小。并且,當k小于真實聚類數時,由于k的增大會大幅增加每個簇的聚合程度,故SSE的下降幅度會很大,而當k到達真實聚類數時,再增加k所得到的聚合程度回報會迅速變小,所以SSE的下降幅度會驟減,然后隨著k值的繼續增大而趨于平緩,也就是說SSE和k的關系圖是一個手肘的形狀,而這個肘部對應的k值就是數據的真實聚類數。當然,這也是該方法被稱為手肘法的原因。
S S E = ∑ i = 1 k ∑ p ∈ C i ∣ p ? m i ∣ 2 SSE=\sum_{i=1}^{k} \sum_{p\in C_i} |p-m_i|^2 SSE=i=1∑k?p∈Ci?∑?∣p?mi?∣2其中,Ci是第i個簇,p是Ci中的樣本點,mi是Ci的質心(Ci中所有樣本的均值),SSE是所有樣本的聚類誤差,代表了聚類效果的好壞。將其轉化為代碼,則如下:
我們再看下sse和k值的變化關系圖:
如上圖所示,k=2 是手肘的肘部。我們可以看出來,但是怎么讓計算機能看懂呢?我又想了一個土辦法:讓i+1的sse值除i的sse值,這樣會得出一個小于1的值,比值越小,變化越巨大。
然后我們在判斷前后兩個比值的差,差最大的就是肘部。我們默認會設置8個分類。所以可以很好的劃分。
如果差值一最大,但是他在差值列表中的索引為0,但是肘部的k值為2。所以索引和k值得差為2。
.接下來,我們要窮盡k值,進行聚類,然后找出效果最好的,并將圖畫出來。
def Confirm_K_value(data_set):# 計算最優的k值#在一個范圍內隨機生成矩陣,lis_k=[]for k in range(1,MAX_CLUSTER):centroids_list,cluster,state=K_means(k,data_set)if not state:# 如果發現有空值,那么直接終止增加Kbreaklis_k.append(calculate_sse(centroids_list[-1],cluster,data_set))print("SSE值",lis_k)max_=find_k_index(lis_k)print("共發現{}個簇".format(max_))centroids_list,cluster,state=K_means(max_,data_set)if not state:print("erro")return# 展示數據show(centroids_list,cluster,data_set)下面描述下畫圖函數,代碼略有繁瑣,后期優化。
def show(centroids_list,cluster,data_set):#簇中心的位置變化情況,樣本對應的簇編號,樣本集合centroid_x = []centroid_y = []for centroid in centroids_list:centroid_x.append(centroid[:,0])centroid_y.append(centroid[:,1])plt.plot(centroid_x, centroid_y, 'r*--',c="blue", markersize=14)# 接下來是畫點lis_x=[[] for i in range(np.unique(cluster).shape[0])]lis_y=[[] for i in range(np.unique(cluster).shape[0])]for i in range(len(data_set)):lis_x[cluster[i]].append(data_set[i][0])lis_y[cluster[i]].append(data_set[i][1])colors=['red','brown','orange','green','cyan','purple','pink','blue','#FFA07A','#20B2AA','#87CEFA','#9ACD32']for i in range(np.unique(cluster).shape[0]):plt.scatter(lis_x[i],lis_y[i],alpha=0.5,c=colors[i])#畫一個散點圖plt.show()【注這個數據集在這個文件同目錄的data目錄下】,下載后需要放到相同位置。數據集
源代碼:
import pandas as pd import numpy as np import matplotlib.pyplot as plt df= pd.read_csv('./data/兩坨散點.csv') data_set=np.array(df)def findClosestCentroids(data,centroids):cluster_indexs=[]for i in range(len(data)):diff=data[i]-centroids# 下面求歐氏距離dist=0for j in range(len(diff[0])):dist+=diff[:,j]**2min_index=np.argmin(dist)cluster_indexs.append(min_index)return np.array(cluster_indexs)# print(np.unique(clustering))# 使用unique可以進行去重復操作。 # 根據聚類重新計算中心點函數: def computMeans(Datas, clustering):centroids = []# print(np.unique(clustering))for i in range(len(np.unique(clustering))): # np.unique計算聚類個數u_k = np.mean(Datas[clustering==i], axis=0) # 求每列的平均值centroids.append(u_k)return np.array(centroids)def create_centroids(k,data_set):min_x,max_x=min(data_set[:,0]),max(data_set[:,0])min_y,max_y=min(data_set[:,1]),max(data_set[:,1])centroids =np.random.random((k,2))centroids_x=min_x+centroids[:,0]*(max_x-min_x)centroids_y=min_y+centroids[:,1]*(max_y-min_y)centroids=[]for i in range(len(centroids_x)):centroids.append([centroids_x[i],centroids_y[i]])return np.array(centroids)def calculate_sse(centroids,clustering,data_set):sum=0for i in range(data_set.shape[0]):diff= data_set[i]-centroids[clustering[i]]sum+=diff[0]**2+diff[1]**2return sum def K_means(k,data_set):# 隨機生成k矩陣centroids =create_centroids(k,data_set)centroids_list=[]for i in range(30):clustering=findClosestCentroids(data_set,centroids)centroids1=computMeans(data_set,clustering)centroids_list.append(centroids1)if centroids.shape!=centroids1.shape:print("出現未選中樣本")# 說明 有點,沒有被任何樣本選中,說明中心點已經多了return centroids_list,[],False #返回中心點的移動軌跡和最終的簇和退出的狀態if np.max(centroids-centroids1)==0:# print("find")breakcentroids=centroids1# print(centroids)cluster=findClosestCentroids(data_set,centroids)return centroids_list,cluster,True #返回中心點的移動軌跡和最終的簇和退出的狀態def show(centroids_list,cluster,data_set):centroid_x = []centroid_y = []for centroid in centroids_list:centroid_x.append(centroid[:,0])centroid_y.append(centroid[:,1])plt.plot(centroid_x, centroid_y, 'r*--',c="blue", markersize=14)lis_x=[[] for i in range(np.unique(cluster).shape[0])]lis_y=[[] for i in range(np.unique(cluster).shape[0])]for i in range(len(data_set)):lis_x[cluster[i]].append(data_set[i][0])lis_y[cluster[i]].append(data_set[i][1])colors=['red','brown','orange','green','cyan','purple','pink','blue','#FFA07A','#20B2AA','#87CEFA','#9ACD32']for i in range(np.unique(cluster).shape[0]):plt.scatter(lis_x[i],lis_y[i],alpha=0.5,c=colors[i])#畫一個散點圖plt.show() MAX_CLUSTER=8 def find_k_index(lis_k):ratios=[ lis_k[i+1]/lis_k[i] for i in range(len(lis_k)-1)]diff=[ ratios[i+1]-ratios[i] for i in range(len(ratios)-1)]return np.argmax(diff)+2 def Confirm_K_value(data_set):# 計算最優的k值#在一個范圍內隨機生成矩陣,lis_k=[]for k in range(1,MAX_CLUSTER):centroids_list,cluster,state=K_means(k,data_set)if not state:breaklis_k.append(calculate_sse(centroids_list[-1],cluster,data_set))print("SSE值",lis_k)max_=find_k_index(lis_k)print("共發現{}個簇".format(max_))centroids_list,cluster,state=K_means(max_,data_set)if not state:print("erro")return# 展示數據show(centroids_list,cluster,data_set)Confirm_K_value(data_set)測試結果
兩個簇情況下:
SSE值 [897.1674886919908, 156.58714828390853, 119.03323146797621, 93.86925045546373, 81.41218770686388]
共發現2個簇
三個簇情況下:
SSE值 [2668.4175360883137, 1139.838296827762, 238.7100927716138, 206.82792773131195, 192.94401994160415]
共發現3個簇
五個簇情況下:
SSE值 [54790.63847837179, 19023.567985249738, 10365.750809115409, 6078.364568727137, 3892.1958462961006, 3695.49840412411, 3418.4365266913137]
共發現5個簇
【存在問題】本次實現的聚類算法,在五個簇的數據集中表現十分不穩定,有待改進。
總結
以上是生活随笔為你收集整理的K-means学习笔记及简易代码实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos7禁用scp
- 下一篇: 德邦面试java_【德邦物流Java面试