深入浅出聚类算法之k-means算法

article/2025/9/1 12:45:14

k-means是一个十分简单的聚类算法,它的思路非常简明清晰,所以经常拿来当做教学。下面就来讲述一下这个模型的细节操作。

内容

  • 模型原理
  • 模型收敛过程
  • 模型聚类个数
  • 模型局限

1. 模型原理
将某一些数据分为不同的类别,在相同的类别中数据之间的距离应该都很近,也就是说离得越近的数据应该越相似,再进一步说明,数据之间的相似度与它们之间的欧式距离成反比。这就是k-means模型的假设。
有了这个假设,我们对将数据分为不同的类别的算法就更明确了,尽可能将离得近的数据划分为一个类别。不妨假设需要将数据{xi}聚为k类,经过聚类之后每个数据所属的类别为{ti},而这k个聚类的中心为{μi}。于是定义如下的损失函数:
这里写图片描述
k-means模型的目的是找寻最佳的{ti},使损失函数最小,之后就可以对聚类中心{μi}直接计算了。由此可见,它既是聚类的最终结果,也是需要估算的模型参数。


2. 模型收敛过程
在k-means的损失函数中存在两个未知的参数:一个是每个数据所属的类别{ti};一个是每个聚类的中心{μi}。这两个未知的参数是相互依存的:如果知道每个数据的所属类别,那么类别的所有数据的平均值就是这个类别的中心;如果知道每个类别的中心,那么就是计算数据与中心的距离,再根据距离的大小可以推断出数据属于哪一个类别。
根据这个思路,我们可以使用***EM算法***(最大期望算法)来估计模型的参数。具体操作如下:

  1. 首先随机生成k个聚类中心点
  2. 根据聚类中心点,将数据分为k类。分类的原则是数据离哪个中心点近就将它分为哪一类别。
  3. 再根据分好的类别的数据,重新计算聚类的类别中心点。
  4. 不断的重复2和3步,直到中心点不再变化。如下图所示:
    这里写图片描述

3. 模型聚类个数
对于非监督学习,训练数据是没有标注变量的。那么除了极少数的情况,我们都是无从知道数据应该被分为几类。k-means算法首先是随机产生几个聚类中心点,如果聚类中心点多了,会造成过拟合;如果聚类中心点少了,会造成欠拟合,所以聚类中心点是很关键的,在这里使用误差平方的变化和来评价模型预测结果好不好。当聚类个数小于真实值时,误差平方和会下降的很快;当聚类个数超过真实值时,误差平方和虽然会继续下降,但是下降的速度会缓减,而这个转折点就是最佳的聚类个数了。
这里写图片描述

这里写图片描述


4. 模型局限
k-means是非常简单的模型,但是它也有两个明显的缺陷,或者说它有两种运用场景不能使用,第一是非均质的数据,因为,模型使用欧氏距离衡量数据间的相似度,因此它要求数据在各个维度上都是均质。第二是不同类别内部方差不相同。模型假设不同类别的内部方差是大致相等的。
这里写图片描述


下面使用鸢尾花数据集进行实战。
引入包:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import seaborn as sns
%matplotlib inline

观察数据:

data = sns.load_dataset("iris")
data.head()
sns.pairplot(data, hue='species')

这里写图片描述
观察两两变量中聚类个数:

def trainModel(data, clusterNum):"""使用KMeans对数据进行聚类"""# max_iter表示EM算法迭代次数,n_init表示K-means算法迭代次数,algorithm="full"表示使用EM算法。model = KMeans(n_clusters=clusterNum, max_iter=100, n_init=10, algorithm="full")model.fit(data)return modeldef computeSSE(model, data):"""计算聚类结果的误差平方和"""wdist = model.transform(data).min(axis=1)sse = np.sum(wdist ** 2)return sseif __name__ == "__main__":col = [['petal_width', 'sepal_length'], ['petal_width', 'petal_length'], ['petal_width', 'sepal_width'], ['sepal_length', 'petal_length'],
['sepal_length', 'sepal_width'], ['petal_length', 'sepal_width']]for i in range(6):      fig = plt.figure(figsize=(8, 8), dpi=80)ax = fig.add_subplot(3, 2, i+1)sse = []for j in range(2, 6):model = trainModel(data[col[i]], j)sse.append(computeSSE(model, data[col[i]]))ax.plot(range(2,6), sse, 'k--', marker="o",markerfacecolor="r", markeredgecolor="k")ax.set_xticks([1,2,3,4,5,6])title = "clusterNum of %s and %s" % (col[i][0], col[i][1])ax.title.set_text(title)plt.show()

