以下博文轉自:https://blog.csdn.net/qq_15738501/article/details/79036255 感謝
最近做了一個數據挖掘的項目,挖掘過程中用到了K-means聚類方法,但是由於根據行業經驗確定的聚類數過多並且並不一定是我們獲取到數據的真實聚類數,所以,我們希望能從數據自身出發去確定真實的聚類數,也就是對數據而言的最佳聚類數。爲此,我查閱了大量資料和博客資源,總結出主流的確定聚類數k的方法有以下兩類。
1.手肘法
1.1 理論
手肘法的核心指標是SSE(sum of the squared errors,誤差平方和),
其中,Ci是第i個簇,p是Ci中的樣本點,mi是Ci的質心(Ci中所有樣本的均值),SSE是所有樣本的聚類誤差,代表了聚類效果的好壞。
手肘法的核心思想是:隨着聚類數k的增大,樣本劃分會更加精細,每個簇的聚合程度會逐漸提高,那麼誤差平方和SSE自然會逐漸變小。並且,當k小於真實聚類數時,由於k的增大會大幅增加每個簇的聚合程度,故SSE的下降幅度會很大,而當k到達真實聚類數時,再增加k所得到的聚合程度回報會迅速變小,所以SSE的下降幅度會驟減,然後隨着k值的繼續增大而趨於平緩,也就是說SSE和k的關係圖是一個手肘的形狀,而這個肘部對應的k值就是數據的真實聚類數。當然,這也是該方法被稱爲手肘法的原因。
1.2 實踐
我們對預處理後數據.csv 中的數據利用手肘法選取最佳聚類數k。具體做法是讓k從1開始取值直到取到你認爲合適的上限(一般來說這個上限不會太大,這裏我們選取上限爲8),對每一個k值進行聚類並且記下對於的SSE,然後畫出k和SSE的關係圖(毫無疑問是手肘形),最後選取肘部對應的k作爲我們的最佳聚類數。python實現如下:
- import pandas as pd
- from sklearn.cluster import KMeans
- import matplotlib.pyplot as plt
- df_features = pd.read_csv(r'C:\預處理後數據.csv',encoding='gbk') # 讀入數據
- '利用SSE選擇k'
- SSE = [] # 存放每次結果的誤差平方和
- for k in range(1,9):
- estimator = KMeans(n_clusters=k) # 構造聚類器
- estimator.fit(df_features[['R','F','M']])
- SSE.append(estimator.inertia_)
- X = range(1,9)
- plt.xlabel('k')
- plt.ylabel('SSE')
- plt.plot(X,SSE,'o-')
- plt.show()
顯然,肘部對於的k值爲4,故對於這個數據集的聚類而言,最佳聚類數應該選4
2. 輪廓係數法
2.1 理論
該方法的核心指標是輪廓系數(Silhouette Coefficient),某個樣本點Xi的輪廓係數定義如下:
其中,a是Xi與同簇的其他樣本的平均距離,稱爲凝聚度,b是Xi與最近簇中所有樣本的平均距離,稱爲分離度。而最近簇的定義是
其中p是某個簇Ck中的樣本。事實上,簡單點講,就是用Xi到某個簇所有樣本平均距離作爲衡量該點到該簇的距離後,選擇離Xi最近的一個簇作爲最近簇。
求出所有樣本的輪廓係數後再求平均值就得到了平均輪廓係數。平均輪廓係數的取值範圍爲[-1,1],且簇內樣本的距離越近,簇間樣本距離越遠,平均輪廓係數越大,聚類效果越好。那麼,很自然地,平均輪廓係數最大的k便是最佳聚類數。
2.2 實踐
我們同樣使用2.1中的數據集,同樣考慮k等於1到8的情況,對於每個k值進行聚類並且求出相應的輪廓係數,然後做出k和輪廓係數的關係圖,選取輪廓係數取值最大的k作爲我們最佳聚類係數,python實現如下:
- import pandas as pd
- from sklearn.cluster import KMeans
- from sklearn.metrics import silhouette_score
- import matplotlib.pyplot as plt
- df_features = pd.read_csv(r'C:\Users\61087\Desktop\項目\爬蟲數據\預處理後數據.csv',encoding='gbk')
- Scores = [] # 存放輪廓係數
- for k in range(2,9):
- estimator = KMeans(n_clusters=k) # 構造聚類器
- estimator.fit(df_features[['R','F','M']])
- Scores.append(silhouette_score(df_features[['R','F','M']],estimator.labels_,metric='euclidean'))
- X = range(2,9)
- plt.xlabel('k')
- plt.ylabel('輪廓係數')
- plt.plot(X,Scores,'o-')
- plt.show()
得到聚類數k與輪廓係數的關係圖:
可以看到,輪廓係數最大的k值是2,這表示我們的最佳聚類數爲2。但是,值得注意的是,從k和SSE的手肘圖可以看出,當k取2時,SSE還非常大,所以這是一個不太合理的聚類數,我們退而求其次,考慮輪廓係數第二大的k值4,這時候SSE已經處於一個較低的水平,因此最佳聚類係數應該取4而不是2。
但是,講道理,k=2時輪廓係數最大,聚類效果應該非常好,那爲什麼SSE會這麼大呢?在我看來,原因在於輪廓係數考慮了分離度b,也就是樣本與最近簇中所有樣本的平均距離。爲什麼這麼說,因爲從定義上看,輪廓係數大,不一定是凝聚度a(樣本與同簇的其他樣本的平均距離)小,而可能是b和a都很大的情況下b相對a大得多,這麼一來,a是有可能取得比較大的。a一大,樣本與同簇的其他樣本的平均距離就大,簇的緊湊程度就弱,那麼簇內樣本離質心的距離也大,從而導致SSE較大。所以,雖然輪廓係數引入了分離度b而限制了聚類劃分的程度,但是同樣會引來最優結果的SSE比較大的問題,這一點也是值得注意的。
總結
從以上兩個例子可以看出,輪廓係數法確定出的最優k值不一定是最優的,有時候還需要根據SSE去輔助選取,這樣一來相對手肘法就顯得有點累贅。因此,如果沒有特殊情況的話,我還是建議首先考慮用手肘法。