K-Means 算法原理及其 Python 与 C# 实现

一、聚类分析概述

聚类分析是数据挖掘和机器学习领域中的一项重要任务,旨在将数据集中的数据点划分成若干个不同的簇(cluster),使得同一簇内的数据点具有较高的相似性,而不同簇之间的数据点具有较高的差异性。聚类分析在许多领域都有着广泛的应用,例如市场细分、图像识别、生物信息学、文档分类等。

其主要目标是发现数据集中潜在的自然分组结构,这种结构通常是未知的且事先未被标记的。与分类任务不同,聚类分析不需要预先知道数据点所属的类别标签,而是通过数据点之间的特征相似性自动进行分组。

二、K-Means 算法原理

K-Means 算法是一种基于划分的聚类算法,其基本思想较为直观且易于理解。给定一个包含n个数据点的数据集X={x1,x2,...,xn}和预先指定的簇的数量k,K-Means 算法的目标是将这n个数据点划分成k个簇 ,使得每个数据点都属于且仅属于一个簇C={C1,C2,..,Ck},并且每个簇内的数据点到其所属簇的质心(centroid)的距离之和最小。

算法的具体步骤如下:

(一)初始化

随机选择k个数据点作为初始的簇质心。这k个质心的选择会对算法的最终结果产生一定的影响,但通常可以通过多次运行算法并取最优结果来减小这种影响。

(二)分配数据点到簇

对于数据集中的每个数据点xi,计算它到k个簇质心的距离(通常使用欧几里得距离公式:,其中d为数据点的维度,xim和xjm分别为数据点xi和簇质心cj在第m维上的坐标)。将数据点xi分配到距离其最近的簇质心所在的簇Cj。

(三)更新簇质心

对于每个簇Cj,重新计算其质心。质心的计算方法是取簇内所有数据点在各个维度上的平均值。即对于簇Cj,其新的质心cj的第m维坐标为:,其中|Cj|为簇Cj中数据点的数量。

(四)重复迭代

重复步骤(二)和(三),直到满足某个停止条件。常见的停止条件包括:簇的分配不再发生变化(即所有数据点都已稳定地分配到各自的簇中)、达到最大迭代次数或者质心的移动距离小于某个阈值。

三、K-Means 算法的优缺点

(一)优点

  1. 简单易懂:K-Means 算法的原理和实现相对较为简单,容易理解和掌握,不需要复杂的数学背景知识。
  2. 计算效率高:算法的计算复杂度相对较低,尤其是在处理大规模数据集时,能够在较短的时间内得到聚类结果。其时间复杂度通常为O(nkt),其中n是数据点的数量,k是簇的数量,t 是迭代次数。当k和t相对较小时,算法的运行速度较快。
  3. 对球形簇效果好:如果数据集中的簇呈现出较为明显的球形分布,K-Means 算法能够很好地将这些簇区分开来,得到较为理想的聚类结果。

(二)缺点

  1. 需要预先指定簇的数量k:在实际应用中,往往很难事先确定数据集中应该划分成多少个簇。如果k值选择不当,可能会导致聚类结果不理想。例如,如果k值过大,可能会将一个原本应该属于同一簇的数据点划分成多个簇;如果k值过小,则可能会将多个不同的簇合并成一个簇。
  2. 对初始质心敏感:算法的初始质心是随机选择的,如果初始质心选择得不好,可能会使算法收敛到局部最优解,而不是全局最优解。不同的初始质心选择可能会导致完全不同的聚类结果。
  3. 对噪声和离群点敏感:由于 K-Means 算法是基于距离的聚类方法,数据集中的噪声点和离群点可能会对簇质心的计算产生较大的影响,从而影响整个聚类结果的准确性。
  4. 只能处理球形簇:K-Means 算法假设簇的形状是球形的,对于非球形的簇,如具有复杂形状或不同密度的簇,该算法可能无法得到准确的聚类结果。

四、K-Means 算法的 Python 实现

(一)数据准备

