手写K-means及K-means++

手写K-means及K-means++经典算法及实战

前段时间在学校,看了一篇关于K-means-u的聚类论文,当时对聚类只是听过,但对许多经典算法和练习都不够,所以今天专门记录一下,当然也查阅了网上许多资料,如果本文哪有纰漏,欢迎各位的批评和建议

关于K-means和K-means++的算法流程,我这里就不细讲了,之前做过一个PPT,点击下方链接即可查看

https://slides.com/huozhang/clusting/fullscreen

K-means

# 手写k-means算法

# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt


def distance(dec1, dec2) -> float:
    return np.sqrt(np.sum(np.square(dec1 - dec2)))  # 计算欧式距离


def K_Means(data, k):
    K = np.random.uniform(0, 10, (k, data.shape[1]))  # 初始化中心点位矩阵
    ret = np.zeros([data.shape[0], data.shape[1]])  # 构造一个答案矩阵
    flag = True  # 定义标记变量
    count = 1
    while flag:

        flag = False
        for i in range(data.shape[0]):
            minIndex = -1  # 定义得到最短距离的时候的临时中心点位
            minDis = np.inf  # 定义最短距离
            for j in range(K.shape[0]):
                dis = distance(data[i], K[j])  # 计算距离
                if dis < minDis:
                    minDis = dis
                    minIndex = j

            # 计算完成后 将所属距离点位填入答案矩阵
            ret[i][0] = minDis
            ret[i][1] = minIndex

        # 计算好位置了,现在开始重新计算均值,查询中心点
        for i in range(k):
            cluster = data[ret[:, 1] == i]
            # print("中心点位——————", cluster)
            if len(cluster) == 0:
                pass
            else:
                center = np.mean(cluster, axis=0)
                # print("center 为", center)
                # print("K[i] 为", K[i])
                if (center == K[i]).all():  # 这里必须用all()因为 ndarry()的性质 具体大家可以百度
                    pass
                else:
                    K[i] = center
                    flag = True

        _X = K[:, 0]
        _Y = K[:, 1]
        plt.scatter(_x, _y)
        plt.scatter(_X, _Y, marker='X', color='r')
        plt.title("%d  of convergence" % count)
        count += 1
        plt.show()


if __name__ == '__main__':
    # 构造三簇数据
    data1 = np.random.uniform(0, 2, (10, 2))
    data2 = np.random.uniform(3, 6, (10, 2))
    data3 = np.random.uniform(8, 10, (10, 2))
    data = np.r_[data1, data2, data3]  # 按列上下合并数据  np.c_[]是按行 左右合并

    _x = data[:, 0]
    _y = data[:, 1]
    plt.rcParams['font.sans-serif'] = 'SimHei'
    # plt.scatter(_x, _y, marker="o")
    # plt.show()
    K_Means(data, 3)  # 自定义定义3个中心

代码进行了可视化展示,可以看看每次收敛效果


K-means++

主要解决K-means初始点位选择问题,k-means++即基本解决了这一问题,点位选择完成过后,进行相应的收敛和迭代,以减少迭代次数和SSE(最大平方误差和)

# 手写k-means++算法

# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
import random
import math


def euler_distance(point1, point2) -> float:
    """
    计算两点之间的欧拉距离,支持多维
    """
    distance = 0.0
    for a, b in zip(point1, point2):  # 将x,y对应成元组进行计算
        distance += math.pow(a - b, 2)
    return math.sqrt(distance)


def distance(point, cluster) -> float:
    min_dist = math.inf
    for i, centroid in enumerate(cluster):
        dist = euler_distance(centroid, point)  # 计算每一个点到每个簇的距离
        if dist < min_dist:
            min_dist = dist
    return min_dist  # 返回离簇最近的距离


def KMeansplus(data, k) -> list:
    """
    K-means++ 主要解决K-means的初始点位选择问题,返回点位后,再进行收敛,此函数仅完成簇点选取
    :param data: 数据集(测试集)
    :param k: 簇个数
    :return: 返回簇x,y列表
    """
    cluster_list = []
    cluster_list.append(random.choice(data).tolist())
    ret = [0 for _ in range(len(data))]  # 构造距离空列表
    # print(ret)
    for _ in range(1, k):  # 这里从第二个点开始,因为第一个点是随机的
        sum_dis = 0
        for i, point in enumerate(data):
            ret[i] = distance(point, cluster_list)
            sum_dis += ret[i]  # 累加距离
        sum_dis *= random.random()  # 利用轮盘法*[0~1]里面的随机数
        for i, point_dis in enumerate(ret):
            sum_dis -= point_dis  # 依次减距离
            if sum_dis <= 0:
                cluster_list.append(data[i].tolist())  # 直到sum_dis为0时 将此点作为第二个点
                break

    return cluster_list  


if __name__ == '__main__':
    # 构造三簇数据
    data1 = np.random.uniform(0, 2, (20, 2))
    data2 = np.random.uniform(3, 6, (20, 2))
    data3 = np.random.uniform(8, 10, (20, 2))
    data = np.r_[data1, data2, data3]  # 按列上下合并数据  np.c_[]是按行 左右合并

    np.random.shuffle(data)

    _x = data[:, 0]
    _y = data[:, 1]
    plt.rcParams['font.sans-serif'] = 'SimHei'
    plt.scatter(_x, _y, marker="o")

    center = KMeansplus(data, 3)  # 自定义定义3个中心
    # print("k-means++ 的中心为\n", center)
    center = np.array(center)
    center_x = center[:, 0]
    center_y = center[:, 1]
    plt.scatter(center_x, center_y, marker='X', c='r')
    plt.show()
    

可以看见在点位选择上基本和以完成收敛的点位非常接近,之后再通过几次收敛达到最佳

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