Python实现K-means聚类过程可视化

一、K-means聚类的定义

机器学习算法可大致分为监督学习(Supervised learning)和无监督学习(Unsupervised learning),当然了,还有半监督学习、弱监督学习等等,这里不展开介绍。

监督学习常用于分类和预测,是让计算机去学习已经创建好的分类模型,使分类(预测)结果更好的接近所给目标值,从而对未来数据进行更好的分类和预测。因此,数据集中的所有变量被分为特征和目标,对应模型的输入和输出;数据集被分为训练集和测试集,分别用于训练模型和模型测试与评估。常见的监督学习算法有Regression(回归)、KNN和SVM(分类)。

无监督学习常用于聚类——输入数据没有标记,也没有确定的结果,而是通过样本间的相似性对数据集进行聚类,使**类内差距最小化,类间差距最大化**。无监督学习的目标不是告诉计算机怎么做,而是让它自己去学习怎样做事情,去分析数据集本身。常用的无监督学习算法有K-means、 PCA(Principle Component Analysis)。

聚类算法又叫做“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。比如在商业中,如果手头有大量的当前和潜在客户的信息,可以使用聚类将客户划分为若干组,以便进一步分析和开展营销活动。再比如,聚类可以用于降维和矢量量化,可以将高维特征压缩到一列当中,常常用于图像、声音和视频等非结构化数据,可以大幅度压缩数据量。

作为聚类算法的典型代表,K-Means可以说是最简单的聚类算法,那它的聚类工作原理是什么呢?


给定一组样本,需要将该组样本的数据划分为k个类别,具体过程总结如下:

1、随机初始化k个聚类中心

2、计算每个样本与每一个初始聚类中心的**欧氏距离**,即评估二者之间的相似程度

dik=∑jn(xj(i)−cj(k))2

其中, xj(i) 表示第 i 个样本的第 j 个特征值, cj(k) 表示第 k 个中心的第 j 个“特征值”, dik 表示第 i 个样本与第 k 个中心的距离。

3、分析每一个样本与各个聚类中心的距离,根据距离的远近来划分样本的类别

4、等所有样本类别划分完毕以后,再次计算聚类中心的位置

5、重复执行2、3、4步骤,直至聚类中心不再变化或者变化范围在指定的区间内即可停止


二、举例分析

可能大家看着上面的描述有点迷糊,没有关系,我在这里举一个具体的例子,同时也会提供相应的数据和代码辅助大家理解。

假设我现在拥有80个样本,每个样本包含2个特征值。

后续代码中调用的文档就是这个

现在,我想把这些样本划分为4个类别,因此,我们令 k=4 。如果我们对样本的结构进行一个简单的划分:

(1)将所有的样本视为一个**二维数组**,其shape为 (80,2),即80行2列;
(2)将4个中心作为一个二维数组,其shape为 (4,2);
(3)完成第2步的距离计算以后,会得到每一个样本与4个初始聚类中心的距离,其shape应为 (80,4) ,此时对每一行求最小值的索引就可算出一个样本应分为哪一类。

numpy没有现成的函数可以一步到位的实现流程(3),所以我们需要使用自定义的函数。


三、知识点补充

在正式讲解源代码前,需要大家有一些知识积累,不明白的地方可以点开对应的位置补充学习。

3-1:class关键字

当我们在Python中使用class关键字定义一个类时,我们可以创建一个对象的蓝图,该对象可以包含属性和方法。类是面向对象编程的基本概念之一,它允许我们将相关的数据和功能组织在一起。

下面是一个简单的示例,展示了如何定义一个类并使用它:

class MyClass: def __init__(self, name): self.name = name def say_hello(self): print(f"Hello, {self.name}!") # 创建一个类的实例 obj = MyClass("Alice") # 调用对象的方法 obj.say_hello() # 输出: Hello, Alice!

让我们逐步解释上述示例中的不同部分:

  1. class MyClass:这是类的定义。类名通常使用驼峰命名法,首字母大写。
  2. def __init__(self, name)::这是一个特殊的方法,称为构造方法或初始化方法。它在创建类的实例时自动调用,并用于初始化对象的属性。self参数指向当前实例对象,name参数是我们传递给构造方法的值。
  3. self.name = name:这是在构造方法中定义的实例变量。self表示当前实例对象,name是构造方法接收的参数,我们将其赋值给实例变量name
  4. def say_hello(self)::这是类中的另一个方法,它可以通过类的实例调用。self参数是必需的,它指向调用该方法的实例对象。
  5. print(f"Hello, {self.name}!"):这是say_hello方法的实现,它使用实例变量name来打印一条问候语。
  6. obj = MyClass("Alice"):这是**创建类的实例**的语法。我们将类名后面加上括号,并传递构造方法所需的参数。
  7. obj.say_hello():这是调用对象的方法的语法。我们使用点号操作符(.)访问对象的方法,并在括号中传递所需的参数(如果有)。

