K-means算法

K-means算法簡述

  1. K-means算法,也稱爲K-平均或者K-均值,一般作爲掌握聚類算法的第一個算法。
  2. 這裏的K爲常數,需事先設定,通俗地說該算法是將沒有標註的 M 個樣本通過迭代的方式聚集成K個簇。
  3. 在對樣本進行聚集的過程往往是以樣本之間的距離作爲指標來劃分。
  • 簡單Demo說明
    在這裏插入圖片描述
    如上圖以 K 爲2,樣本集爲M 來描述KMean算法,算法執行步驟如下:
  1. 選取K個點做爲初始聚集的簇心(也可選擇非樣本點);
  2. 分別計算每個樣本點到 K個簇核心的距離(這裏的距離一般取歐氏距離或餘弦距離),找到離該點最近的簇核心,將它歸屬到對應的簇;
  3. 所有點都歸屬到簇之後, M個點就分爲了 K個簇。之後重新計算每個簇的重心(平均距離中心),將其定爲新的“簇核心”;
  4. 反覆迭代 2 - 3 步驟,直到達到某個中止條件。
  • 注:常用的中止條件有迭代次數、最小平方誤差MSE、簇中心點變化率;

由上述Demo可知,對於KMean算法來說有三個比較重要的因素要考慮,分別如下所述;

K-means算法思考

  1. K值的選擇: k 值對最終結果的影響至關重要,而它卻必須要預先給定。給定合適的 k 值,需要先驗知識,憑空估計很困難,或者可能導致效果很差。

  2. 異常點的存在:K-means算法在迭代的過程中使用所有點的均值作爲新的質點(中心點),如果簇中存在異常點,將導致均值偏差比較嚴重。 比如一個簇中有2、4、6、8、100五個數據,那麼新的質點爲24,顯然這個質點離絕大多數點都比較遠;在當前情況下,使用中位數6可能比使用均值的想法更好,使用中位數的聚類方式叫做K-Mediods聚類(K中值聚類)。

  3. 初值敏感:K-means算法是初值敏感的,選擇不同的初始值可能導致不同的簇劃分規則。爲了避免這種敏感性導致的最終結果異常性,可以採用初始化多套初始節點構造不同的分類規則,然後選擇最優的構造規則。針對這點後面因此衍生了:二分K-Means算法、K-Means++算法、K-Means||算法、Canopy算法等。

常用的幾種距離計算方法

通常情況下,在聚類算法中,樣本的屬性主要由其在特徵空間中的相對距離來表示。
這就使得距離這個概念,對於聚類非常重要。以下是幾種最常見的距離計算方法。

  • 歐式距離(又稱 2-norm 距離)
    在歐幾里德空間中,點 x=(x1,…,xn) 和 y=(y1,…,yn) 之間的歐氏距離爲:
    在這裏插入圖片描述
    在歐幾里德度量下,兩點之間線段最短。

  • 餘弦距離(又稱餘弦相似性)
    兩個向量間的餘弦值可以通過使用歐幾里德點積公式求出:
    a⋅b=∥a∥∥b∥cosθ
    所以:
    在這裏插入圖片描述
    也就是說,給定兩個屬性向量 A 和 B,其餘弦距離(也可以理解爲兩向量夾角的餘弦)由點積和向量長度給出,如下所示:
    在這裏插入圖片描述
    這裏的 Ai 和 Bi 分別代表向量 A 和 B 的各分量。

  • 曼哈頓距離(Manhattan Distance, 又稱 1-norm 距離)
    曼哈頓距離的定義,來自於計算在規劃爲方型建築區塊的城市(如曼哈頓)中行車的最短路徑。
    假設一個城市是完備的塊狀劃分,從一點到達另一點必須要按照之間所隔着的區塊的邊緣走,沒有其他捷徑(如下圖):
    在這裏插入圖片描述
    因此,曼哈頓距離就是:在直角座標系中,兩點所形成的線段對 x 和 y 軸投影的長度總和。
    從點 (x1,y1) 到點 (x2,y2),曼哈頓距離爲:
    |x1−x2|+|y1−y2|

KMean 簡單編碼樣例

import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs  # 導入產生模擬數據的方法
from sklearn.cluster import KMeans

# 1. 產生模擬數據
k = 5
X, Y = make_blobs(n_samples=1000, n_features=2, centers=k, random_state=1)

