热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

K-Means算法原理理解以及上手实例

本文将大致梳理K-Means算法的流程,并且使用python3实现kmeans算法对简单欧式空间数据集的聚类问题,以及结果评价算法—轮廓系数的实现,最后将提供本次实例的详细注释代码1.算

本文将大致梳理K-Means算法的流程, 并且使用python3实现kmeans算法对简单欧式空间数据集的聚类问题,以及结果评价算法—轮廓系数的实现,最后将提供本次实例的详细注释代码

1.算法原理:

K-means算法是最为经典的基于划分的聚类方法,是十大经典数据挖掘算法之一。K-means算法的基本思想是:以空间中k个点为中心进行聚类,对最靠近他们的对象归类。通过迭代的方法,逐次更新各聚类中心的值,直至得到最好的聚类结果。

选择K个不相同的点作为初始质心  
repeat  
    将每个点指派到最近的质心,形成K个簇  
    重新计算每个簇的质心  
until 簇不发生变化或达到最大迭代次数  

1.1.质心计算:

对于分类后的产生的k个簇,分别计算到簇内其他点距离均值最小的点作为质心(对于拥有坐标的簇可以计算每个簇坐标的均值作为质心)

1.2.距离度量:

将对象点分到距离聚类中心最近的那个簇中需要最近邻的度量策略,在欧式空间中采用的是欧式距离,在处理文档中采用的是余弦相似度函数,有时候也采用曼哈顿距离作为度量,不同的情况实用的度量公式是不同的。
欧式距离
余弦相似度
曼哈顿距离

1.3.聚类效果评价

轮廓系数(Silhouette Coefficient)结合了聚类的凝聚度(Cohesion)和分离度(Separation),用于评估聚类的效果。该值处于-1~1之间,值越大,表示聚类效果越好。具体计算方法如下:

  1. 对于每个样本点i,计算点i与其同一个簇内的所有其他元素距离的平均值,记作a(i),用于量化簇内的凝聚度。
  2. 选取i外的一个簇b,计算i与b中所有点的平均距离,遍历所有其他簇,找到最近的这个平均距离,记作b(i),即为i的邻居类,用于量化簇之间分离度。
  3. 对于样本点i,轮廓系数s(i) = (b(i) – a(i))/max{a(i),b(i)}
  4. 计算所有i的轮廓系数,求出平均值即为当前聚类的整体轮廓系数,度量数据聚类的紧密程度

从上面的公式,不难发现若s(i)小于0,说明i与其簇内元素的平均距离小于最近的其他簇,表示聚类效果不好。如果a(i)趋于0,或者b(i)足够大,即a(i)远远小于b(i),那么s(i)趋近与1,说明聚类效果比较好。


2.上手实例

下面将通过从准备数据到完成聚类并寻找最佳参数等一系列完整的步骤来讲解第一个算法实现的示例,最后我将提供此次示例的完整项目代码以及基于上一篇博客的VSM模型的计算结果来进行文档的聚类项目代码,本次示例使用语言为python3

2.1.准备数据

作为算法实现的测试数据,最好能够了解数据的分布情况,以便结合代码的运行结果来检测代码实现的正确与否。因此,我准备了一组基于欧式空间的坐标集数据,数据大致分布如下:
test

2.2.数据预处理

初始数据只有点的坐标和序号,因此需要计算出每两点之间的欧式距离并保存为距离表,方便后续计算时查找距离避免重复计算

# 各点间距离计算
def cal_dis(path, data):
    tes_dis = {}
    for i in range(0, len(data) - 1):
        for j in range(i + 1, len(data)):
            name = str(i+1) + '-' + str(j+1)
            # 欧式距离计算公式
            dist = math.sqrt(math.pow(data[i]["x"] - data[j]["x"], 2) + math.pow(data[i]["y"] - data[j]["y"], 2))
            tes_dis[name] = int(round(dist))

    save_file(path, tes_dis)
    pass
# 数据转换为距离表形式
def data_transform(data, path):
    points = {}
    point_name = []
    for i in data:
        name = i.split('-')
        if name[0] not in point_name:
            point_name.append(int(name[0]))
        if name[1] not in point_name:
            point_name.append(int(name[1]))
        if name[0] not in points:
            points[name[0]] = {}
            points[name[0]][name[1]] = data[i]
        else:
            points[name[0]][name[1]] = data[i]
    points[str(max(point_name))] = {}  # 将最后一个点加入到距离表中
    save_file(path, points)