这里写图片描述
通过这个图,我们基本上可以判断出应该分为三类,这也与实际情况是相同的。我们选择一组进行可视化聚类结果。

petal_data = data[['petal_width', 'petal_length']]
model = trainModel(petal_data, 3)
fig = plt.figure(figsize=(6,6), dpi=80)
ax = fig.add_subplot(1,1,1)
colors = ["r", "b", "g"]
ax.scatter(petal_data.petal_width, petal_data.petal_length, c=[colors[i] for i in model.labels_],marker="o", alpha=0.8)
ax.scatter(model.cluster_centers_[:, 0], model.cluster_centers_[:, 1], marker="*", c=colors, edgecolors="white",s=700., linewidths=2)
yLen = petal_data.petal_length.max() - petal_data.petal_length.min()
xLen = petal_data.petal_width.max() - petal_data.petal_width.min()
lens = max(yLen+1, xLen+1) / 2.
ax.set_xlim(petal_data.petal_width.mean()-lens, petal_data.petal_width.mean()+lens)
ax.set_ylim(petal_data.petal_length.mean()-lens, petal_data.petal_length.mean()+lens)
ax.set_ylabel("petal_length")
ax.set_xlabel("petal_width")

这里写图片描述
这个效果是很好的,与实际的情况一致!


http://chatgpt.dhexx.cn/article/Lw2qVACE.shtml

相关文章

c++函数调用

一个函数就是完成一项任务的独立代码块,它以名字作为整个代码块的代表 函数完成任务后,如果有数值需要返回,则在声明函数时,在函数名前写好返回值类型 如果不需要返回任何值,则在函数名前写 void 有时,一个…

c语言中函数调用的过程

一.程序在内存中的占用。 要学习C语言中函数调用的过程,必须要知道程序在内存中各个区域的分布。 C语言的函数调用的过程主要分布在栈中,所以我们今天主要研究栈。 二.几个基本的汇编指令。 call:1.将当前指令的下一条指令的地址保存到栈中…

函数定义和调用

<1>定义函数 定义函数的格式如下&#xff1a; def 函数名(): 代码 demo: 定义一个函数&#xff0c;能够完成打印信息的功能 def printInfo(): print(’------------------------------------’) print(’ 人生苦短&#xff0c;我用Python’) print(’----------------…

Shell函数调用

文章目录 一.函数基本格式二.函数调用2.1函数中调用2.2函数调用函数2.3外部调用2.4案例 三.总结 在shell脚本中&#xff0c;有些命令或者某些操作需要频繁的使用时&#xff0c;每次都重新写太过繁琐&#xff0c;这时我们就可以使用函数&#xff0c;当需要使用时&#xff0c;直接…

类的函数调用

父类和子类的函数调用 1.用指针&#xff08;引用&#xff09;调用函数的时候&#xff0c;被调用的函数取决于指针&#xff08;引用&#xff09;的类型&#xff1b; 2.涉及多态性时&#xff0c;采用虚函数和动态绑定&#xff0c;函数调用在运行时绑定&#xff0c;而非在编译时…

函数调用栈分析

进程在虚拟地址空间的布局 操作系统把磁盘上可执行文件加载到内存运行之前, 需要做很多工作, 其中很重要的一件事就是把可执行文件中的代码, 数据存放到内存 中合适的位置, 并分配和初始化程序运行过程中必须的堆栈, 所有准备工作完成之后操作系统才会调度程序起来运行. 进程…

C语言的函数调用过程

C语言的函数调用过程 先上一段代码 #include<stdio.h> int Add(int x, int y) {int z 0;z x y;return z; } #include <stdio.h> int main() {int a 10;int b 20;int c Add(a, b);return 0; } 123456789101112131415 这个函数计算两个数的值。接下来我们通…

C--函数调用

C函数的调用约定 编译器实现函数调用时所遵循的一系列规则称为函数的“调用约定&#xff08;Calling Convention&#xff09;” 对于C语言来说&#xff0c;运行在X86-64平台上的编译器基本都会根据操作系统的不同选择使用几种常见的调用约定。例如&#xff0c;在wiindows下通常…

C语言 函数调用的过程

