当前位置: 首页 > news >正文

【ML】K均值聚类及Python手写实现(详细)

目录

一、闵可夫斯基距离

1.1 曼哈顿距离

1.2 欧式距离

1.3 VDM距离

二、K_means聚类算法

2.1 算法流程

2.2 算法描述

三、Python实现K_means聚类算法

3.1 导入第三方模块

3.2 加载数据集函数

3.3 计算欧式距离函数

3.4 K_means聚类函数

3.5 聚类结果可视化函数

3.6 主函数

四、运行结果

4.1 终端输出

4.2 可视化输出

五、整体总结

六、总代码


写在前面

本篇文章介绍了K均值聚类算法的原理,以及使用Python从零实现该算法。若有不足,欢迎指正。

一、闵可夫斯基距离

对于给定两个样本:

闵可夫斯基距离公式:

n:代表特征数;

1.1 曼哈顿距离

p=1时,闵可夫斯基即是曼哈顿距离:

1.2 欧式距离

p=2时,闵可夫斯基距离即是欧氏距离:

1.3 VDM距离

上面计算距离的公式只适用于“连续属性”“有序的离散属性”,而对于“无序的离散属性”上面公式显然不成立,需要用到下面的VDM公式,表示属性u上两个离散值a与b之间的距离:

:表示在属性u上取值为a的样本数;

:表示在第i个样本簇中在属性u上取值为a的样本数;

k:表示样本簇数(聚类数);

二、K_means聚类算法

2.1 算法流程

2.2 算法描述

输入:输入样本集D和聚类数k。

过程:

首先从样本集D中随机选取k个样本作为初始的均值向量centers。

然后下面进入迭代聚类,初始化每一个cluster为空集,然后对每一个样本分别对每一个均值向量center计算距离,选择其中距离最小的,并将该样本划分进对应的簇中。

对划分好的每一个簇cluster重新计算得到新的均值向量new_center,并比较旧的均值向量,如果不相等则更新均值向量centers,如果相等则保持均值向量不变。直到所有的均值向量都不更新时跳出迭代循环,得到最终的聚类结果。

输出:输出聚类的最终结果clusters。

总之,整体流程初始化均值向量——>聚类——>更新均值向量——>输出簇。

三、Python实现K_means聚类算法

3.1 导入第三方模块

import numpy as np import pandas as pd import matplotlib.pyplot as plt

(1)numpy、pandas模块用于数值处理;

(2)matplotlib.pyplot用于可视化数据;

3.2 加载数据集函数

#? 加载数据集 # return: 返回数据集dataSet和特征名称labels def loadDataSet(): dataSet = [[0.697, 0.460], [0.774, 0.376], [0.634, 0.264], [0.608, 0.318], [0.556, 0.215], [0.403, 0.237], [0.481, 0.149], [0.437, 0.211], [0.666, 0.091], [0.243, 0.267], [0.245, 0.057], [0.343, 0.099], [0.639, 0.161], [0.657, 0.198], [0.360, 0.370], [0.593, 0.042], [0.719, 0.103], [0.359, 0.188], [0.339, 0.241], [0.282, 0.257], [0.748, 0.232], [0.714, 0.346], [0.483, 0.312], [0.478, 0.437], [0.525, 0.369], [0.751, 0.489], [0.532, 0.472], [0.473, 0.376], [0.725, 0.445], [0.446, 0.459]] labels = ['密度','含糖率'] # 查看数据集 df = pd.DataFrame(data=dataSet, columns=labels) #print(df) return dataSet, labels

变量说明:

dataSet:西瓜集的数据集,一个二维矩阵,一共30行样本数据,第1列表示“密度”,第2表示“含糖率”;

labels:存放样本的特征名称;

df:数据集的二维结构;终端输出如下方图3-1;

返回值:

返回数据集dataSet,和特征名称labels;

函数作用:

加载数据集;

3.3 计算欧式距离函数

#? 求欧式距离 # params: sample:单个数据样本, center:k个样本的均值向量 # return: 返回单个样本sample属于哪一个簇的索引cls def distance(sample, centers): dist = np.power(sample - centers, 2).sum(axis=1) # 欧式距离 cls = np.argmin(dist) # 选出最小距离的索引 #print(dist) #print(cls) return cls

变量说明:

sample:传入的单个样本数据;

centers:k个均值向量;