最终计算出的距离表样例:

    "1": {
            "2": 2,
            "3": 2,
            "4": 70,
            "5": 4,
            "6": 72,
            "7": 99,
            "8": 70,
            "9": 102,
            "10": 103,
            "11": 102,
            "12": 67,
            "13": 74
        }

2.3.编写K-Means主函数

在参数部分选择使用k值的范围可以避免多次的手动调节,函数会自动遍历k值范围并找到最优解,根据肘部法则默认k值从2开始;对于每个k值最好进行多次重复计算,因为kmeans算法对初始点的选取十分敏感,离群点以及噪点对算法结果影响很大,算法很容易陷入局部最优,局部最优问题将会在结果部分做进一步分析。

# 参数:k值的范围(默认从2开始),每个k值重复计算次数,每次计算迭代次数,距离表数据
def k_means(k_range, repetition, iterations, data):
    coefficient_list = []  # 记录每次计算轮廓系数
    result_centers = []  # 记录每次计算结果中心点
    # k值遍历
    for k in range(2, k_range + 1):
        # 每次计算重复多次,避免局部最优解
        for times in range(0, repetition):
            center_point = []
            for i in range(0, k):  # 初始化随机选取k个不同中心点
                random_center = str(random.randint(0, len(data) - 1))
                while random_center in center_point:
                    random_center = str(random.randint(0, len(data) - 1))
                center_point.append(random_center)

            for index in range(0, iterations):  # 迭代计算
                # 将每次重新计算得到的中心点作为下一次参数
                center_point = cal_k_means(center_point, data)
            # 计算每次聚类后的轮廓系数
            coefficient = silhouette_coefficient(center_point, data)
            coefficient_list.append(coefficient)
            result_centers.append(center_point)

    # 以轮廓系数最大的结果作为最优解
    best_coef = max(coefficient_list)
    best_centers = result_centers[coefficient_list.index(best_coef)]
    # 结果输出
    cout_best_result(repetition * iterations * k_range, best_coef, best_centers, data)
    pass
# k-means 计算主函数,根据中心点划分簇,再重新计算每个簇的中心点
def cal_k_means(center_point_list, data):
    # 根据中心点划分簇
    cluster = divide_cluster(center_point_list, data)

    center_point = []
    for each in cluster:  # 重新计算中心点
        center_num = each['center']
        center_dis_count = 0  # 中心点到其他点距离和
        each_dis = []  # 每个点到其他点的距离和
        for each_point in each['member']:
            center_dis = search_distance(center_num, each_point, data)
            dis_count = center_dis  # 到其他点距离和
            center_dis_count += center_dis

            for target_point in each['member']:  # 计算到簇内其他点的距离和
                if target_point == each_point:
                    continue
                dis_count += search_distance(target_point, each_point, data)  # 查询距离表
            each_dis.append(dis_count)

        each_dis.append(center_dis_count)
        min_point = each_dis.index(min(each_dis))  # 选取距离和最小的点为中心点

        # 更新中心点
        if min_point == (len(each_dis) - 1):
            center_point.append(each['center'])
        else:
            center_point.append(each['member'][min_point])

    return center_point
# 根据中心点划分簇
def divide_cluster(center_list, data):
    cluster = []
    center_point = []

    for i in center_list:
        temp = {'center': i, 'member': []}
        cluster.append(temp)
        center_point.append(i)

    for point in data:  # 计算每点到每个中心点距离
        dis = []
        if point in center_point:
            continue
        for j in cluster:
            center_num = j['center']
            temp_dis = search_distance(center_num, point, data)
            dis.append(temp_dis)

        point_cluster = dis.index(min(dis))  # 划分到最小的簇
        cluster[point_cluster]['member'].append(point)

    return cluster
# 在距离表中查询距离
def search_distance(a, b, data):
    if a in data[b]:
        distance = data[b][a]
    else:
        distance = data[a][b]
    return distance

到现在就算完成了kmeans函数的编写,但是我们无法判断每一次聚类结果的好坏,因此,我们引入了轮廓系数来量化聚类结果。

2.4.评价函数:轮廓系数计算函数

