K-means算法詳解及實現

主要的KMeans算法的原理和應用,在學習典過程中,我們要帶着以下幾個問題去學習
以下問題摘自於https://blog.csdn.net/qq_33011855/article/details/81482511
1、簡述一下K-means算法的原理和工作流程
2、K-means中常用的到中心距離的度量有哪些?
3、K-means中的k值如何選取?
4、K-means算法中初始點的選擇對最終結果有影響嗎?
5、K-means聚類中每個類別中心的初始點如何選擇?
6、K-means中空聚類的處理
7、K-means是否會一直陷入選擇質心的循環停不下來?
8、如何快速收斂數據量超大的K-means?
9、K-means算法的優點和缺點是什麼?

一、原理和流程

1、原理

對給定的無標記的樣本數據集,事先確定聚類簇數K,讓簇內的樣本儘可能緊密分佈在一起,使簇間的距離儘可能大。K-Means作爲無監督的聚類算法,其類似於全自動分類,簇內越相似,聚類效果越好,實現較簡單,聚類效果好,因此被廣泛使用。用以下的效果圖更能直觀地看出其過程:
在這裏插入圖片描述

2、流程

(1)隨即確定K個初始點作爲質心(這裏如何確定k將在下面給出解釋)
(2)將數據集中的每個點分配到一個簇中,即爲每個點找距離其最近的質心,並將其分配給之心所對應的簇
(3)簇分好後,計算每個簇所有點的平均值,將平均值作爲對應簇新的質心
(4)循環2、3步驟,直到質心不變

Step1:從數據集D中隨機選擇k個樣本作爲初始的k個質心向量
Step2:計算數據集中樣本Xi分別到k個質心的歐幾里得距離d1, d2……dk. 於是我們得到與Xi距離最小的質心並且把Xi劃分到和這個質心同一cluster中。
Step3:對數據集中所有樣本進行Step2操作
Step4:重新計算k個簇裏面的向量均值(就是把k個質心在新的簇下刷新一遍),然後重複Step2- Step4。直到所有的k個質心向量都沒有發生變化。

僞代碼是:(要會寫)

創建k個點作爲起始質心(經常是隨機選擇)
當任意一個點的簇分配結果發生改變時
	對數據集中的每個數據點
		對每個質心
			計算質心和數據點之間的距離
		將數據點分配到距離其最近的簇
	對每一個簇,計算簇中所有點的均值並將均值作爲新的質心

上面“最近”質心,意味着需要進行某種距離的計算,即下文要介紹的K-means中常用的到中心距離的度量有哪些?

二、K-means中常用的到中心距離的度量有哪些

這裏最常用的有以下兩種(我們這裏只簡單介紹下二維的)

  1. 曼哈頓距離
    d12=x1x2+y1y2 d _ { 12 } = \left| x _ { 1 } - x _ { 2 } \right| + \left| y _ { 1 } - y _ { 2 } \right|
  2. 歐幾里得距離(別稱“歐式距離”)
    d12=(x1x2)2+(y1y2)2 d _ { 12 } = \sqrt { \left( x _ { 1 } - x _ { 2 } \right) ^ { 2 } + \left( y _ { 1 } - y _ { 2 } \right) ^ { 2 } }
假設兩個點座標是(11),(45)
則曼哈頓距離是7,歐式距離是5

