欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

K-means 算法原理

程序员文章站 2022-07-14 19:22:24
...

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()

分类效果:
K-means 算法原理