首先,我们需要准备用于聚类的数据。这里假设我们有一个二维数据集,可以使用 numpy 库来生成一些随机数据点。示例代码如下:

import numpy as np

# 生成随机数据点
np.random.seed(0)
X = np.random.randn(100, 2)

上述代码生成了一个包含 100 个二维数据点的数据集 X,这些数据点服从标准正态分布。

(二)K-Means 算法实现

接下来,我们实现 K-Means 算法的核心代码。

def kmeans(X, k, max_iterations=100):
    # 随机选择初始质心
    centroids = X[np.random.choice(X.shape[0], k, replace=False)]

    for _ in range(max_iterations):
        # 分配数据点到簇
        clusters = [[] for _ in range(k)]
        for x in X:
            distances = [np.linalg.norm(x - centroid) for centroid in centroids]
            cluster_index = np.argmin(distances)
            clusters[cluster_index].append(x)

        # 更新簇质心
        new_centroids = []
        for cluster in clusters:
            if len(cluster) > 0:
                new_centroid = np.mean(cluster, axis=0)
                new_centroids.append(new_centroid)

        # 检查质心是否收敛
        if np.allclose(centroids, new_centroids):
            break

        centroids = new_centroids

    return centroids, clusters

在上述代码中,kmeans 函数接受数据集 X、簇的数量 k 和最大迭代次数 max_iterations 作为参数。函数首先随机选择 k 个初始质心,然后在迭代过程中,不断地将数据点分配到最近的簇中,并更新簇质心,直到质心收敛或者达到最大迭代次数。

(三)结果可视化

为了直观地展示聚类结果,我们可以使用 matplotlib 库来绘制数据点和簇质心。

import matplotlib.pyplot as plt

def plot_clusters(X, centroids, clusters):
    plt.scatter(X[:, 0], X[:, 1], c='gray', s=50)
    for i, centroid in enumerate(centroids):
        cluster_points = np.array(clusters[i])
        plt.scatter(cluster_points[:, 0], cluster_points[:, 1], label=f'Cluster {i + 1}')
        plt.scatter(centroid[0], centroid[1], c='red', marker='x', s=200)
    plt.legend()
    plt.show()

最后,我们可以调用上述函数来执行 K-Means 算法并展示结果。

# 执行 K-Means 算法
k = 3
centroids, clusters = kmeans(X, k)

# 绘制聚类结果
plot_clusters(X, centroids, clusters)

上述代码将数据集 X 划分为 3 个簇,并绘制出数据点和簇质心的分布情况。通过可视化结果,我们可以直观地看到 K-Means 算法对数据的聚类效果。

五、K-Means 算法的 C# 实现

(一)数据准备

在 C# 中,我们首先需要准备数据。这里假设我们使用 System.Numerics 命名空间中的 Vector2 结构来表示二维数据点。可以使用以下代码生成随机数据点:

using System;
using System.Numerics;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 生成随机数据点
        Random random = new Random(0);
        List<Vector2> X = new List<Vector2>();
        for (int i = 0; i < 100; i++)
        {
            float x = (float)random.NextDouble();
            float y = (float)random.NextDouble();
            X.Add(new Vector2(x, y));
        }
    }
}

上述代码生成了一个包含 100 个二维数据点的列表 X,这些数据点的坐标值在 0 到 1 之间随机生成。

(二)K-Means 算法实现

接下来,我们实现 K-Means 算法的核心代码。

