十分钟快速讲明白K均值聚类算法

article/2025/9/1 12:48:08

K均值聚类算法

无监督学习

在一个典型的监督学习中,我们有一个有标签的训练集,我们的目标是找到能够区分正样本和负样本的决策边界,在这里的监督学习中,我们有一系列标签,我们需要据此拟合一个假设函数。

与此不同的是,在非监督学习中,我们的数据没有附带任何标签,我们拿到的数据就是这样的:

在这里我们有一系列点,却没有标签。

因此,我们的训练集可以写成只有 x ( 1 ) , x ( 2 ) ⋯ x ( m ) x^{(1)}\;,\;x^{(2)}\;\cdots\;x^{(m)} x(1),x(2)x(m)。我们没有任何标签 y y y。因此,图上画的这些点没有标签信息。

也就是说,在非监督学习中,我们需要将一系列无标签的训练数据,输入到一个算法中,然后我们告诉这个算法,快去为我们找找这个数据的内在结构给定数据。

我们可能需要某种算法帮助我们寻找一种结构。

图上的数据看起来可以分成两个分开的点集(称为簇),一个能够找到我圈出的这些点集的算法,就被称为聚类算法。

当然,此后我们还将提到其他类型的非监督学习算法,它们可以为我们找到其他类型的结构或者其他的一些模式,而不只是簇。

我们将先介绍聚类算法。

K-均值算法

K-均值是最普及的聚类算法,算法接受一个未标记的数据集,然后将数据聚类成不同的组。

假设我有一个无标签的数据集,并且我想将其分成两个簇,现在我利用K均值算法。具体操作如下:

第一步是随机生成两点,这两点就叫做聚类中心,也就是图上两个叉:

我选取两个点的原因是因为我想将我的数据聚成两类。

K-均值算法是一个迭代算法,它会去做两件事情:第一个是簇分配,第二个是移动聚类中心。

K均值算法中,每次内循环的第一步就是要进行簇分配。也就是说,我要遍历每个样本,也就是图上的每个绿色的点,然后根据每一个点是与红色聚类中心更近还是与蓝色聚类中心更近来将每个数据点分配给两个聚类中心之一。

具体来说,就是要遍历我们的数据集,然后将每个点染成红色或者蓝色,这取决于某个点是离红色更近还是蓝色更近。

内循环的第二步是移动聚类中心。

我们要做的是将两个聚类中心,也就是上面红蓝两个叉将其移动到同色的点的均值处。因此我们要做的是找出所有红色的点然后计算它们的均值,也就是所有红色的点的平均位置,然后将红色聚类中心移动到这里。蓝色聚类点实行同样的操作。

接下来我们重复上面的步骤,直到最后完成要求:

K-均值是一个迭代算法,假设我们想要将数据聚类成n个组,其方法为:

1.首先选择K个随机的点,称为聚类中心(cluster centroids)

2.对于数据集中的每一个数据,按照距离K个中心点的距离,将其与距离最近的中心点关联起来,与同一个中心点关联的所有点聚成一类。

3.计算每一个组的平均值,将该组所关联的中心点移动到平均值的位置

4.重复步骤2-4直至中心点不再变化

我们用 μ 1 , μ 2 ⋯ μ k \mu^1\;,\;\mu^2\;\cdots\;\mu^k μ1,μ2μk来表示聚类中心,用 c ( 1 ) , c ( 2 ) , ⋯ , c ( m ) c^{(1)}\;,\;c^{(2)}\;,\;\cdots\;,\;c^{(m)} c(1),c(2),,c(m)来存储与第 i i i个实例数据最近的聚类中心的索引,K-均值算法的伪代码如下:

x 
Repeat{for i = 1 to m: c(i):=index(from 1 to k) of cluster centroid
closest to x(i) 
for k = 1 to K: μk := average (mean) of points assignedto cluster k ​
}

算法分为两个步骤,第一个for循环是赋值步骤,即:对于每一个样例 i i i,计算其应该属于的类。第二个for循环是聚类中心的移动,即:对于每一个类K,重新计算该类的质心。

K-均值算法也可以很便利地用于将数据分为许多不同组,即使在没有非常明显区分的组群的情况下也可以。下图所示的数据集包含身高和体重两项特征构成的,利用K-均值算法将数据分为三类,用于帮助确定将要生产的T-恤衫的三种尺寸。

优化目标

K-均值最小化问题,是要最小化所有的数据点与其所关联的聚类中心点之间的距离之和,因此
K-均值的代价函数(又称畸变函数 Distortion function)为:
J ( c ( 1 ) , c ( 2 ) , ⋯ , , c ( m ) , μ 1 , μ 2 , ⋯ , μ K ) = 1 m ∑ i = 1 m ∣ ∣ X ( i ) − μ c ( i ) ∣ ∣ 2 J(c^{(1)}\;,\;c^{(2)}\;,\;\cdots\;,\;,c^{(m)}\;,\;\mu_1\;,\;\mu_2\;,\;\cdots\;,\;\mu_K)=\frac{1}{m}\sum_{i=1}^{m}||X^{(i)}-\mu_{c^{(i)}}||^2 J(c(1),c(2),,,c(m),μ1,μ2,,μK)=m1i=1m∣∣X(i)μc(i)2