上述示例只是一个简单的类的用法示例。在实际应用中,类可以包含更多的属性和方法,以及其他特性,如继承、类变量、静态方法等。类的使用可以帮助我们更好地组织和管理代码,提高代码的可重用性和可维护性。

补充

在Python中,f是格式化字符串字面值(Formatted String Literal)的前缀。通过在字符串前面加上f前缀,我们可以在字符串中嵌入表达式,并使用花括号({})将表达式括起来

在上述例子中,f"Hello, {self.name}!"是一个格式化字符串,其中{self.name}是一个表达式,用于引用类的实例变量name的值。在运行时,表达式会被替换为对应的值,然后生成最终的字符串。


3-2:matplotlib.pyplot

import matplotlib.pyplot as plt 是一条Python导入语句,用于导入 matplotlib.pyplot 模块并将其命名为 plt,以便在代码中更方便地使用。

matplotlib.pyplotmatplotlib 库中的一个子模块,它提供了一组用于创建图形和绘制图表的函数。通过导入 matplotlib.pyplot,我们可以使用其中的函数来创建、配置和显示图形。

一般来说,当我们在Python中进行数据可视化时,常常会使用 matplotlib.pyplot 模块。它提供了丰富的绘图函数,可以**绘制折线图、散点图、柱状图、饼图等多种类型的图表**。

导入语句 import matplotlib.pyplot as plt 的作用是将 matplotlib.pyplot 模块导入到当前的命名空间中,并将其命名为 plt,这样我们就可以使用 plt 来调用 matplotlib.pyplot 模块中的函数,而不需要每次都写完整的模块名。

例如,我们可以使用 plt.plot() 函数来绘制折线图,使用 plt.scatter() 函数来绘制散点图,使用 plt.xlabel() 函数来设置 x 轴标签等等。

下面是一个简单的示例,展示了如何使用 matplotlib.pyplot 绘制一个简单的折线图:

import matplotlib.pyplot as plt # x 和 y 数据 x = [1, 2, 3, 4, 5] y = [2, 4, 6, 8, 10] # 绘制折线图 plt.plot(x, y) # 设置标题和轴标签 plt.title("My First Plot") plt.xlabel("X") plt.ylabel("Y") # 显示图形 plt.show()

稍微扩展一下,如果此时我想把生成的图片保存到指定的地址,则需要借助matplotlib.pyplot模块中的另一个函数——savefig()。

假设我现在想把图片保存到桌面的指定位置,则完整代码如下:

import matplotlib.pyplot as plt # x 和 y 数据 x = [1, 2, 3, 4, 5] y = [2, 4, 6, 8, 10] # 绘制折线图 plt.plot(x, y) # 设置标题和轴标签 plt.title("My First Plot") plt.xlabel("X") plt.ylabel("Y") #保存图片 plt.savefig("C:\\Users\\LXLTX\\Desktop\\.ipynb_checkpoints\\1.png") # 显示图形 plt.show()

这里要强调一下路径,系统自动提供的路径是\,要想正确运行,需要\\。没看明白啥意思的,自己回去看图对比一下就明白了,新手这里很容易犯错!

在上述代码中,savefig() 函数的参数是一个字符串,【所以用双引号框起来】表示保存文件的路径和文件名。你需要将 "/path/to/save/figure.png" 替换为你想要保存的实际路径和文件名。

当你运行这段代码时,savefig() 函数将会将当前图形保存为指定路径下的一个名为 figure.png 的文件。你可以根据需要将文件名更改为其他名称或更改保存的文件格式(如 .jpg.svg 等)。

注意,savefig() 函数应该在 show() 函数之前调用,因为 show() 函数会清空当前图形。所以确保在保存图形之前先调用 savefig(),然后再调用 show() 来显示图形。


四、源代码分析