class KMeans
{
    public static (List<Vector2>, List<List<Vector2>>) Cluster(List<Vector2> X, int k, int maxIterations = 100)
    {
        // 随机选择初始质心
        List<Vector2> centroids = new List<Vector2>();
        Random random = new Random();
        for (int i = 0; i < k; i++)
        {
            int index = random.Next(X.Count);
            centroids.Add(X[index]);
        }

        for (int iteration = 0; iteration < maxIterations; iteration++)
        {
            // 分配数据点到簇
            List<List<Vector2>> clusters = new List<List<Vector2>>();
            for (int i = 0; i < k; i++)
            {
                clusters.Add(new List<Vector2>());
            }

            foreach (Vector2 x in X)
            {
                float minDistance = float.MaxValue;
                int clusterIndex = 0;
                for (int i = 0; i < k; i++)
                {
                    float distance = Vector2.Distance(x, centroids[i]);
                    if (distance < minDistance)
                    {
                    minDistance = distance;
                    clusterIndex = i;
                    }
                }
                clusters[clusterIndex].Add(x);
            }

            // 更新簇质心
            List<Vector2> newCentroids = new List<Vector2>();
            for (int i = 0; i < k; i++)
            {
                if (clusters[i].Count > 0)
                {
                    Vector2 sum = new Vector2(0, 0);
                    foreach (Vector2 x in clusters[i])
                    {
                        sum += x;
                    }
                    newCentroids.Add(sum / clusters[i].Count);
                }
                else
                {
                    newCentroids.Add(centroids[i]);
                }
            }

            // 检查质心是否收敛
            bool converged = true;
            for (int i = 0; i < k; i++)
            {
                if (Vector2.Distance(centroids[i], newCentroids[i]) > 0.0001)
                {
                    converged = false;
                    break;
                }
            }

            if (conged)
            {
                break;
            }

            centroids = newCentroids;
        }

        return (centroids, clusters);
    }
}

在上述代码中,Cluster 方法接受数据点列表 X、簇的数量 k 和最大迭代次数 maxIterations 作为参数。方法首先随机选择 k 个初始质心,然后在迭代过程中,不断地将数据点分配到最近的簇中,并更新簇质心,直到质心收敛或者达到最大迭代次数。

(三)结果可视化(使用第三方库)

在 C# 中进行可视化可以使用一些第三方库,如 Avalonia.ControlsWindowsForms 等。这里以 WindowsForms 为例,简单介绍如何可视化聚类结果。首先,创建一个新的 Windows Forms Application 项目,然后在 Form1.cs 文件中添加以下代码:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Numerics;
using System.Collections.Generic;

namespace KMeansVisualization
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            // 执行 K-Means 算法
            int k = 3;
            List<Vector2> X = new List<Vector2>();
            // 这里添加生成数据点的代码,与前面数据准备部分相同

            var (centroids, clusters) = KMeans.Cluster(X, k);

            // 绘制数据点和簇质心
            Graphics g = e.Graphics;
            Brush grayBrush = new SolidBrush(Color.Gray);
            Brush redBrush = new SolidBrush(Color.Red);
            Pen redPen = new Pen(Color.Red);
            for (int i = 0; i < k; i++)
            {
                foreach (Vector2 x in clusters[i])
                {
                    g.FillEllipse(grayBrush, x.X * this.ClientSize.Width, x.Y * this.ClientSize.Height, 5, 5);
                }
                Vector2 centroid = centroids[i];
                g.FillEllipse(redBrush, centroid.X * this.ClientSize.Width, centroid.Y * this.ClientSize.Height, 10, 10);
                g.DrawEllipse(redPen, centroid.X * this.ClientSize.Width - 20, centroid.Y * this.ClientSize.Height - 20, 40, 40);
            }
        }
    }
}

上述代码在 Form1Paint 事件处理方法中执行 K-Means 算法,并绘制出数据点和簇质心的分布情况。需要注意的是,在实际应用中,可能需要根据数据的范围和窗口的大小进行适当的坐标转换,以确保图形能够正确地显示在窗口中。

六、K-Means 算法的应用场景

(一)市场细分

在市场营销中,企业通常希望将客户群体划分为不同的细分市场,以便更好地了解不同客户群体的需求和行为特征,从而制定针对性的营销策略。K-Means 算法可以根据客户的属性数据,如年龄、性别、收入、消费习惯等,将客户划分为不同的簇。例如,将高收入、高消费频率的客户划分为一个簇,将低收入、低消费频率的客户划分为另一个簇等。通过这种方式,企业可以针对不同的客户簇推出不同的产品或服务,提高营销效果和客户满意度。

(二)图像识别

