Advertisement

数据挖掘十大算法(十):K-means聚类算法

阅读量:

一,K-means算法原理

基本算法

K-means是一种广泛使用的聚类技术,在机器学习领域具有重要地位。该过程接受一个数据集合作为输入,并通过迭代优化的方式将其划分为若干类别。具体来说,在每一步骤中首先确定需要划分的类别数量k,并随机选择初始质心位置;接着建立组织结构,并将每个数据实例分配给离它最近的质心;随后重新计算所有质心的位置;最后检查质心位置是否有变化。若发生变动则继续迭代;否则结束过程。

初始中心点的选取

确定初始中心点的过程对于聚类结果具有重要影响。实验证明了不同的初始中心点可能导致不同的聚类效果。在选择合适的初始中心点时,请考虑以下原则:

初始中心点之间的间距应该较大。因此,可以采取的策略是:

第一步:计算所有样本点两两之间的距离,并选择距离最大的两个样本点对作为初始中心点。然后将这两个样本移除。

step2:如果初始中心点个数满足k个,则退出该过程;否则,在剩余样本中选择C3

在这里插入图片描述

这个问题属于双目标优化领域。通过约束其中一个变量或参数来实现,并使另一个指标达到其极值状态。从而可以选择一个合适的C3点作为第3个初始中心点。

如果要寻找第4个初始中心点,思路和寻找第3个初始中心点是相同的。

误差平方和(Sum of Squared Error)

误差平法和,SSE,用于评价聚类的结果的好坏,SSE的定义如下。

在这里插入图片描述

通常情况下,在数据集的不同类别中引入更多的划分线会减少Sum of Squared Errors(SSE)。当将类别数量设置为等于样本总数时(即每个样本单独成为一个类别),此时每个类别仅包含其自身的一个数据点作为中心点,则Sum of Squared Errors(SSE)达到最小值零。

k值的确定

通常来说,在2到10之间的范围内取值时不会导致过大的问题。进而可以通过绘制SSE-k曲线图来确定合适的k值位置。

在这里插入图片描述

可以看到,k=5之后,SSE下降的变得很缓慢了,因此最佳的k值为5。

二,基本原理的Python实现

复制代码
    # K-means Algorithm is a clustering algorithm
    import numpy as np
    import matplotlib.pyplot as plt
    import random
    
    
    def get_distance(p1, p2):
    diff = [x - y for x, y in zip(p1, p2)]
    distance = np.sqrt(sum(map(lambda x: x ** 2, diff)))
    return distance
    
    
    # 计算多个点的中心
    def calc_center_point(cluster):
    N = len(cluster)
    
    m = np.array(cluster).transpose().tolist()  # m的shape是(2, N)
    
    center_point = [sum(x) / N for x in m]  # 这里其实就是分别对x,y求平均
    return center_point
    
    
    # 检查两个点是否有差别
    def check_center_diff(center, new_center):
    n = len(center)
    for c, nc in zip(center, new_center):
        if c != nc:
            return False
    return True
    
    
    # K-means算法的实现
    def K_means(points, center_points):
    N = len(points)  # 样本个数
    n = len(points[0])  # 单个样本的维度
    k = len(center_points)  # k值大小
    
    tot = 0
    while True:  # 迭代
        temp_center_points = []  # 记录中心点
    
        clusters = []  # 记录聚类的结果
        for c in range(0, k):
            clusters.append([])  # 初始化
    
        # 针对每个点,寻找距离其最近的中心点(寻找组织)
        for i, data in enumerate(points):
            distances = []
            for center_point in center_points:
                distances.append(get_distance(data, center_point))
    
            index = distances.index(min(distances))  # 找到最小的距离的那个中心点的索引,
            clusters[index].append(data)  # 那么这个中心点代表的簇,里面增加一个样本(要理解这里)
    
    
        tot += 1
        print('Epoch:{} Clusters:{}'.format(tot, len(clusters)))
        k = len(clusters)
        colors = ['r.', 'g.', 'b.', 'k.', 'y.']  # 颜色和点的样式
        for i, cluster in enumerate(clusters):
            data = np.array(cluster)
            data_x = [x[0] for x in data]
            data_y = [x[1] for x in data]
            plt.subplot(2, 3, tot)
            plt.plot(data_x, data_y, colors[i])
            plt.axis([0, 1000, 0, 1000])
    
        # 重新计算中心点(该步骤可以与下面判断中心点是否发生变化这个步骤,调换顺序)
        for cluster in clusters:
            temp_center_points.append(calc_center_point(cluster))
    
        # 在计算中心点的时候,需要将原来的中心点算进去
        for j in range(0, k):
            if len(clusters[j]) == 0:  # 这里是说一旦某一个epoch中,某一个聚类中一个样本都没有
                temp_center_points[j] = center_points[j]
    
    
        # 判断中心点是否发生变化:即,判断聚类前后样本的类别是否发生变化
        for c, nc in zip(center_points, temp_center_points):
            if not check_center_diff(c, nc):
                center_points = temp_center_points[:]  # 复制一份
                break
        else:  # 如果没有变化,那么退出迭代,聚类结束
            break
    plt.show()
    return clusters, temp_center_points  # 返回聚类的结果
    
    
    # 随机获取一个样本集,用于测试K-means算法
    def get_test_data():
    N = 1000
    
    # 产生点的区域
    area_1 = [0, N / 4, N / 4, N / 2]
    area_2 = [N / 2, 3 * N / 4, 0, N / 4]
    area_3 = [N / 4, N / 2, N / 2, 3 * N / 4]
    area_4 = [3 * N / 4, N, 3 * N / 4, N]
    area_5 = [3 * N / 4, N, N / 4, N / 2]
    
    areas = [area_1, area_2, area_3, area_4, area_5]
    
    
    # 在各个区域内,随机产生一些点
    points = []
    for area in areas:
        rnd_num_of_points = random.randint(50, 200)
        for r in range(0, rnd_num_of_points):
            rnd_add = random.randint(0, 100)
            rnd_x = random.randint(area[0] + rnd_add, area[1] - rnd_add)
            rnd_y = random.randint(area[2], area[3] - rnd_add)
            points.append([rnd_x, rnd_y])
    
    # 自定义中心点,目标聚类个数为5,因此选定5个中心点
    center_points = [[0, 250], [500, 500], [500, 250], [500, 250], [500, 750]]
    
    return points, center_points
    
    
    if __name__ == '__main__':
    
    points, center_points = get_test_data()
    clusters, temp_center_points = K_means(points, center_points)
    print('#######最终结果##########')
    # for i, cluster in enumerate(clusters): #  打印所有点
    #     print('cluster ', i, ' ', cluster)
    print('最后中心点为:')
    print(temp_center_points)