正式开始源代码分析前,有两点说明一下:

  1. 我认为比较难理解的,都在第三部分进行了分析,如果看的不明白,可以倒回去仔细看看前面的内容。
  2. 代码总共分为三个部分——导入包、定义类、主程序。

4-1:import

import numpy as np import matplotlib import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation

这些是导入所需的库和模块。numpy用于处理数值计算,matplotlib用于绘图,FuncAnimation是matplotlib.animation模块中用于创建动画的类。


4-2:初始化

class KMeans(object): def __init__(self, data): ''' data: 要分类的数据,二维数组,每一行是一个样本,列数为样本特征数 ''' self.data = data self.calc_classes = np.frompyfunc( self.calc_distance, data.shape[1], 1) self.fig, self.ax = plt.subplots()

定义了一个名为KMeans的类,用于执行 K-Means 聚类算法。在初始化方法__init__中,将传入的数据保存到self.data中,calc_classes是一个自定义的函数,用于将所有样本进行分类。self.figself.ax是用于绘图的FigureAxes对象。

np.frompyfunc 是 NumPy 提供的一个函数,用于将一个 Python 函数转换为一个通用的 NumPy 函数。在这段代码中,self.calc_distance 是一个自定义的函数,用于计算单个样本与每个中心的距离,并将其归类到最近的一类。np.frompyfunc 的作用是将 self.calc_distance 转换为一个通用的函数对象,可以对数组中的每个元素进行计算

具体来说,np.frompyfunc 的参数包括三个:

  • 第一个参数是要转换的函数,即 self.calc_distance
  • 第二个参数是函数的输入参数个数,即 data.shape[1],表示输入参数的个数与数据的特征数相同
  • 第三个参数是函数的输出参数个数,即 1,表示函数的返回值为一个标量。

通过这个转换,self.calc_classes 成为了一个能够对数组中的每个元素进行计算的函数。在后续的代码中,self.calc_classes 被用于对数据进行分类操作,将每个样本归类到最近的一类。


self.fig, self.ax = plt.subplots()怎么理解?

plt.subplots() 是 Matplotlib 提供的一个函数,用于创建一个新的图形(Figure)和一个或多个子图(Axes)。在这段代码中,self.figself.ax 是通过解构赋值的方式将 plt.subplots() 的返回值分别赋给了 self.figself.ax

具体来说,plt.subplots() 的返回值是一个包含图形和子图对象的元组。self.fig 是图形对象(Figure),可以理解为整个绘图窗口,而 self.ax 是子图对象(Axes),可以理解为图形中的一个绘图区域。通过这个赋值操作,我们可以在后续的代码中使用 self.figself.ax 对图形和子图进行操作,例如设置图形的标题、坐标轴标签、绘制数据等。


4-3:距离

def calc_distance(self, *features): ''' 计算单个样本与每个中心的距离,然后将其归于最近的一类 features: 样本的特征值 返回样本新的类别 ''' x = np.array(features) return np.argmin(np.square(self.center - x).sum(axis=1))

calc_distance方法用于计算单个样本与每个中心的距离,并将其归类到最近的一类。它接收样本的特征值作为参数,并返回样本新的类别。

def calc_distance(self, *features):的*是什么意思?

在函数定义中,* 是一个特殊的符号,表示参数的可变数量。在这段代码中,def calc_distance(self, *features): 中的 *features 表示可以接受任意数量的参数,并将这些参数作为一个元组传递给函数体内部

具体来说,当调用 calc_distance 函数时,可以传递任意数量的参数给 calc_distance,这些参数会被收集到 features 这个元组中。在函数体内部,可以通过遍历 features 元组来访问传递的参数值。

这种使用 * 收集参数的方式称为可变数量参数(Variable-length Arguments),也被称为参数解包(Argument Unpacking)。它允许函数接受不确定数量的参数,使函数更加灵活。在这段代码中,*features 的作用是接受样本的特征值作为参数,并将它们打包成一个元组供后续计算距离使用。


x = np.array(features)

这行代码将 features 元组转换为 NumPy 数组。在代码中,np.array() 是 NumPy 库提供的一个函数,用于将输入数据转换为 NumPy 数组

具体来说,x = np.array(features)features 元组转换为一个 NumPy 数组,并将结果赋值给变量 x。这样做的目的是为了方便后续对数据进行计算和处理,因为 NumPy 数组提供了许多方便的函数和方法来操作数组数据。