在图像识别领域,K-Means 算法可以用于图像分割和图像压缩等任务。例如,在图像分割中,可以将图像中的像素根据颜色、亮度等特征进行聚类,将相似的像素划分为同一个簇,从而实现图像的分割,将图像中的不同物体或区域分离出来。在图像压缩中,可以对图像中的颜色进行聚类,用聚类中心的颜色来表示图像中的多个相似颜色,从而减少图像数据的存储空间。

(三)生物信息学

在生物信息学中,K-Means 算法可以用于基因表达数据分析、蛋白质结构分类等任务。例如,在基因表达数据分析中,可以根据基因在不同样本中的表达水平将基因划分为不同的簇,从而发现具有相似表达模式的基因群,这些基因群可能与特定的生物学过程或疾病相关。在蛋白质结构分类中,可以根据蛋白质的结构特征将蛋白质划分为不同的簇,帮助研究人员更好地理解蛋白质的功能和进化关系。

(四)文档分类

在文本处理和文档分类领域,K-Means 算法可以根据文档的词汇特征将文档划分为不同的簇。例如,可以将具有相似主题或词汇分布的文档划分为同一个簇。在进行大规模文档管理时,这种聚类方法可以帮助用户快速定位和浏览相关文档,提高文档管理的效率。

七、K-Means 算法的优化与改进

(一)K-Means++ 算法

K-Means++ 是对传统 K-Means 算法的一种改进,主要针对初始质心的选择问题。其基本思想是使初始质心的选择更加合理,从而降低算法收敛到局部最优解的可能性。

K-Means++ 算法在选择初始质心时,首先随机选择一个数据点作为第一个质心。然后,对于每个数据点xi,计算它到已选质心的最小距离,并根据这些距离选择下一个质心。选择概率与 成正比,即距离已选质心越远的数据点被选中作为新质心的概率越大。重复这个过程,直到选择出k个质心。

以下是 K-Means++ 算法在 Python 中的简单实现示例:

def kmeanspp(X, k, max_iterations=100):
    # 选择第一个质心
    centroids = [X[np.random.choice(X.shape[0])]]

    # 选择剩余的质心
    for _ in range(1, k):
        distances = []
        for x in X:
            min_distance = min([np.linalg.norm(x - centroid) for centroid in centroids])
            distances.append(min_distance)
        # 根据距离的平方概率选择下一个质心
        probabilities = np.array(distances) ** 2
        probabilities /= np.sum(probabilities)
        centroids.append(X[np.random.choice(X.shape[0], p=probabilities)])

    # 执行 K-Means 算法
    for _ in range(max_iterations):
        # 分配数据点到簇
        clusters = [[] for _ in range(k)]
        for x in X:
            distances = [np.linalg.norm(x - centroid) for centroid in centroids]
            cluster_index = np.argmin(distances)
            clusters[cluster_index].append(x)

        # 更新簇质心
        new_centroids = []
        for cluster in clusters:
            if len(cluster) > 0:
                new_centroid = np.mean(cluster, axis=0)
                new_centroids.append(new_centroid)

        # 检查质心是否收敛
        if np.allclose(centroids, new_centroids):
            break

        centroids = new_centroids

    return centroids, clusters

在 C# 中实现 K-Means++ 的关键部分如下:

class KMeansPlusPlus
{
    public static (List<Vector2>, List<List<Vector2>>) Cluster(List<Vector2> X, int k, int maxIterations = 100)
    {
        // 选择第一个质心
        List<Vector2> centroids = new List<Vector2>();
        Random random = new Random();
        int firstCentroidIndex = random.Next(X.Count);
        centroids.Add(X[firstCentroidIndex]);

        // 选择剩余的质心
        for (int i = 1; i < k; i++)
        {
            List<float> distances = new List<float>();
            foreach (Vector2 x in X)
            {
                float minDistance = float.MaxValue;
                foreach (Vector2 centroid in centroids)
                {
                    float distance = Vector2.Distance(x, centroid);
                    if (distance < minDistance)
                    {
                        minDistance = distance;
                    }
                }
                distances.Add(minDistance);
            }

            // 根据距离的平方概率选择下一个质心
            float sumOfSquares = distances.Sum(d => d * d);
            List<float> probabilities = distances.Select(d => d * d / sumOfSquares).ToList();
            int nextCentroidIndex = random.Next(X.Count);
            centroids.Add(X[nextCentroidIndex]);
        }

        // 执行 K-Means 算法(与之前的 K-Means 实现中的迭代部分相同)
        //...

        return (centroids, clusters);
    }
}