在这里插入图片描述
在这里插入图片描述

Python实战

数据展示:

复制代码
658985	4.285136
    -3.453687	3.424321
    4.838138	-1.151539
    -5.379713	-3.362104
    0.972564	2.924086
    -3.567919	1.531611
    0.450614	-3.302219
    -3.487105	-1.724432
    2.668759	1.594842
    -3.156485	3.191137
    3.165506	-3.999838
    -2.786837	-3.099354
    4.208187	2.984927
    -2.123337	2.943366
    0.704199	-0.479481
    -0.392370	-3.963704
    2.831667	1.574018
    -0.790153	3.343144
    2.943496	-3.357075
    -3.195883	-2.283926
    2.336445	2.875106
    -1.786345	2.554248
    2.190101	-1.906020
    -3.403367	-2.778288
    1.778124	3.880832
    -1.688346	2.230267
    2.592976	-2.054368
    -4.007257	-3.207066
    2.257734	3.387564
    -2.679011	0.785119
    0.939512	-4.023563
    -3.674424	-2.261084
    2.046259	2.735279
    -3.189470	1.780269
    4.372646	-0.822248
    -2.579316	-3.497576
    1.889034	5.190400
    -0.798747	2.185588
    2.836520	-2.658556
    -3.837877	-3.253815
    2.096701	3.886007
    -2.709034	2.923887
    3.367037	-3.184789
    -2.121479	-4.232586
    2.329546	3.179764
    -3.284816	3.273099
    3.091414	-3.815232
    -3.762093	-2.432191
    3.542056	2.778832
    -1.736822	4.241041
    2.127073	-2.983680
    -4.323818	-3.938116
    3.792121	5.135768
    -4.786473	3.358547
    2.624081	-3.260715
    -4.009299	-2.978115
    2.493525	1.963710
    -2.513661	2.642162
    1.864375	-3.176309
    -3.171184	-3.572452
    2.894220	2.489128
    -2.562539	2.884438
    3.491078	-3.947487
    -2.565729	-2.012114
    3.332948	3.983102
    -1.616805	3.573188
    2.280615	-2.559444
    -2.651229	-3.103198
    2.321395	3.154987
    -1.685703	2.939697
    3.031012	-3.620252
    -4.599622	-2.185829
    4.196223	1.126677
    -2.133863	3.093686
    4.668892	-2.562705
    -2.793241	-2.149706
    2.884105	3.043438
    -2.967647	2.848696
    4.479332	-1.764772
    -4.905566	-2.911070
