改进的 K-Means 聚类方法介绍

news/2024/5/20 8:34:13 标签: python, 神经网络, 人工智能, 聚类

引言

数据科学的一个中心假设是,紧密度表明相关性。彼此“接近”的数据点是相似的。如果将年龄、头发数量和体重绘制在空间中,很可能许多人会聚集在一起。这就是 k 均值聚类背后的直觉。

我们随机生成 K 个质心,每个簇一个,并将每个数据点分配给与该数据点最近的质心对应的簇。然后,我们生成新的质心,每个质心都是属于该簇的所有点的平均值。然后重复这个过程直到收敛。

我们可以使用欧几里德距离作为距离度量并计算每个数据点与质心之间的距离。

def cross_euclidean_distance(x, y=None):
    y = x if y is None else y 
    assert len(x.shape) >= 2
    assert len(y.shape) >= 2
    return euclidean_distance(x[..., :, None, :], y[..., None, :, :])

这样我们就可以执行 K 均值聚类算法的核心了。

def fit(self, X):
    cluster_ind = np.repeat(0, X.shape[0])
    idx = np.random.randint(X.shape[0], size=self.m_clusters)
    self.centroids = X.loc[idx].to_numpy()

    cross_dist = cross_euclidean_distance(X.to_numpy(), self.centroids)
    cluster_ind = np.argmin(cross_dist, axis = 1)

    for _ in range(self.max_iter):
        #Calculating new centroids
        for i in range(self.m_clusters):
            X_i = X[cluster_ind == i]
            self.centroids[i] = X_i.mean(axis = 0).to_numpy()

        #Assigne data points to new cluster and check if cluster assignment chenges
        cross_dist = cross_euclidean_distance(X.to_numpy(), self.centroids)
        cluster_ind_new = np.argmin(cross_dist, axis = 1)
        if not (cluster_ind_new == cluster_ind).any():
            break
        cluster_ind = cluster_ind_new

K-means算法保证收敛,但可能不会收敛到全局最优。相反,它经常陷入局部最优。本文的其余部分将致力于解决这个问题。

根据种子的不同,算法可能会陷入局部最优

关注公众号 [小Z的科研日常] ,查看最新技术分享。

简单的解决方案

最简单的解决方案就是使用不同的起始质心多次运行算法,然后选择最佳的聚类分配。

为了有效地做到这一点,我们需要一种衡量方法来量化集群分配的好坏。其中一种衡量标准是欧几里得畸变。每个点到质心的平均距离对应于它们分配到的簇。运行该算法几次并保存欧几里得失真最低的算法,并希望它是全局最优的。

python">def euclidean_distortion(X, z):
    X, z = np.asarray(X), np.asarray(z)
    assert len(X.shape) == 2
    assert len(z.shape) == 1
    assert X.shape[0] == z.shape[0]
    
    distortion = 0.0
    for c in np.unique(z):
        Xc = X[z == c]
        mu = Xc.mean(axis=0)
        distortion += ((Xc - mu) ** 2).sum()
    return distortion

更智能的采样

幸运的是,我们可以做得更好。这种方法的主要问题是计算成本较高。为了缓解这个问题,我们可以调整起始条件,以便我们更有可能找到全局最优值。为了了解如何实现,让我们看一个玩具问题。

我们希望我们的算法找到总共 10 个数据组。看起来有 10 个不同的簇。当我们运行算法时,我们发现我们可能会得到一个或两个集群错误。

使用 K = 10 运行 K 均值聚类后的聚类分配。我们看到分配并不完美。紫色簇太大,而黄色和青绿色簇争夺同一组。

两个组成为一个超级集群,而另一个组则由两个集群共享。发生这种情况可能是因为两个初始质心靠得太近。为了减少这种情况发生的可能性,我们可以在如何对初始质心进行采样方面变得更加智能。

我们可以不从数据中均匀采样质心,而是从加权分布中顺序采样质心,其中每个权重由到已采样质心的距离决定。这确保了我们不太可能对接近已采样质心的质心进行采样。其算法很简单。在这里,我简单地将概率视为每个数据点到最近质心的归一化距离。这确保我们不会对同一个数据点进行两次采样。

python">idx = np.random.choice(range(X.shape[0]), size=1)
centroids = X.loc[idx].to_numpy()
while len(centroids)<self.m_clusters:
    distances = cross_euclidean_distance(centroids, X.to_numpy())
    prob_vec = distances.min(axis = 0)
    prob_vec = prob_vec**2/np.sum(prob_vec**2)
    #Note: zero proability that new centorid is allready a centroid
    idx = np.append(idx, np.random.choice(X.shape[0], size=1, p = prob_vec)) 
    centroids = X.loc[idx].to_numpy()

self.centroids = centroids

智能分配

然而,上述方法可能仍然有些不足。我们只是增加了不出现错误初始配置的机会。为了确保我们得到全局最大值的分配,我们仍然需要运行算法多次。这样做的问题是,每次重新启动算法时,我们都会丢弃进度。更好的方法是找出我们当前任务的问题所在以及如何改进。考虑这个集群分配。

这个簇分配非常糟糕,有两个超级簇和两对质心,彼此距离太近。

黄色和青绿色的簇占据了太多的空间,而棕色、紫色、粉色和橙色的簇则占据了很少的空间。通过智能分配算法,我们可以识别两个最近的质心,并将其中一个移动到最需要的位置。

也就是说,到距质心最远的点。如果我们进行了太多的跳跃,我们只需保存最佳的收敛统计数据,就像之前的重复过程一样。这个的实现是在下面的代码中完成的。