通过使用 K-Means++ 算法选择初始质心,可以在一定程度上提高 K-Means 算法的聚类效果,尤其是对于那些容易受到初始质心影响的数据集。

(二)二分 K-Means 算法

二分 K-Means 算法是另一种对 K-Means 算法的改进策略。它采用自顶向下的分裂策略,从包含所有数据点的一个簇开始,逐步将簇分裂成更小的簇,直到达到指定的簇数量 。

算法的基本步骤如下:

  1. 首先将整个数据集看作一个簇。
  2. 对于当前的每个簇,计算将其分裂成两个簇后的总误差平方和(SSE)。总误差平方和的计算方法是计算每个数据点到其所属簇质心的距离的平方和。
  3. 选择使总误差平方和增加最小的簇进行分裂,使用 K-Means 算法(通常是 K-Means++ 来选择初始质心)将该簇分裂成两个簇。
  4. 重复步骤 2 和 3,直到得到k个簇。

以下是二分 K-Means 算法在 Python 中的实现示例:

def bisecting_kmeans(X, k, max_iterations=100):
    # 初始将所有数据点看作一个簇
    clusters = [X]
    centroids = [np.mean(X, axis=0)]

    while len(clusters) < k:
        # 找到要分裂的簇
        best_cluster_index = -1
        best_increase = float('inf')
        for i, cluster in enumerate(clusters):
            # 尝试分裂当前簇
            centroid1, centroid2, sub_clusters = split_cluster(cluster, max_iterations)
            # 计算分裂后的 SSE 增加量
            increase = calculate_sse(cluster, centroid1, centroid2, sub_clusters)
            if increase < best_increase:
                best_increase = increase
                best_cluster_index = i

        # 对选定的簇进行分裂
        cluster_to_split = clusters[best_cluster_index]
        centroid1, centroid2, sub_clusters = split_cluster(cluster_to_split, max_iterations)
        clusters.pop(best_cluster_index)
        clusters.extend(sub_clusters)
        centroids.pop(best_cluster_index)
        centroids.extend([centroid1, centroid2])

    return centroids, clusters

def split_cluster(cluster, max_iterations):
    # 使用 K-Means++ 选择初始质心并执行 K-Means 算法分裂簇
    centroid1, centroid2, sub_clusters = kmeanspp(cluster, 2, max_iterations)
    return centroid1, centroid2, sub_clusters

def calculate_sse(cluster, centroid1, centroid2, sub_clusters):
    # 计算分裂后的 SSE 增加量
    sse = 0
    for i, sub_cluster in enumerate(sub_clusters):
        centroid = [centroid1, centroid2][i]
        for x in sub_cluster:
            sse += np.linalg.norm(x - centroid) ** 2
    return sse

在 C# 中实现二分 K-Means 算法的主要逻辑如下:

