K-means聚类算法(事先数据并没有类别之分!所有的数据都是一样的)

K-means聚类

  • 1 概述
  • 2 核心思想
  • 3 算法步骤
  • 4 代码实现


1 概述

K-means算法是集简单和经典于一身的基于距离的聚类算法

采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。

该算法认为类簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。

2 核心思想

通过迭代寻找k个类簇的一种划分方案,使得用这k个类簇的均值来代表相应各类样本时所得的总体误差最小。

k个聚类具有以下特点:各聚类本身尽可能的紧凑,而各聚类之间尽可能的分开。

k-means算法的基础是最小误差平方和准则,

其代价函数是:

![在这里插入图片描述]()


   式中,μc(i)表示第i个聚类的均值。

各类簇内的样本越相似,其与该类均值间的误差平方越小,对所有类所得到的误差平方求和,即可验证分为k类时,各聚类是否是最优的。

上式的代价函数无法用解析的方法最小化,只能有迭代的方法。

3 算法步骤

(1) 随机选取 k个聚类质心点
(2)计算每个点,与K个中心点的距离,然后将每个点聚集到与之最近的中心点
(3)新的聚集出来之后,计算每个聚集的新中心点
(4)迭代步骤2和步骤3,直至满足退出条件(条件有两个:
1聚类中心点不再变化,
2迭代次数达到某个值;
具体计算时,可以取其中一个条件,或者两个条件同时取到)

4 代码实现

import numpy as np
import matplotlib.pyplot as plt

# 计算两点之间的欧式距离
def distance(e1, e2):
    return np.sqrt((e1[0]-e2[0])**2+(e1[1]-e2[1])**2)

# 集合中心,arr是一个包含元组的列表,e就是一个二元组,e[0]是x,e[1]是y
def means(arr):
    return np.array([np.mean([e[0] for e in arr]), np.mean([e[1] for e in arr])])

# arr中距离a最远的元素,用于初始化聚类中心
def farthest(k_arr, arr):
    """
    :param k_arr:一个初始点
    :param arr:所有点
    :return:返回与目前聚类中心点距离最远的点
    """
    f = [0, 0]
    # 最远距离
    max_d = 0
    # e就是一个点
    for e in arr:
        d = 0
        # i只能是0,for语句只循环了一次
        for i in range(k_arr.__len__()):
            # 算出每个点到所有聚类中心的距离和
            d = d + np.sqrt(distance(k_arr[i], e))

        # 如果d比目前最大的距离大,那最大距离就变为现在得到的最大距离
        if d > max_d:
            max_d = d
            # f就是距离这些聚类中心最远的点
            f = e
    return f
"""
# arr中距离a最近的元素,用于聚类
def closest(a, arr):

    c = arr[1]
    min_d = distance(a, arr[1])
    arr = arr[1:]
    for e in arr:
        d = distance(a, e)
        if d < min_d:
            min_d = d
            c = e
    return c
"""

if __name__=="__main__":
    ## 生成二维随机坐标,手上有数据集的朋友注意,理解arr改起来就很容易了
    ## arr是一个数组,每个元素都是一个二元组,代表着一个坐标
    ## arr形如:[ (x1, y1), (x2, y2), (x3, y3) ... ]

    # arr是随机得到的包含100个点的列表
    arr = np.random.randint(100, size=(100, 1, 2))[:, 0, :]
    print("得到的arr是:", arr)

    ## 初始化聚类中心和聚类容器
    m = 5

    # r是一个随机数
    r = np.random.randint(arr.__len__() - 1)
    print("得到的r是:",r)

    # 随机得到其中一个点,k_arr也就是其中的一个聚类中心
    k_arr = np.array([arr[r]])
    print("得到的k_arr是:",k_arr)


    cla_arr = [[]]

    # 循环4次,每次都往聚类中心增加1个点,1开始只有1个点,再增加4个
    # 这个循环的目的就是得到聚类中心
    for i in range(m-1):
        # 得到距离已有聚类中心点,最远的一个点
        k = farthest(k_arr, arr)

        # 这样k_arr列表中的点的个数在不断增加
        k_arr = np.concatenate([k_arr, np.array([k])])
        print("第%d次,k_arr是%s"%(i,k_arr))
        # cla_arr就是2维列表,循环4次,就在行的维度上由原来的1行再增加5行,每行存放输入一个类的点,为之后的计算做准备
        cla_arr.append([])
        print("cla_arr是:" )
        print(cla_arr)


    ## 迭代聚类,
    # 迭代20次
    n = 20
    # 这里copy给了另一个变量,不copy也行,结果不受影响
    cla_temp = cla_arr

    # 核心的聚类过程,一共聚类
    for i in range(n):    # 迭代n次
        for e in arr:    # 把集合里每一个元素聚到最近的类
            ki = 0        # 假定距离第一个中心最近
            # 计算每个点与聚类中心的距离
            min_d = distance(e, k_arr[ki])
            for j in range(1, k_arr.__len__()):
                if distance(e, k_arr[j]) < min_d:    # 找到更近的聚类中心
                    # 得到最小距离
                    min_d = distance(e, k_arr[j])
                    # 该点就是j类
                    ki = j

            # cla_temp就是存储着点,第一行就是类1,第2行就是类2.。。。
            cla_temp[ki].append(e)
        # 迭代更新聚类中心
        for k in range(k_arr.__len__()):
            # 如果已经迭代了n次了,就不迭代了
            if n - 1 == i:
                break
            # 得到聚类中心,
            # 这里差一个判断语句,如果得到的聚类中心和之前的一样,就可以提前结束程序了,否则白白增加计算时长
            k_arr[k] = means(cla_temp[k])
            # 存放不同类的点的列表清空
            cla_temp[k] = []

    ## 可视化展示
    col = ['HotPink', 'Aqua', 'Chartreuse', 'yellow', 'LightSalmon']
    # 循环5次
    for i in range(m):
        # 显示出中心点,k_arr是2维列表
        plt.scatter(k_arr[i][0], k_arr[i][1], linewidth=10, color=col[i])
        # 显示出点
        plt.scatter([e[0] for e in cla_temp[i]], [e[1] for e in cla_temp[i]], color=col[i])
    plt.show()