通过将数据转换为 NumPy 数组,可以利用 NumPy 提供的广播(Broadcasting)功能,以及各种数学和统计函数,更有效地进行数据处理和计算。在这段代码中,x 数组将被用于计算样本之间的距离。


return np.argmin(np.square(self.center - x).sum(axis=1))

这行代码使用 NumPy 来计算样本与聚类中心之间的距离,并返回距离最小的聚类中心的索引。

让我们逐步解释这行代码的功能:

  1. self.center 是一个 NumPy 数组,表示聚类的中心点。假设它的形状为 (k, n),其中 k 是聚类中心的数量,n 是特征的数量。
  2. x 是一个 NumPy 数组,表示一个样本的特征值。假设它的形状为 (n,),即一个一维数组。
  3. self.center - x 执行了数组的减法操作,将样本特征值与每个聚类中心的特征值进行逐元素的减法运算。这将得到一个形状为 (k, n) 的数组,其中每一行表示一个聚类中心与样本之间的差值。
  4. np.square(self.center - x) 对差值数组中的每个元素进行平方运算,得到一个形状相同的数组,表示每个差值的平方。
  5. .sum(axis=1) 对每一行的元素进行求和操作,得到一个形状为 (k,) 的一维数组,其中每个元素表示对应聚类中心与样本之间的距离的平方和。
  6. np.argmin() 函数返回数组中最小元素的索引。通过对距离的平方和数组调用 np.argmin(),可以找到距离最小的聚类中心的索引。

因此,整个表达式 np.argmin(np.square(self.center - x).sum(axis=1)) 的作用是找到样本与聚类中心之间距离最小的聚类中心的索引,并将该索引作为函数的返回值。


4-4:聚类

def clustering(self, k): ''' k: 要聚类的数量 ''' self.k = k self.sizes = np.linspace(40, 100, num=k) choices = np.random.randint(0, self.data.shape[0], size=k) self.center = np.copy(self.data[choices]) anim = FuncAnimation(self.fig, func=self.update, frames=np.arange(8), init_func=self.setup, interval=1000) anim.save('clustering.gif', dpi=80, writer='pillow')

clustering方法用于执行 K-Means 聚类过程。它接收一个参数k,表示要聚类的数量。在该方法中,首先设置了聚类的数量self.k,然后创建了一个等差数列self.sizes,用于控制绘图中点的大小。接着,从数据中随机选择k行作为初始中心点,并将其保存到self.center中。最后,使用FuncAnimation创建动画对象anim,并将动画保存为 GIF 格式。

self.sizes = np.linspace(40, 100, num=k)

这行代码使用 NumPy 的 linspace() 函数生成一个等差数列,用于初始化聚类的大小(即每个聚类中样本的数量)。

让我们逐步解释这行代码的功能:

  1. k 是聚类中心的数量,表示要将数据集分成多少个聚类。
  2. np.linspace(start, stop, num) 是 NumPy 提供的一个函数,用于生成一个等差数列。它接受三个参数:
  • start:数列的起始值,这里是 40。
  • stop:数列的结束值,这里是 100。
  • num:数列中的元素数量,这里是 k,即聚类中心的数量。

self.sizes 是一个 NumPy 数组,用于存储每个聚类的大小。通过调用 np.linspace(40, 100, num=k),将生成的等差数列赋值给 self.sizes

这行代码的作用是根据聚类中心的数量 k,生成一个等差数列,用于初始化每个聚类的大小。在这个例子中,数列的起始值是 40,结束值是 100,且数列中的元素数量与聚类中心的数量相等。


choices = np.random.randint(0, self.data.shape[0], size=k)

这行代码使用 NumPy 的 random.randint() 函数生成一个包含 k 个随机整数的数组,用于初始化聚类的初始中心点的选择。

让我们逐步解释这行代码的功能:

  1. self.data 是一个 NumPy 数组,表示输入的数据集。假设它的形状为 (n_samples, n_features),其中 n_samples 是样本的数量,n_features 是特征的数量。
  2. np.random.randint(low, high, size) 是 NumPy 提供的一个函数,用于生成指定范围内的随机整数。它接受三个参数:
  • low:随机整数的最小值(包含在范围内),这里是 0。
  • high:随机整数的最大值(不包含在范围内),这里是 self.data.shape[0],即数据集中样本的数量。
  • size:生成的随机整数的数量,这里是 k,即聚类中心的数量。

choices 是一个 NumPy 数组,用于存储随机选择的初始中心点的索引。通过调用 np.random.randint(0, self.data.shape[0], size=k),将生成的随机整数数组赋值给 choices