class BisectingKMeans
{
    public static (List<Vector2>, List<List<Vector2>>) Cluster(List<Vector2> X, int k, int maxIterations = 100)
    {
        // 初始将所有数据点看作一个簇
        List<List<Vector2>> clusters = new List<List<Vector2>> { X };
        List<Vector2> centroids = new List<Vector2> { CalculateCentroid(X) };

        while (clusters.Count < k)
        {
            // 找到要分裂的簇
            int bestClusterIndex = -1;
            float bestIncrease = float.MaxValue;
            for (int i = 0; i < clusters.Count; i++)
            {
                // 尝试分裂当前簇
                var (centroid1, centroid2, subClusters) = SplitCluster(clusters[i], max_iterations);
                // 计算分裂后的 SSE 增加量
                float increase = CalculateSSE(clusters[i], centroid1, centroid2, subClusters);
                if (increase < bestIncrease)
                {
                    bestIncrease = increase;
                    bestClusterIndex = i;
                }
            }

            // 对选定的簇进行分裂
            var clusterToSplit = clusters[bestClusterIndex];
            var (centroid1, centroid2, subClusters) = SplitCluster(clusterToSplit, max_iterations);
            clusters.RemoveAt(bestClusterIndex);
            clusters.AddRange(subClusters);
            centroids.RemoveAt(bestClusterIndex);
            centroids.Add(centroid1);
            centroids.Add(centroid2);
        }

        return (centroids, clusters);
    }

    private static Vector2 CalculateCentroid(List<Vector2> cluster)
    {
        Vector2 sum = new Vector2(0, 0);
        foreach (Vector2 x in cluster)
        {
            sum += x;
        }
        return sum / cluster.Count;
    }

    private static (Vector2, Vector2, List<List<Vector2>>) SplitCluster(List<Vector2> cluster, int maxIterations)
    {
        // 使用 K-Means++ 选择初始质心并执行 K-Means 算法分裂簇
        var (centroids, subClusters) = KMeansPlusPlus.Cluster(cluster, 2, maxIterations);
        return (centroids[0], centroids[1], subClusters);
    }

    private static float CalculateSSE(List<Vector2> cluster, Vector2 centroid1, Vector2 centroid2, List<List<Vector2>> subClusters)
    {
        // 计算分裂后的 SSE 增加量
        float sse = 0;
        for (int i = 0; i < subClusters.Count; i++)
        {
            Vector2 centroid = i == 0? centroid1 : centroid2;
            foreach (Vector2 x in subClusters[i])
            {
                sse += Vector2.Distance(x, centroid) * Vector2.Distance(x, centroid);
            }
        }
        return sse;
    }
}

二分 K-Means 算法的优点在于它不需要预先指定k值,而是逐步分裂簇来达到合适的簇数量。并且由于每次分裂都是基于使总误差平方和增加最小的原则,因此在一定程度上可以得到较好的聚类结果。

(三)引入权重

在实际应用中,数据集中的每个数据点可能具有不同的重要性或权重。例如,在市场细分中,一些大客户的数据点可能比小客户的数据点更重要;在图像识别中,图像中某些关键区域的数据点可能比其他区域的数据点更关键。

为了考虑数据点的权重,可以对 K-Means 算法进行修改。在计算簇质心时,不再是简单地取数据点的平均值,而是根据数据点的权重进行加权平均。在分配数据点到簇时,也可以根据数据点到簇质心的加权距离进行分配。

以下是在 Python 中引入权重的 K-Means 算法示例:

def weighted_kmeans(X, weights, k, max_iterations=100):
    # 随机选择初始质心
    centroids = X[np.random.choice(X.shape[0], k, replace=False)]

    for _ in range(max_iterations):
        # 分配数据点到簇
        clusters = [[] for _ in range(k)]
        for x, w in zip(X, weights):
            distances = [np.linalg.norm(x - centroid) * w for centroid in centroids]
            cluster_index = np.argmin(distances)
            clusters[cluster_index].append(x)

        # 更新簇质心(加权平均)
        new_centroids = []
        for cluster in clusters:
            if len(cluster) > 0:
                weighted_sum = np.sum([x * w for x, w in zip(cluster, weights)], axis=0)
                total_weight = np.sum([w for w in weights])
                new_centroid = weighted_sum / total_weight
                new_centroids.append(new_centroid)

        # 检查质心是否收敛
        if np.allclose(centroids, new_centroids):
            break

        centroids = new_centroids

    return centroids, clusters

在 C# 中引入权重的 K-Means 算法实现如下:

