機器學習~K-Means

概述

k-means算法是一種聚類算法,所謂聚類,是指在數據中發現數據對象之間的關係,將數據進行分組,組內的相似性越大,組間的差別越大,則聚類效果越好。
聚類算法與分類算法不同,聚類算法屬於無監督學習,通俗來講:分類就是向事物分配標籤,聚類就是將相似的事物放在一起。
聚類算法通常用來尋找相似的事物,比如:銀行尋找優質客戶,信用卡詐騙,社交劃分社區圈等等。

原理

首先K-means中的K類似與KNN中的參數K,是指將數據聚類成K個類別。

算法原理:

先從沒有標籤的元素集合A中隨機取K個元素,作爲K個子集各自的重心。
分別計算剩下的元素到K個子集重心的距離,根據距離將這些元素分別劃歸到最近的子集。(這裏的距離可以使用歐式距離或其他的距離量度)
根據聚類結果,重新計算重心(即子集中所有元素各個維度的算數平均數)
將集合A中全部元素按照新的中心然後再重新聚類
重複第4步,直到聚類結果不再發生變化。

示例

看着算法的步驟有點懵逼,我們來看個簡單的例子。
1.假設畫布上有四個點,如下:
在這裏插入圖片描述
我們想將其聚類成兩類,首先我們先隨機選取兩個點,比如A,B兩點選取兩個類別的重心點,然後分別計算所有元素到這兩個重心的距離,此處採用歐式距離。
計算如下:

dotA = np.array([1,1])
dotB = np.array([2,1])
DATA = [[1,1],[2,1],[4,3],[5,4]]

def getDistances(data,dot):
    distances = []
    for dt in data:
        d = round(sqrt(np.sum((dt-dot)**2)),3)
        distances.append(d)
    return np.array(distances)
    
disA = getDistances(DATA,dotA)
disB = getDistances(DATA,dotB)
print("距離第一個重心點的距離:" , disA)
print("距離第二個重心點的距離:" , disB)

輸出:

                        A     B     C     D
距離第一個重心點的距離: [0.    1.    3.606 5.   ]
距離第二個重心點的距離: [1.    0.    2.828 4.243]

然後我們根據距離進行分類:

print(disA<disB) #距離第一個重心點近的是True
#out:[ True False False False]
print(disA>disB) #距離第二個重心點近的是True
#out:[False  True  True  True]

所以分類後,A聚爲一類,將BCD聚爲一類。如圖
在這裏插入圖片描述
2.然後我們根據聚類結果,再次計算重心。
第一個類別的重心重新計算後和原值一樣沒有變化,對第二個類別的重心重新計算。j計算後新的重心爲
(1,1) (1,1)
((2+4+5)3,(1+3+4)3)=>(11/3,8/3) (\frac{(2+4+5)}{3},\frac{(1+3+4)}{3}) =>(11/3,8/3)
然後繼續計算四個點到重心的距離:

						A     B     C     D
距離第一個重心點的距離: [0.    1.    3.606 5.   ]
距離第二個重心點的距離: [3.145 2.357 0.471 1.886]

繼續根據距離分類:

print(disA<disB) #距離第一個重心點近的是True
#out:[ True True False False]
print(disA>disB) #距離第二個重心點近的是True
#out:[False  False  True  True]

即將AB聚爲一類,CD聚爲一類,如下圖:
在這裏插入圖片描述
似乎已經達到我們想要的結果,依照算法流程可以驗證一下,如果再一次劃分重心,聚類結果不改變,那麼這便是聚類的最終結果。
3.再次重新計算重心:
(1+22,1+12)=>(1.5,1) (\frac{1+2}{2},\frac{1+1}{2}) => (1.5,1)
(4+52,3+42)=>(4.5,3.5) (\frac{4+5}{2},\frac{3+4}{2}) => (4.5,3.5)
計算四個點到新重心的距離:

						A     B     C     D
距離第一個重心點的距離: [0.5   0.5   3.202 4.61 ]
距離第二個重心點的距離: [4.301 3.536 0.707 0.707]

繼續根據距離分類:

print(disA<disB) #距離第一個重心點近的是True
#out:[ True True False False]
print(disA>disB) #距離第二個重心點近的是True
#out:[False  False  True  True]

我們發現聚類後的結果和上次的一樣,所以聚類算法停止迭代,聚類完成。
所以聚類後的結果爲:AB爲一類,CD爲一類

Sklearn實現

#使用sklearn生成聚類數據集
import numpy as np
import matplotlib.pyplot as plt

from sklearn.datasets.samples_generator import make_blobs

# data爲樣本特徵,target爲樣本簇類別, 共1000個樣本,每個樣本2個特徵,共4個簇,簇中心在[-1,-1], [0,0],[1,1], [2,2], 簇方差分別爲[0.4, 0.2, 0.2],隨機種子爲666
data,target=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],
                       shuffle=True, random_state=666)
# plt.scatter(data[:, 0], data[:, 1], marker='o',c=target)
plt.scatter(data[:, 0], data[:, 1], marker='o')
plt.show()