在這裏插入圖片描述
補充:(圖片來源
在這裏插入圖片描述

三、K-means中的k值如何選取

以下博文轉自:https://blog.csdn.net/qq_15738501/article/details/79036255
通常我們會採用手肘法來確定k的值

1、手肘法

手肘法的核心指標是SSE(sum of the squared errors,誤差平方和),
SSE=i=1kpCipmi2 S S E = \sum _ { i = 1 } ^ { k } \sum _ { p \in C _ { i } } \left| p - m _ { i } \right| ^ { 2 }

其中,CiC_i是第ii個簇,ppCiC_i中的樣本點,mim_iCiC_i的質心(CiC_i中所有樣本的均值),SSE是所有樣本的聚類誤差,代表了聚類效果的好壞。

手肘法的核心思想是:隨着聚類數k的增大,樣本劃分會更加精細,每個簇的聚合程度會逐漸提高,那麼誤差平方和SSE自然會逐漸變小。並且,當k小於真實聚類數時,由於k的增大會大幅增加每個簇的聚合程度,故SSE的下降幅度會很大,而當k到達真實聚類數時,再增加k所得到的聚合程度回報會迅速變小,所以SSE的下降幅度會驟減,然後隨着k值的繼續增大而趨於平緩,也就是說SSE和k的關係圖是一個手肘的形狀,而這個肘部對應的k值就是數據的真實聚類數。當然,這也是該方法被稱爲手肘法的原因。

實踐:我們對 預處理後數據.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與SSE的關係圖如下:
在這裏插入圖片描述
顯然,肘部對於的k值爲4,故對於這個數據集的聚類而言,最佳聚類數應該選4

2、輪廓係數法

該方法的核心指標是輪廓係數(Silhouette Coefficient),某個樣本點XiX_i的輪廓係數定義如下
S=bamax(a,b) S = \frac { b - a } { \max ( a , b ) }

其中,aaXiX_i與同簇的其他樣本的平均距離,稱爲凝聚度bbXiX_i與最近簇中所有樣本的平均距離,稱爲分離度。而最近簇的定義是:
Cj=argminCk1npCzpXi2 C _ { j } = \arg \min _ { C _ { k } } \frac { 1 } { n } \sum _ { p \in C _ { z } } \left| p - X _ { i } \right| ^ { 2 }

其中pp是某個簇CkC_k中的樣本。事實上,簡單點講,就是用XiX_i到某個簇所有樣本的平均距離作爲衡量該點到該簇的距離後,選擇離XiX_i最近的一個簇作爲最近簇。

求出所有樣本的輪廓係數後再求平均值就得到了平均輪廓係數。平均輪廓係數的取值範圍爲[-1,1],且簇內樣本的距離越近,簇間樣本距離越遠,平均輪廓係數越大,聚類效果越好。那麼,很自然地,平均輪廓係數最大的k便是最佳聚類數

實踐:我們同樣使用上面的數據集,同樣考慮kk等於1188的情況,對於每個kk值進行聚類並且求出相應的輪廓係數,然後做出kk和輪廓係數的關係圖,選取輪廓係數取值最大的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比較大的問題,這一點也是值得注意的

3、總結

從以上兩個例子可以看出,輪廓係數法確定出的最優k值不一定是最優的,有時候還需要根據SSE去輔助選取,這樣一來相對手肘法就顯得有點累贅。因此,如果沒有特殊情況的話,我還是建議首先考慮用手肘法

四、代碼實現

def kmeans(k):
    m, n = 100, 20  # 構造樣本:100行、20列
    x = 10 * np.random.random((m, n))  # 0-1

    # 隨機選擇k個初始中心點
    init_cent_sample = set()
    while len(init_cent_sample) < k:
        init_cent_sample.add(np.random.randint(0, m))  # [low, high)
        
    # 保存簇中心點(聚類中心點)
    cent = x[list(init_cent_sample)]

    # 記錄每個樣本的類歸屬    [每個樣本所歸屬的簇中心點, 該樣本距離此簇中心點的距離]
    cluster_assessment = np.zeros((m, 2))  # np.array([min_idx, min_dist])

    # 記錄每個類的中心點在本次迭代後是否有過改變
    cent_changed = True
    while cent_changed:
        cent_changed = False

        for j in range(m):
            # 記錄每個樣本距離最近的類
            min_idx = -1
            # 記錄每個樣本的最小類距
            min_dist = math.inf

            for i in range(k):
                d = distance(x[j], cent[i])
                if d < min_dist:
                    min_idx = i
                    min_dist = d

            # 記錄此樣本的中心點是否發生變化(只有所有樣本所歸屬的簇類中心點都不變時,才停止迭代)
            if min_idx != cluster_assessment[j][0]:
                cluster_assessment[j] = np.array([min_idx, min_dist])
                cent_changed = True
                
        print(cluster_assessment)

        # 更新每個類的中心點:均值
        for i in range(k):
            cent_i_samples = np.where(cluster_assessment[:, 0] == i)
            if len(cent_i_samples) > 0:
                print(cent_i_samples)
                cent[i] = np.mean(x[cent_i_samples], axis=0)


# 計算距離
def distance(a, b):
    return math.sqrt(sum(pow(a - b, 2)))

五、其他問題的解答

  • K-means算法中初始點的選擇對最終結果有影響嗎?
    會有影響的,不同的初始值結果可能不一樣(選不好可能會得到局部最優)

  • K-means聚類中每個類別中心的初始點如何選擇?
    • 隨機法:最簡單的確定初始類簇中心點的方法是隨機選擇K個點作爲初始的類簇中心點。
    • 這k個點的距離儘可能遠:首先隨機選擇一個點作爲第一個初始類簇中心點,然後選擇距離該點最遠的那個點作爲第二個初始類簇中心點,然後再選擇距離前兩個點的最近距離最大的點作爲第三個初始類簇的中心點,以此類推,直到選出k個初始類簇中心。
    • 可以對數據先進行層次聚類(博客後期會更新這類聚類算法),得到K個簇之後,從每個類簇中選擇一個點,該點可以是該類簇的中心點,或者是距離類簇中心點最近的那個點。

  • K-means中空聚類的處理
    • 選擇一個距離當前任何質心最遠的點。這將消除當前對總平方誤差影響最大的點。
    • 從具有最大SSE的簇中選擇一個替補的質心,這將分裂簇並降低聚類的總SSE。如果有多個空簇,則該過程重複多次。
    • 如果噪點或者孤立點過多,考慮更換算法,如密度聚類(博客後期會更新這類聚類算法)

  • K-means是否會一直陷入選擇質心的循環停不下來?
    不會,有數學證明Kmeans一定會收斂,大概思路是利用SSE的概念(也就是誤差平方和),即每 個點到自身所歸屬質心的距離的平方和,這個平方和是一個凸函數,通過迭代一定可以到達它的局部最優解
    • 迭代次數設置
    • 設定收斂判斷距離

  • 如何快速收斂數據量超大的K-means?
    相關解釋可以去這個博客稍做了解https://blog.csdn.net/sunnyxidian/article/details/89630815

  • K-means算法的優點和缺點是什麼?

    K-Means的主要優點
    (1)原理簡單,容易實現
    (2)可解釋度較強

    K-Means的主要缺點:(對應改進和優化暫時還沒補充)
    (1)K值很難確定
    (2)局部最優
    (3)對噪音和異常點敏感
    (4)需樣本存在均值(限定數據種類)
    (5)聚類效果依賴於聚類中心的初始化
    (6)對於非凸數據集或類別規模差異太大的數據效果不好


  • K-Means與KNN有什麼區別
    1)KNN是分類算法,K-means是聚類算法;
    2)KNN是監督學習,K-means是非監督學習

  • 如何對K-means聚類效果進行評估?
    輪廓係數(Silhouette Coefficient),是聚類效果好壞的一種評價方式。最早由 Peter J. Rousseeuw 在 1986 提出。它結合凝聚度分離度兩種因素。可以用來在相同原始數據的基礎上用來評價不同算法、或者算法不同運行方式對聚類結果所產生的影響。

  • em算法與k-means的關係,用em算法推導解釋k means

  • 用hadoop實現看k means

  • 如何判斷自己實現的 LR、Kmeans 算法是否正確?

References

  • https://blog.csdn.net/WangZixuan1111/article/details/98970139
  • https://blog.csdn.net/WangZixuan1111/article/details/98947683
  • https://blog.csdn.net/orangefly0214/article/details/86538865
  • https://mp.weixin.qq.com/s?__biz=Mzg2MjI5Mzk0MA==&mid=2247484061&idx=1&sn=2f9d5def9ec4615a9f4234c931124b92&chksm=ce0b5846f97cd150a479c37ee24f497ae864065415f23ca7f28f67206d949c718430f607919f&scene=21#wechat_redirect
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章