K-Means详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40793975/article/details/82112935

K-Means详解

第十七次写博客,本人数学基础不是太好,如果有幸能得到读者指正,感激不尽,希望能借此机会向大家学习。这一篇文章是原型聚类中介绍的第一种算法,以标准K-Means为基础,不仅对K-Means的特点和“后处理”进行了细致介绍,还对基于此聚类方法衍生出来的二分K-均值和小批量K-均值进行了延伸。其他有关于原型聚类算法的讨论可以移步到该类算法的导航页《原型聚类算法综述(原型聚类算法开篇)》

标准K-均值(K-Means)算法简介

  标准K-均值(K-Means)使用贪心法对优化目标进行迭代优化,根据有效性指标的不同,迭代更新的公式也不同,最后得到的聚类质量不尽相似,以内部指标中的SSE(误差平方和)度量方法为例,具体步骤如下所示

如上图所示,算法第1行先随机选取聚类中心,第4行到第8行根据当前聚类中心对簇划分进行更新,第9到第第16行根据当前簇划分对聚类中心进行更新。算法第17行的迭代更新停止条件比较严格,可以通过设定阈值来加快收敛速度。

K-Means的优点和缺点

  K-Means算法简单,可靠性较高,可以适用于各种数据类型,但本身也有很多局限:
  1) 如何选择初始聚类中心
    初始中心的随机选择很有可能导致局部最优,尤其在真实样本分布中簇的数量较多时,这时,试图通过多次运行K-Means算法来找到全局最优的方法通常是不可行的,这时就需要对得到的聚类模型进行“后处理”(见【3】)或采用不依赖于初始中心选择的衍生算法,例如二分K-均值算法(见【4】);
  2) 收到簇属性(形状、密度、大小)的影响很大
    K-Means仅能在各个簇的尺寸相似、密度相似,并且非球形形状的数据集上有良好的表现,任何不满足上述要求的簇属性都有可能导致局部最优,因此对于这类数据需要其他的替代算法;
  3) 运算效率较低
    当数据集较大时,这时会导致每次更新中的计算量的大大增加,为了克服这个问题,衍生出了采用增量更新的“小批量K-均值”算法(见【5】),与传统K-均值相比,虽然有可能需要更多地迭代才能收敛,但每次迭代仅需处理一个(或几个)样本点,计算效率大大增加,且效果不错;
  4) 离群点的影响
   【1】中的算法采用SSE作为簇有效性的评价标准,并由此推导出采用簇均值对聚类中心更新的迭代公式,这种基于样本均值的更新方法非常容易受到离群点的影响,因此一般是在运行K-Means算法前,先进行离群点检测,或采用使用样本中位数的聚类中心更新方法的有效性度量标准(例如SAE)。需要注意的是,根据聚类任务的不同,某些离群点所体现出来的特征正是人们所需要的,而像是SAE这种度量标准往往会忽略这些重要信息,因此,在K-Means之前进行离群点检测是一种较好的方法。

K-Means的“后处理”

1) “空簇”的影响
  K-Means算法往往需要使用者自己指定簇的数量,这时算法并不能保证每个簇均含有数据集中的样本点,这些簇被称为“空簇”,空簇的存在会大大降低簇的有效性,这时就需要对得到的聚类模型进行后处理,处理这类问题的方法主要有:
  a.将距离所有聚类中心最远的样本点作为空簇的聚类中心;
  b.选取有效性最低的簇,将这个簇分裂为两个新簇。
2) 局部最优的处理
  如【2】中所述,随着真实样本分布中簇的数量增加,K-Means就越可能趋于局部最优,为了不通过盲目的增加期望簇的个数来解决这个问题,我们将关注点转移到单个簇的有效性上来,并通过对单个簇的交替“分裂”和“合并”,来提高聚类模型的有效性,并且保持期望簇的个数不变。具体的“分裂”和“合并”的方法如下:
  a.分裂一个簇:选择有效性最低的簇,或者在某个属性上的标准差最大的簇;
  b.引进一个新的聚类中心:可以选择距离所有聚类中心最远的样本点作为新的聚类中心,或者在所有的样本点中随机选取一点,或者在符合某一条件的样本点集中随机选取一点;
  c.合并两个簇:一种是选择聚类中心最近的两个簇进行合并,这称为“质心法”,另一种方法是合并两个使得有效性下降最小的簇进行合并;
  d.拆散一个簇:拆散一个使得有效性下降最小的簇,即删除该簇的聚类中心,并将该簇中的样本点重新分配到其他的簇中。

