K-means 算法原理

1. 聚類

K-means算法是一種常用的聚類算法,所謂的聚類就是指給定NN個樣本的數據集,需要構造 kk 個簇(類),使得這 kk 個簇之間的關係最小。

2. K-means算法基本步驟

  1. 隨機初始化kk個點,作爲聚類中心
  2. 在第ii次迭代中,對於每個樣本點,選取距離最近的聚類中心,歸爲該類
  3. 遍歷一遍之後,更新聚類中心,其中更新規則爲:聚類中心取當前類的平均值
  4. 重複步驟2、3,直到滿足迭代次數,或者聚類狀態不發生改變

3. 算法優化

3.1 輪廓係數

輪廓係數(Silhouette Coefficient)結合了聚類的凝聚度(Cohesion)和分離度(Separation),用於評估聚類的效果。該值處於[1,1][-1,1]之間,值越大,表示聚類效果越好。具體計算方法如下:

  1. 對於每個樣本點ii,計算點ii與其同一個簇內的所有其他元素距離的平均值,記作a(i)a(i),用於量化簇內的凝聚度。
  2. 選取i外的一個簇bb,計算iibb中所有點的平均距離,遍歷所有其他簇,找到最近的這個平均距離,記作b(i)b(i),即爲ii的鄰居類,用於量化簇之間分離度。
  3. 對於樣本點 ii ,輪廓係數s(i)=b(i)a(i)max{a(i),b(i)}s(i) = \frac {b(i) – a(i)} {max\{a(i),b(i)\}}
  4. 計算所有i的輪廓係數,求出平均值即爲當前聚類的整體輪廓係數,度量數據聚類的緊密程度

3.2 K值的選擇

方法一:輪廓係數法

可以通過枚舉的方法,令 kk221010(隨便的一個固定值,但不能太大),對於每一個 kk 值進行 K-means 算法,然後取輪廓係數最大的那個 kk 作爲最終聚類的簇的數目。

方法二:誤差平方和法

定義誤差平方和公式如下:
SSE=i=1kdCidcenteri2SSE = \sum_{i=1}^k\sum_{d\in C_i} |d-center_i|^2
其中 CiC_i 是第 ii 個簇,其中 dd 是 簇CiC_i 的樣本,centericenter_i 是第 ii 個簇的聚類中心
隨着聚類數 kk 的增大,樣本劃分會更加精細,每個簇的聚合程度會逐漸提高,那麼誤差平方和SSE自然會逐漸變小。並且,當k小於真實聚類數時,由於kk的增大會大幅增加每個簇的聚合程度,故SSE的下降幅度會很大,而當kk到達真實聚類數時,再增加kk所得到的聚合程度回報會迅速變小,所以SSE的下降幅度會驟減,然後隨着kk值的繼續增大而趨於平緩,也就是說SSE和kk的關係圖是一個手肘的形狀,而這個肘部對應的kk值就是數據的真實聚類數。–轉自手肘法理論

3.3 初始點的選擇

  1. 從給定的樣本集合中隨機選擇一個樣本作爲第一個聚類中心
  2. 然後對於樣本集合中的每一個樣本點 xx,計算離該樣本xx與最近的聚類中心的距離 D(x)D(x)
  3. 概率的選擇D(x)較大的點作爲新的樣本中心
  4. 重複步驟2和3,直到聚類中心達到 kk 個爲止

4. 代碼

以鳶尾花數據爲例,下面是k-means的代碼

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
# 加載數據
def loadData():
    iris = load_iris()
    #print(iris)
    X = np.array(iris.data[:])
    #print(X.shape)
    # 以鳶尾花後兩個特徵作爲聚類特徵
    # plt.scatter(X[:, 2], X[:, 3], c="red", marker='o', label='two features')
    # plt.xlabel('petal length')
    # plt.ylabel('petal width')
    # plt.legend(loc=2)
    # plt.show()
    return X[:,2:]
# 第一次隨機獲取k箇中心點
def getCenterPoints(X, k):
    m, n = X.shape
    centerPoints = np.zeros((k, n))
    for i in range(k):
        id = int(np.random.uniform(0, m))
        centerPoints[i, :] = X[id, :]
    return centerPoints

#得到兩個數據點之間的歐氏距離
def getDis(x, y):
    return np.sqrt(np.sum((x - y) **2 ))
# k-means算法
def KMeans(X, k):
    m = X.shape[0]
    cluster = np.mat(np.zeros((m, 2)))
    #1. 隨機獲取中心點
    centerPoints = getCenterPoints(X, k)
    for times in range(100):
        for i in range(m):
            minDist, minID = 1000000.0, -1
            for j in range(k):
                dis = getDis(X[i, :], centerPoints[j, :])
                if dis < minDist:
                    minDist, minID = dis, j
            #2.對於每個樣本點,選取最近的中心點,歸爲該類
            cluster[i, :] = minID, minDist**2
        #3. 更新中心點
        for i in range(k):
            pointsInCluster = X[np.nonzero(cluster[:, 0].A == i)[0]]  # 獲取簇類所有的點
            centerPoints[i, :] = np.mean(pointsInCluster, axis=0)  # 對矩陣的行求均值
    return centerPoints, cluster
#對聚類好的數據進行畫圖
def showCluster(X, k, cluster, centerPoints):
    m = X.shape[0]
    mark = ['or', 'ob', 'og']
    for i in range(m):
        markIndex = int(cluster[i, 0])
        plt.plot(X[i, 0], X[i, 1], mark[markIndex])
    mark = ['Dr', 'Db', 'Dg']
    # 繪製中心點
    for i in range(k):
        plt.plot(centerPoints[i, 0], centerPoints[i, 1], mark[i])
    plt.show()

def main():
    X, k = loadData(), 3
    centerPoints, cluster = KMeans(X, k)
    showCluster(X, k, cluster, centerPoints)
if __name__ == '__main__':
    main()

分類效果:
在這裏插入圖片描述

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