复制代码
    # coding:utf-8
    
    import numpy as np
    import matplotlib.pyplot as plt
    
    
    def loadDataSet(fileName):
    '''
    加载测试数据集,返回一个列表,列表的元素是一个坐标
    '''
    dataList = []
    with open(fileName) as fr:
        for line in fr.readlines():
            curLine = line.strip().split('\t')
            fltLine = list(map(float, curLine))
            dataList.append(fltLine)
    return dataList
    
    
    def randCent(dataSet, k):
    '''
    随机生成k个初始的质心
    '''
    n = np.shape(dataSet)[1]  # n表示数据集的维度
    centroids = np.mat(np.zeros((k, n)))
    
    for j in range(n):
        minJ = min(dataSet[:, j])
        rangeJ = float(max(dataSet[:, j]) - minJ)
        centroids[:, j] = np.mat(minJ + rangeJ * np.random.rand(k, 1))
    return centroids
    
    
    def kMeans(dataSet, k):
    '''
    KMeans算法,返回最终的质心坐标和每个点所在的簇
    '''
    m = np.shape(dataSet)[0]  # m表示数据集的长度(个数)
    clusterAssment = np.mat(np.zeros((m, 2)))  # 这里存储的是(类别,distance)
    # step:固定初始中心点
    centroids = randCent(dataSet, k)  # 保存k个初始质心的坐标
    
    clusterChanged = True
    iterIndex=1  # 迭代次数
    
    while clusterChanged:
        clusterChanged = False
    
        # step2:找组织
        for i in range(m):  # 遍历每一个样本
            minDist = np.inf
            minIndex = -1
            for j in range(k):
                distJI = np.linalg.norm(np.array(centroids[j, :])-np.array(dataSet[i, :]))
                if distJI < minDist:
                    minDist = distJI
                    minIndex = j
    
            if clusterAssment[i, 0] != minIndex:  # 判断该样本所属的类与之前是否发生变化,只要有一个样本发生变化,迭代继续
                clusterChanged = True
    
            clusterAssment[i, :] = minIndex, minDist**2
        print("第%d次迭代后%d个质心的坐标:\n%s" % (iterIndex, k, centroids))  # 第一次迭代的质心坐标就是初始的质心坐标
        iterIndex = iterIndex + 1
    
        old_centroids = centroids.copy()
        for cent in range(k):
            ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]  # get all the point in this cluster
            centroids[cent, :] = np.mean(ptsInClust, axis=0)
    
        # 我这里采用双重判断
        if (centroids == old_centroids).all():
            pass
        else:
            clusterChanged = True
    
    return centroids, clusterAssment
    
    
    def showCluster(dataSet, k, centroids, clusterAssment):
    '''
    数据可视化,只能画二维的图(若是三维的坐标图则直接返回1)
    这个画图方法不好,后期会更新
    '''
    numSamples, dim = dataSet.shape
    
    if dim != 2:
        return 1
    
    mark = ['or', 'ob', 'og', 'ok', 'oy', 'om', 'oc', '^r', '+r', 'sr', 'dr', '<r', 'pr']
    
    # draw all samples
    for i in range(numSamples):
    
        markIndex = int(clusterAssment[i, 0])
        plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
    
    mark = ['Pr', 'Pb', 'Pg', 'Pk', 'Py', 'Pm', 'Pc', '^b', '+b', 'sb', 'db', '<b', 'pb']
    # draw the centroids
    for i in range(k):
        plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12)
    
    plt.show()
    
    
    if __name__ == '__main__':
    dataMat = np.mat(loadDataSet('./testSet'))
    k = 4  # 选定k值
    cent, clust = kMeans(dataMat, k)
    
    showCluster(dataMat, k, cent, clust)

输出结果:

在这里插入图片描述
加粗样式

三,sklearn实现

复制代码
    import matplotlib.pyplot as plt
    import seaborn as sns
    import numpy as np
    from sklearn.cluster import KMeans
    from scipy.spatial import Voronoi, voronoi_plot_2d
    
    # 该数据集表示玩具制造商的产品数据:
    #
    # 第一个值代表一个玩具:
    #    0-2: 人形公仔
    #    3-5: 积木
    #    6-8: 汽车
    #
    # 第二个值是购买玩具最多的年龄组:
    #    0: 5 year-olds
    #    1: 6 year-olds
    #    2: 7 year-olds
    #    3: 8 year-olds
    #    4: 9 year-olds
    #    5: 10 year-olds
    
    x = np.array([[0, 4], [1, 3], [2, 5], [3, 2], [4, 0], [5, 1], [6, 4], [7, 5], [8, 3]])
    
    # model
    kmeans = KMeans(n_clusters=3, random_state=0).fit(x)
    
    # Plot the data
    sns.set_style("darkgrid")
    plt.scatter(x[:, 0], x[:, 1], c=kmeans.labels_, cmap=plt.get_cmap("winter"))
    
    # 以下为画分割线的方式
    # Save the axes limits of the current figure
    x_axis = plt.gca().get_xlim()
    y_axis = plt.gca().get_ylim()
    
    # Draw cluster boundaries and centers
    centers = kmeans.cluster_centers_
    plt.scatter(centers[:, 0], centers[:, 1], marker='x')
    vor = Voronoi(centers)
    voronoi_plot_2d(vor, ax=plt.gca(), show_points=False, show_vertices=False)
    
    # Resize figure as needed
    plt.gca().set_xlim(x_axis)
    plt.gca().set_ylim(y_axis)
    
    # Remove ticks from the plot
    plt.xticks([])
    plt.yticks([])
    
    plt.tight_layout()
    plt.show()
在这里插入图片描述

全部评论 (0)

还没有任何评论哟~