dist:存储单个样本sample与各个均值向量center的欧式距离,是一个一维向量(因为均值向量不止一个);

cls:存储一维向量dist中最小值的索引;

返回值:

返回单个样本sample划分到指定簇的索引cls;

函数作用:

计算欧氏距离,并对单样本sample划分进合适的簇;

3.4 K_means聚类函数

#? K_means聚类 # params: dataSet:数据集, k:聚类数 # return: 返回最终的各个均值向量centers和划分好的所有簇clusters # 关键变量: clusters:各个簇, centers:各个均值向量 def k_means(dataSet, k): dataSet = np.array(dataSet) #? 选取均值向量centers np.random.seed(123) # 设置随机数种子(保证每次运行文件随机数不变) centers = dataSet[np.random.choice(dataSet.shape[0], k, replace=False)] # 初始化k个均值向量(随机选取k个样本) #print(centers) #? 进入循环迭代训练 while True: #? 计算所有簇clusters clusters = [[] for i in range(k)] # 初始化k个簇 #print(clusters) for sample in dataSet: clusters_index = distance(sample, centers) # 计算欧氏距离 clusters[clusters_index].append(sample) # 划分到对应簇 #? 计算新均值向量centers new_center0 = np.array(clusters[0]).mean(axis=0) new_center1 = np.array(clusters[1]).mean(axis=0) new_center2 = np.array(clusters[2]).mean(axis=0) # 判断是否满足训练完成的条件 if (new_center0 == centers[0]).all() and (new_center1 == centers[1]).all() and (new_center2 == centers[2]).all(): break #? 更新均值向量centers else: centers[0] = new_center0 centers[1] = new_center1 centers[2] = new_center2 return centers, clusters

变量说明:

dataSet:数据集;

k:聚类数;

centers:存储各个簇的均值向量;

clusters:存储划分出来的各个簇;

sample:数据集中的单个样本;

new_center0:计算出的新第一个簇的均值向量;

返回值:

返回最终划分好的所有簇clusters,以及每个簇的均值向量(中心);

函数作用:

该函数中首先随机选取了k个样本作为每个簇的初始均值向量。然后进入循环迭代聚类,先遍历各个样本求其欧氏距离对其进行簇的划分,再重新计算各个簇的均值向量,进行更新每个簇的均值向量,直到所有簇的均值向量都不更新跳出循环,说明所有簇已经聚类完毕。

3.5 聚类结果可视化函数

#? 可视化 # params: dataSet:数据集, clusters:簇, centers:均值向量 def clusters_show(dataSet, clusters, centers): plt.figure(figsize=(12, 5)) # 设置整个画布尺寸 color = ["m", "b", "c"] # 设置色彩集 marker = ["*", "^", "p"] # 设置标记集 #? 绘制聚类之前的散点图 plt.subplot(1,2,1) plt.title("Before K_means") # 图像标题 plt.xlabel("density", loc="center") # x轴名称 plt.ylabel("sugar", loc="center") # y轴名称 dataSet = np.array(dataSet) plt.scatter(dataSet[:, 0], # 散点的横坐标 dataSet[:, 1], # 散点的纵坐标 c='m', # 散点的颜色【品红】 marker='.', # 散点的样式【点】 s=150, # 散点的面积【150】 label='sample') # 散点的图例 plt.legend() # (不稳定)显示图例 plt.gca().set_aspect(1) # x、y轴单位长度相等 #? 绘制聚类之后的散点图 plt.subplot(1,2,2) plt.title("After K_means") # 图像标题 plt.xlabel("density", loc="center") # x轴名称 plt.ylabel("sugar", loc="center") # y轴名称 for index, cluster in enumerate(clusters): cluster = np.array(cluster) plt.scatter(cluster[:, 0], # 散点的横坐标 cluster[:, 1], # 散点的纵坐标 c=color[index], # 散点的颜色 marker=marker[index], # 散点的样式 s=100, # 散点的面积 label=index) # 散点的图例 #? 绘制聚类后的中心点(均值向量) plt.scatter(centers[:, 0], # 散点横坐标 centers[:, 1], # 散点纵坐标 c='r', # 散点的颜色【红色】 marker='o', # 散点的样式【圆】 s=100, # 散点的面积【100】 label="centers") # 散点的图例 plt.legend() # (不稳定)显示图例 plt.gca().set_aspect(1) # 使x、y轴单位长度相等 plt.show()