例题&#xff1a;求两个整数中的较小者&#xff0c;用函数调用实现。 【代码实现】 int Min(int x, int y) {if (x < y){return x;}elsereturn y; } int main() {int Min(int x, int y);int a, b, c;printf("输入两个要比较的整数&#xff1a;\n");scanf("%…

函数调用的过程分析

一、函数调用机制 局部变量占用的内存是在程序执行过程中”动态”地建立和释放的。这种”动态”是通过栈由系统自动管理进行的。当任何一个函数调用发生时,系统都要作以下工作: 1)建立栈帧空间;2)保护现场: 主调函数运行状态和返回地址入栈&#xff1b;3)为被调函数传递数据(…

C/C++ 函数调用是如何实现的?

一、写在前面的话 C/C 函数调用方式与栈原理是 C/C 开发必须要掌握的基础知识&#xff0c;也是高级技术岗位面试中高频题。我真的真的真的建议无论是使用 C/C 的学生还是广大 C/C 开发者&#xff0c;都该掌握此回答中所介绍的知识。 如果你看不懂接下来第二部分在说什么&#…

函数调用过程

今天突然看到有人私信我说一直没写函数调用过程&#xff08;栈帧的形成和销毁过程&#xff09;这篇博文&#xff0c;赶紧补上。 刚看的栈帧内容时&#xff0c;我很迷惑&#xff0c;我觉得栈帧创建和销毁很麻烦&#xff0c;几句话根本说不完&#xff0c;而且我好像描述不清楚他…

浅谈函数调用!

导语 | 在任意一门编程语言中&#xff0c;函数调用基本上都是非常常见的操作&#xff1b;我们都知道&#xff0c;函数是由调用栈实现的&#xff0c;不同的函数调用会切换上下文&#xff1b;但是&#xff0c;你是否好奇&#xff0c;对于一个函数调用而言&#xff0c;其底层到底…

函数调用流程

函数调用模型 1. 函数调用流程函数调用流程分析函数参数调用代码分析自右向左入栈顺序的优点 2. 调用惯例函数参数的传递顺序和方式栈的维护方式调用管理表 3. 函数变量传递分析分析图 1. 函数调用流程 栈(stack)是现代计算机程序里最为重要的概念之一&#xff0c;几乎每一个程…

函数调用栈

函数调用栈 我们在编程中写的函数&#xff0c;会被编译器编译为机器指令&#xff0c;写入可执行文件&#xff0c;程序执行的时候&#xff0c;会把这个可执行文件加载到内存&#xff0c;在虚拟地址空间中的代码段存放。 如果在一个函数中调用另一个函数&#xff0c;编译器就会…

函数调用和使用

1.函数是什么 函数&#xff08;Function&#xff09;能实现的功能从简单到复杂&#xff0c;各式各样&#xff0c;但其本质是相通的&#xff1a;“喂”给函数一些数据&#xff0c;它就能内部消化&#xff0c;给你“吐”出你想要的东西。 2.定义和调用函数 2.1 定义函数 #…

C语言——函数的调用

函数的调用 传值调用 函数的形参和实参分别占有不同的内存块&#xff0c;对形参的修改不会影响实参。 传址调用 1.传址调用是把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式 2.这种传参方式可以让函数和函数外边的变量建立起真正的联系&#xff0c;也就…

C语言之函数调用

C语言之函数调用 “温故而知新&#xff0c;可以为师矣”&#xff01; 让我们开启函数的道路吧&#xff01; 今天主要讲函数的调用方式&#xff01; 在讲之前&#xff0c;先回顾一下实际参数和形式参数的区别&#xff1b; 1.在定义函数时函数名后面括号中的变量名称为“形式参数…

C语言函数的调用

函数调用&#xff08;Function Call&#xff09;&#xff0c;就是使用已经定义好的函数。函数调用的一般形式为&#xff1a; functionName(param1, param2, param3 ...);functionName 是函数名称&#xff0c;param1, param2, param3 …是实参列表。实参可以是常数、变量、表达…

Windows编程-001

如果建立的是Win32控制台工程&#xff08;入口函数是main函数&#xff09;的话&#xff0c;WinMain函数不能作为入口函数&#xff0c;如果想要解决这个问题的话&#xff0c;可以打开项目属性->链接器->系统->子系统&#xff0c;把子系统对应的“控制台”改为“窗口”。…