这行代码的作用是根据数据集中样本的数量,生成一个包含 k 个随机整数的数组,用于初始化聚类的初始中心点的选择。随机整数的范围是从 0(包含)到数据集中样本的数量(不包含),且生成的随机整数的数量与聚类中心的数量相等。


self.center = np.copy(self.data[choices])

这行代码使用 NumPy 的 copy() 函数创建了一个数据集的副本,并将副本中的特定样本作为聚类的初始中心点。

让我们逐步解释这行代码的功能:

  1. self.data 是一个 NumPy 数组,表示输入的数据集。假设它的形状为 (n_samples, n_features),其中 n_samples 是样本的数量,n_features 是特征的数量。
  2. choices 是一个包含 k 个随机整数的数组,表示从数据集中选择的聚类初始中心点的索引。
  3. self.data[choices] 表示从数据集中选择了 choices 数组中指定的索引对应的样本。这将返回一个形状为 (k, n_features) 的子数组,其中包含了选定的聚类初始中心点的特征值。
  4. np.copy() 是 NumPy 提供的一个函数,用于创建数组的副本。通过调用 np.copy(self.data[choices]),将选定的聚类初始中心点的子数组创建为一个新的副本。
  5. self.center 是一个 NumPy 数组,用于存储聚类的中心点。通过将选定的聚类初始中心点的副本赋值给 self.center,将初始化聚类的中心点。

这行代码的作用是从数据集中选择指定索引的样本,并将其作为聚类的初始中心点。它首先创建了一个数据集的副本,然后从副本中选择特定的样本作为聚类的初始中心点,并将其存储在 self.center 数组中。


4-5:动画初始化

def setup(self, colors=['r', 'g', 'b', 'k']): ''' 动画初始化函数 ''' cs = self.get_classified_sample() tmp = [] for i in np.arange(self.k): tmp.append(self.ax.scatter(cs[i][:,0], cs[i][:,1], c=colors[i], animated=True)) for i in np.arange(self.k): tmp.append(self.ax.scatter(self.center[i,0], self.center[i,1], c=colors[i], s=150, marker='x', animated=True)) self.sc = tuple(tmp) for i in np.arange(self.k): self.center[i,:] = cs[i].mean(axis=0) return self.sc

setup方法是动画的初始化函数,用于设置初始状态。在该方法中,首先调用get_classified_sample方法将所有样本分为k类,并将结果保存到cs中。然后,使用循环绘制已分类的样本和中心点,并将绘图对象保存到tmp列表中。最后,将tmp转换为元组并保存到self.sc中,更新每个簇的中心点,并返回self.sc


4-6:分类

def get_classified_sample(self): ''' 将所有样本分为k类 返回一个列表,列表中的每个元素是被归于同一类的样本 ''' cols = list(self.data.T) self.classes = self.calc_classes(*cols) return [self.data[self.classes==i] for i in np.arange(self.k)]

get_classified_sample方法用于将所有样本分为k类,并返回一个列表,列表中的每个元素是被归类到同一类的样本。它首先将数据的每个特征转置为列表cols,然后使用self.calc_classes方法计算所有样本的类别,并将结果保存到self.classes中。最后,根据类别将样本分组并返回结果。

cols = list(self.data.T)

这行代码将 self.data 中的数据进行转置,并将转置后的结果转换为一个列表。

假设 self.data 是一个二维数组,其中每一行代表一个样本,每一列代表一个特征。通过使用 .T 属性,我们可以获取到 self.data 的转置,即将行和列进行交换。

然后,使用 list() 函数将转置后的结果转换为一个列表。这将创建一个新的列表,其中包含了原始数据集中每个特征的值。


self.classes = self.calc_classes(*cols)

这行代码调用了 self.calc_classes() 方法,并将 cols 列表中的元素作为参数传递给该方法。然后,将返回的结果赋值给了 self.classes 属性。

根据代码中的注释,self.calc_classes() 方法的作用是计算样本的类别标签,即将每个样本分配到最近的聚类中心点所属的类别。

通过将 cols 列表中的元素使用 * 运算符进行解包,将每个特征作为单独的参数传递给 self.calc_classes() 方法。这样做的目的是将每个特征作为独立的维度,以便进行聚类计算。


return [self.data[self.classes==i] for i in np.arange(self.k)]