变量说明:

dataSet:数据集;

clusters:所有簇;

centers:各个簇的均值向量;

函数作用:

该函数首先自定义了一些颜色集和标记集,用于后面区别聚类后的不同类别的样本散点。

可视化函数中画了两个子图分别来显示聚类前(title:Before K_means)聚类后(title: After K_means)的样本散点分布。通过两个子图在同一个画布中的对比可以看出聚类的效果。首先绘制了聚类前的子图;又绘制了聚类后的子图,聚类后的子图中不同类别的样本在图中呈现的散点样式、颜色均不同,易于用肉眼看出聚类的不同类别分布;紧接着又在聚类后的子图中绘制了不同类别的中心(均值向量)centers,用于呈现聚类的中心点。最终的可视化呈现效果可在结果分析中可以看到。

3.6 主函数

#? 主函数 if __name__=='__main__': dataSet, labels = loadDataSet() centers, clusters = k_means(dataSet, 3) # 可视化输出 clusters_show(dataSet, clusters, centers) # 终端输出 print("=====================第一个簇=====================") print(f'第1个簇共有样本 {len(clusters[0])} 个') print(f'均值向量:{centers[0]}') print(pd.DataFrame(clusters[0], columns=labels)) print("=====================第二个簇=====================") print(f'第2个簇共有样本 {len(clusters[1])} 个') print(f'均值向量:{centers[1]}') print(pd.DataFrame(clusters[1], columns=labels)) print("=====================第三个簇=====================") print(f'第3个簇共有样本 {len(clusters[2])} 个') print(f'均值向量:{centers[2]}') print(pd.DataFrame(clusters[2], columns=labels))

函数作用:

将上述所有的函数模块连接成一个整体,相互协作共同实现K_means聚类算法。通过调用k_means()函数可以看到我们选择的聚类数为3。下面就是调用可视化函数进行可视化输出;在下面是设置在终端输出的一些信息:每个簇包含的样本总数、其均值向量、所包含的所有样本数据。

四、运行结果

4.1 终端输出

第1个簇中包含了9个样本数据,其均值向量为[0.633,0.162]:

第2个簇中包含了12个样本数据,其均值向量为[0.601,0.405]:

第3个簇中包含了9个样本数据,其均值向量为[0.335,0.214]:

4.2 可视化输出

左图为聚类前的样本分布散点图,右图为聚类后的样本被不同标记不同颜色标记类别后的散点图。

从聚类后的效果可以看到聚类了3类“星号”、“三角形”、“五边形”分别表示了这3类,“红色点”代表每一类的中心center(均值向量),通过观察发现center大致都分布在其类别的中心位置,符合理论聚类效果还是很好的,能够很好的对样本进行类别集中划分。

五、整体总结

理解上述代码中的一些基本知识:

1、np.power(sample - centers, 2).sum(axis=1)

axis=1 : 横向(行)求sum; axis=0 : 纵向(列)求sum;

sample : 一维向量(数组只有一行数据); centers : 二维向量(数组有多行数据);

np.power(一维 - 二维, 2) : sample分别于centers中的每一行数据进行减法并power操作, 返回一个二维向量;

.sum(axis=1) : 对上面得到的二维向量按行求和, 得到一个一维向量;

2、cls = np.argmin(dist)

np.argmin(dist) : dist这里是一个一维向量, 。argmin()函数返回其最小值的索引;

3、(new_center0 == centers[0]).all()

两者均为数组类型,每个数组具有2个元素,使整体的bool值明确还需使用.all()/.any()来明确其真值;

4、dataSet[np.random.choice(dataSet.shape[0], k, replace=False)]

dataSet.shape[0] : 查看dataSet有多少行数据,此处是一个整数;

np.random.choice(dataSet.shape[0], k, replace=False) : 在上面求得的整数中随机取k个数值,replace=False限制随机取数时不可以重复取同一个数值;

dataSet[数值] : 取索引为指定数值的dataSet元素;

5、plt.sactter(..., label=...) 后使用 plt.legend()

问题所在: 绘制散点图时已设置标签,如plt.sactter(..., label=...),最后plt.show时图像中不显示图例;

解决方法: plt.scatter(..., label=...)后使用plt.legend();

