不能用双层for循环计算距离,因numpy python层开销大;应利用广播机制一次性计算距离矩阵;需处理空簇、用位移范数而非相等判断收敛、必须用k-means++初始化及特征缩放。

为什么不能用循环算每个点到中心的距离
直接写两层 for 循环计算每个样本到每个中心的欧氏距离,euclidean_distance 调用次数会是 n_samples × n_clusters 级别。在 10 万样本、10 个簇时就是百万次调用——NumPy 的 Python 层开销会吃掉大部分性能。
真正高效的做法是利用广播机制一次性算出整个距离矩阵:
def batch_distances(X, centroids):
diff = X[:, np.newaxis, :] - centroids[np.newaxis, :, :]
return np.sqrt(np.sum(diff**2, axis=2))
关键点:
-
X[:, np.newaxis, :]把形状从(n, f)变成(n, 1, f);centroids[np.newaxis, :, :]把(k, f)变成(1, k, f) - 广播后自动扩展为
(n, k, f),一次完成全部平方差 -
axis=2表示沿特征维度求和,结果是(n, k)距离矩阵
空簇必须显式处理,否则会崩
当某轮分配后某个簇没分到任何样本,X[labels == i].mean(axis=0) 会返回全 nan,后续迭代中 nan 会污染所有距离计算,最终 np.argmin 报 ValueError: All-NaN slice encountered。
稳妥做法不是跳过或报错,而是就地重置:
- 检测
len(X[labels == i]) == 0 - 用
X[np.random.randint(len(X))]随机选一个已有样本作为新质心 - 避免用
np.random.rand()生成全新坐标——它可能落在数据分布之外,拖慢收敛
收敛判断别只看质心“是否相等”
用 np.array_equal(old_centroids, new_centroids) 判断收敛几乎总失败:浮点运算累积误差会让两个本该“稳定”的质心在第 15 位小数就不同。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
实际应监控质心移动距离:
- 计算每个质心的位移范数:
np.linalg.norm(new_centroids - old_centroids, axis=1) - 取最大位移:
np.max(...) - 与阈值(如
1e-4)比较,而非判等
这个阈值要结合数据量纲设——如果特征值在 [0, 1000] 区间,1e-4 就太严;若已标准化到 [0, 1],这个值刚好。
K-Means++ 初始化不是可选项,是必选项
随机选 k 个样本当初始质心,容易让多个中心挤在同一高密度区域,导致后续迭代卡在极差局部最优。K-Means++ 通过概率加权选择第二及以后的中心,能显著提升聚类质量。
核心逻辑只有三步:
- 第一个中心:随机选一个样本
- 对每个未选样本,计算它到**当前所有已选中心**的最短距离
d(x) - 按
d(x)²概率分布再选下一个中心
这一步不用自己手写完整逻辑,sklearn.cluster.KMeans(init='k-means++') 是参考实现,但如果你坚持纯 NumPy,重点在于:别省略平方,也别用线性距离做权重——那是 K-Means|| 的变种,收敛行为不同。
最常被忽略的其实是特征缩放:K-Means 对量纲极度敏感。两个特征单位差三个数量级时,距离计算基本由大单位特征主导。不做 StandardScaler 或手动归一化,batch_distances 再快也没意义。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