这行代码使用列表推导式返回一个列表,其中每个元素是一个子集,包含了根据类别标签 self.classes 对原始数据集 self.data 进行分组后的样本。

代码中使用了 NumPy 库的 np.arange() 函数来生成一个包含从 0 到 self.k-1 的整数的数组。这些整数代表聚类的类别标签。

列表推导式中的循环遍历这个整数数组,对于每个类别标签 i,使用布尔索引 self.classes==i 来选择 self.data 中类别为 i 的样本。

具体地,self.classes==i 返回一个布尔数组,其中元素的值为 True 或 False,表示对应位置的样本是否属于类别 i。然后,使用这个布尔数组对 self.data 进行索引,得到属于类别 i 的样本子集。

最终,这个列表推导式将返回一个包含 self.k 个子集的列表,每个子集代表一个聚类类别的样本集合。


4-7:更新

def update(self, j): cs = self.get_classified_sample() for i in np.arange(self.k): self.sc[i].set_offsets(cs[i]) self.sc[i]._sizes[0] = self.sizes[(i+j) % self.k] self.sc[i+self.k].set_offsets(self.center[i]) for i in np.arange(self.k): self.center[i,:] = cs[i].mean(axis=0) return self.sc

update方法是动画的更新函数,每一帧都会调用该函数。在该方法中,首先调用get_classified_sample方法将所有样本重新分组,并将结果保存到cs中。然后,使用循环更新每个簇的点的坐标、大小和中心点的坐标。最后,更新每个簇的中心点,并返回self.sc

for i in np.arange(self.k): self.sc[i].set_offsets(cs[i]) self.sc[i]._sizes[0] = self.sizes[(i+j) % self.k] self.sc[i+self.k].set_offsets(self.center[i])

这段代码是用于更新散点图对象的属性,以便在动画中展示聚类的过程。下面是对每行代码的解释:

  1. self.sc[i].set_offsets(cs[i]): 这行代码用于设置第 i 个散点图对象的位置偏移量(offsets),即将该散点图对象的位置移动到聚类中心点 cs[i] 的位置。cs[i] 是一个包含两个元素的列表,表示聚类中心点的 x 和 y 坐标。
  2. self.sc[i]._sizes[0] = self.sizes[(i+j) % self.k]: 这行代码用于设置第 i 个散点图对象的大小(sizes)。self.sizes 是一个包含 self.k 个元素的列表,表示每个聚类的大小。(i+j) % self.k 的结果是将索引 i+j 循环在 0 到 self.k-1 之间,以实现不同聚类大小的循环变化。
  3. self.sc[i+self.k].set_offsets(self.center[i]): 这行代码用于设置第 i+self.k 个散点图对象的位置偏移量(offsets),即将该散点图对象的位置移动到聚类中心点 self.center[i] 的位置。self.center 是一个包含 self.k 个元素的列表,表示每个聚类的中心点的位置。

通过不断更新散点图对象的位置和大小,动画可以展示聚类过程中样本的移动和聚集情况。


for i in np.arange(self.k): self.center[i,:] = cs[i].mean(axis=0)

这行代码计算每个聚类的中心点,并将结果存储在 self.center 数组中。

对于每个类别标签 ics[i] 是一个包含属于该类别的样本点的数组。mean(axis=0) 是一个 NumPy 数组方法,用于计算沿指定轴的平均值。在这里,axis=0 表示沿着第一个轴(即行)计算平均值,即计算每个特征维度的平均值。

通过将每个聚类的样本点的均值赋值给 self.center[i],就可以更新聚类的中心点位置。


4-8:主程序

if __name__ == "__main__": data = np.loadtxt('C:/Users/LXLTX/Desktop/test.txt') km = KMeans(data) km.clustering(4)

if __name__ == "__main__":条件下,首先从文件中加载数据,并将其保存到data中。然后,创建一个KMeans的实例km,并将数据传递给它。最后,调用km.clustering(4)开始执行 K-Means 聚类过程,并将结果保存为动画。


问题1:if __name__ == “__main__”

if __name__ == "__main__": 是一个常见的 Python 代码约定,用于判断当前模块是否被直接执行,而不是被导入为一个模块。

当一个 Python 文件被直接执行时,Python 解释器会将特殊变量 __name__ 设置为 "__main__",以表示该文件正在作为主程序执行。如果该文件被导入为一个模块,__name__ 的值将是模块的名称。

