1. 程式人生 > >K-means演算法及python sklearn實現

K-means演算法及python sklearn實現

目錄

前言

例項推演

 K值的確定

輪廓係數

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]