K-Means算法是無監督的聚類算法,它實現起來比較簡單,聚類效果也不錯,因此應用很廣泛。K-Means算法有大量的變體,比如最傳統的K-Means算法,在其基礎上優化變體方法:包括初始化優化K-Means++, 距離計算優化elkan K-Means算法和大數據情況下的優化Mini Batch K-Means算法。
1、K-Means原理
K-Means算法的基本思想很簡單,對於給定的樣本集,按照樣本之間的距離大小,將樣本集划分為K個簇。讓簇內的點盡量緊密的連在一起,而讓簇間的距離盡量的大。
如果用數據表達式表示,假設簇划分為(C1,C2,...Ck),則我們的目標是最小化平方誤差E:
其中μi是簇Ci的均值向量,有時也稱為質心,表達式為:
如果我們想直接求上式的最小值並不容易,這是一個NP難的問題,因此只能采用啟發式的迭代方法。
如圖,我們可以直觀的理解k-means的啟發式算法。
上圖a表達了初始的數據集,假設k=2。在圖b中,我們隨機選擇了兩個k類所對應的類別質心,即圖中的紅色質心和藍色質心,然后分別求樣本中所有點到這兩個質心的距離,並標記每個樣本的類別為和該樣本距離最小的質心的類別,如圖c所示,經過計算樣本和紅色質心和藍色質心的距離,我們得到了所有樣本點的第一輪迭代后的類別。此時我們對我們當前標記為紅色和藍色的點分別求其新的質心,如圖4所示,新的紅色質心和藍色質心的位置已經發生了變動。圖e和圖f重復了我們在圖c和圖d的過程,即將所有點的類別標記為距離最近的質心的類別並求新的質心。最終我們得到的兩個類別如圖f。
當然在實際K-Mean算法中,我們一般會多次運行圖c和圖d,才能達到最終的比較優的類別。
2、K-Means算法流程
首先我們看看K-Means算法的一些要點。
1)對於K-Means算法,首先要注意的是k值的選擇,一般來說,我們會根據對數據的先驗經驗選擇一個合適的k值,如果沒有什么先驗知識,則可以通過交叉驗證選擇一個合適的k值。
2)在確定了k的個數后,我們需要選擇k個初始化的質心,就像上圖b中的隨機質心。由於我們是啟發式方法,k個初始化的質心的位置選擇對最后的聚類結果和運行時間都有很大的影響,因此需要選擇合適的k個質心,最好這些質心不能太近。
下面總結下傳統K-means算法的基本流程:
3、初始化優化K-Means++
在上節我們提到,k個初始化的質心的位置選擇對最后的聚類結果和運行時間都有很大的影響,因此需要選擇合適的k個質心。如果僅僅是完全隨機的選擇,有可能導致算法收斂很慢。K-Means++算法就是對K-Means隨機初始化質心的方法的優化。
K-Means++的對於初始化質心的優化策略也很簡單,如下:
a) 從輸入的數據點集合中隨機選擇一個點作為第一個聚類中心μ1
b) 對於數據集中的每一個點xi,計算它與已選擇的聚類中心中最近聚類中心的距離\(D(x_i) = arg\;min||x_i- \mu_r||_2^2\;\;r=1,2,...k_{selected}\)
c) 選擇一個新的數據點作為新的聚類中心,選擇的原則是:D(x)較大的點,被選取作為聚類中心的概率較大
d) 重復b和c直到選擇出k個聚類質心
e) 利用這k個質心來作為初始化質心去運行標准的K-Means算法
4、距離計算優化elkan K-Means
在傳統的K-Means算法中,我們在每輪迭代時,要計算所有的樣本點到所有的質心的距離,這樣會比較的耗時。
elkan K-Means算法就是從這塊入手加以改進,利用了兩邊之和大於等於第三邊,以及兩邊之差小於第三邊的三角形性質,來減少不必要的距離的計算。
主要有以下兩種規律:
利用上邊的兩個規律,elkan K-Means比起傳統的K-Means迭代速度有很大的提高。但是如果我們的樣本的特征是稀疏的,有缺失值的話,這個方法就不使用了,此時某些距離無法計算,則不能使用該算法。
5、大樣本優化Mini Batch K-Means
在統的K-Means算法中,要計算所有的樣本點到所有的質心的距離。如果樣本量非常大,即使加上elkan K-Means優化也依舊。因此Mini Batch K-Means應運而生。
顧名思義,Mini Batch,也就是用樣本集中的一部分的樣本來做傳統的K-Means,這樣可以避免樣本量太大時的計算難題,算法收斂速度大大加快。當然此時的代價就是我們的聚類的精確度也會有一些降低。一般來說這個降低的幅度在可以接受的范圍之內。
在Mini Batch K-Means中,我們會選擇一個合適的批樣本大小batch size,batch size個樣本一般是通過無放回的隨機采樣得到的。
為了增加算法的准確性,我們一般會多跑幾次Mini Batch K-Means算法,用得到不同的隨機采樣集來得到聚類簇,選擇其中最優的聚類簇。
6、K-Means算法的優缺點
K-Means的主要優點有:
1)原理比較簡單,實現也是很容易,收斂速度快。
2)聚類效果較優。
3)算法的可解釋度比較強。
4)主要需要調參的參數僅僅是簇數k。
K-Means的主要缺點有:
1)K值的選取不好把握
2)對於不是凸的數據集比較難收斂
3)如果各隱含類別的數據不平衡,比如各隱含類別的數據量嚴重失衡,或者各隱含類別的方差不同,則聚類效果不佳。
4) 采用迭代的啟發式算法,得到的結果只是局部最優。
5) 對噪音和異常點比較的敏感。
7、K-Means算法和KNN算法的聯系與區別