因此,通过使用 if __name__ == "__main__": 条件语句,我们可以在代码中指定只有当该文件作为主程序执行时才运行特定的代码块。这样做的好处是,我们可以在模块被导入时避免执行一些不必要的代码,或者在模块被导入时执行一些初始化操作。

在你提供的代码中,if __name__ == "__main__": 用于标识以下代码块是主程序的入口点。这意味着只有当该文件作为主程序执行时,才会执行 if __name__ == "__main__": 之后的代码。这样可以确保在其他地方导入该模块时不会立即执行聚类算法,而只有在直接执行该文件时才会执行聚类算法。


问题2:为什么km.clustering()只输入了一个参数

在这段代码中,km.clustering() 方法只接受一个参数是因为在定义 clustering() 方法时,第一个参数 self 是隐式传递的。在调用方法时,不需要显式地传递 self 参数,Python 会自动将实例对象 km 作为 self 参数传递给 clustering() 方法。

因此,km.clustering(4) 中的 4 是作为 clustering() 方法的第二个参数传递的,用于指定聚类的数量。


问题3:为什么要先创建一个实例km

在这段代码中,首先创建了一个 KMeans 类的实例 km。这是因为 KMeans 类是一个定义了聚类算法的模板或蓝图,而实例化 km 对象则是为了在具体的数据上应用该聚类算法。

通过创建 km 实例,我们可以使用 km 对象调用类中定义的方法,例如 clustering() 方法。这样做的好处是可以在不同的实例之间保持数据和状态的隔离,每个实例都可以独立地执行聚类操作,并且它们之间的结果不会相互干扰。

另外,通过实例化 km 对象,我们可以将数据传递给 KMeans 类的构造函数,以便在类内部进行初始化和设置。在这个例子中,data 是从文件中加载的数据,通过将其传递给 KMeans 构造函数,可以在 KMeans 类内部使用该数据进行聚类计算。

因此,创建 km 实例是为了在具体数据上应用聚类算法,并且可以通过实例对象调用类的方法进行操作和访问结果。


呼,这篇文章写了好几个小时,应该找遍全网都没有这么仔细的了,希望大家能有所收获!

k-means是一种常见的聚类算法,它将数据分成k个簇,每个簇内的数据点相似度较高,而不同簇之间的相似度较低。它的基本思想是:将数据点分成k个簇,使得每个簇内的点与该簇内所有点的距离之和最小。 下面是一个简单的k-means聚类算法的实例: 1. 首先,随机选择k个数据点作为初始簇中心点。 2. 对于每个数据点,计算其与每个簇中心点之间的距离,并将其分配到距离最近的簇中。 3. 对于每个簇,重新计算该簇的中心点,即将该簇中所有数据点的坐标取平均值作为新的中心点。 4. 重复步骤2和步骤3,直到簇中心点不再发生变化或达到最大迭代次数。 下面是Python代码实现一个简单的k-means聚类算法: ```python import numpy as np import pandas as pd import matplotlib.pyplot as plt # 随机生成数据 data = pd.DataFrame(np.random.randn(100, 2)) # 设置簇数K K = 3 # 随机选择K个数据点作为初始簇中心点 centroids = data.sample(n=K) # 初始化簇分配结果 clusters = pd.Series(np.zeros(len(data))) # 设置最大迭代次数 max_iter = 10 # 迭代计算 for i in range(max_iter): # 对于每个数据点,计算其与每个簇中心点之间的距离,并将其分配到距离最近的簇中 for j in range(len(data)): distances = [np.linalg.norm(data.iloc[j] - centroids[k]) for k in range(K)] cluster = np.argmin(distances) clusters[j] = cluster # 对于每个簇,重新计算该簇的中心点 for k in range(K): centroids.iloc[k] = data[clusters == k].mean() # 可视化结果 colors = ['red', 'green', 'blue'] for k in range(K): plt.scatter(data[clusters == k][0], data[clusters == k][1], c=colors[k]) plt.scatter(centroids[0], centroids[1], c='black', marker='x') plt.show() ``` 这段代码首先随机生成了100个二维数据点,然后随机选择3个数据点作为初始簇中心点。接着,它通过迭代计算不断更新簇分配结果和簇中心点,直到簇中心点不再发生变化或达到最大迭代次数。最后,它将结果可视化出来。 运行结果可能会每次都不同,但一般情况下,我们会得到三个不同颜色的簇中心点和对应的数据点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值