機器學習基礎:Kmeans算法及其優化
CONTENT
1. 算法原理
對於給定的樣本集,按照樣本之間的距離大小,將樣本集划分為K個簇。讓簇內的點盡量緊密的連在一起,而讓簇間的距離盡量的大。即:
求解思想是:
- 先初始k個簇的質心;
- 然后分別求樣本中所有點到這k質心的距離,並標記每個樣本的類別為和該樣本距離最小的質心的類別;
- 更新k個質心;
- 重復以上兩步, 如果所有的k個質心向量都沒有發生變化,則停止。
Kmeans的思想比較簡單,用下面一組圖就可以形象的描述:
上圖a表達了初始的數據集,假設k=2。在圖b中,我們隨機選擇了兩個k類所對應的類別質心,即圖中的紅色質心和藍色質心,然后分別求樣本中所有點到這兩個質心的距離,並標記每個樣本的類別為和該樣本距離最小的質心的類別,如圖c所示,經過計算樣本和紅色質心和藍色質心的距離,我們得到了所有樣本點的第一輪迭代后的類別。此時我們對我們當前標記為紅色和藍色的點分別求其新的質心,如圖4所示,新的紅色質心和藍色質心的位置已經發生了變動。圖e和圖f重復了我們在圖c和圖d的過程,即將所有點的類別標記為距離最近的質心的類別並求新的質心。最終我們得到的兩個類別如圖f。當然在實際K-Mean算法中,我們一般會多次運行圖c和圖d,才能達到最終的比較優的類別。
2. 算法流程
3. 算法優化
3.1 kmeans++
kmeans中k個初始化的質心的位置選擇對最后的聚類結果和運行時間都有很大的影響,因此需要選擇合適的k個質心。如果僅僅是完全隨機的選擇,有可能導致算法收斂很慢。K-Means++算法就是對K-Means隨機初始化質心的方法的優化。K-Means++的對於初始化質心的優化策略如下:
3.2 Elkan kmeans
在傳統的K-Means算法中,我們在每輪迭代時,要計算所有的樣本點到所有的質心的距離,這樣會比較的耗時。那么,對於距離的計算有沒有能夠簡化的地方呢?elkan K-Means算法就是從這塊入手加以改進。它的目標是減少不必要的距離的計算。主要基於以下兩種策略:
利用上邊的兩個規律,elkan K-Means比起傳統的K-Means迭代速度有很大的提高。但是如果我們的樣本的特征是稀疏的,有缺失值的話,這個方法就不使用了,此時某些距離無法計算,則不能使用該算法。
3.3 Mini Batch kmeans
在統的K-Means算法中,要計算所有的樣本點到所有的質心的距離。如果樣本量非常大,比如達到10萬以上,特征有100以上,此時用傳統的K-Means算法非常的耗時,就算加上elkan K-Means優化也依舊。在大數據時代,這樣的場景越來越多。此時Mini Batch K-Means應運而生。
Mini Batch,也就是用樣本集中的一部分的樣本來做傳統的K-Means,這樣可以避免樣本量太大時的計算難題,算法收斂速度大大加快。當然此時的代價就是我們的聚類的精確度也會有一些降低。一般來說這個降低的幅度在可以接受的范圍之內。
一般是通過無放回的隨機采樣得到batch, 然后僅僅用batch size個樣本來做K-Means聚類,一般會多跑幾次Mini Batch K-Means算法,用得到不同的隨機采樣集來得到聚類簇,選擇其中最優的聚類簇。
4. 與KNN的區別
K-Means是無監督學習的聚類算法,沒有樣本輸出;而KNN是監督學習的分類算法,有對應的類別輸出。KNN基本不需要訓練,對測試集里面的點,只需要找到在訓練集中最近的k個點,用這最近的k個點的類別來決定測試點的類別。而K-Means則有明顯的訓練過程,找到k個類別的最佳質心,從而決定樣本的簇類別。兩者也有一些相似點,兩個算法都包含一個過程,即找出和某一個點最近的點。兩者都利用了最近鄰(nearest neighbors)的思想。
5. 算法小結
6. sklearn代碼實踐
在scikit-learn中,包括兩個K-Means的算法,一個是傳統的K-Means算法,對應的類是KMeans。另一個是基於采樣的Mini Batch K-Means算法,對應的類是MiniBatchKMeans。
6.1 評估方法
常見的方法有輪廓系數Silhouette Coefficient
和Calinski-Harabasz Index
, 一般使用Calinski-Harabasz Index
。其計算方法如下:
類別內部數據的協方差越小越好,類別之間的協方差越大越好,這樣的Calinski-Harabasz分數會高。在scikit-learn中, Calinski-Harabasz Index
對應的方法是metrics.calinski_harabaz_score
。
6.2 KMeans主要參數
KMeans類的主要參數有:
-
n_clusters: 即我們的k值,一般需要多試一些值以獲得較好的聚類效果。k值好壞的評估標准在下面會講。
-
max_iter: 最大的迭代次數,一般如果是凸數據集的話可以不管這個值,如果數據集不是凸的,可能很難收斂,此時可以指定最大的迭代次數讓算法可以及時退出循環。
-
n_init:用不同的初始化質心運行算法的次數。由於K-Means是結果受初始值影響的局部最優的迭代算法,因此需要多跑幾次以選擇一個較好的聚類效果,默認是10,一般不需要改。如果你的k值較大,則可以適當增大這個值。
-
init: 即初始值選擇的方式,可以為完全隨機選擇'random',優化過的'k-means++'或者自己指定初始化的k個質心。一般建議使用默認的'k-means++'。
-
algorithm:有“auto”, “full” or “elkan”三種選擇。"full"就是我們傳統的K-Means算法, “elkan”是我們原理篇講的elkan K-Means算法。默認的"auto"則會根據數據值是否是稀疏的,來決定如何選擇"full"和“elkan”。一般數據是稠密的,那么就是 “elkan”,否則就是"full"。一般來說建議直接用默認的"auto"
6.3 MiniBatchKMeans主要參數
MiniBatchKMeans類的主要參數比KMeans類稍多,主要有:
-
n_clusters: 即我們的k值,和KMeans類的n_clusters意義一樣。
-
max_iter:最大的迭代次數, 和KMeans類的max_iter意義一樣。
-
n_init:用不同的初始化質心運行算法的次數。這里和KMeans類意義稍有不同,KMeans類里的n_init是用同樣的訓練集數據來跑不同的初始化質心從而運行算法。而MiniBatchKMeans類的n_init則是每次用不一樣的采樣數據集來跑不同的初始化質心運行算法。
-
batch_size:即用來跑Mini Batch KMeans算法的采樣集的大小,默認是100.如果發現數據集的類別較多或者噪音點較多,需要增加這個值以達到較好的聚類效果。
-
init: 即初始值選擇的方式,和KMeans類的init意義一樣。
-
init_size: 用來做質心初始值候選的樣本個數,默認是batch_size的3倍,一般用默認值就可以了。
-
reassignment_ratio: 某個類別質心被重新賦值的最大次數比例,這個和max_iter一樣是為了控制算法運行時間的。這個比例是占樣本總數的比例,乘以樣本總數就得到了每個類別質心可以重新賦值的次數。如果取值較高的話算法收斂時間可能會增加,尤其是那些暫時擁有樣本數較少的質心。默認是0.01。如果數據量不是超大的話,比如1w以下,建議使用默認值。如果數據量超過1w,類別又比較多,可能需要適當減少這個比例值。具體要根據訓練集來決定。
-
max_no_improvement:即連續多少個Mini Batch沒有改善聚類效果的話,就停止算法, 和reassignment_ratio, max_iter一樣是為了控制算法運行時間的。默認是10.一般用默認值就足夠了。
6.4 實驗
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets.samples_generator import make_blobs
from sklearn.cluster import KMeans, MiniBatchKMeans
from sklearn import metrics
# X為樣本特征,Y為樣本簇類別, 共10000個樣本,每個樣本2個特征,共6個簇,簇中心在[-1,-1], [0,0],[1,1], [2,2],[3,5],[1,6] 簇方差分別為[0.4, 0.2, 0.2,0.1,0.3]
X, y = make_blobs(n_samples=10000, n_features=2, centers=[[-1,-1], [0,0], [1,1], [2,2],[3,5],[1,6]], cluster_std=[0.4, 0.2, 0.2, 0.2,0.1,0.3],
random_state =9)
plt.scatter(X[:, 0], X[:, 1], marker='o')
plt.show()
def get_cluster(k, method='kmeans'):
if method == 'kmeans':
return KMeans(n_clusters=k, random_state=9)
elif method == 'mini_batch_kmeans':
return MiniBatchKMeans(n_clusters=k, batch_size = 200, random_state=9)
else:
print('Not valid')
return None
def exp(method='kmeans'):
res = []
for k in range(2, 11):
cluster = get_cluster(k, method)
if not cluster:
break
y_pred = cluster.fit_predict(X)
score = metrics.calinski_harabasz_score(X, y_pred)
res.append((k, X, y_pred, score))
return res
def show_res(res):
plt.figure(figsize=(26, 15))
for i, (k, X, y_pred, score) in enumerate(res):
plt.subplot(3,3,i+1)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.title(f'k={k}, score:{score}')
plt.show()
res = exp()
show_res(res)
res = exp('mini_batch_kmeans')
show_res(res)