6、plt.gca().set_aspect(1)

对某一子图使用,使该图像的x、y轴的单位长度相等;

六、总代码

import numpy as np import pandas as pd import matplotlib.pyplot as plt #? 加载数据集 # return: 返回数据集dataSet和特征名称labels def loadDataSet(): dataSet = [[0.697, 0.460], [0.774, 0.376], [0.634, 0.264], [0.608, 0.318], [0.556, 0.215], [0.403, 0.237], [0.481, 0.149], [0.437, 0.211], [0.666, 0.091], [0.243, 0.267], [0.245, 0.057], [0.343, 0.099], [0.639, 0.161], [0.657, 0.198], [0.360, 0.370], [0.593, 0.042], [0.719, 0.103], [0.359, 0.188], [0.339, 0.241], [0.282, 0.257], [0.748, 0.232], [0.714, 0.346], [0.483, 0.312], [0.478, 0.437], [0.525, 0.369], [0.751, 0.489], [0.532, 0.472], [0.473, 0.376], [0.725, 0.445], [0.446, 0.459]] labels = ['密度','含糖率'] # 查看数据集 df = pd.DataFrame(data=dataSet, columns=labels) #print(df) return dataSet, labels #? 求欧式距离 # params: sample:单个数据样本, center:k个样本的均值向量 # return: 返回单个样本sample属于哪一个簇的索引cls def distance(sample, centers): dist = np.power(sample - centers, 2).sum(axis=1) # 欧式距离 cls = np.argmin(dist) # 选出最小距离的索引 #print(dist) #print(cls) return cls #? K_means聚类 # params: dataSet:数据集, k:聚类数 # return: 返回最终的各个均值向量centers和划分好的所有簇clusters # 关键变量: clusters:各个簇, centers:各个均值向量 def k_means(dataSet, k): dataSet = np.array(dataSet) #? 选取均值向量centers np.random.seed(123) # 设置随机数种子(保证每次运行文件随机数不变) centers = dataSet[np.random.choice(dataSet.shape[0], k, replace=False)] # 初始化k个均值向量(随机选取k个样本) #print(centers) #? 进入循环迭代训练 while True: #? 计算所有簇clusters clusters = [[] for i in range(k)] # 初始化k个簇 #print(clusters) for sample in dataSet: clusters_index = distance(sample, centers) # 计算欧氏距离 clusters[clusters_index].append(sample) # 划分到对应簇 #? 计算新均值向量centers new_center0 = np.array(clusters[0]).mean(axis=0) new_center1 = np.array(clusters[1]).mean(axis=0) new_center2 = np.array(clusters[2]).mean(axis=0) # 判断是否满足训练完成的条件 if (new_center0 == centers[0]).all() and (new_center1 == centers[1]).all() and (new_center2 == centers[2]).all(): break #? 更新均值向量centers else: centers[0] = new_center0 centers[1] = new_center1 centers[2] = new_center2 return centers, clusters #? 可视化 # params: dataSet:数据集, clusters:簇, centers:均值向量 def clusters_show(dataSet, clusters, centers): plt.figure(figsize=(12, 5)) # 设置整个画布尺寸 color = ["m", "b", "c"] # 设置色彩集 marker = ["*", "^", "p"] # 设置标记集 #? 绘制聚类之前的散点图 plt.subplot(1,2,1) plt.title("Before K_means") # 图像标题 plt.xlabel("density", loc="center") # x轴名称 plt.ylabel("sugar", loc="center") # y轴名称 dataSet = np.array(dataSet) plt.scatter(dataSet[:, 0], # 散点的横坐标 dataSet[:, 1], # 散点的纵坐标 c='m', # 散点的颜色【品红】 marker='.', # 散点的样式【点】 s=150, # 散点的面积【150】 label='sample') # 散点的图例 plt.legend() # (不稳定)显示图例 plt.gca().set_aspect(1) # x、y轴单位长度相等 #? 绘制聚类之后的散点图 plt.subplot(1,2,2) plt.title("After K_means") # 图像标题 plt.xlabel("density", loc="center") # x轴名称 plt.ylabel("sugar", loc="center") # y轴名称 for index, cluster in enumerate(clusters): cluster = np.array(cluster) plt.scatter(cluster[:, 0], # 散点的横坐标 cluster[:, 1], # 散点的纵坐标 c=color[index], # 散点的颜色 marker=marker[index], # 散点的样式 s=100, # 散点的面积 label=index) # 散点的图例 #? 绘制聚类后的中心点(均值向量) plt.scatter(centers[:, 0], # 散点横坐标 centers[:, 1], # 散点纵坐标 c='r', # 散点的颜色【红色】 marker='o', # 散点的样式【圆】 s=100, # 散点的面积【100】 label="centers") # 散点的图例 plt.legend() # (不稳定)显示图例 plt.gca().set_aspect(1) # 使x、y轴单位长度相等 plt.show() #? 主函数 if __name__=='__main__': dataSet, labels = loadDataSet() centers, clusters = k_means(dataSet, 3) # 可视化输出 clusters_show(dataSet, clusters, centers) # 终端输出 print("=====================第一个簇=====================") print(f'第1个簇共有样本 {len(clusters[0])} 个') print(f'均值向量:{centers[0]}') print(pd.DataFrame(clusters[0], columns=labels)) print("=====================第二个簇=====================") print(f'第2个簇共有样本 {len(clusters[1])} 个') print(f'均值向量:{centers[1]}') print(pd.DataFrame(clusters[1], columns=labels)) print("=====================第三个簇=====================") print(f'第3个簇共有样本 {len(clusters[2])} 个') print(f'均值向量:{centers[2]}') print(pd.DataFrame(clusters[2], columns=labels))
http://www.jsqmd.com/news/730103/

