自定义K-Means与Scikit-learn K-Means聚类结果差异咨询
问题背景
我正在学习K-Means聚类算法,通过自行实现该算法加深理解。使用的数据集包含500人(250男250女)的身高体重数据,为简化问题仅使用男性数据,预设将数据分为大、中、小3个T恤尺码类别。
自定义K-Means实现
自行编写的K-Means代码如下:
data = df[['Height','Weight']].loc['Male'] data = data.reset_index(drop = True) data['Label'] = '' # 预设初始质心,固定大、中、小对应的初始值 K = {'large': [180,120], 'medium': [170, 80], 'small': [150,60], } # 用固定次数迭代替代未明确的停止条件 for loop_num in range(50): for i in range(len(data)): # 计算每个点到三个质心的"距离"(自定义逻辑) a = min( abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['large'][0]**2 + K['large'][1]**2)), abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['medium'][0]**2 + K['medium'][1]**2)), abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['small'][0]**2 + K['small'][1]**2)) ) # 根据计算结果标记类别 if a == abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['large'][0]**2 + K['large'][1]**2)): data.loc[i,'Label'] = 3 elif a == abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['medium'][0]**2 + K['medium'][1]**2)): data.loc[i,'Label'] = 2 elif a == abs((data['Height'].loc[i]**2 + data['Weight'].loc[i]**2)-(K['small'][0]**2 + K['small'][1]**2)): data.loc[i,'Label'] = 1 # 更新质心:取每个类别的身高体重均值 K = {'large': [float(data.loc[data['Label'] == 3, ['Height']].mean()),float(data.loc[data['Label'] == 3, ['Weight']].mean())], 'medium': [float(data.loc[data['Label'] == 2, ['Height']].mean()),float(data.loc[data['Label'] == 2, ['Weight']].mean())], 'small': [float(data.loc[data['Label'] == 1, ['Height']].mean()),float(data.loc[data['Label'] == 1, ['Weight']].mean())], } print(K)
多次运行后结果稳定且符合预期:
{'large': [179.60638297872342, 132.56382978723406], 'medium': [169.23076923076923, 96.38461538461539], 'small': [150.6595744680851, 75.7872340425532]}
Scikit-learn K-Means实现
使用Scikit-learn的KMeans代码如下:
clusterer = KMeans(n_clusters=3,init='random',tol = 30) X = data[['Height','Weight']] clusterer.fit(X) print(clusterer.cluster_centers_)
即使调整参数,每次运行得到的cluster_centers_结果都不同,且不符合预期,例如某次运行结果为:
[[168.85227273 141.78409091] [170.82432432 105.01351351] [169.44578313 69.86746988]]
两种实现的聚类中心可视化
- 自定义实现:

- Scikit-learn实现:

差异原因分析
距离计算逻辑本质不同
自定义代码中使用的"距离"是abs((x²+y²)-(cx²+cy²)),这并非标准K-Means的欧氏距离平方(标准公式为(x-cx)² + (y-cy)²)。这个自定义逻辑等价于比较每个点到原点的平方距离与质心到原点的平方距离的差值,相当于把数据按到原点的距离分组,刚好契合你预设的大中小尺码的直观预期,但这不是真正的K-Means聚类逻辑。初始质心与类别绑定的差异
自定义代码的初始质心是固定的,且迭代过程中始终将标签3、2、1与大、中、小类别绑定,不会出现质心顺序混乱的情况。而Scikit-learn的init='random'会随机选择初始质心,每次运行的初始点不同,且聚类结果的标签是无意义的整数,顺序不固定,加上数据分布特性,容易陷入局部最优解。数据未标准化的影响
身高(cm)和体重(kg)的量纲差异很大,标准K-Means对量纲敏感,Scikit-learn的K-Means直接使用原始数据时,体重的数值范围更大,会被赋予更高权重,导致聚类结果不符合按尺码分类的预期。而自定义的距离计算逻辑刚好规避了这个问题(身高的平方值权重更高,符合尺码分类的直觉)。迭代停止条件设置不合理
自定义代码使用固定50次迭代,确保质心充分收敛。而Scikit-learn的tol=30设置过大(默认阈值为1e-4),表示质心变化超过30才会继续迭代,这会导致迭代提前停止,质心还未收敛到稳定状态。
内容的提问来源于stack exchange,提问作者Toyz