python">n_worst = int(round(X.shape[0]*self.worst_prec))
best_reroll_centroids = self.centroids
best_reroll_distortion = euclidean_distortion(X, cluster_ind)
for i in range(self.n_rerolls):
    # Caculate new centroids
    for i in range(self.m_clusters):
        X_i = X[cluster_ind == i]
        self.centroids[i] = X_i.mean(axis = 0).to_numpy()

    # Find the two centroids that are closest and pick the centoid with the lowest average distance to other centroids
    centroid_dist = cross_euclidean_distance(self.centroids)
    cetorid_dist_inf = centroid_dist + np.diag(np.repeat(np.inf, centroid_dist.shape[0])) # Add inf to diag
    worst_pair = np.unravel_index((cetorid_dist_inf).argmin(), cetorid_dist_inf.shape) # Find indexes of worst pair
    worst_ind = worst_pair[0] if (np.mean(centroid_dist[worst_pair[0]])<np.mean(centroid_dist[worst_pair[1]])) else worst_pair[1]

    # Assign the old centroid to be the one closest to the poinst that are furthest away from the current centroids
    min_dists = np.min(cross_dist, axis = 1)
    high_dists_ind = np.argpartition(min_dists, -n_worst)[-n_worst:]
    X_high = X.loc[high_dists_ind]
    self.centroids[worst_ind] = X_high.mean(axis = 0).to_numpy()

    # Itterate until convergence
    for _ in range(self.max_iter):
        #Calculating new centroids
        for i in range(self.m_clusters):
            X_i = X[cluster_ind == i]
            self.centroids[i] = X_i.mean(axis = 0).to_numpy()

        #Assigne data points to new cluster and check if cluster assignment chenges
        cross_dist = cross_euclidean_distance(X.to_numpy(), self.centroids)
        cluster_ind_new = np.argmin(cross_dist, axis = 1)
        if not (cluster_ind_new == cluster_ind).any():
            break
        cluster_ind = cluster_ind_new
   
    distortion = euclidean_distortion(X, cluster_ind)
    if distortion<best_reroll_distortion:
        best_reroll_distortion = distortion
        best_reroll_centroids = self.centroids
   
self.centroids = best_reroll_centroids 

下图显示了正在运行的算法。首先,粉红色的簇被移动。

粉红色的质心已经跳到绿松石色的星团上

我们看到粉红色的质心已经跳到绿松石色的星团上。此配置仍远未达到最佳状态。紫色和粉红色的簇所占的量太少,而黄色和绿松石色的簇所占的量太多。这可以通过重复该过程几次来解决。

    可以观察到紫色质心跳到粉色簇 

可以观察到紫色质心跳转到黄色质心

通过最后的跳跃,我们可以简单地运行直到收敛,最终获得接近最佳的集群分配。


http://www.niftyadmin.cn/n/5362397.html

相关文章

江科大stm32学习笔记11——旋转编码器计次

一、接线 旋转编码器&#xff0c;旋钮会不断接触断开触点产生电波。 由于两个电波之间相差90&#xff0c;即为正交波&#xff0c;一个电波处于高电平时另一个处于低电平&#xff0c;所以可以用来判断旋转方向。 二、代码 复制粘贴4-1的工程文件&#xff0c;重命名为“5-2 旋转…

深度学习(9)--pydot库和graphviz库安装流程详解

目录 一.pydot库安装 二.graphviz库安装 一.pydot库安装 pydot的安装可直接在编译器安装相关包&#xff0c;以PyCharm举例&#xff1a; 如果搜索可用软件包显示为空&#xff0c;记得在此处把使用Conda软件包管理器”点亮 二.graphviz库安装 点击链接下载安装包graphviz-2.38…

【服务器】RAID(独立磁盘冗余阵列)

RAID&#xff08;独立磁盘冗余阵列&#xff09; 一、RAID的介绍二、RAID的分类#2-1 RAID 02-2 RAID 1#2-3 RAID 32-4 RAID 52-5 RAID 62-6 RAID 10(先做镜像&#xff0c;再做条带化)2-7 RAID 01&#xff08;先做条带&#xff0c;再做镜像&#xff09;2-8 RAID比较 三、磁盘阵列…

linux远程执行命令后中断联系使远程机独立运行

背景 正常逻辑上通过网络远程在另外一台机器执行一个命令后&#xff0c;就跟本机没有关系了&#xff0c;但是事实上并不是这样的&#xff0c;当执行ssh 后&#xff0c;很多时候也会出现远程反馈信息的情况&#xff0c;换句话就是可以通过ssh实现远程执行调用。但是有些时候并不…

中科大计网学习记录笔记(五):协议层次和服务模型

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

Loadbalancer如何优雅分担服务负荷

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Loadbalancer如何优雅分担服务负荷 前言Loadbalancer基础&#xff1a;数字世界的分配大师1. 分发请求&#xff1a;2. 健康检查&#xff1a;3. 会话保持&#xff1a;4. 可伸缩性&#xff1a;5. 负载均衡…

c语言:贪吃蛇的实现

目录 贪吃蛇实现的技术前提&#xff1a; Win32 API介绍 控制台程序&#xff08;console&#xff09; 控制台屏幕上的坐标 GetStdHandle GetConsoleCursorInfo CONSOLE_CURSOR_INFO SetConsoleCursorInfo SetConsoleCursorPosition GetAsyncKeyState 宽字符的打印 …

从编程中理解:大脑的无意识与有意识状态

在编程中,模拟大脑的无意识与有意识状态是一个复杂而富有挑战性的任务,这需要设计出能够根据情境和内部模型进行智能决策的系统。在Unity游戏引擎中,我们可以利用C#编写AI行为控制脚本,以金庸武侠世界中的角色为例,来阐述这一概念。 设想一个场景,在Unity构建的“倚天屠…