相关文章:

  • 3分钟掌握完整网页截图:告别零碎片段,拥抱完整内容保存
  • 冰雪传奇点卡版官方网站:三端互通全解析,随时随地畅玩
  • W55MH32 芯片 MicroPython 实战 (2):GPIO 通用输入输出
  • 中文乱码 ubuntu autodl
  • Windows下PyGMT安装报错‘GMTCLibNotFoundError’?手把手教你从零配置GMT 6.3.0环境
  • LLM在文本分析与差异检测中的实践应用
  • 技术日报|mattpocock技能库三连冠单日揽星7321总量破3.7万,微软VibeVoice语音AI再度上榜
  • SpringBoot 接口性能如何快速定位?轻量级应用监控工具开源啦,一键接入,轻松定位!
  • DIO32321 低功耗 USB2.0 高速开关技术文档
  • 从非结构化数据到结构化:Anything-Extract项目实战与架构解析
  • 传承与奉献:资深技术人如何做好“传帮带”?
  • 桌面美化与效率结合,这款免费桌面工具能管理倒计时、宠物和加密
  • 海棠山铁哥戳破《灵魂摆渡・浮生梦》伪 AI 骗局,《第一大道》纯 AI 写实告别躺平
  • DeepSeek V1 到 V4 完整技术路线:每一代到底解决了什么问题?
  • taotoken 多模型聚合能力如何赋能智能客服场景开发
  • 从播客剪辑到游戏音效:用GoldWave 6.78搞定你的所有音频需求(附基础操作指南)
  • 协同自动驾驶中的V2V-GoT框架:技术原理与工程实践
  • CS3106 双节电池均衡芯片技术文档(完整版)
  • AArch64 SIMDFP寄存器存储指令详解与优化实践
  • 基于可逆残差网络与互信息最大化的化工泵故障诊断【附代码】
  • 2026合肥生殖中心擅长多囊医生推荐:安医不孕不育推荐医生,安医专治不孕不育医生,安医多囊专家,实力盘点! - 优质品牌商家
  • 网络运维效率翻倍:手把手教你用Docker Compose一键部署PHPIPAM 1.6
  • Visual Studio调试时遇到ntdll.dll的PDB文件缺失?别慌,这3个方法帮你搞定(附详细步骤)
  • 告别手动点开始!用SUMO的gui_only配置实现配置文件一打开就自动仿真
  • 第 3 章:Gradle 进阶工程能力
  • 为什么92%的PHP团队在LLM长连接上踩坑?Swoole协程池、FD复用、上下文隔离三大致命盲区全解析,
  • 零基础快速启用 OpenClaw,保姆级零代码部署教程
  • 为编程助手 Claude Code 配置 Taotoken 作为后端模型服务提供方
  • VoXtream2流式TTS架构与动态语速控制技术解析
  • ARM SVE2指令集SQSHL:饱和移位原理与应用