二分K-均值(Bisecting K-Means)算法简介

  二分K-均值(Bisecting K-Means,以下简称BK-Means)是K-Means算法的衍生,他采取的划分策略是,从原始数据集开始,每次只将一个簇“一分为二”,直至产生期望的簇个数 k 为止,这种划分方法降低了K-Means算法趋于局部最优的风险,因此不会受到选取初始聚类中心带来的影响。具体的算法介绍如下:

算法第1行:将原始数据集作为一个簇,进行初始化;
算法第3行:选取被分裂簇的方法多种多样,例如,可以选择有效性最低的簇、含样本点数最多的簇等,不同的方法产生的聚类结果也不同;
算法第4-9行:在上一步给定的待分裂簇上运行K-Means,将该簇划分为两个新簇,这里采用在簇上多次运行K-Means的方式,选择其中使得总有效性上升最明显的分裂方式作为这次运行的结果,以此来降低局部最优风险;
算法第10行:将新的簇加入簇集合中,重复运行上述步骤,直至达到期望的簇个数 k

小批量K-均值(Mini-batch K-Means)算法简介

  作为K-Means算法的衍生算法,Mini-batch K-Means(以下简称MBK-Means)算法拥有更高的运算效率,与标准K-均值的主要区别是在迭代优化过程中MBK-Means并没有使用全部的训练样本,而是根据需要在样本集中随机选取一部分的样本点,并以这些样本为基础进行交替优化:
  (1)对于随机选取的这批样本,以原来的聚类中心为基础进行簇划分;
  (2)以新的簇划分为基础,使用这批样本对聚类中心进行更新
  下面通过论文中给出的算法步骤进行具体分析,如下所示

算法第1行:定义了 k 个簇、每批规模为 b 、迭代次数为 t 、总的训练集为 X
算法第2行:从原始训练集 X 中随机取出 k 个样本点作为初始的聚类中心;
算法第3行:数组 v 初始化为0,其中数组 v 记录了每次迭代中被划分到每个簇的样本点数量之和;
算法第4行:开始次数为 t 的迭代优化
算法第6-8行:对于随机取出的规模为 b 小批量样本集 M 中的每个样本点 x ,函数 f ( C , x ) 根据欧氏距离确定了 x 所属的簇,并将其存储在数组 d 中,对索引为 x 的值进行更新,其中,数组 d 存储了原始训练集 X 中每个样本的簇标记;
算法第9-14行:对于同一小批量样本集 M 中的每个样本点 x ,通过数组 d 可知其簇标记 c ,然后将数组 v 中索引为 c 的值增一,这里需要注意的是, v 中记录的不只是这次迭代,还有之前每次迭代的记录总和(第3行)。将迭代中心的学习率 η 设置为 1 v [ c ] ,通过式子 c ( 1 η ) c + η x 对聚类中心更新;
算法第15行:在满足一开始设置的迭代次数时,退出最外层循环。


代码实现及对比

下面是我自己实现的代码,包括standard   K-Means、bi K-Means与mini-batch   K-Means这三种算法,以及Sklearn中对上述算法的实现我也一并放入代码中,之后对这几种算法的聚类效果和运行时间再进行对比。

代码细节

"""

@author: Ἥλιος
@CSDN:https://blog.csdn.net/qq_40793975/article/details/82112935

"""
print(__doc__)

import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice


# 加载数据集(从文件中)
def load_Data1(filename):
    data_set = []
    with open(filename) as fi:
        for line in fi.readlines():
            cur_line = line.strip().split('\t')
            flt_line = []
            for i in cur_line:
                flt_line.append(float(i))
            data_set.append(flt_line)
    data_mat = np.mat(data_set)  # 转化为矩阵形式
    return data_mat


