K-means聚类算法(又称K-均值聚类算法),是机器学习中常见的无监督学习方法,由于简洁和效率使得他成为所有聚类算法中最广泛使用的。给定一个数据点集合和需要的聚类数目k,k由用户指定,k均值算法根据某个距离函数反复把数据分入k个聚类中。
先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。一旦全部对象都被分配了,每个聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是以下任何一个:
1、没有对象被重新分配给不同的聚类;
2、聚类中心不再发生变化;
3、误差平方和局部最小。
1、从n个数据中选取k个对象作为初始聚类中心;(这一步也造成了此算法每次运行的结果并不完全一致!)
2、分别计算其他样本点到各个聚类中心向量的距离,根据最小距离(例如欧氏距离)将其划分到距离最近的类;
3、计算每个类中数据点的平均值,更新各个类的中心变量;
4、重新计算每个聚类的均值,直到聚类中心不再变化。
1.简单易懂:K-means算法原理简单,容易理解和实现,对于初学者来说,它是入门聚类分析的一个很好的选择。
2.计算效率高:K-means的时间复杂度大致是线性的,这使得它在处理大数据集时比较有效率。
3.广泛应用:K-means可以用于各种数据聚类问题,并且在市场细分、社交网络分析、图像压缩等领域有广泛应用。
4.易于解释:K-means产生的聚类结果比较容易解释,因为每个簇都有一个中心,可以通过分析中心的特征来解释簇的特性。
1.需要指定簇的数量:K-means算法需要事先知道要形成的簇的数量(K值),而这通常是通过先验知识或多次尝试确定的。不正确的K值会导致不理想的聚类结果。
2.对初始簇中心敏感:算法的最终结果很大程度上取决于初始簇中心的选择,而这些通常是随机选取的。不同的随机种子可能会导致完全不同的聚类结果。
3.对异常值敏感:K-means对离群点和噪声比较敏感,异常值可能会极大地扭曲簇的中心。
4.可能陷入局部最优:K-means试图找到全局最优的簇中心,但实际上可能会陷入局部最优,尤其是在簇的形状和大小差异较大时。
5.难以处理高维数据:在高维空间中,距离度量变得不太可靠,这个现象被称为“维度的诅咒”,可能会降低K-means算法的效果。
import numpy as np
import matplotlib.pyplot as plt
# 生成数据:4 个簇 + 噪声点
np.random.seed(42)
cluster1 = np.random.normal([3, 3], [1.0, 1.5], size=(100, 2))
cluster2 = np.random.normal([8, 8], [1.2, 0.8], size=(100, 2))
cluster3 = np.random.normal([12, 4], [1.1, 1.3], size=(100, 2))
cluster4 = np.random.normal([6, 12], [1.3, 1.0], size=(100, 2))
noise = np.random.uniform(low=0, high=15, size=(30, 2))
data = np.vstack((cluster1, cluster2, cluster3, cluster4, noise))
# 初始化参数
K = 4
max_iters = 20
tol = 1e-4
centroids = data[np.random.choice(data.shape[0], K, replace=False)]
labels = np.zeros(data.shape[0])
objective_values = []
# K-means 核心实现
for iteration in range(max_iters):
# Step 1: 分配簇
for i, point in enumerate(data):
distances = np.linalg.norm(point - centroids, axis=1)
labels[i] = np.argmin(distances)
# Step 2: 计算目标函数
objective = sum(np.linalg.norm(data[labels == k] - centroids[k], axis=1).sum() for k in range(K))
objective_values.append(objective)
# Step 3: 更新簇中心
new_centroids = np.zeros_like(centroids)
for k in range(K):
cluster_points = data[labels == k]
if len(cluster_points) > 0:
new_centroids[k] = cluster_points.mean(axis=0)
# 检查收敛
if np.linalg.norm(new_centroids - centroids) < tol:
break
centroids = new_centroids
# 数据可视化
plt.figure(figsize=(20, 10))
# 图 1: 原始数据分布
plt.subplot(2, 3, 1)
plt.scatter(data[:, 0], data[:, 1], c='gray', alpha=0.6, label='Data Points')
plt.title("Original Data with Noise")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend()
# 图 2: 动态过程
plt.subplot(2, 3, 2)
plt.scatter(data[:, 0], data[:, 1], c='gray', alpha=0.6, label='Data Points')
for i, center in enumerate(centroids):
plt.scatter(center[0], center[1], color=f'C{i}', edgecolor='black', s=200, label=f'Centroid {i+1}')
plt.title("Dynamic Process of Centroids")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend()
# 图 3: 最终聚类结果
plt.subplot(2, 3, 3)
colors = ['red', 'blue', 'green', 'purple']
for k in range(K):
cluster_points = data[labels == k]
plt.scatter(cluster_points[:, 0], cluster_points[:, 1], color=colors[k], label=f"Cluster {k+1}", alpha=0.7)
plt.scatter(centroids[:, 0], centroids[:, 1], s=300, c='yellow', edgecolor='black', label='Centroids', marker='X')
plt.title("Final Clustering Result")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend()
# 图 4: 目标函数收敛曲线
plt.subplot(2, 3, 4)
plt.plot(range(1, len(objective_values) + 1), objective_values, marker='o', color='orange')
plt.title("Objective Function Convergence")
plt.xlabel("Iteration")
plt.ylabel("Objective Function Value")
plt.grid(True)
# 显示图像
plt.tight_layout()
plt.show()