K均值聚类是一种经典的无监督学习算法,核心目标是将无标签的样本数据划分成K个簇,让同一个簇内的样本相似度尽可能高,不同簇之间的样本相似度尽可能低。在C#中实现该算法,需要先理解算法的核心流程,再逐步编写对应的代码逻辑。

K均值聚类核心原理
K均值聚类的执行流程可以分为几个固定步骤,首先需要从所有样本中随机选择K个样本作为初始聚类中心,然后遍历所有样本,计算每个样本到各个聚类中心的距离,将样本划分到距离最近的聚类中心所在的簇中。完成所有样本的分类后,重新计算每个簇的均值,将这个均值作为新的聚类中心,重复上述分类和更新中心的步骤,直到聚类中心不再发生变化或者达到预设的迭代次数。
C#实现步骤拆解
1. 定义样本和聚类中心结构
首先需要定义存储样本数据和聚类中心的数据结构,这里以二维样本为例,每个样本包含两个特征维度,聚类中心的结构和样本结构一致。
// 定义样本结构,包含x和y两个特征维度
public class DataPoint
{
public double X { get; set; }
public double Y { get; set; }
}
// 定义聚类中心结构,和样本结构一致,额外存储所属簇的样本列表
public class ClusterCenter
{
public double X { get; set; }
public double Y { get; set; }
// 存储该簇包含的所有样本
public List<DataPoint> Points { get; set; } = new List<DataPoint>();
}
2. 计算两个样本之间的欧氏距离
K均值聚类通常使用欧氏距离衡量样本之间的相似度,距离越小说明两个样本越相似。下面实现欧氏距离的计算逻辑。
// 计算两个数据点的欧氏距离
public static double CalculateDistance(DataPoint p1, DataPoint p2)
{
double dx = p1.X - p2.X;
double dy = p1.Y - p2.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
3. 初始化聚类中心
初始化聚类中心时,从所有样本中随机选择K个不重复的样本作为初始中心,避免初始中心选择不当导致算法收敛到局部最优。
// 随机初始化K个聚类中心
public static List<ClusterCenter> InitializeCenters(List<DataPoint> dataPoints, int k)
{
List<ClusterCenter> centers = new List<ClusterCenter>();
Random random = new Random();
// 用来记录已经选中的样本索引,避免重复选择
HashSet<int> selectedIndexes = new HashSet<int>();
while (centers.Count < k)
{
int index = random.Next(dataPoints.Count);
if (!selectedIndexes.Contains(index))
{
selectedIndexes.Add(index);
DataPoint point = dataPoints[index];
centers.Add(new ClusterCenter { X = point.X, Y = point.Y });
}
}
return centers;
}
4. 样本分类与中心更新
遍历所有样本,将每个样本分配到距离最近的聚类中心所在的簇,然后重新计算每个簇的均值作为新的聚类中心。
// 执行一次样本分类和中心更新,返回是否发生中心变化
public static bool UpdateClusters(List<DataPoint> dataPoints, List<ClusterCenter> centers)
{
// 先清空每个簇的样本列表
foreach (var center in centers)
{
center.Points.Clear();
}
// 遍历所有样本,分配到最近的簇
foreach (var point in dataPoints)
{
double minDistance = double.MaxValue;
ClusterCenter closestCenter = null;
foreach (var center in centers)
{
double distance = CalculateDistance(point, new DataPoint { X = center.X, Y = center.Y });
if (distance < minDistance)
{
minDistance = distance;
closestCenter = center;
}
}
closestCenter.Points.Add(point);
}
// 更新每个簇的中心,计算所有样本的均值
bool centerChanged = false;
foreach (var center in centers)
{
if (center.Points.Count == 0) continue;
double newX = center.Points.Average(p => p.X);
double newY = center.Points.Average(p => p.Y);
if (Math.Abs(newX - center.X) > 0.0001 || Math.Abs(newY - center.Y) > 0.0001)
{
center.X = newX;
center.Y = newY;
centerChanged = true;
}
}
return centerChanged;
}
5. 完整算法整合
将上述步骤整合起来,实现完整的K均值聚类算法,支持设置最大迭代次数避免无限循环。
// 完整的K均值聚类算法实现
public static List<ClusterCenter> KMeansClustering(List<DataPoint> dataPoints, int k, int maxIterations = 100)
{
// 初始化聚类中心
List<ClusterCenter> centers = InitializeCenters(dataPoints, k);
int iteration = 0;
bool centerChanged = true;
// 循环迭代直到中心不再变化或者达到最大迭代次数
while (centerChanged && iteration < maxIterations)
{
centerChanged = UpdateClusters(dataPoints, centers);
iteration++;
}
return centers;
}
使用示例
下面是一段调用上述K均值聚类算法的示例代码,使用一组随机生成的二维样本进行测试。
class Program
{
static void Main(string[] args)
{
// 生成测试样本数据
List<DataPoint> testData = new List<DataPoint>
{
new DataPoint { X = 1.0, Y = 1.0 },
new DataPoint { X = 1.5, Y = 2.0 },
new DataPoint { X = 3.0, Y = 4.0 },
new DataPoint { X = 5.0, Y = 7.0 },
new DataPoint { X = 3.5, Y = 5.0 },
new DataPoint { X = 4.5, Y = 5.0 },
new DataPoint { X = 3.5, Y = 4.5 }
};
// 调用K均值聚类,分成2个簇
List<ClusterCenter> result = KMeansClustering(testData, 2);
// 输出聚类结果
for (int i = 0; i < result.Count; i++)
{
Console.WriteLine($"簇{i + 1}的中心坐标:({result[i].X:F2}, {result[i].Y:F2})");
Console.WriteLine($"簇{i + 1}包含的样本数量:{result[i].Points.Count}");
foreach (var point in result[i].Points)
{
Console.WriteLine($" 样本坐标:({point.X:F2}, {point.Y:F2})");
}
}
}
}
注意事项
- K值的选择对聚类结果影响很大,实际使用时可以通过肘部法则等方法选择合适的K值。
- 初始聚类中心的选择可能导致算法收敛到局部最优,可以多次运行算法选择效果最好的一次结果。
- 如果样本的特征维度超过2维,只需要扩展
DataPoint和ClusterCenter的属性,同时调整距离计算的逻辑即可适配更高维度的数据。 - 当某个簇中没有样本时,可以重新随机选择一个样本作为该簇的新中心,避免空簇影响聚类结果。