# 加载数据集(自建数据集)
def load_Data2(n_samples=1500):
    # 带噪声的圆形数据
    noisy_circles = datasets.make_circles(n_samples=n_samples, factor=.5, noise=.05)

    # 带噪声的月牙形数据
    noisy_moons = datasets.make_moons(n_samples=n_samples, noise=.05)

    # 随机分布数据
    no_structure = np.random.rand(n_samples, 2), np.ones((1, n_samples), dtype=np.int32).tolist()[0]

    # 各向异性分布数据(Anisotropicly distributed data)
    random_state = 170
    X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state)
    transformation = [[0.6, -0.6], [-0.4, 0.8]]
    X_aniso = np.dot(X, transformation)
    aniso = (X_aniso, y)

    # 不同方差的气泡形数据(blobs with varied variances)
    varied = datasets.make_blobs(n_samples=n_samples, cluster_std=[1.0, 2.5, 0.5], random_state=random_state)

    # 相同方差的气泡形数据
    blobs = datasets.make_blobs(n_samples=n_samples, random_state=8)

    # 合并数据集
    data_sets = [noisy_circles, noisy_moons, no_structure, aniso, varied, blobs]
    cluster_nums = [2, 2, 3, 3, 3, 3]
    data_mats = []
    for i in range(data_sets.__len__()):
        X, y = data_sets[i]
        X = StandardScaler().fit_transform(X)  # 对数据集进行标准化处理
        X_mat = np.mat(X)
        y_mat = np.mat(y)
        data_mats.append((X_mat, y_mat))

    # 展示数据集
    plt.figure(figsize=(2.5, 14))
    plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
    for i in range(data_sets.__len__()):
        X, y = data_sets[i]
        X = StandardScaler().fit_transform(X)  # 对数据集进行标准化处理
        colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a', '#f781bf', '#a65628', '#984ea3', '#999999',
                                             '#e41a1c', '#dede00']), int(max(y) + 1))))
        plt.subplot(len(data_sets), 1, i+1)
        if i == 0:
            plt.title("Self-built Data Set", size=18)
        plt.scatter(X[:, 0], X[:, 1], c=colors[y], s=10)
        plt.xlim(-2.5, 2.5)
        plt.ylim(-2.5, 2.5)

    plt.show()

    return data_mats, cluster_nums


# 计算样本点A与B间距离(欧氏距离)
def dist_Euclid(VecA, VecB):
    return np.sqrt(np.sum(np.power(VecA-VecB, 2)))


# 对k个聚类中心随机初始化(随机选取数据集中的k个样本点)
def rand_initial_center1(data_mat, k):
    (m, n) = np.shape(data_mat)
    centroids = np.mat(np.zeros((k, n), dtype=np.float32))
    for i in range(k):
        index = int(np.random.rand()*m)
        centroids[i, :] = data_mat[index, :]
    return centroids


# 对k个聚类中心随机初始化(在样本空间范围内随机选取)
def rand_initial_center2(data_mat, k):
    n = np.shape(data_mat)[1]
    centroids = np.mat(np.zeros((k, n), dtype=np.float32))
    for i in range(n):
        minJ = np.min(data_mat[:, i])
        maxJ = np.max(data_mat[:, i])
        centroids[:, i] = np.mat(np.random.rand(k, 1)*(maxJ - minJ)) + minJ
    return centroids


# 标准K-均值
def standard_KMeans(data_mat, k, dist_measure=dist_Euclid):
    (m, n) = np.shape(data_mat)
    centroids = rand_initial_center1(data_mat, k)  # 初始化质心
    cluster_assment = np.mat(np.zeros((m, 2)), dtype=np.float32)  # 存储每个样本点的簇隶属和距该簇的质心距离
    cluster_changed = True  # 簇质心发生改变
    while cluster_changed:
        cluster_changed = False
        for i in range(m):  # M步:更新样本点的簇信息
            min_dist_ji = np.inf  # 样本点与当前所有质心之间的最短距离
            min_dist_index = -1  # 距离当前样本点最近的质心的簇标号
            for j in range(k):
                dist_ji = dist_measure(centroids[j, :], data_mat[i, :])  # 计算当前样本点与所有簇质心之间的距离
                if dist_ji < min_dist_ji:   # 如果该距离小于当前的最小距离
                    min_dist_ji = dist_ji
                    min_dist_index = j
            if cluster_assment[i, 0] != min_dist_index:  # 如果样本点的簇隶属发生改变就继续迭代
                cluster_changed = True
            cluster_assment[i, 0] = min_dist_index
            cluster_assment[i, 1] = min_dist_ji
        for i in range(k):   # E步:更新各个簇质心
            pts_in_cluste = data_mat[np.nonzero(cluster_assment[:, 0].A == i)[0], :]  # 提取隶属于当前簇的所有样本点
            if np.shape(pts_in_cluste)[0] != 0:
                centroids[i, :] = np.mean(pts_in_cluste, axis=0)

        # # 动态显示(!不要再Pycharm中运行,会变成幻灯片)
        # plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=cluster_assment[:, 0].T.A[0])
        # plt.scatter(centroids[:, 0].T.tolist()[0], centroids[:, 1].T.tolist()[0], s=100, c='red', marker='x')
        # plt.show()
        # plt.pause(1)
        # plt.clf()

    return centroids, cluster_assment