8、K值得評估標准
不像監督學習的分類問題和回歸問題,我們的無監督聚類沒有樣本輸出,也就沒有比較直接的聚類評估方法。但是我們可以從簇內的稠密程度和簇間的離散程度來評估聚類的效果。常見的方法有輪廓系數Silhouette Coefficient和CH系數Calinski-Harabasz Index。
(1) 輪廓系數
輪廓系數(Silhouette Coefficient),是聚類效果好壞的一種評價方式。
輪廓系數的計算綜合考慮了內聚度和分離度兩種因素。
1)計算 簇內不相似度a(i) :i向量到同簇內其他點不相似程度(距離)的平均值,體現凝聚度
2)計算 簇間不相似度b(i) :i向量到其他簇的平均不相似程度(距離)的最小值,bi =min{bi1, bi2, ..., bik},體現分離度
那么第i個對象的輪廓系數就為:
所有樣本的s i 的均值稱為聚類結果的輪廓系數,定義為S,是該聚類是否合理、有效的度量。
聚類結果的輪廓系數的取值在【-1,1】之間,最佳值為1,最差值為-1。接近0的值表示重疊的群集。負值通常表示樣本已分配給錯誤的聚類,因為不同的聚類更為相似。
(2)Calinski-Harabasz
Calinski-Harabasz Index,這個計算簡單直接,得到的Calinski-Harabasz分數值s越大則聚類效果越好。
Calinski-Harabasz分數值s的數學計算公式是:
其中m為訓練集樣本數,k為類別數。Bk為類別之間的協方差矩陣,Wk為類別內部數據的協方差矩陣。tr為矩陣的跡。
在scikit-learn中,對應得方法如下:
9、代碼實現
首先我們隨機創建一些二維數據作為訓練集,先用傳統得Kmeans算法看看聚類情況。
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets.samples_generator import make_blobs
from sklearn.cluster import KMeans
# X為樣本特征,Y為樣本簇類別, 共1000個樣本,每個樣本2個特征,共4個簇,簇中心在[-1,-1], [0,0],[1,1], [2,2], 簇方差分別為[0.4, 0.2, 0.2]
X, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1,-1], [0,0], [1,1], [2,2]], cluster_std=[0.4, 0.2, 0.2, 0.2],
random_state =9)
y_pred = KMeans(n_clusters=4, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()
用Calinski-Harabasz Index評估的k=4時候聚類分數
from sklearn import metrics
# metrics.silhouette_score(X, y_pred) # 輪廓系數
metrics.calinski_harabaz_score(X, y_pred) #CH系數
5924.050613480169
現在我們再看看用MiniBatchKMeans的效果,我們將batch size設置為200. 由於我們的4個簇都是凸的,所以其實batch size的值只要不是非常的小,對聚類的效果影響不大。
from sklearn.cluster import MiniBatchKMeans
for index, k in enumerate((2,3,4,5)):
plt.subplot(2,2,index+1)
y_pred = MiniBatchKMeans(n_clusters=k, batch_size = 200, random_state=9).fit_predict(X)
score= metrics.calinski_harabaz_score(X, y_pred)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.text(.99, .01, ('k=%d, score: %.2f' % (k,score)),
transform=plt.gca().transAxes, size=10,
horizontalalignment='right')
plt.show()
參考資料:
https://www.cnblogs.com/pinard/p/6164214.html
https://www.cnblogs.com/pinard/p/6169370.html
https://www.cnblogs.com/lliuye/p/9144312.html