轮廓系数计算的原理以及流程已经在上文介绍过了,以下是具体的实现:

# 计算轮廓系数
def silhouette_coefficient(center_list, data):
    cohesion = 0.0  # 簇内凝聚度
    separation = 0.0  # 簇间分离度
    separation_list = []
    coefficient_list = []

    cluster = divide_cluster(center_list, data)

    for point in data:
        this_cluster = ''
        for i in cluster:
            # 计算簇内凝聚度
            if point in i['member'] or point == i['center']:
                this_cluster = i['center']
                if point != i['center']:
                    center_dis = search_distance(point, i['center'], data)
                else:
                    center_dis = 0
                for j in i['member']:
                    if j == point:
                        continue
                    cohesion += search_distance(point, j, data)
                cohesion += center_dis
                if len(i['member']) != 0:  # 取平均值
                    cohesion = float(cohesion/len(i['member']))
                else:
                    cohesion = float(cohesion / 1.0)
                break

        # 簇间分离度计算
        for other in cluster:
            if other['center'] == this_cluster:
                continue
            center_dis = search_distance(point, other['center'], data)
            for other_point in other['member']:
                separation += search_distance(point, other_point, data)
            separation += center_dis
            if len(other['member']) != 0:  # 取平均值
                separation = float(separation / (len(other['member']) + 1))
            else:
                separation = float(separation / 1.0)
            separation_list.append(separation)

        # 计算每个点的轮廓系数
        separation = min(separation_list)
        coefficient = (separation - cohesion)/max(separation, cohesion)
        coefficient_list.append(coefficient)

    # 返回平均值作为此次分类的轮廓系数
    coefficient = float(sum(coefficient_list)/len(coefficient_list))
    return coefficient

2.5.结果分析

如果不对每一个k值进行多次重复计算就会有很大几率得到局部最优解:

# 参数:k值的范围(默认从2开始),每个k值重复计算次数,每次计算迭代次数,距离表数据
 k_means(3, 1, 20, dta)

1

进一步分析发现:对于这个小型的数据集,函数在第一次迭代完成后中心点就基本稳定了,因此,如果初始化选取的中心点位置不佳的话函数就会陷入局部最优,对此,最简单的解决方法就是多次计算,然后选取最优解。对K-Means算法的进一步优化请查看下面的参考资料。

下面是此次示例的工程代码下载链接:

K-Means 实例工程代码

3.参考资料

https://blog.csdn.net/u013719780/article/details/78413770

https://www.cnblogs.com/dudumiaomiao/p/5839905.html

https://blog.csdn.net/qll125596718/article/details/8243404/

https://blog.csdn.net/github_36326955/article/details/54999612


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • EPPlus绘制刻度线的方法及示例代码
    本文介绍了使用EPPlus绘制刻度线的方法,并提供了示例代码。通过ExcelPackage类和List对象,可以实现在Excel中绘制刻度线的功能。具体的方法和示例代码在文章中进行了详细的介绍和演示。 ... [详细]
  • 正常情况下,我们完成一件事情的过程中,可能会存在多种条件限制如:用户去ATM机取钱->输入取款密码->输入正确,取钱成功|输入错误,退卡。这样的情况下,需要根据不同的条件,执行不同的逻 ... [详细]
  • 一、简介在面向对象的程序设计中类和对象是其重要角色,我们知道对象是由类实例化而来,那么类又是怎么生成的呢?答案是通过元类。本篇文章将介绍元类相关知识,并剖析元类生成类的过程,以及元 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 基于dlib的人脸68特征点提取(眨眼张嘴检测)python版本
    文章目录引言开发环境和库流程设计张嘴和闭眼的检测引言(1)利用Dlib官方训练好的模型“shape_predictor_68_face_landmarks.dat”进行68个点标定 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 这篇文章给大家分享的是有关python3怎样中文转换编码的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。示例:处理 ... [详细]
  • 环境准备—之—linux下安装python3和pip3
    转自上海悠悠https:www.cnblogs.comyoyoketangp10195102.html前言centos7自带有python,但是却是python2版本的 ... [详细]
  • importurllib.requestimportos#用于获取煎蛋网页面的函数defurl_open(url):requrllib.request.Request(url) ... [详细]
author-avatar
皮蓬
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有