# # standard K-Means
# data_mats, cluster_nums = load_Data2()
# plt.figure(figsize=(2.5, 14))
# plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
# for i in range(len(data_mats)):
#     data_mat = data_mats[i][0]  # 获取自建数据集
#     k = cluster_nums[i]  # 获取自建数据集的簇标记
#     t0 = time.time()  # 计算运行时间
#     centroids, cluster_assment = standard_KMeans(data_mat, k)  # 自己实现的
#     # y_pred = KMeans(n_clusters=k, random_state=170).fit_predict(data_mat)  # sklearn的实现
#     t1 = time.time()
#
#     y_pred = np.array(cluster_assment[:, 0].T, dtype=np.int32)[0]  # 预测的簇标记,用于画图(使用sklearn的K_Means时可以注释掉)
#     colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a', '#f781bf', '#a65628', '#984ea3', '#999999',
#                                          '#e41a1c', '#dede00']), int(max(y_pred) + 1))))
#     plt.subplot(len(data_mats), 1, i + 1)
#     if i == 0:
#         plt.title("Self-programming Implementation", size=10)
#         # plt.title("Sklearn Implementation", size=15)
#     plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=colors[y_pred], s=10)
#     plt.xlim(-2.5, 2.5)
#     plt.ylim(-2.5, 2.5)
#     plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'), transform=plt.gca().transAxes, size=15,
#              horizontalalignment='right')
# plt.show()


# 二分K-均值
def bi_KMeans(data_mat, k, dist_measure=dist_Euclid):
    global cent_mat
    (m, n) = np.shape(data_mat)
    centroids0 = np.mean(data_mat, axis=0, dtype=np.float32)  # 选择整个数据集的质心作为初始质心
    cluster_assment = np.mat(np.zeros((m, 2)), dtype=np.float32)  # 存储每个样本点的簇隶属和距该簇的质心距离
    cent_list = [centroids0]  # 质心集合(数组存储为了向其尾部继续添加新的质心)
    for i in range(m):
        cluster_assment[i, 1] = dist_measure(data_mat[i, :], centroids0)
    while cent_list.__len__() < k:  # 直至产生k个簇为止
        min_sse = np.inf  # 最小的总SSE
        for i in range(cent_list.__len__()):  # 查找使总SSE最小的簇进行划分
            pts_in_cluster = data_mat[np.nonzero(cluster_assment[:, 0].A == i)[0], :]
            centroids, split_cluster_assment = standard_KMeans(pts_in_cluster, 2)  # 自己实现的K-Means
            sse_split = np.sum(np.power(split_cluster_assment[:, 1], 2))  # 被选择的簇划分之后的SSE
            sse_nonsplit = np.sum(np.power(cluster_assment[np.nonzero(cluster_assment[:, 0].A != i)[0], 1],
                                           2))  # 未被选择到的样本点的总SSE
            sse_overall = sse_nonsplit + sse_split  # 总SSE
            # print("Overall SSE: ", sse_overall)  # 划分不同簇时的总SSE
            if sse_overall < min_sse:
                min_sse = sse_overall
                best_cluster_tosplit = i  # 当前步最优的划分簇
                best_new_cent = centroids.copy()  # 划分最优簇得到的两个新的簇质心
                best_new_assement = split_cluster_assment.copy()  # 划分最优簇得到的新的簇隶属信息
                # print(best_new_assement[:, 0].A)
        best_new_assement[np.nonzero(best_new_assement[:, 0].A == 1)[0], 0] = cent_list.__len__()
        best_new_assement[np.nonzero(best_new_assement[:, 0].A == 0)[0], 0] = best_cluster_tosplit
        # print("The best cluster ToSplit is: ", best_cluster_tosplit)  # 当前最佳划分簇
        cent_list[best_cluster_tosplit] = best_new_cent[0, :]  # 更新质点信息
        cent_list.append(best_new_cent[1, :])  # 更新质点信息
        cluster_assment[np.nonzero(cluster_assment[:, 0].A == best_cluster_tosplit)[0], :] = best_new_assement  # 更新簇隶属信息
        cent_mat = []  # 簇质心的集合(数组形式,下面会转化为矩阵形式)
        for cent in cent_list:  # 簇质心的集合(转化为矩阵形式,统一输出)
            cent_mat.append(cent.tolist()[0])
        cent_mat = np.mat(cent_mat)

        # # 动态显示(!不要再Pycharm中运行,会变成幻灯片)
        # plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=cluster_assment[:, 0].T.A[0])
        # plt.scatter(cent_mat[:, 0].T.tolist()[0], cent_mat[:, 1].T.tolist()[0], s=100, c='red', marker='x')
        # plt.show()
        # plt.pause(1)
        # plt.clf()

    return cent_mat, cluster_assment