class WeightedKMeans
{
    public static (List<Vector2>, List<List<Vector2>>) Cluster(List<Vector2> X, List<float> weights, int k, int maxIterations = 100)
    {
        // 随机选择初始质心
        List<Vector2> centroids = new List<Vector2>();
        Random random = new Random();
        for (int i = 0; i < k; i++)
        {
            int index = random.Next(X.Count);
            centroids.Add(X[index]);
        }

        for (int iteration = 0; iteration < maxIterations; iteration++)
        {
            // 分配数据点到簇
            List<List<Vector2>> clusters = new List<List<Vector2>>();
            for (int i = 0; i < k; i++)
            {
                clusters.Add(new List<Vector2>());
            }

            for (int i = 0; i < X.Count; i++)
            {
                Vector2 x = X[i];
                float w = weights[i];
                float minDistance = float.MaxValue;
                int clusterIndex = 0;
                for (int j = 0; j < k; j++)
                {
                    float distance = Vector2.Distance(x, centroids[j]) * w;
                    if (distance < minDistance)
                    {
                        minDistance = distance;
                        clusterIndex = j;
                    }
                }
                clusters[clusterIndex].Add(x);
            }

            // 更新簇质心(加权平均)
            List<Vector2> newCentroids = new List<Vector2>();
            for (int i = 0; i < k; i++)
            {
                if (clusters[i].Count > 0)
                {
                    Vector2 weightedSum = new Vector2(0, 0);
                    float totalWeight = 0;
                    for (int j = 0; j < clusters[i].Count; j++)
                    {
                        Vector2 x = clusters[i][j];
                        float w = weights[j];
                        weightedSum += x * w;
                        totalWeight += w;
                    }
                    newCentroids.Add(weightedSum / totalWeight);
                }
                else
                {
                    newCentroids.Add(centroids[i]);
                }
            }

            // 检查质心是否收敛
            bool converged = true;
            for (int i = 0; i < k; i++)
            {
                if (Vector2.Distance(centroids[i], newCentroids[i]) > 0.0001)
                {
                    converged = false;
                    break;
                }
            }

            if (converged)
            {
                break;
            }

            centroids = newCentroids;
        }

        return (centroids, clusters);
    }
}

通过引入权重,可以使 K-Means 算法更好地适应具有不同重要性数据点的数据集,提高聚类结果的准确性和实用性。

综上所述,K-Means 算法虽然是一种经典的聚类算法,但通过各种优化和改进策略,如 K-Means++ 算法改善初始质心选择、二分 K-Means 算法自顶向下分裂簇、引入权重考虑数据点重要性等,可以在不同的应用场景下提高其聚类性能,使其能够更好地处理各种复杂的数据聚类问题。在实际应用中,可以根据数据的特点和需求选择合适的优化方法或组合多种方法来获得更理想的聚类结果。

相关推荐
江上挽风&sty20 分钟前
python爬虫--小白篇【爬取B站视频】
爬虫·python
yivifu26 分钟前
利用cnocr库完成中文扫描pdf文件的文字识别
python·pdf·numpy·pymupdf·cnocr
Bdawn27 分钟前
【通义实验室】开源【文本生成图片】大模型
人工智能·python·llm
觅远30 分钟前
python+img2pdf 快速图片转pdf+(img2pdf.ExifOrientationError处理、文件被打开或占用报错处理)
python·pdf·pillow
m0_7482336442 分钟前
Python Flask Web框架快速入门
前端·python·flask
宸码43 分钟前
【机器学习】手写数字识别的最优解:CNN+Softmax、Sigmoid与SVM的对比实战
人工智能·python·神经网络·算法·机器学习·支持向量机·cnn
F20226974861 小时前
使用 Python 爬取某网站简历模板(bs4/lxml+协程)
开发语言·python
cdg==吃蛋糕1 小时前
pdf读取函数,可以读取本地pdf和url的在线pdf转换为文字
python·pdf
前程的前程也迷茫1 小时前
flask程序线程问题
python·flask
睡觉狂魔er1 小时前
自动驾驶控制与规划——Project 1: 车辆纵向控制
人工智能·机器学习·自动驾驶