3、K-Means聚类算法原理:算法步骤、距离度量、K值选择(肘部法则)

各位同学好,今天我们来聊聊K-Means聚类算法。说实话,这个算法是我入行环境数据分析时接触的第一个聚类方法。记得当时拿到一堆PM2.5和臭氧的监测数据,领导让我找出污染模式,我第一个想到的就是它。为什么?因为K-Means简单、直观、跑得快,而且效果往往还不错。

K-Means的核心思想其实很朴素:物以类聚,人以群分。你想想看,如果两个监测站点的污染特征很相似,那它们大概率属于同一个污染模式。K-Means就是帮我们把数据自动分成K个簇,让同一个簇内的样本尽可能相似,不同簇的样本尽可能不同。

3.1 算法步骤:四步走,简单粗暴

K-Means的算法步骤,我习惯把它总结为四步。这四步我在项目里反复用过,闭着眼睛都能写出来。

  1. 初始化:随机选K个样本作为初始聚类中心。嗯,这里有个坑,我后面会讲。
  2. 分配:计算每个样本到K个中心的距离,把它分给最近的那个中心。
  3. 更新:重新计算每个簇的中心(其实就是取平均值)。
  4. 迭代:重复第2步和第3步,直到中心不再变化,或者变化很小。

说白了,就是不断调整中心位置,直到每个簇都稳定下来。我做过一个实验,用某市一年的空气质量数据跑K-Means,迭代了大概15次就收敛了,速度非常快。

核心要点: K-Means的收敛速度通常很快,但初始中心的选择会影响最终结果。我建议多跑几次,选效果最好的。

3.2 距离度量:欧氏距离是默认选项,但不是唯一选项

距离度量是K-Means的灵魂。你用什么距离,直接决定了聚类结果长什么样。

最常用的是欧氏距离,公式很简单:

d(x, y) = sqrt((x1 - y1)² + (x2 - y2)² + ... + (xn - yn)²)

我在处理PM2.5、PM10、SO₂、NO₂这四维数据时,默认就用欧氏距离。但有一次我遇到了一个问题——数据量纲差异太大。PM2.5的数值在0-500之间,而SO₂只有0-50。如果不做标准化,PM2.5会主导距离计算,SO₂几乎被忽略。

避坑指南: 我曾经因为没做标准化,聚类结果全被PM2.5带偏了。后来我养成了一个习惯:跑K-Means之前,先做Z-score标准化或者Min-Max归一化。这一步不能省。

除了欧氏距离,还有曼哈顿距离和余弦相似度。曼哈顿距离对异常值更鲁棒,余弦相似度适合文本或方向数据。但在环境数据中,我个人99%的情况都用欧氏距离,简单有效。

3.3 K值选择:肘部法则,最实用的方法

K值怎么选?这是K-Means里最让人头疼的问题。你想想看,如果K选小了,不同污染模式混在一起;K选大了,又过度细分,失去意义。

肘部法则是我最推荐的方法。它的思路很简单:

  • 尝试不同的K值(比如K=1到10)
  • 计算每个K值对应的SSE(误差平方和),也就是所有样本到其簇中心的距离平方和
  • 画一条K-SSE曲线,找到那个"拐点"——也就是肘部位置

为什么会有拐点?因为随着K增大,SSE会不断下降。但K超过某个值后,SSE下降速度会明显变慢。这个转折点,就是最合适的K值。

我给大家画了一张流程图,把K-Means的完整流程和肘部法则的关系展示出来:

K-Means聚类 + 肘部法则 流程图 输入环境监测数据 标准化 / 归一化 肘部法则选择K值 尝试K=1~10,计算SSE,找拐点 随机初始化K个中心 分配样本到最近中心 更新簇中心(取均值) 迭代 收敛? 是 → 输出结果 输出聚类结果

这张图把整个流程串起来了。注意看,肘部法则是在聚类之前做的,它决定了K值。选好K之后,再跑K-Means迭代。

3.4 实战经验:肘部法则的坑与技巧

肘部法则虽然好用,但也不是万能的。我遇到过两种情况:

  • 拐点不明显:有时候SSE曲线是平滑下降的,没有明显的肘部。这时候我会结合业务需求来定K值。比如做污染模式分析,K=3或K=4通常比较合理,对应轻度、中度、重度污染。
  • 数据量太大:如果数据有几十万条,计算所有K值的SSE会很慢。我建议先采样,或者用Mini-Batch K-Means加速。
我的小技巧: 除了肘部法则,我还会用轮廓系数(Silhouette Score)辅助判断。轮廓系数同时考虑了簇内紧密度和簇间分离度,值越接近1越好。两个方法结合使用,选K值更有把握。

3.5 代码示例:用Python实现肘部法则

下面是我在项目中常用的代码片段,直接复制就能用:

from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import pandas as pd

# 加载数据(假设是环境监测数据)
data = pd.read_csv('air_quality.csv')
features = ['PM2.5', 'PM10', 'SO2', 'NO2']
X = data[features]

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 肘部法则:计算不同K值的SSE
sse = []
K_range = range(1, 11)

for k in K_range:
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X_scaled)
    sse.append(kmeans.inertia_)  # inertia_就是SSE

# 画肘部图
plt.figure(figsize=(8, 5))
plt.plot(K_range, sse, 'bo-')
plt.xlabel('K值')
plt.ylabel('SSE(误差平方和)')
plt.title('肘部法则:选择最佳K值')
plt.grid(True)
plt.show()

跑完这段代码,你会看到一条曲线。找到那个拐点,就是你的K值。我个人习惯把拐点附近的K值都试一遍,对比聚类结果的可解释性,选最符合业务逻辑的那个。

3.6 小结

K-Means聚类算法,说白了就是三步:选K、算距离、迭代。肘部法则帮我们解决最头疼的K值选择问题。我在环境数据分析项目中,用这套方法成功识别出了不同季节的污染模式——比如冬季的燃煤型污染和夏季的光化学型污染,聚类结果非常清晰。

嗯,今天就讲到这里。记住一句话:K-Means不是银弹,但用好了,它是你工具箱里最趁手的那把螺丝刀

核心公式回顾:
欧氏距离:d(x, y) = √(Σ(xi - yi)²)
SSE(肘部法则用):SSE = Σ(d(x, center)²)

专注资料整理