# # bi K-Means
# data_mats, cluster_nums = load_Data2()
# plt.figure(figsize=(2.5, 14))
# plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
# for i in range(len(data_mats)):
#     data_mat = data_mats[i][0]  # 获取自建数据集
#     k = cluster_nums[i]  # 获取自建数据集的簇标记
#     t0 = time.time()  # 计算运行时间
#     # centroids, cluster_assment = standard_KMeans(data_mat, k)  # 自己实现的
#     # y_pred = KMeans(n_clusters=k, random_state=170).fit_predict(data_mat)  # sklearn的实现
#     centroids, cluster_assment = bi_KMeans(data_mat, k)  # 自己实现的
#     t1 = time.time()
#
#     y_pred = np.array(cluster_assment[:, 0].T, dtype=np.int32)[0]  # 预测的簇标记,用于画图(使用sklearn的K_Means时可以注释掉)
#     colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a', '#f781bf', '#a65628', '#984ea3', '#999999',
#                                          '#e41a1c', '#dede00']), int(max(y_pred) + 1))))
#     plt.subplot(len(data_mats), 1, i + 1)
#     if i == 0:
#         # plt.title("standard K-Means(Self-programming Implementation)", size=10)
#         # plt.title("standard K-Means(Sklearn Implementation)", size=15)
#         plt.title("Self-programming Implementation", size=10)
#     plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=colors[y_pred], s=10)
#     plt.xlim(-2.5, 2.5)
#     plt.ylim(-2.5, 2.5)
#     plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'), transform=plt.gca().transAxes, size=15,
#              horizontalalignment='right')
# plt.show()


# Mini-batch K-均值
def mk_KMeans(data_mat, k, dist_measure=dist_Euclid, t=150, b=10):  # t-迭代次数,b-minibatch的大小
    (m, n) = np.shape(data_mat)
    centroids = rand_initial_center1(data_mat, k)  # 初始化质心
    cluster_assment = np.mat(np.zeros((m, 2)), dtype=np.float32)  # 存储每个样本点的簇隶属和距该簇的质心距离
    v_list = np.ones((1, k)).tolist()[0]  # 记录了每次迭代中被划分到每个簇的样本点数量之和
    for i in range(t):
        mini_batch = rand_initial_center1(data_mat, b)  # 随机选择b个样本点用作mini-batch
        d_list = np.ones((1, b), dtype=np.int32).tolist()[0]  # 记录了每次迭代中minibatch中样本点的簇隶属
        for j in range(b):  # 对minibatch中的样本点进行簇划分
            min_dist_ji = np.inf
            min_dist_index = -1
            for l in range(k):
                dist_ji = dist_measure(mini_batch[j, :], centroids[l, :])
                if dist_ji < min_dist_ji:
                    min_dist_ji = dist_ji
                    min_dist_index = l
            d_list[j] = min_dist_index
        for j in range(b):  # 对簇质心进行更新
            cluster_index = d_list[j]
            v_list[cluster_index] += 1
            eta = 1/v_list[cluster_index]  # 学习率
            centroids[cluster_index, :] = (1-eta)*centroids[cluster_index, :]+eta*mini_batch[j, :]
    for i in range(m):  # 更新所有样本点的簇信息
        min_dist_ji = np.inf
        min_dist_index = -1
        for j in range(k):
            dist_ji = dist_measure(data_mat[i, :], centroids[j, :])
            if dist_ji < min_dist_ji:
                min_dist_ji = dist_ji
                min_dist_index = j
        cluster_assment[i, 0] = min_dist_index
        cluster_assment[i, 1] = min_dist_ji

    # # 静态显示
    # plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=cluster_assment[:, 0].T.A[0])
    # plt.scatter(centroids[:, 0].T.tolist()[0], centroids[:, 1].T.tolist()[0], s=100, c='red', marker='x')
    # plt.show()

    return centroids, cluster_assment


# # mini-batch K-Means
# data_mats, cluster_nums = load_Data2()
# plt.figure(figsize=(2.5, 14))
# plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
# for i in range(len(data_mats)):
#     data_mat = data_mats[i][0]  # 获取自建数据集
#     k = cluster_nums[i]  # 获取自建数据集的簇标记
#     t0 = time.time()  # 计算运行时间
#     # centroids, cluster_assment = standard_KMeans(data_mat, k)  # 自己实现的
#     # y_pred = KMeans(n_clusters=k, random_state=170).fit_predict(data_mat)  # sklearn的实现
#     # centroids, cluster_assment = bi_KMeans(data_mat, k)  # 自己实现的
#     # centroids, cluster_assment = mk_KMeans(data_mat, k)  # 自己实现的
#     y_pred = MiniBatchKMeans(n_clusters=k, max_iter=150, batch_size=10).fit_predict(data_mat)  # sklearn的实现
#     t1 = time.time()
#
#     # y_pred = np.array(cluster_assment[:, 0].T, dtype=np.int32)[0]  # 预测的簇标记,用于画图(使用sklearn的K_Means时可以注释掉)
#     colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a', '#f781bf', '#a65628', '#984ea3', '#999999',
#                                          '#e41a1c', '#dede00']), int(max(y_pred) + 1))))
#     plt.subplot(len(data_mats), 1, i + 1)
#     if i == 0:
#         # plt.title("standard K-Means(Self-programming Implementation)", size=10)
#         # plt.title("standard K-Means(Sklearn Implementation)", size=15)
#         # plt.title("bi K-Means(Self-programming Implementation)", size=10)
#         # plt.title("mini-batch K-Means(Self-programming Implementation)", size=10)
#         plt.title("mini-batch K-Means(Sklearn Implementatio)n", size=15)
#     plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=colors[y_pred], s=10)
#     plt.xlim(-2.5, 2.5)
#     plt.ylim(-2.5, 2.5)
#     plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'), transform=plt.gca().transAxes, size=15,
#              horizontalalignment='right')
# plt.show()

算法对比

  上图所示,由左至右依次是:原始数据集及标签、自己实现的标准K-均值、Sklearn中的K-均值、自己实现的二分K-均值、自己实现的小批量K-均值、Sklearn中的小批量K-均值,可以看出虽然这几种算法的聚类效果差不多,但是实际上在多次运行后的结果中,标准K-均值比起二分K-均值的聚类错误率更高一些,还有一点要注意的是小批量K-均值是这三种算法里运行速度最快的,而标准K-均值则运行得最慢。P.s.Sklearn中的算法实现比起我自己写的代码运行速度要快一些,具体原因是初始化质心的方式不同,Sklearn没有使用简单的随机初始化,而是使用“K-Means++”,这是源代码中的一段介绍:
  ‘k-means++’ : selects initial cluster centers for k-mean clustering in a smart way to speed up convergence. See section Notes in k_init for more details.
这里附上有关“K-Means++”的论文,有兴趣的话可以看一看,“k-means++: The advantages of careful seeding” Arthur, David, and Sergei Vassilvitskii, Proceedings of the eighteenth annual ACM-SIAM symposium on Discrete algorithms, Society for Industrial and Applied Mathematics (2007)


参考资料

【1】《机器学习》周志华
【2】《数据挖掘》
【3】《Web-Scale K-Means Clustering》 D. Sculley

猜你喜欢

转载自blog.csdn.net/qq_40793975/article/details/82112935