# 2. 模型構建
km = KMeans(n_clusters=k, init='k-means++', max_iter=30)
km.fit(X)

# 獲取簇心
centroids = km.cluster_centers_
# 獲取歸集後的樣本所屬簇對應值
y_kmean = km.predict(X)

# 呈現未歸集前的數據
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.yticks(())
plt.show()

plt.scatter(X[:, 0], X[:, 1], c=y_kmean, s=50, cmap='viridis')
plt.scatter(centroids[:, 0], centroids[:, 1], c='black', s=100, alpha=0.5)
plt.show()


代碼運行結果:
在這裏插入圖片描述
歸集後的數據圖:
在這裏插入圖片描述

KMeans類的主要參數有:

  1. n_clusters: 即k值,一般需要多試一些值以獲得較好的聚類效果。k值好壞的評估標準在下面會講。
  2. max_iter: 最大的迭代次數,一般如果是凸數據集的話可以不管這個值,如果數據集不是凸的,可能很難收斂,此時可以指定最大的迭代次數讓算法可以及時退出循環。
  3. n_init:用不同的初始化質心運行算法的次數。由於K-Means是結果受初始值影響的局部最優的迭代算法,因此需要多跑幾次以選擇一個較好的聚類效果,默認是10,一般不需要改。如果你的k值較大,則可以適當增大這個值。
  4. init: 即初始值選擇的方式,可以爲完全隨機選擇’random’,優化過的’k-means++’或者自己指定初始化的k個質心。一般建議使用默認的’k-means++’。
  5. algorithm:有“auto”, “full” or “elkan”三種選擇。”full”就是我們傳統的K-Means算法, “elkan”是elkan K-Means算法。默認的”auto”則會根據數據值是否是稀疏的,來決定如何選擇”full”和“elkan”。一般數據是稠密的,那麼就是 “elkan”,否則就是”full”。一般來說建議直接用默認的”auto”
  6. random_state:表示產生隨機數的方法。默認情況下的缺省值爲None,此時的隨機數產生器是np.random所使用的RandomState實例。

KMean算法的算法優缺點與適用場景

優點:

  • 理解容易,聚類效果不錯;
  • 處理大數據集的時候,該算法可以保證較好的伸縮性和高效率;
  • 當簇近似高斯分佈的時候,效果非常不錯。

缺點:

  • K值是用戶給定的,在進行數據處理前,K值是未知的,給定合適的 k 值,需要先驗知識,憑空估計很困難,或者可能導致效果很差。
  • 對初始簇中心點是敏感的。
  • 不適合發現非凸形狀的簇或者大小差別較大的簇。
  • 特殊值(離羣值或稱爲異常值)對模型的影響比較大。

KMean 衍生算法

KMeans 初始值敏感示例圖
在這裏插入圖片描述

二分K-Means算法

  • 解決K-Means算法對初始簇心比較敏感的問題,二分K-Means算法是一種弱化初始質心的一種算法,具體思路步驟如下:

    1. 將所有樣本數據作爲一個簇放到一個隊列中;
    2. 從隊列中選擇一個簇進行K-means算法劃分(第一次迭代時就是相當與將所有樣本進行K=2的劃分),劃分爲兩個子簇,並將子簇添加到隊列中循環迭代第二步操作,直到中止條件達到(聚簇數量、最小平方誤差、迭代次數等)
    3. 隊列中的簇就是最終的分類簇集合。
  • 從隊列中選擇劃分聚簇的規則一般有兩種方式;分別如下:
    1. 對所有簇計算誤差和SSE(SSE也可以認爲是距離函數的一種變種),選擇SSE最大的聚簇進行劃分操作(優選這種策略);
    在這裏插入圖片描述

    1. 選擇樣本數據量最多的簇進行劃分操作。

K-Means++算法

  • 解決K-Means算法對初始簇心比較敏感的問題,K-Means++算法和K-Means算法的區別主要在於初始的K箇中心點的選擇方面,K-Means算法使用隨機給定的方式,K-Means++算法採用下列步驟給定K個初始質點:
    1. 從數據集中任選一個節點作爲第一個聚類中心;
    2. 對數據集中的每個點x,計算x到所有已有聚類中心點的距離和D(X),基於D(X)採用線性概率選擇出下一個聚類中心點(距離較遠的一個點成爲新增的一個聚類中心點而不是最遠的一個點是由於最遠點可能爲異常點,這裏的選取規則是計算出M個距離團較遠的點,然後隨機選擇出一點);
    3. 重複步驟2直到找到k個聚類中心點。