其中 μ c ( i ) \mu_{c^{(i)}} μc(i)代表与 x ( i ) x^{(i)} x(i)最近的聚类中心点。
我们的的优化目标便是找出使得代价函数最小的 c ( 1 ) , c ( 2 ) , ⋯ , , c ( m ) c^{(1)}\;,\;c^{(2)}\;,\;\cdots\;,\;,c^{(m)} c(1),c(2),,,c(m) μ 1 , μ 2 , ⋯ , μ K \mu_1\;,\;\mu_2\;,\;\cdots\;,\;\mu_K μ1,μ2,,μK:
min ⁡ c ( 1 ) , ⋯ , c ( m ) , μ 1 , ⋯ , μ K J ( c ( 1 ) , c ( 2 ) , ⋯ , , c ( m ) , μ 1 , μ 2 , ⋯ , μ K ) \min_{c^{(1)}\;,\;\cdots\;,\;c^{(m)}\;,\;\mu_1\;,\;\cdots\;,\;\mu_K}\;J(c^{(1)}\;,\;c^{(2)}\;,\;\cdots\;,\;,c^{(m)}\;,\;\mu_1\;,\;\mu_2\;,\;\cdots\;,\;\mu_K) c(1),,c(m),μ1,,μKminJ(c(1),c(2),,,c(m),μ1,μ2,,μK)

回顾刚才给出的:
K-均值迭代算法,我们知道,第一个循环是用于减小 c ( i ) c^{(i)} c(i)引起的代价,而第二个循环则是用于减小 μ i \mu_i μi引起的代价。迭代的过程一定会是每一次迭代都在减小代价函数,不然便是出现了错误。

随机初始化

接下来讨论一下如何初始化K均值聚类算法,更重要的是,这将引导我们如何使算法避免局部最优解。

这是我们的K均值算法:

其中我们没有讨论的就是最开始的如何初始化聚类中心,有几种不同的方法可以用来随机初始化聚类中心。

但是,事实证明,有一种方法比其他大多数可能考虑到的方法更值得推荐。

我们通常是这样初始化聚类中心的:
(1)我们应该选择 K < m K<m K<m,即聚类中心点的个数要小于所有训练实例的数量
(2)随机选择K个训练实例,然后令K个聚类中心分别与这K个训练实例相等

具体来说,K均值算法可能会落在局部最优,这取决于初始化的情况。我们给出下面的例子:

我们可能的分类如下:


为了解决这个问题,我们通常需要多次运行K均值算法,每一次都重新进行随机初始化,最后再比较多次运行K均值的结果,选择代价函数最小的结果。

以下是具体的做法:

这种方法在K较小的时候还是可行的,但是如果K值较大,这么做也是不会有明显的改善。

选择聚类数

如何去选择聚类数量或者说如何去选择参数K的值?

说实话,这个问题没有什么好的答案,也没有能自动处理的方法。

目前为止,用来决定聚类数量最常用的方法仍然是通过观察可视化的图或者通过观察聚类算法的输出等等。

我们举上面的例子来看,对于上面的数据集,我们该如何选择聚类中心的数目,是选择K=2还是选择K=4?

当人们在讨论选择聚类数量的方法时,有一个可能会谈及的方法叫作“肘部法则”。

关于“肘部法则”,我们所需要做的是改变K值,也就是聚类类别数目的总数。我们用一个聚类来运行K均值聚类方法。这就意味着,所有的数据都会分到一个聚类里,然后计算成本函数或者计算畸变函数J。K代表聚类数字。

我们可能会得到一条类似于这样的曲线。像一个人的肘部。这就是“肘部法则”所做的,让我们来看这样一个图,看起来就好像有一个很清楚的肘在那儿。好像人的手臂,如果你伸出你的胳膊,那么这就是你的肩关节、肘关节、手。这就是“肘部法则”。你会发现这种模式,它的畸变值会迅速下降,从1到2,从2到3之后,你会在3的时候达到一个肘点。在此之后,畸变值就下降的非常慢,看起来就像使用3个聚类来进行聚类是正确的,这是因为那个点是曲线的肘点,畸变值下降得很快,K=3之后就下降得很慢,那么我们就选K=3。当你应用“肘部法则”的时候,如果你得到了一个像上面这样的图,那么这将是一种用来选择聚类个数的合理方法。

但是我们有时会得到一条模糊的曲线,如下图所示:

看上去畸变值是连续下降的,可能是3,也可能是4。

对于“肘部法则”快速的小结:它是一个值得尝试的方法,但是不能期望它能够解决任何问题。

最后还有一种选择K值的思路:

通常人们使用K均值聚类是为了得到一些聚类用于后面的目的或者说是下游目的。也许你想用K均值来做市场分割,就像我们之前说的T恤尺寸的例子。

比如我是否需要选择3种T恤尺寸。因此我选择K=3,可能有小号,中号和大号三种尺寸的T恤,或者我们可以选择K=5,那么我们就可能有特小号、小号、中号、大号和特大号尺寸的T恤。所以我们可以有3种或者5种。

如果我们用K=3来进行分类,可以得到下面的结果:

然而如果我们用K=5来跑K均值算法:

综上所述:没有所谓最好的选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的。选择的时候思考我们运用K-均值算法聚类的动机是什么,然后选择能最好服务于该目的标聚类数。


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

相关文章

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

k-means是一个十分简单的聚类算法&#xff0c;它的思路非常简明清晰&#xff0c;所以经常拿来当做教学。下面就来讲述一下这个模型的细节操作。 内容 模型原理模型收敛过程模型聚类个数模型局限 1. 模型原理 将某一些数据分为不同的类别&#xff0c;在相同的类别中数据之间的…

c++函数调用

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

c语言中函数调用的过程

一.程序在内存中的占用。 要学习C语言中函数调用的过程&#xff0c;必须要知道程序在内存中各个区域的分布。 C语言的函数调用的过程主要分布在栈中&#xff0c;所以我们今天主要研究栈。 二.几个基本的汇编指令。 call&#xff1a;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 …是实参列表。实参可以是常数、变量、表达…