目录
一、前言
KMeans算法在众多领域都有广泛应用,因其简单高效而成为数据挖掘和机器学习中进行聚类分析的首选工具之一。它可用于客户细分以理解不同用户群体的行为模式;图像分割来简化图像处理任务;异常检测以识别不符合常规模式的数据点;以及作为推荐系统的一部分,帮助发现用户的偏好模式等。
二、算法简介
KMeans聚类算法是一种无监督学习方法,旨在将数据集划分为K个簇(clusters),使得每个数据点归属于与其最近的簇中心(质心)的那个簇,其目标是最小化各簇内数据点到簇中心的距离平方和。算法通过迭代过程实现这一目标,包括初始化K个质心、分配数据点给最近的质心、基于分配的数据点重新计算质心位置,直至质心不再发生显著变化或达到最大迭代次数。
三、实验任务
今天给大家介绍一个机器学习重要算法——聚类算法,可以把类似特征数据分成相同一类,这里称为簇,方便大家探寻数据之间的联系,以及进行精细的数据分析,一般会结合 matplotlib python绘图库一起使用,帮助大家直观的画出各分类结果的散点图。
这里首先带大家了解一下 KMeans 函数的用法,使用其内置函数帮助我们分析数据,然后带着大家逐步了解其算法内部的原理,自己手写一个 KMeans 算法,这样有助于实现符合我们预期的预测结果。
这里我参考了B站up主的视频,他讲的非常好,要是看了这篇文章还是没懂的话可以看一下up主的原视频,我把链接会放到评论区中,这篇文章结合了我自己的理解,还有对一些难懂的用法结合实例带着大家一起学习。
四、代码解析
4.1 前期准备
下载代码所需要的库,在终端运行以下代码即可
pip install matplotlib
pip install -U scikit-learn
4.2 KMeans函数运用
from sklearn.cluster import KMeans
import numpy as np
# 创建数据集
X = np.array([[1,1],[1,2],[1,3],[2,4],[2,5],[2,6]])
# 创建kmeans模型,n_clusters设置簇值为2,只分两类
kmeans = KMeans(n_clusters=2,max_iter=300)
kmeans.fit(X)
X_pre = [[2,7]]
print(kmeans.cluster_centers_)
print(kmeans.labels_)
print("X_pre类别为:{}".format(kmeans.predict(X_pre)[0]))
这里数据集就是自己设定的六组数据进行聚类,设置簇的数量为2(即将所有数据分为两类),很明显前三组数据为一个簇,后三组数据为一个簇。
创建一个 KMeans 模型,设置超参 n_clusters 为2,max_iter 为300(后面会补充说明超参和最大迭代次数的定义,以及如何合理设置),这个模型拟合和我前几篇文章中提到的逻辑回归模型和线性回归模型拟合是一样的,不清楚的朋友可以看一下前面的帖子,最后使用拟合好的模型预测一个新的数据 X_pre。
kmeans.cluster_centers_ 表示求出每个簇对应的中心点,上述二维数组中,两个簇的中心点分别为(2,5),(1,2),很明显就是后三个数据的坐标均值和前三个数据的坐标均值,kmeans.labels_ 表示所求簇对应的标签,这里只有两个簇,所以标签为0和1,kmeans.predict 表示用拟合好的该模型对新数据进行预测,返回标签。
4.3 KMeans 算法实现过程
每次迭代过程中的任务
1、初始化聚类中心:随机选择 n_clusters 个初始聚类中心。
2、分配数据点:将每个数据点分配给最近的聚类中心。
3、更新聚类中心:重新计算每个聚类的中心位置,通常是通过计算每个聚类内所有数据点的均值。
重复步骤2和3:直到聚类中心不再发生显著变化或达到最大迭代次数为止。
了解算法实现过程,我们就可以自己实现聚类算法了。
4.4 手写 KMeans 算法
import matplotlib.pyplot as plt
import numpy as np
导入必要的模块,matplotlib 用于散点图的绘制
def KMeans(X,k,max_iter=300):
定义一个函数 KMeans,设置三个超参,X 表示用于聚类的数据,K 表示聚类的簇值,max_iter表示最大迭代次数。
1、初始化簇类中心
# 从数据集中随机抽取K个作为中心点
centers = X[np.random.choice(X.shape[0],k,replace=False)]
# labels数组用于存储样本标签
labels = np.zeros(X.shape[0])
首先初始化 k 个簇的中心点,random.choice 顾名思义随机挑选,X.shape[0] 表示数据大小,k表示挑选个数,replace = False 设定不重复选择,总体代码含义就是在X数据中挑选k个数据,来作为初始簇中心点。
labels 设定标签,每个标签表示一个簇的名称,np.zeros 初始化每个簇标签为0。
2、分配数据点
for i in range(max_iter):
# 分配样本到最近的点
distances = np.sqrt(((X - centers[:,np.newaxis])**2).sum(axis=2))
# 观察样本距离最近的中心点
new_labels = np.argmin(distances,axis=0)
循环 max_iter 次,这里表示迭代次数,计算每个点到中心点的欧式距离,找出每个点距离最近的中心点,根据三个中心点的索引[0,1,2]来更新每个点的标签。
欧式距离计算:
np.sqrt() 计算括号类数值的算术平方根,首先大家要明白,我们数据集X是一个二维数组,形状为(X.shape[0],2),centers也是一个二维数组,形状为(k,2),我们要得到的是每一个数据点坐标与每一个中心点坐标之间的差向量,通过差向量来计算两点距离。比如A(a,b),B(c,d),那么A,B两点之间的距离就是,如果两个向量直接相减的话,就会出现报错,报错原因是形状不匹配。
测试样例:
import numpy as np
x = [[1,2],[3,4],[5,6]]
y = [[5,6],[1,2]]
X = np.array(x)
Y = np.array(y)
print(X-Y)
ValueError: operands could not be broadcast together with shapes (3,2) (2,2)
很明显,形状不相同的数组是无法直接运算的,所以这里运用了广播机制进行匹配运算,现在大家只需要了解广播机制可以允许维度存在差异而进行匹配,后面会结合具体例子带大家了解广播机制的。
这里用[:,np.newasix]方法让centers数组增加了一个维度(关于[:,np.newasix]的用法,前面帖子给大家做过演示,这里就不多赘述),变为三维数组,形状为(k,1,2)。与X数组进行差值运算得到一个形状为(k,X.shape[0],2)的数组,新数组中存储了所有点与中心点的差向量,最后对差向量进行平方运算,sum(axis=2) 表示在数据沿着第三维度就和(axis 的用法之前也提到过),就是对于刚刚差值的平方进行求和,再对结果取根号,最终得到的是一个形状为 (k,X.shape[0]) 的二维数组。第一维度K表示k个中心点,第二维度的值表示所有点到该中心点的欧式距离。
new_labels 用于更新标签,np.argmin 用于比较当前维度数组的最小值,返回当前索引。给大家举一个例子就明白了。
例:
import numpy as np
# 假设有四个数据点,分为两簇
distances = np.array([[1,4,2,8],
[4,2,0,9]]) # [1,4,2,8]表示所有数据点到第一个中心点的欧式距离
labels = [0,0,0,0] # 初始标签值
new_labels = np.argmin(distances,axis=0)
print(new_labels)
运行结果:
1 小于 4,返回0,4 大于 2,返回1
3、更新簇类中心
# 更新中心点
for j in range(k):
centers[j] = X[new_labels == j].mean(axis=0)
# 如果函数开始收敛,则提前结束循环
if (labels == new_labels).all():
break
else:
labels = new_labels
每一次迭代的最后任务就是更新中心点,上一步划分了簇,将每个数据点保存到距离最近的中心点中,这里将所有归为一类(簇)的数据点求出平均值,求平均值的方法很简单,就是将所有点的x和y分别求平均值,作为该簇新的中心点坐标。
mean 函数用来求平均值,axis=0 沿着竖直方向操作,这里给大家举个例子就好理解了。
例:
import numpy as np
# 簇的个数
k = 2
X = np.array([[1, 2], # 样本1
[2, 3], # 样本2
[10, 11], # 样本3
[11, 12]]) # 样本4
new_labels = np.array([0, 0, 1, 1]) # 样本1和2属于簇0,样本3和4属于簇1
centers = np.zeros((k,2))
for i in range(k):
centers[i] = X[new_labels == i].mean(axis=0)
print(centers)
运行结果:
最后当函数收敛时,跳出循环,收敛条件是当 new_labels 不再变化,返回 new_labels 和 centers
# 生成数据集
X = np.vstack((np.random.randn(100,2) * 0.75 + np.array([1,0]),
np.random.randn(100,2) * 0.25 + np.array([-0.5,0.5]),
np.random.randn(100,2) * 0.5 + np.array([-0.5,-0.5])))
代码生成了一个包含三个不同簇的数据集。每个簇都是从不同的二维正态分布中随机抽取的样本。具体来说:
- 第一个簇有100个样本,它们是从均值为
[1, 0]
,协方差矩阵为0.75^2
的正态分布中随机抽取的。 - 第二个簇也有100个样本,均值为
[-0.5, 0.5]
,协方差为0.25^2
。 - 第三个簇同样有100个样本,均值为
[-0.5, -0.5]
,协方差为0.5^2
。
由于 np.random.randn
生成的是标准正态分布(均值为0,方差为1)的随机数,所以通过乘以缩放因子(如 0.75
)并加上位移(如 [1, 0]
),可以生成具有特定均值和方差的样本。
这样可以观察到明显的三个簇的散点图分布。
labels,centers = KMeans(X,k=3)
# 绘制出散点图
plt.scatter(X[:,0],X[:,1],c=labels)
plt.scatter(centers[:,0],centers[:,1],marker='x',s=200,linewidths=3,color='g')
plt.show()
c=labels
这个参数指定了数据点的颜色,labels
是一个与 X
中的数据点数量相同的数组,其中每个元素对应一个类别标签。
五、名词解释
5.1 迭代次数
每一次迭代会更新一组参数,在神经网络中,每一次迭代都会更新神经元之间的权重,使得模型可以学习到更多更准确的特征,而在这个模型当中,每一次循环(迭代)都会更新一下数据点的labels和中心点坐标centers,最后找到最合适的centers、labels,自动跳出循环。
超参和普通参数:
在机器学习和深度学习中,“超参数”是指那些在模型训练开始之前就需要设定好的参数, 它们不是直接从数据中学习得来的。超参数通常用于控制模型的学习过程,影响模型如何被训练以及最终的表现形式。 一些常见的超参数包括学习率(Learning Rate)、正则化系数(Regularization Parameter)、批次大小(Batch Size)、训练迭代次数(Number of Epochs)、隐藏层的节点数量(Number of Nodes in Hidden Layers)等。 总结一下,超参数和普通参数的主要区别在于:参数是从数据中学习到的;超参数是由用户根据经验和实验来设定的。
迭代次数的影响
迭代次数过多:
优点:更多的迭代可以使得聚类中心更加稳定,收敛到更优解。
缺点:更多的迭代意味着更高的计算成本和更长的运行时间。对于大规模数据集来说,这可能导致算法变得非常慢。
迭代次数过少:
优点:更快的计算速度。
缺点:算法可能不会充分收敛,导致最终的聚类中心不是全局最优解,而是局部最优解。这可能导致聚类结果不够准确。
5.2 广播机制
在 NumPy 中,广播机制允许不同形状的数组之间进行运算。广播通过从后向前比较数组的维度大小来对齐数据,如果两个数组在某个维度上的大小相同,或者其中一个大小为1,则可以进行广播;如果在任意维度上两个数组的大小都不匹配且不为1,则无法进行广播。通过广播,较小的数组会被“拉伸”至与较大数组相匹配的形状,从而实现元素级别的运算。
这里B数组复制三份,每个维度与A数组进行加法运算,最后就得到 4*3 的数组,相当于A数组与每一个B数组都进行运算。
结合具体实例带着大家分析。
例:
import numpy as np
A = np.array([[1,2],[2,3]])
B = np.array([[[1,8],[2,5]],[[2,4],[3,6]]])
C = B - A
print(C)
运行结果:
A 数组为二维数组,形状为(2,2),B 数组为三维数组,形状为(2,2,2),低维度可以与高维度数组进行广播运算,如果在任意维度上两个数组的大小既不相同,也不为1,那么这两个数组不能进行广播运算。
六、到此结束
大家辛苦啦,制作不易,希望大家能点赞关注,多多支持!!!