缺點:
由於聚類中心點選擇過程中的內在有序性,在擴展方面存在着性能方面的問題(第k個聚類中心點的選擇依賴前k-1個聚類中心點的值)。

K-Means||算法

解決K-Means++算法缺點而產生的一種算法;
主要思路是改變每次遍歷時候的取樣規則,並非按照K-Means++算法每次遍歷只獲取一個樣本,
而是每次獲取K個樣本,重複該取樣操作O(logn)次,然後再將這些抽樣出來的樣本聚類出K個點,
最後使用這K個點作爲K-Means算法的初始聚簇中心點。
實踐證明:一般5次重複採用就可以保證一個比較好的聚簇中心點。

Canopy算法

Canopy算法屬於一種“粗”聚類算法,執行速度較快,但精度較低,算法執行
步驟如下:

  1. 給定樣本列表L=x 1 ,x, 2 …,x m 以及先驗值r 1 和r 2 (r 1 >r 2 )
  2. 從列表L中獲取一個節點P,計算P到所有聚簇中心點的距離(如果不存在聚簇中心,那麼此時點P形成一個新的聚簇),並選擇出最小距離D(P,a j )
  3. 如果距離D小於r1,表示該節點屬於該聚簇,添加到該聚簇列表中
  4. 如果距離D小於r2,表示該節點不僅僅屬於該聚簇,還表示和當前聚簇中心點非常近,所以將該聚簇的中心點設置爲該簇中所有樣本的中心點,並將P從列表L中刪除
  5. 如果距離D大於r1,那麼節點P形成一個新的聚簇
  6. 直到列表L中的元素數據不再有變化或者元素數量爲0的時候,結束循環操作。
    在這裏插入圖片描述
    Canopy算法常用應用場景
  • 由於K-Means算法存在初始聚簇中心點敏感的問題,常用使用Canopy+K-Means算法混合形式進行模型構建
  • 先使用canopy算法進行“粗”聚類得到K個聚類中心點
  • K-Means算法使用Canopy算法得到的K個聚類中心點作爲初始中心點,進行“細”聚類

優點:

  • 執行速度快(先進行了一次聚簇中心點選擇的預處理)
  • 不需要給定K值,應用場景多
  • 能夠緩解K-Means算法對於初始聚類中心點敏感的問題

Mini Batch K-Means算法

Mini Batch K-Means算法是K-Means算法的一種優化變種,採用小規模的數據子集(每次訓練使用的數據集是在訓練算法的時候隨機抽取的數據子集)減少計算時間,同時試圖優化目標函數;
Mini Batch K-Means算法可以減少K-Means算法的收斂時間,而且產生的結果效果只是略差於標準K-Means算法算法步驟如下:

  1. 首先抽取部分數據集,使用K-Means算法構建出K個聚簇點的模型;
  2. 繼續抽取訓練數據集中的部分數據集樣本數據,並將其添加到模型中,分配給距離最近的聚簇中心點;
  3. 更新聚簇的中心點值;
  4. 循環迭代第二步和第三步操作,直到中心點穩定或者達到迭代次數,停止計算操作。

K-Means和Mini Batch K-Means算法比較案例

基於scikit包中的創建模擬數據的API創建聚類數據,使用K-means算法和MiniBatch K-Means算法對數據進行分類操作,比較這兩種算法的聚類效果以及聚類的消耗時間長度。

import time
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.cluster import MiniBatchKMeans, KMeans
from sklearn.metrics.pairwise import pairwise_distances_argmin
from sklearn.datasets.samples_generator import make_blobs

# 設置屬性防止中文亂碼
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False

# 初始化三個中心
centers = [[1, 1], [-1, -1], [1, -1]]
clusters = len(centers)  # 聚類的數目爲3
# 產生50000組二維的數據,中心是意思三個中心點,標準差是.7
total_samples = 50000
X, Y = make_blobs(n_samples=total_samples, centers=centers, cluster_std=0.7, random_state=28)

