K-means演算法及python sklearn實現
目錄
K-means演算法
前言
根據訓練樣本是否包含標籤資訊,機器學習可以分為監督學習和無監督學習。聚類演算法是典型的無監督學習,其訓練樣本中只包含樣本特徵,不包含樣本的標籤資訊。在聚類演算法中,利用樣本的特徵,將具有相似屬性的樣本劃分到同一類別中。
K-means演算法,也被稱為K-均值或K-平均演算法,是一種廣泛使用的聚類演算法。K-means演算法是基於相似性的無監督的演算法,通過比較樣本之間的相似性,將較為相似的樣本劃分到同一類別中。由於K-means演算法簡單、易於實現的特點, K-means
K-Means演算法的概述
基本K-Means演算法的思想很簡單,事先確定常數K,常數K意味著最終的聚類類別數,首先隨機選定初始點為質心,並通過計算每一個樣本與質心之間的相似度(這裡為歐式距離),將樣本點歸到最相似的類中,接著,重新計算每個類的質心(即為類中心),重複這樣的過程,知道質心不再改變,最終就確定了每個樣本所屬的類別以及每個類的質心。由於每次都要計算所有的樣本與每一個質心之間的相似度,故在大規模的資料集上,K-Means演算法的收斂速度比較慢。
K-Means演算法的基本原理
K-Means演算法是較為經典的聚類演算法,假設訓練資料集X為:{x1,x2,⋯,xn},其中,每一個樣本xj為m維的向量。此時的樣本為一個m×n的矩陣
假設有k個類,分別為:{C1,⋯,Ck}。k-Means演算法通過歐式距離的度量方法計算每一個樣本xj到質心之間的距離,並將其劃分到較近的質心所屬的類別中並重新計算質心,重複以上的過程,直到質心不再改變為止,上述的過程可以總結為:
- 初始化常數K,隨機選取初始點為質心
- 重複計算以下過程,直到質心不再改變
計算樣本與每個質心之間的相似度,將樣本歸類到最相似的類中
重新計算質心
- 輸出最終的質心以及每個類
K-Means與矩陣分解
K-Means的目標函式
最終使得質心不再改變,這就意味著每一個樣本被劃分到了最近的質心所屬的類別中,即:
例項推演
1、資料準備
下面我們用一個案例來看一下:
樣本 X0 X1 1 7 5 2 5 7 3 7 7 4 3 3 5 4 6 6 1 4 7 0 0 8 2 2 9 8 7 10 6 8 11 5 5 12 3 7
上表有兩個解釋變數,每個樣本有兩個特徵。用python演示一下
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
X0 = np.array([7, 5, 7, 3, 4, 1, 0, 2, 8, 6, 5, 3])
X1 = np.array([5, 7, 7, 3, 6, 4, 0, 2, 7, 8, 5, 7])
plt.figure()
plt.axis([-1, 9, -1, 9])
plt.grid(True)
plt.plot(X0, X1, 'k.')
2、隨機選取重心
假設K-Means初始化時,隨機設定兩個重心,將第一個類的重心設定在第5個樣本,第二個類的重心設定在第11個樣本.那麼我們可以把每個例項與兩個重心的距離都計算出來,將其分配到最近的類裡面。計算結果如下表所示:
樣本 |
X0 |
X1 |
與C1距離 |
與C2距離 |
上次聚類結果 |
新聚類結果 |
是否改變 |
1 |
7 |
5 |
3.16 |
2.00 |
None |
C2 |
YES |
2 |
5 |
7 |
1.41 |
2.00 |
None |
C1 |
YES |
3 |
7 |
7 |
3.16 |
2.83 |
None |
C2 |
YES |
4 |
3 |
3 |
3.16 |
2.83 |
None |
C2 |
YES |
5 |
4 |
6 |
0.00 |
1.41 |
None |
C1 |
YES |
6 |
1 |
4 |
3.61 |
4.12 |
None |
C1 |
YES |
7 |
0 |
0 |
7.21 |
7.07 |
None |
C2 |
YES |
8 |
2 |
2 |
4.47 |
4.24 |
None |
C2 |
YES |
9 |
8 |
7 |
4.12 |
3.61 |
None |
C2 |
YES |
10 |
6 |
8 |
2.83 |
3.16 |
None |
C1 |
YES |
11 |
5 |
5 |
1.41 |
0.00 |
None |
C2 |
YES |
12 |
3 |
7 |
1.41 |
2.83 |
None |
C1 |
YES |
C1重心 |
4 |
6 |
|||||
C2重心 |
5 |
5 |
新的重心位置和初始聚類結果如下圖所示。第一類用X表示,第二類用點表示。重心位置用稍大的點突出顯示。
C1 = [1, 4, 5, 9, 11]#屬於C1的index
C2 = list(set(range(12)) - set(C1))#屬於C2的index
X0C1, X1C1 = X0[C1], X1[C1]#屬於C1的座標
X0C2, X1C2 = X0[C2], X1[C2]#屬於C2的座標
plt.figure()
plt.axis([-1, 9, -1, 9])
plt.grid(True)
plt.plot(X0C1, X1C1, 'rx')
plt.plot(X0C2, X1C2, 'g.')
plt.plot(4,6,'rx',ms=15.0)
plt.plot(5,5,'g.',ms=15.0)
3、重新計算重心
接下來我們需要重新計算兩個類的重心,把重心移動到新位置,並重新計算各個樣本與新重心的距離,並根據距離遠近為樣本重新歸類。結果如下表所示:
樣本 |
X0 |
X1 |
與C1距離 |
與C2距離 |
上次聚類結果 |
新聚類結果 |
是否改變 |
1 |
7 |
5 |
3.49 |
2.58 |
C2 |
C2 |
NO |
2 |
5 |
7 |
1.34 |
2.89 |
C1 |
C1 |
NO |
3 |
7 |
7 |
3.26 |
3.75 |
C2 |
C1 |
YES |
4 |
3 |
3 |
3.49 |
1.94 |
C2 |
C2 |
NO |
5 |
4 |
6 |
0.45 |
1.94 |
C1 |
C1 |
NO |
6 |
1 |
4 |
3.69 |
3.57 |
C1 |
C2 |
YES |
7 |
0 |
0 |
7.44 |
6.17 |
C2 |
C2 |
NO |
8 |
2 |
2 |
4.75 |
3.35 |
C2 |
C2 |
NO |
9 |
8 |
7 |
4.24 |
4.46 |
C2 |
C1 |
YES |
10 |
6 |
8 |
2.72 |
4.11 |
C1 |
C1 |
NO |
11 |
5 |
5 |
1.84 |
0.96 |
C2 |
C2 |
NO |
12 |
3 |
7 |
1.00 |
3.26 |
C1 |
C1 |
NO |
C1重心 |
3.80 |
6.40 |
|||||
C2重心 |
4.57 |
4.14 |
C1 = [1, 2, 4, 8, 9, 11]
C2 = list(set(range(12)) - set(C1))
X0C1, X1C1 = X0[C1], X1[C1]
X0C2, X1C2 = X0[C2], X1[C2]
plt.figure()
plt.axis([-1, 9, -1, 9])
plt.grid(True)
plt.plot(X0C1, X1C1, 'rx')
plt.plot(X0C2, X1C2, 'g.')
plt.plot(3.8,6.4,'rx',ms=12.0)
plt.plot(4.57,4.14,'g.',ms=12.0)
4、重複計算
接下來再重複一次上面的做法,把重心移動到新位置,並重新計算各個樣本與新重心的距離,並根據距離遠近為樣本重新歸類。結果如下表所示:
C1 = [0, 1, 2, 4, 8, 9, 10, 11]
C2 = list(set(range(12)) - set(C1))
X0C1, X1C1 = X0[C1], X1[C1]
X0C2, X1C2 = X0[C2], X1[C2]
plt.figure()
plt.axis([-1, 9, -1, 9])
plt.grid(True)
plt.plot(X0C1, X1C1, 'rx')
plt.plot(X0C2, X1C2, 'g.')
plt.plot(5.5,7.0,'rx',ms=12.0)
plt.plot(2.2,2.8,'g.',ms=12.0)
再重複上面的方法就會發現類的重心不變了,K-Means會在條件滿足的時候停止重複聚類過程。通常,條件是前後兩次迭代的成本函式值的差達到了限定值,或者是前後兩次迭代的重心位置變化達到了限定值。如果這些停止條件足夠小,K-Means就能找到最優解。不過這個最優解不一定是全域性最優解。
區域性最優解
前面介紹過K-Means的初始重心位置是隨機選擇的。有時,如果運氣不好,隨機選擇的重心會導致K-Means陷入區域性最優解。
K-Means最終會得到一個區域性最優解,這些類可能沒有實際意義,可能是更有合理的聚類結果。為了避免區域性最優解,K-Means通常初始時要重複執行十幾次甚至上百次。每次重複時,它會隨機的從不同的位置開始初始化。最後把最小的成本函式對應的重心位置作為初始化位置。
K值的確定
如果問題中沒有指定的值,可以通過肘部法則這一技術來估計聚類數量。肘部法則會把不同值的成本函式值畫出來。隨著值的增大,平均畸變程度會減小;每個類包含的樣本數會減少,於是樣本離其重心會更近。但是,隨著值繼續增大,平均畸變程度的改善效果會不斷減低。值增大過程中,畸變程度的改善效果下降幅度最大的位置對應的值就是肘部。下面讓我們用肘部法則來確定最佳的值。
其中需要解釋的一點是:
每個類的畸變程度等於該類重心與其內部成員位置距離的平方和。也即我們前面所說的每個類的類內離差平方和。若類內部的成員彼此間越緊湊則類的畸變程度越小,反之,若類內部的成員彼此間越分散則類的畸變程度越大。
下圖資料明顯可分成兩類:
import numpy as np
cluster1 = np.random.uniform(0.5, 1.5, (2, 10))
cluster2 = np.random.uniform(3.5, 4.5, (2, 10))
X = np.hstack((cluster1, cluster2)).T
plt.figure()
plt.axis([0, 5, 0, 5])
plt.grid(True)
plt.plot(X[:,0],X[:,1],'k.')
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
K = range(1, 10)
meandistortions = []
for k in K:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
meandistortions.append(sum(np.min(cdist(X, kmeans.cluster_centers_, 'euclidean'), axis=1)) / X.shape[0])
plt.plot(K, meandistortions, 'bx-')
plt.xlabel('k')
聚類評估:輪廓係數(Silhouette Coefficient )
- 計算樣本i到同簇其他樣本的平均距離ai。ai 越小,說明樣本i越應該被聚類到該簇。將ai 稱為樣本i的簇內不相似度。
- 計算樣本i到其他某簇Cj 的所有樣本的平均距離bij,稱為樣本i與簇Cj 的不相似度。定義為樣本i的簇間不相似度:bi =min{bi1, bi2, ..., bik}
- si接近1,則說明樣本i聚類合理
- si接近-1,則說明樣本i更應該分類到另外的簇
- 若si 近似為0,則說明樣本i在兩個簇的邊界上。
import numpy as np
from sklearn.cluster import KMeans
from sklearn import metrics
plt.figure(figsize=(8, 10))
plt.subplot(3, 2, 1)
x1 = np.array([1, 2, 3, 1, 5, 6, 5, 5, 6, 7, 8, 9, 7, 9])
x2 = np.array([1, 3, 2, 2, 8, 6, 7, 6, 7, 1, 2, 1, 1, 3])
X = np.array(list(zip(x1, x2))).reshape(len(x1), 2)
plt.xlim([0, 10])
plt.ylim([0, 10])
plt.title('test')
plt.scatter(x1, x2)
colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'b']
markers = ['o', 's', 'D', 'v', '^', 'p', '*', '+']
tests = [2, 3, 4, 5, 8]
subplot_counter = 1
for t in tests:
subplot_counter += 1
plt.subplot(3, 2, subplot_counter)
kmeans_model = KMeans(n_clusters=t).fit(X)
for i, l in enumerate(kmeans_model.labels_):
plt.plot(x1[i], x2[i], color=colors[l], marker=markers[l],ls='None')
plt.xlim([0, 10])
plt.ylim([0, 10])
plt.title('K = %s, si = %.03f' % (t, metrics.silhouette_score(X, kmeans_model.labels_,metric='euclidean')))
很顯然,這個資料集包括三個類。在k=3的時候輪廓係數是最大的。在k=8的時候,每個類的樣本不僅彼此很接近,而且與其他類的樣本也非常接近,因此這時輪廓係數是最小的。
k-means的優缺點
優點:
1、理解容易,聚類效果好;
2、處理大資料時,該演算法可以保證較好的伸縮性和高效性;
3、當簇近似高斯分佈時,效果很好
缺點:
1、K值需要自己設定,不同K值結果不同;
2、對於初試設定的重心非常敏感;
3、不適合發現非凸形狀的簇或者大小差別較大的簇
4、特殊值(離群值)對模型的影響較大
這個網址可以展現聚類視覺化的的過程,大家可以參考。
本次K-means主要是對錶十大資料探勘演算法,所以不展開講,之後會對K-means的延伸演算法包括K-means++、MiniBatchKMeans、K-Mediods等進行講解
sklearn中對於kmeans演算法的引數
引數:
n_clusters:整形,預設值=8 【生成的聚類數,即產生的質心(centroids)數。】 max_iter:整形,預設值=300 執行一次k-means演算法所進行的最大迭代數。 n_init:整形,預設值=10 用不同的質心初始化值執行演算法的次數,最終解是在inertia意義下選出的最優結果。 init:有三個可選值:’k-means++’, ‘random’,或者傳遞一個ndarray向量。 此引數指定初始化方法,預設值為 ‘k-means++’。 (1)‘k-means++’ 用一種特殊的方法選定初始質心從而能加速迭代過程的收斂 (2)‘random’ 隨機從訓練資料中選取初始質心。 (3)如果傳遞的是一個ndarray,則應該形如 (n_clusters, n_features) 並給出初始質心。 precompute_distances:三個可選值,‘auto’,True 或者 False。 預計算距離,計算速度更快但佔用更多記憶體。 (1)‘auto’:如果 樣本數乘以聚類數大於 12million 的話則不預計算距離。This corresponds to about 100MB overhead per job using double precision. (2)True:總是預先計算距離。 (3)False:永遠不預先計算距離。 tol:float形,預設值= 1e-4 與inertia結合來確定收斂條件。 n_jobs:整形數。 指定計算所用的程序數。內部原理是同時進行n_init指定次數的計算。 (1)若值為 -1,則用所有的CPU進行運算。若值為1,則不進行並行運算,這樣的話方便除錯。 (2)若值小於-1,則用到的CPU數為(n_cpus + 1 + n_jobs)。因此如果 n_jobs值為-2,則用到的CPU數為總CPU數減1。 random_state:整形或 numpy.RandomState 型別,可選 用於初始化質心的生成器(generator)。如果值為一個整數,則確定一個seed。此引數預設值為numpy的隨機數生成器。 copy_x:布林型,預設值=True 當我們precomputing distances時,將資料中心化會得到更準確的結果。如果把此引數值設為True,則原始資料不會被改變。如果是False,則會直接在原始資料 上做修改並在函式返回值時將其還原。但是在計算過程中由於有對資料均值的加減運算,所以資料返回後,原始資料和計算前可能會有細小差別。
屬性:
cluster_centers_:向量,[n_clusters, n_features] (聚類中心的座標)
Labels_: 每個點的分類 inertia_:float形 每個點到其簇的質心的距離之和。
Notes: 這個k-means運用了 Lioyd’s 演算法,平均計算複雜度是 O(k*n*T),其中n是樣本量,T是迭代次數。 計算複雜讀在最壞的情況下為 O(n^(k+2/p)),其中n是樣本量,p是特徵個數。(D. Arthur and S. Vassilvitskii, ‘How slow is the k-means method?’ SoCG2006) 在實踐中,k-means演算法時非常快的,屬於可實踐的演算法中最快的那一類。但是它的解只是由特定初始值所產生的區域性解。所以為了讓結果更準確真實,在實踐中要用不同的初始值重複幾次才可以。
Methods:
fit(X[,y]): 計算k-means聚類。 fit_predictt(X[,y]): 計算簇質心並給每個樣本預測類別。 fit_transform(X[,y]): 計算簇並 transform X to cluster-distance space。 get_params([deep]): 取得估計器的引數。 predict(X):predict(X) 給每個樣本估計最接近的簇。 score(X[,y]): 計算聚類誤差 set_params(**params): 為這個估計器手動設定引數。 transform(X[,y]): 將X轉換為群集距離空間。 在新空間中,每個維度都是到叢集中心的距離。 請注意,即使X是稀疏的,轉換返回的陣列通常也是密集的。
sklearn例項實現
我們還是基於上面推倒的資料案例:
import numpy as np
X0 = np.array([7, 5, 7, 3, 4, 1, 0, 2, 8, 6, 5, 3])
X1 = np.array([5, 7, 7, 3, 6, 4, 0, 2, 7, 8, 5, 7])
X = np.array(list(zip(X0, X1))).reshape(len(X0), 2)#壓縮資料整合在一起
from sklearn.cluster import KMeans#匯入KMeans包
km = KMeans(n_clusters=2).fit(X)#按照k=2進行計算
接下來我們看下結果:
km.labels_
out:array([0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0], dtype=int32)
centroids = km.cluster_centers_
print(centroids)
out:
[[5.625 6.5 ]
[1.5 2.25 ]]
import matplotlib.pyplot as plt
plt.figure()
plt.scatter(X[:, 0], X[:, 1], c=km.labels_)#原始資料散點圖,按照分類檢視
centroids = km.cluster_centers_
plt.scatter(centroids[:, 0], centroids[:, 1],
marker='x', s=169, linewidths=3,
color='r', zorder=10)#重心紅色X進行突出
輪廓係數
from sklearn.cluster import KMeans
from sklearn import metrics
scores = []
for k in range(2,12):
labels = KMeans(n_clusters=k).fit(X).labels_
score = metrics.silhouette_score(X, labels)
scores.append(score)
scores
out: [0.5438942330933946, 0.3842109168420305, 0.355936245851519, 0.32130908377531114, 0.29601716489116875, 0.2552005855998481, 0.19930860195228847, 0.15131817710529702, 0.07047330194279122, 0.07047330194279122]