生成的結果如下圖:
在這裏插入圖片描述
使用sklearn進行聚類,我們首先來看一下sklearn中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”是對於稠密特徵的數據改進的算法。默認的"auto"則會根據數據值是否是稀疏的,來決定如何選擇"full"和“elkan”。一般來說建議直接用默認的"auto"
from sklearn.cluster import KMeans
#首先來嘗試分爲2類
KMeansCluster = KMeans(n_clusters=2, random_state=666)
y2 = KMeansCluster.fit_predict(data)
plt.scatter(data[:, 0], data[:, 1], marker='o',c=y2)
plt.show()

在這裏插入圖片描述

#嘗試分爲三類
KMeansCluster3 = KMeans(n_clusters=3, random_state=666)
y3 = KMeansCluster3.fit_predict(data)
plt.scatter(data[:, 0], data[:, 1], marker='o',c=y3)
plt.show()

在這裏插入圖片描述
我們繼續劃分爲四類,結果如下圖:
在這裏插入圖片描述
當我們繼續劃分爲5類的時候,就會發現如下圖的情況:
在這裏插入圖片描述
明顯發生了過擬合的情況,所以問題來了,當我們可以可視化聚類結果的時候,比如上面這個例子,我們可以知道K=4的時候,是最合適的。但是當不能可視化聚類結果的時候,我們如何知道K選擇到什麼地步聚類效果是最好的?
所以我們需要和以前的算法意義,評估聚類效果。

聚類效果的評估

不像監督學習的分類問題和迴歸問題,我們的無監督聚類沒有樣本輸出,也就沒有比較直接的聚類評估方法。但是我們可以從簇內的稠密程度和簇間的離散程度來評估聚類的效果。常見的方法有輪廓係數Silhouette Coefficient和Calinski-Harabasz Index(CH)。這裏使用CH法來評估。
在這裏插入圖片描述
其中m爲訓練集樣本數,k爲類別數。Bk爲類別之間的協方差矩陣,Wk爲類別內部數據的協方差矩陣。tr爲矩陣的跡。
通過公式可以得出CH越大代表着類自身越緊密,類與類之間越分散,即更優的聚類結果。
我們可以求解上面幾次聚類結果的CH值

from sklearn import metrics
# 求解CH值
y2_score = metrics.calinski_harabaz_score(data, y2) 
y3_score = metrics.calinski_harabaz_score(data, y3) 
y4_score = metrics.calinski_harabaz_score(data, y4) 
y5_score = metrics.calinski_harabaz_score(data, y5) 
disY = [y2_score,y3_score,y4_score,y5_score]
#繪製圖像
plt.plot([2,3,4,5], disY , 'bx-')
plt.show()

在這裏插入圖片描述
我們發現K=4時,CH值時最大的,即聚類效果是最好的。

KMeans存在的幾個問題

初始重心選擇

上面的例子中,初始重心是隨機選取的,但是數據量特別大時,隨機選取的可能會造成迭代多次。所以初始重心該如何選取,因爲不是一類的,所以重心應該是離得越遠越好。
於是可以採用Kmeans++算法:假設已經選取了n個初始中心(0<n<K),則在選取第n+1個重心時:距離當前n個重心越遠的點會有更高的概率被選爲第n+1個重心。Kmeans++算法是選擇下一個重心時,永遠選擇距離最遠的那個點。

K值選擇

有時候我們並不知道數據集適合劃分爲多少類,所以也不知道K值該如何選取。
此時可以計算多個K值對應的損失函數的值,選取在拐點的k值即可。由於折線像胳膊,拐點即肘部,又稱爲肘部法則。
參考之前損失函數的定義,我們肯定希望每個點到聚類重心點的距離越小越好,所以此處的損失函數可以理解爲:每個點到聚類重心的距離的平方和的均值
可以將每個K值對應的損失函數值求解出來,如下:

from scipy.spatial.distance import cdist
K = range(1, 10)
disJ = []
for k in K:
    kmeans = KMeans(n_clusters=k)
    kmeans.fit(data)
    #理解爲計算某個與其所屬類聚中心的歐式距離
    #最終是計算所有點與對應中心的距離的平方和的均值
    disJ .append(sum(np.min(cdist(data, kmeans.cluster_centers_, 'euclidean'), axis=1)) / data.shape[0])

plt.plot(K, disJ , 'bx-')
plt.show()

求解的圖如下:
在這裏插入圖片描述
我們可以看到圖中貌似有兩個“肘部”,但是可以注意到在k=4之後,損失函數的值的變化越來越小,所以我們選擇K=4。同時也符合我們上面根據評估效果得到的結論。

基於密度聚類(DBSCAN)

對於非球面形狀的數據,Kmeans數據無法處理,比如下圖:
對於Kmeans來說,根據距離聚類,會聚成左圖,顯然結果不是我們想要的,此時便需要基於密度聚類,比如DBSCAN,如右圖:
在這裏插入圖片描述

mini batch kmeans

當數據量比較大時,聚類收斂比較慢,可以使用mini batch kmeans。其具體做法是在原有的數據量上隨機採樣一批合適大小的數據,作爲樣本,對這些樣本進行kmeans聚類。

參考

用scikit-learn學習K-Means聚類
深入理解K-Means聚類算法
K-means算法介紹
K-means聚類算法的三種改進(K-means++,ISODATA,Kernel K-means)介紹與對比

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章