# 構建kmeans算法
k_means = KMeans(init='k-means++', n_clusters=clusters, random_state=28)
t0 = time.time()  # 當前時間
k_means.fit(X)  # 訓練模型
km_batch = time.time() - t0  # 使用kmeans訓練數據的消耗時間
print("K-Means算法模型訓練消耗時間:%.4fs" % km_batch)

# 構建MiniBatchKMeans算法
batch_size = 100
mbk = MiniBatchKMeans(init='k-means++', n_clusters=clusters, batch_size=batch_size, random_state=28)
t0 = time.time()
mbk.fit(X)
mbk_batch = time.time() - t0
print("Mini Batch K-Means算法模型訓練消耗時間:%.4fs" % mbk_batch)

# 預測結果
km_y_hat = k_means.predict(X)
mbkm_y_hat = mbk.predict(X)

# print(km_y_hat[:10])
# print(mbkm_y_hat[:10])
# print(k_means.cluster_centers_)
# print(mbk.cluster_centers_)

#獲取聚類中心點並聚類中心點進行排序
k_means_cluster_centers = k_means.cluster_centers_  # 輸出kmeans聚類中心點
mbk_means_cluster_centers = mbk.cluster_centers_  # 輸出mbk聚類中心點
# print("K-Means算法聚類中心點:\ncenter=", k_means_cluster_centers)
# print("Mini Batch K-Means算法聚類中心點:\ncenter=", mbk_means_cluster_centers)
# 方便後面畫圖
order = pairwise_distances_argmin(k_means_cluster_centers, mbk_means_cluster_centers)

# 畫圖
plt.figure(figsize=(12, 6), facecolor='w')
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.9)
cm = mpl.colors.ListedColormap(['#FFC2CC', '#C2FFCC', '#CCC2FF'])
cm2 = mpl.colors.ListedColormap(['#FF0000', '#00FF00', '#0000FF'])
# 子圖1:原始數據
plt.subplot(221)
plt.scatter(X[:, 0], X[:, 1], c=Y, s=6, cmap=cm, edgecolors='none')
plt.title(u'原始數據分佈圖')
plt.xticks(())
plt.yticks(())
plt.grid(True)
# 子圖2:K-Means算法聚類結果圖
plt.subplot(222)
plt.scatter(X[:, 0], X[:, 1], c=km_y_hat, s=6, cmap=cm, edgecolors='none')
plt.scatter(k_means_cluster_centers[:, 0], k_means_cluster_centers[:, 1], c=range(clusters), s=60, 
           cmap=cm2,edgecolors='none')
plt.title(u'K-Means算法聚類結果圖')
plt.xticks(())
plt.yticks(())
plt.text(-3.8, 3, 'train time: %.2fms' % (km_batch * 1000))
plt.grid(True)
# 子圖三Mini Batch K-Means算法聚類結果圖
plt.subplot(224)
plt.scatter(X[:, 0], X[:, 1], c=mbkm_y_hat, s=6, cmap=cm, edgecolors='none')
plt.scatter(mbk_means_cluster_centers[:, 0], mbk_means_cluster_centers[:, 1], c=range(clusters), s=60, 
       cmap=cm2, edgecolors='none')
plt.title(u'Mini Batch K-Means算法聚類結果圖')
plt.xticks(())
plt.yticks(())
plt.text(-3.8, 3, 'train time: %.2fms' % (mbk_batch * 1000))
plt.grid(True)

#
different = list(map(lambda x: (x != 0) & (x != 1) & (x != 2), mbkm_y_hat))
for k in range(clusters):
    different += ((km_y_hat == k) != (mbkm_y_hat == order[k]))
identic = np.logical_not(different)
different_nodes = len(list(filter(lambda x: x, different)))

plt.subplot(223)
# 兩者預測相同的
plt.plot(X[identic, 0], X[identic, 1], 'w', markerfacecolor='#bbbbbb', marker='.')
# 兩者預測不相同的
plt.plot(X[different, 0], X[different, 1], 'w', markerfacecolor='m', marker='.')
plt.title(u'Mini Batch K-Means和K-Means算法預測結果不同的點')
plt.xticks(())
plt.yticks(())
plt.text(-3.8, 2, 'total nodes %d and different nodes: %d' % (total_samples, different_nodes))

plt.show()
————————————————

在這裏插入圖片描述

  • 注:主要整理自
    北風網機器學習教程
    李燁機器學習極簡入門教程

原文鏈接:https://blog.csdn.net/u013850277/article/details/88411966

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