Python k-means 练习:分配、更新与评估
把分配、更新和评估拆成练习,k-means 就没那么神秘。这些例子只使用二维小数据,每个质心都能手工核对。
练习:计算平方欧氏距离
平方距离保留最近邻排序,还能省去开方。只有当实际单位对报告很重要时,才需要欧氏距离。
def squared_distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b))
print(squared_distance((0, 0), (3, 4)))25 个平方单位
练习:给每个点分配簇
分配就是对所有点做列表推导。每个点选择最近质心的索引。把索引也放进排序键,平局时结果才确定。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
centroids = [(0, 0), (5, 5)]
labels = [min(range(len(centroids)), key=lambda i: (distance(point, centroids[i]), i)) for point in points]
print(labels)每个簇两个点
练习:更新一个质心
质心是成员在每个坐标上的均值。必须等所有分配完成后才能更新,否则会用只完成一半的聚类结果。
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
labels = [0, 0, 1, 1]
members = [point for point, label in zip(points, labels) if label == 0]
centroid = (
sum(x for x, _ in members) / len(members),
sum(y for _, y in members) / len(members),
)
print(tuple(round(value, 1) for value in centroid))簇 0 的均值
练习:计算簇内平方和
簇内平方和是每个点到所属质心的平方距离之和。数值越小簇越紧,但 k 增大时它一定下降,所以不能只看它选 k。
def squared_distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b))
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
labels = [0, 0, 1, 1]
centroids = [(0, 0.5), (5, 5.5)]
inertia = sum(squared_distance(point, centroids[label]) for point, label in zip(points, labels))
print(round(inertia, 1))聚类有多紧?
练习:迭代并评估
反复分配和更新,直到标签不再变化,然后同时报告标签与簇内平方和。多试几种初始化,因为循环只保证局部最优。
def distance(a, b):
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
points = [(0, 0), (0, 1), (5, 5), (5, 6)]
centroids = [(0, 0), (5, 5)]
labels = None
for _ in range(10):
new_labels = [min(range(len(centroids)), key=lambda i: (distance(point, centroids[i]), i)) for point in points]
new_centroids = []
for index in range(len(centroids)):
members = [point for point, label in zip(points, new_labels) if label == index]
new_centroids.append((
sum(x for x, _ in members) / len(members),
sum(y for _, y in members) / len(members),
) if members else centroids[index])
if new_labels == labels:
break
labels = new_labels
centroids = new_centroids
inertia = sum(distance(point, centroids[label]) ** 2 for point, label in zip(points, labels))
print(labels)
print(round(inertia, 1))标签稳定后停止
你的练习清单
- 计算距离前先缩放特征。
- 先完成所有点的分配,再更新任何质心。
- 用确定性规则处理距离平局。
- 显式处理空簇。
- 报告 k 与簇内平方和,并比较多种初始化。
常见问题
k 怎么选?
没有通用规则。可以结合领域知识、观察不同 k 的簇内平方和,并判断聚类对实际任务是否有用。
为什么每次运行聚类都不同?
k-means 找到的是依赖初始化的局部最优。固定随机种子,或运行多次并保留簇内平方和最小的结果。
缩放会改变 k-means 吗?
会。距离把每个坐标同等看待,数值范围更大的特征可能主导分配。
练习 k-means 循环
12 道题,覆盖距离、分配、质心更新、簇内平方和、缩放与收敛。
继续阅读: