文章目录
-
- 前言
- 1. 无监督学习的定位与目标
- 2. K-Means:最简单但也最容易误用
-
- 2.1 算法核心步骤
- 2.2 K-Means++ 初始化
- 2.3 K-Means 的三个硬伤
- 2.4 Mini-Batch K-Means:大数据场景的实用方案
- 3. K 值选择:三种方法各有盲区
-
- 3.1 肘部法则
- 3.2 轮廓系数
- 3.3 Gap Statistic
- 3.4 三种方法的对比与选择
- 4. 层次聚类:不需要预设 K 的探索利器
-
- 4.1 凝聚 vs 分裂
- 4.2 簇间距离的连接方式
- 4.3 树状图:层次聚类的核心优势
- 4.4 层次聚类的计算瓶颈
- 5. DBSCAN:密度视角的突破
-
- 5.1 核心思想:从"距离"到"密度"
- 5.2 ε 参数选择:k-距离图
- 5.3 DBSCAN 对 K-Means 的突破
- 5.4 DBSCAN 的致命弱点:密度不均
- 6. HDBSCAN:自适应密度的解决方案
-
- 6.1 从 DBSCAN 到 HDBSCAN
- 6.2 HDBSCAN 的核心参数
- 6.3 HDBSCAN 的概率输出
- 7. 聚类评估:没有真值标签时怎么办
-
- 7.1 内部评估指标对比
- 7.2 稳定性评估
- 7.3 业务可解释性:最终验收标准
- 8. 从聚类到半监督:无监督不是终点
- 9. 实战:电商用户分群全流程
-
- 9.1 RFM 特征构建
- 9.2 四种算法对比
- 9.3 簇画像与业务解读
- 9.4 算法选型的实际建议
- 10. 四种聚类算法的选型决策表
-
- 聚类算法速查表
- 11. 避坑清单
前言
监督学习有标签来衡量"对错"——准确率、F1、AUC 都是围绕真值标签构建的。但现实世界的数据绝大多数没有标签:电商平台需要把用户分成"高价值/成长型/流失风险"几类,运维团队需要从海量监控指标中识别"正常模式"与"异常模式",产品经理需要从用户行为日志中发现"自然的使用习惯分组"。这些场景的共同点是——没有标准答案。
没有标准答案意味着三个核心挑战:算法选择不能靠"谁的准确率高"来决定,结果评估不能靠"与真值对比"来衡量,业务落地不能靠"指标达标"来验收。本文围绕这三个挑战展开,从 K-Means 的球形假设出发,到层次聚类的树状结构,再到 DBSCAN/HDBSCAN 的密度视角,最后用电商用户分群实战串联完整流程。
1. 无监督学习的定位与目标
无监督学习在工业场景中有三个截然不同的定位,目标不同,方法选择和评估标准也完全不同。
#mermaid-svg-O8T4F1tXmfpdOH6H{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O8T4F1tXmfpdOH6H .error-icon{fill:#552222;}#mermaid-svg-O8T4F1tXmfpdOH6H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O8T4F1tXmfpdOH6H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O8T4F1tXmfpdOH6H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O8T4F1tXmfpdOH6H .marker.cross{stroke:#333333;}#mermaid-svg-O8T4F1tXmfpdOH6H svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O8T4F1tXmfpdOH6H p{margin:0;}#mermaid-svg-O8T4F1tXmfpdOH6H .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster-label text{fill:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster-label span{color:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster-label span p{background-color:transparent;}#mermaid-svg-O8T4F1tXmfpdOH6H .label text,#mermaid-svg-O8T4F1tXmfpdOH6H span{fill:#333;color:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H .node rect,#mermaid-svg-O8T4F1tXmfpdOH6H .node circle,#mermaid-svg-O8T4F1tXmfpdOH6H .node ellipse,#mermaid-svg-O8T4F1tXmfpdOH6H .node polygon,#mermaid-svg-O8T4F1tXmfpdOH6H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O8T4F1tXmfpdOH6H .rough-node .label text,#mermaid-svg-O8T4F1tXmfpdOH6H .node .label text,#mermaid-svg-O8T4F1tXmfpdOH6H .image-shape .label,#mermaid-svg-O8T4F1tXmfpdOH6H .icon-shape .label{text-anchor:middle;}#mermaid-svg-O8T4F1tXmfpdOH6H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O8T4F1tXmfpdOH6H .rough-node .label,#mermaid-svg-O8T4F1tXmfpdOH6H .node .label,#mermaid-svg-O8T4F1tXmfpdOH6H .image-shape .label,#mermaid-svg-O8T4F1tXmfpdOH6H .icon-shape .label{text-align:center;}#mermaid-svg-O8T4F1tXmfpdOH6H .node.clickable{cursor:pointer;}#mermaid-svg-O8T4F1tXmfpdOH6H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O8T4F1tXmfpdOH6H .arrowheadPath{fill:#333333;}#mermaid-svg-O8T4F1tXmfpdOH6H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O8T4F1tXmfpdOH6H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O8T4F1tXmfpdOH6H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O8T4F1tXmfpdOH6H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O8T4F1tXmfpdOH6H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O8T4F1tXmfpdOH6H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster text{fill:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H .cluster span{color:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O8T4F1tXmfpdOH6H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O8T4F1tXmfpdOH6H rect.text{fill:none;stroke-width:0;}#mermaid-svg-O8T4F1tXmfpdOH6H .icon-shape,#mermaid-svg-O8T4F1tXmfpdOH6H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O8T4F1tXmfpdOH6H .icon-shape p,#mermaid-svg-O8T4F1tXmfpdOH6H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O8T4F1tXmfpdOH6H .icon-shape .label rect,#mermaid-svg-O8T4F1tXmfpdOH6H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O8T4F1tXmfpdOH6H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O8T4F1tXmfpdOH6H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O8T4F1tXmfpdOH6H :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
无监督学习目标
探索性分析
预处理辅助
业务分群
发现数据结构有几类?什么形状?
方法:多种聚类 + 可视化评估:业务可解释性
聚类 → 特征工程簇ID作为新特征
聚类 → 半监督伪标签 → 分类器
评估:下游任务性能提升
用户画像 / 产品分类
方法:可解释聚类评估:业务指标 + 可解释性
关键:簇必须能翻译成业务语言
探索性分析的目标是"看看数据长什么样"——这时候需要尝试多种聚类算法,结合可视化来判断数据结构。预处理辅助的目标是"让下游模型更好"——聚类结果作为新特征或伪标签喂给监督模型,评估标准是下游任务的性能提升。业务分群的目标是"产生可执行的业务策略"——每个簇必须能用业务语言描述(“近30天活跃但客单价下降的成长型用户”),而不是"簇0和簇1"。
很多聚类项目失败的原因不是算法选错,而是目标定位不清晰——用探索性分析的方法做业务分群,结果簇无法翻译成业务策略;用业务分群的方法做预处理,结果簇的统计特性不适合作为特征。
2. K-Means:最简单但也最容易误用
2.1 算法核心步骤
K-Means 的迭代过程可以用四步概括:
#mermaid-svg-Vv9adHuBUCphv5HD{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Vv9adHuBUCphv5HD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Vv9adHuBUCphv5HD .error-icon{fill:#552222;}#mermaid-svg-Vv9adHuBUCphv5HD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Vv9adHuBUCphv5HD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Vv9adHuBUCphv5HD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Vv9adHuBUCphv5HD .marker.cross{stroke:#333333;}#mermaid-svg-Vv9adHuBUCphv5HD svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Vv9adHuBUCphv5HD p{margin:0;}#mermaid-svg-Vv9adHuBUCphv5HD .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Vv9adHuBUCphv5HD .cluster-label text{fill:#333;}#mermaid-svg-Vv9adHuBUCphv5HD .cluster-label span{color:#333;}#mermaid-svg-Vv9adHuBUCphv5HD .cluster-label span p{background-color:transparent;}#mermaid-svg-Vv9adHuBUCphv5HD .label text,#mermaid-svg-Vv9adHuBUCphv5HD span{fill:#333;color:#333;}#mermaid-svg-Vv9adHuBUCphv5HD .node rect,#mermaid-svg-Vv9adHuBUCphv5HD .node circle,#mermaid-svg-Vv9adHuBUCphv5HD .node ellipse,#mermaid-svg-Vv9adHuBUCphv5HD .node polygon,#mermaid-svg-Vv9adHuBUCphv5HD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Vv9adHuBUCphv5HD .rough-node .label text,#mermaid-svg-Vv9adHuBUCphv5HD .node .label text,#mermaid-svg-Vv9adHuBUCphv5HD .image-shape .label,#mermaid-svg-Vv9adHuBUCphv5HD .icon-shape .label{text-anchor:middle;}#mermaid-svg-Vv9adHuBUCphv5HD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Vv9adHuBUCphv5HD .rough-node .label,#mermaid-svg-Vv9adHuBUCphv5HD .node .label,#mermaid-svg-Vv9adHuBUCphv5HD .image-shape .label,#mermaid-svg-Vv9adHuBUCphv5HD .icon-shape .label{text-align:center;}#mermaid-svg-Vv9adHuBUCphv5HD .node.clickable{cursor:pointer;}#mermaid-svg-Vv9adHuBUCphv5HD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Vv9adHuBUCphv5HD .arrowheadPath{fill:#333333;}#mermaid-svg-Vv9adHuBUCphv5HD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Vv9adHuBUCphv5HD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Vv9adHuBUCphv5HD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vv9adHuBUCphv5HD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Vv9adHuBUCphv5HD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vv9adHuBUCphv5HD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Vv9adHuBUCphv5HD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Vv9adHuBUCphv5HD .cluster text{fill:#333;}#mermaid-svg-Vv9adHuBUCphv5HD .cluster span{color:#333;}#mermaid-svg-Vv9adHuBUCphv5HD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Vv9adHuBUCphv5HD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Vv9adHuBUCphv5HD rect.text{fill:none;stroke-width:0;}#mermaid-svg-Vv9adHuBUCphv5HD .icon-shape,#mermaid-svg-Vv9adHuBUCphv5HD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vv9adHuBUCphv5HD .icon-shape p,#mermaid-svg-Vv9adHuBUCphv5HD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Vv9adHuBUCphv5HD .icon-shape .label rect,#mermaid-svg-Vv9adHuBUCphv5HD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vv9adHuBUCphv5HD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Vv9adHuBUCphv5HD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Vv9adHuBUCphv5HD :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
否
随机初始化K个质心
分配步骤每个点归入最近质心
更新步骤质心移动到簇内均值
质心是否变化?
收敛输出簇分配
这个过程本质上是在最小化簇内平方和(SSE):
SSE
=
∑
k
=
1
K
∑
x
i
∈
C
k
∥
x
i
−
μ
k
∥
2
\\text{SSE} = \\sum_{k=1}^{K} \\sum_{x_i \\in C_k} \\|x_i – \\mu_k\\|^2
SSE=k=1∑Kxi∈Ck∑∥xi−μk∥2
其中
μ
k
\\mu_k
μk 是第
k
k
k 个簇的质心。K-Means 保证收敛(SSE 单调递减且有下界),但不保证找到全局最优——初始质心位置不同,最终结果可能不同。
2.2 K-Means++ 初始化
随机初始化的问题是可能把多个质心放在同一个簇附近,导致其他簇被忽略。K-Means++ 的策略是让初始质心彼此远离:
x
x
x,计算到已选质心的最短距离
D
(
x
)
D(x)
D(x)
D
(
x
)
2
D(x)^2
D(x)2 的概率选择下一个质心(距离越远越可能被选)
sklearn 的 KMeans 默认使用 K-Means++ 初始化,不需要手动设置。
2.3 K-Means 的三个硬伤
K-Means 被广泛使用,但很多人忽视了它的三个核心假设,这些假设在很多真实数据中并不成立。
硬伤一:假设簇是球形的。 K-Means 用欧氏距离度量相似性,隐含假设每个簇在各方向上的展开程度相似。当真实簇是长条形、环形、月牙形时,K-Means 会把它们强行切成球形块。
from sklearn.datasets import make_moons
from sklearn.cluster import KMeans
# 生成月牙形数据
X, _ = make_moons(n_samples=500, noise=0.05, random_state=42)
# K-Means 会把两个月牙各切一半
km = KMeans(n_clusters=2, random_state=42)
labels = km.fit_predict(X)
# 结果:两个月牙各被切成两半,而不是一个月牙一个簇
硬伤二:需要预先指定 K。 真实场景中,簇的数量往往未知。K 的选择本身就是一个难题(见下一节)。
硬伤三:对异常值敏感。 质心是簇内均值,一个极端点可以把质心拉得很远。这导致 K-Means 在有噪声的数据上表现不稳定。
2.4 Mini-Batch K-Means:大数据场景的实用方案
当样本量超过 10 万时,标准 K-Means 每次迭代都要计算所有点到所有质心的距离,计算量
O
(
n
K
d
)
O(nKd)
O(nKd) 很大。Mini-Batch K-Means 每次迭代只用一个随机子集(如 1024 个样本)来更新质心,速度提升一个数量级,精度损失很小。
from sklearn.cluster import MiniBatchKMeans
# 10万+样本的场景
mbkm = MiniBatchKMeans(
n_clusters=8,
batch_size=1024,
n_init=3,
random_state=42
)
labels = mbkm.fit_predict(X_large)
3. K 值选择:三种方法各有盲区
K 值选择是 K-Means 最核心的调参问题,常见的三种方法各有适用场景和盲区。
3.1 肘部法则
肘部法则画出 SSE 随 K 变化的曲线,找"拐点"——SSE 下降速度明显放缓的位置。
from sklearn.cluster import KMeans
import numpy as np
sse = []
K_range = range(2, 15)
for k in K_range:
km = KMeans(n_clusters=k, random_state=42)
km.fit(X)
sse.append(km.inertia_)
# 画肘部曲线
# 拐点处 SSE 下降速度骤减,该 K 值即为候选
肘部法则的盲区:当数据簇之间没有明显分离时,SSE 曲线是平滑的,根本没有明显拐点。此时肘部法则变成主观判断,不同人看同一张图可能选不同的 K。
3.2 轮廓系数
轮廓系数同时考虑簇内紧密度和簇间分离度:
s
(
i
)
=
b
(
i
)
−
a
(
i
)
max
(
a
(
i
)
,
b
(
i
)
)
s(i) = \\frac{b(i) – a(i)}{\\max(a(i), b(i))}
s(i)=max(a(i),b(i))b(i)−a(i)
其中
a
(
i
)
a(i)
a(i) 是点
i
i
i 到同簇其他点的平均距离(越小越好),
b
(
i
)
b(i)
b(i) 是点
i
i
i 到最近其他簇所有点的平均距离(越大越好)。
s
(
i
)
∈
[
−
1
,
1
]
s(i) \\in [-1, 1]
s(i)∈[−1,1],越接近 1 聚类越好。
from sklearn.metrics import silhouette_score
silhouette_scores = []
for k in K_range:
km = KMeans(n_clusters=k, random_state=42)
labels = km.fit_predict(X)
score = silhouette_score(X, labels)
silhouette_scores.append(score)
# 选 silhouette_score 最大的 K
best_k = K_range[np.argmax(silhouette_scores)]
轮廓系数的盲区:它偏好球形簇——因为
a
(
i
)
a(i)
a(i) 和
b
(
i
)
b(i)
b(i) 都基于欧氏距离计算。对于密度聚类发现的非球形簇,轮廓系数可能给出低分,但聚类结果实际上是好的。
3.3 Gap Statistic
Gap Statistic 的思路是:把真实数据的 SSE 和均匀随机分布的 SSE 对比,找两者差距最大的 K。
import numpy as np
from sklearn.cluster import KMeans
def gap_statistic(X, K_range, n_refs=10):
gaps = []
for k in K_range:
# 真实数据的 log(SSE)
km = KMeans(n_clusters=k, random_state=42)
km.fit(X)
log_sse_real = np.log(km.inertia_)
# 参考分布(均匀随机)的 log(SSE)
ref_sses = []
for _ in range(n_refs):
# 在数据边界内均匀采样
X_ref = np.random.uniform(
X.min(axis=0), X.max(axis=0), size=X.shape
)
km_ref = KMeans(n_clusters=k, random_state=42)
km_ref.fit(X_ref)
ref_sses.append(np.log(km_ref.inertia_))
gap = np.mean(ref_sses) – log_sse_real
gaps.append(gap)
return gaps
# 选 Gap 最大的 K,或 Gap 开始下降的第一个 K
Gap Statistic 的盲区:参考分布假设均匀分布,当数据本身有空间结构(如集中在某些区域)时,均匀参考可能不合适,导致 K 估计偏差。
3.4 三种方法的对比与选择
#mermaid-svg-LBNRmowZ4zmW5ZUx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LBNRmowZ4zmW5ZUx .error-icon{fill:#552222;}#mermaid-svg-LBNRmowZ4zmW5ZUx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LBNRmowZ4zmW5ZUx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .marker.cross{stroke:#333333;}#mermaid-svg-LBNRmowZ4zmW5ZUx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LBNRmowZ4zmW5ZUx p{margin:0;}#mermaid-svg-LBNRmowZ4zmW5ZUx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster-label text{fill:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster-label span{color:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster-label span p{background-color:transparent;}#mermaid-svg-LBNRmowZ4zmW5ZUx .label text,#mermaid-svg-LBNRmowZ4zmW5ZUx span{fill:#333;color:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .node rect,#mermaid-svg-LBNRmowZ4zmW5ZUx .node circle,#mermaid-svg-LBNRmowZ4zmW5ZUx .node ellipse,#mermaid-svg-LBNRmowZ4zmW5ZUx .node polygon,#mermaid-svg-LBNRmowZ4zmW5ZUx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .rough-node .label text,#mermaid-svg-LBNRmowZ4zmW5ZUx .node .label text,#mermaid-svg-LBNRmowZ4zmW5ZUx .image-shape .label,#mermaid-svg-LBNRmowZ4zmW5ZUx .icon-shape .label{text-anchor:middle;}#mermaid-svg-LBNRmowZ4zmW5ZUx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .rough-node .label,#mermaid-svg-LBNRmowZ4zmW5ZUx .node .label,#mermaid-svg-LBNRmowZ4zmW5ZUx .image-shape .label,#mermaid-svg-LBNRmowZ4zmW5ZUx .icon-shape .label{text-align:center;}#mermaid-svg-LBNRmowZ4zmW5ZUx .node.clickable{cursor:pointer;}#mermaid-svg-LBNRmowZ4zmW5ZUx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .arrowheadPath{fill:#333333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LBNRmowZ4zmW5ZUx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LBNRmowZ4zmW5ZUx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LBNRmowZ4zmW5ZUx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster text{fill:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx .cluster span{color:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LBNRmowZ4zmW5ZUx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LBNRmowZ4zmW5ZUx rect.text{fill:none;stroke-width:0;}#mermaid-svg-LBNRmowZ4zmW5ZUx .icon-shape,#mermaid-svg-LBNRmowZ4zmW5ZUx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LBNRmowZ4zmW5ZUx .icon-shape p,#mermaid-svg-LBNRmowZ4zmW5ZUx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LBNRmowZ4zmW5ZUx .icon-shape .label rect,#mermaid-svg-LBNRmowZ4zmW5ZUx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LBNRmowZ4zmW5ZUx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LBNRmowZ4zmW5ZUx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LBNRmowZ4zmW5ZUx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
分离明显
分离不明显
不确定
是
否
是
否
是
否
K值选择
数据簇分离度
肘部法则找SSE拐点
轮廓系数找最大值
Gap Statistic与随机分布对比
拐点是否清晰?
选定K
轮廓系数峰值是否明显?
Gap最大值是否明显?
结合业务判断K的选择最终要有业务意义
实际工程中,建议同时使用三种方法:如果三种方法指向同一个 K,置信度高;如果分歧大,需要回到业务层面判断——"分成 4 类"在业务上是否有对应的策略和资源。
4. 层次聚类:不需要预设 K 的探索利器
4.1 凝聚 vs 分裂
层次聚类有两种方向:
- 凝聚(Agglomerative,自底向上):每个点初始为一个簇,逐步合并最相似的簇,直到所有点归为一簇
- 分裂(Divisive,自顶向下):所有点初始为一簇,逐步分裂,直到每个点为一簇
sklearn 只实现了凝聚层次聚类(AgglomerativeClustering),因为分裂法的计算复杂度更高。
4.2 簇间距离的连接方式
合并哪两个簇取决于"簇间距离"的定义,sklearn 提供三种连接方式:
| Ward | 合并后总SSE增加最小 | 偏好大小相似的球形簇 |
| Complete | 两簇间最远点距离 | 偏好紧凑的簇,对噪声敏感 |
| Average | 两簇间所有点对的平均距离 | 介于 Ward 和 Complete 之间 |
Ward 是默认推荐——它等价于"合并导致信息损失最小的选择",与 K-Means 的 SSE 目标函数一致。
4.3 树状图:层次聚类的核心优势
层次聚类最大的优势是不需要预设 K——先用算法生成完整的合并树,然后通过切割树来获得任意数量的簇。
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
import matplotlib.pyplot as plt
# 计算连接矩阵
Z = linkage(X, method='ward')
# 画树状图
plt.figure(figsize=(12, 6))
dendrogram(Z, truncate_mode='lastp', p=30)
plt.axhline(y=Z[–4, 2], color='r', linestyle='–') # 切割线
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Sample Index')
plt.ylabel('Distance')
plt.show()
# 在指定距离切割,获得簇标签
labels = fcluster(Z, t=4, criterion='maxclust')
树状图的价值在于可视化的探索过程——观察哪些簇先合并(相似度高)、哪些最后才合并(差异大),可以直观判断数据的层次结构。这对于产品分类(大类→中类→小类)、用户分群(粗分→细分)等有天然层级的场景特别适合。
4.4 层次聚类的计算瓶颈
凝聚层次聚类的复杂度是
O
(
n
2
log
n
)
O(n^2 \\log n)
O(n2logn) 到
O
(
n
3
)
O(n^3)
O(n3),因为每步合并都要计算所有簇对之间的距离。样本量超过 5000 时,计算时间明显变长。sklearn 提供了 connectivity 参数(如 KNN 图)来限制可合并的簇对,把复杂度降到接近
O
(
n
log
n
)
O(n \\log n)
O(nlogn),但会牺牲一些合并自由度。
5. DBSCAN:密度视角的突破
5.1 核心思想:从"距离"到"密度"
K-Means 和层次聚类都是基于"距离"的——点与点的距离决定归属。DBSCAN 换了一个视角:基于密度——一个区域如果点的密度足够高,就是一个簇的一部分。
DBSCAN 定义了三种点:
- 核心点:在
ϵ
\\epsilon
ϵ 半径内有不少于 min_samples 个邻居 - 边界点:在某个核心点的
ϵ
\\epsilon
ϵ 半径内,但自身不是核心点 - 噪声点:既不是核心点也不是边界点
#mermaid-svg-VTnJhLBgtz939bPI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VTnJhLBgtz939bPI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VTnJhLBgtz939bPI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VTnJhLBgtz939bPI .error-icon{fill:#552222;}#mermaid-svg-VTnJhLBgtz939bPI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VTnJhLBgtz939bPI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VTnJhLBgtz939bPI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VTnJhLBgtz939bPI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VTnJhLBgtz939bPI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VTnJhLBgtz939bPI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VTnJhLBgtz939bPI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VTnJhLBgtz939bPI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VTnJhLBgtz939bPI .marker.cross{stroke:#333333;}#mermaid-svg-VTnJhLBgtz939bPI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VTnJhLBgtz939bPI p{margin:0;}#mermaid-svg-VTnJhLBgtz939bPI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VTnJhLBgtz939bPI .cluster-label text{fill:#333;}#mermaid-svg-VTnJhLBgtz939bPI .cluster-label span{color:#333;}#mermaid-svg-VTnJhLBgtz939bPI .cluster-label span p{background-color:transparent;}#mermaid-svg-VTnJhLBgtz939bPI .label text,#mermaid-svg-VTnJhLBgtz939bPI span{fill:#333;color:#333;}#mermaid-svg-VTnJhLBgtz939bPI .node rect,#mermaid-svg-VTnJhLBgtz939bPI .node circle,#mermaid-svg-VTnJhLBgtz939bPI .node ellipse,#mermaid-svg-VTnJhLBgtz939bPI .node polygon,#mermaid-svg-VTnJhLBgtz939bPI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VTnJhLBgtz939bPI .rough-node .label text,#mermaid-svg-VTnJhLBgtz939bPI .node .label text,#mermaid-svg-VTnJhLBgtz939bPI .image-shape .label,#mermaid-svg-VTnJhLBgtz939bPI .icon-shape .label{text-anchor:middle;}#mermaid-svg-VTnJhLBgtz939bPI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VTnJhLBgtz939bPI .rough-node .label,#mermaid-svg-VTnJhLBgtz939bPI .node .label,#mermaid-svg-VTnJhLBgtz939bPI .image-shape .label,#mermaid-svg-VTnJhLBgtz939bPI .icon-shape .label{text-align:center;}#mermaid-svg-VTnJhLBgtz939bPI .node.clickable{cursor:pointer;}#mermaid-svg-VTnJhLBgtz939bPI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VTnJhLBgtz939bPI .arrowheadPath{fill:#333333;}#mermaid-svg-VTnJhLBgtz939bPI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VTnJhLBgtz939bPI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VTnJhLBgtz939bPI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VTnJhLBgtz939bPI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VTnJhLBgtz939bPI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VTnJhLBgtz939bPI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VTnJhLBgtz939bPI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VTnJhLBgtz939bPI .cluster text{fill:#333;}#mermaid-svg-VTnJhLBgtz939bPI .cluster span{color:#333;}#mermaid-svg-VTnJhLBgtz939bPI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VTnJhLBgtz939bPI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VTnJhLBgtz939bPI rect.text{fill:none;stroke-width:0;}#mermaid-svg-VTnJhLBgtz939bPI .icon-shape,#mermaid-svg-VTnJhLBgtz939bPI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VTnJhLBgtz939bPI .icon-shape p,#mermaid-svg-VTnJhLBgtz939bPI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VTnJhLBgtz939bPI .icon-shape .label rect,#mermaid-svg-VTnJhLBgtz939bPI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VTnJhLBgtz939bPI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VTnJhLBgtz939bPI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VTnJhLBgtz939bPI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
DBSCAN 参数
ε (epsilon)邻域半径
min_samples核心点最小邻居数
ε 太大 → 不同簇合并ε 太小 → 簇碎片化
min_samples 太小 → 噪声也成簇min_samples 太大 → 真实簇被当噪声
DBSCAN 优势
不需要预设簇数 K
能发现任意形状的簇
自动标记噪声点
DBSCAN 局限
密度不均时表现差稀疏簇可能全部被当噪声
ε 和 min_samples 调参困难
高维空间距离失效维度灾难
5.2 ε 参数选择:k-距离图
ε 的选择可以用 k-距离图辅助——把每个点到其第 k 近邻的距离排序画图,找"拐点"。
from sklearn.neighbors import NearestNeighbors
import numpy as np
# k = min_samples
k = 5
nn = NearestNeighbors(n_neighbors=k)
nn.fit(X)
distances, _ = nn.kneighbors(X)
# 每个点到第 k 近邻的距离
k_distances = distances[:, –1]
k_distances_sorted = np.sort(k_distances)
# 画 k-距离图
# 拐点对应的距离值即为推荐的 ε
5.3 DBSCAN 对 K-Means 的突破
用月牙形数据对比:
from sklearn.cluster import DBSCAN, KMeans
from sklearn.datasets import make_moons
X, _ = make_moons(n_samples=500, noise=0.05, random_state=42)
# K-Means:把两个月牙各切一半
km_labels = KMeans(n_clusters=2, random_state=42).fit_predict(X)
# DBSCAN:正确识别两个月牙
db_labels = DBSCAN(eps=0.2, min_samples=5).fit_predict(X)
# DBSCAN 能沿月牙的形状跟踪密度,不依赖球形假设
5.4 DBSCAN 的致命弱点:密度不均
DBSCAN 用一个全局的 ε 判断密度,当数据中不同簇的密度差异大时(如一个簇很紧密、一个簇很松散),单一 ε 无法同时适应两种密度——紧密的簇会被识别,松散的簇会被当噪声。
这是 HDBSCAN 出现的核心动机。
6. HDBSCAN:自适应密度的解决方案
6.1 从 DBSCAN 到 HDBSCAN
HDBSCAN(Hierarchical DBSCAN)的核心改进是:不再依赖单一 ε 值。它在不同 ε 值上运行 DBSCAN,构建一棵层次密度树,然后从树中自动选择最稳定的簇。
#mermaid-svg-mOwn9ptkBFA0EBtj{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mOwn9ptkBFA0EBtj .error-icon{fill:#552222;}#mermaid-svg-mOwn9ptkBFA0EBtj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mOwn9ptkBFA0EBtj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mOwn9ptkBFA0EBtj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mOwn9ptkBFA0EBtj .marker.cross{stroke:#333333;}#mermaid-svg-mOwn9ptkBFA0EBtj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mOwn9ptkBFA0EBtj p{margin:0;}#mermaid-svg-mOwn9ptkBFA0EBtj .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster-label text{fill:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster-label span{color:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster-label span p{background-color:transparent;}#mermaid-svg-mOwn9ptkBFA0EBtj .label text,#mermaid-svg-mOwn9ptkBFA0EBtj span{fill:#333;color:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj .node rect,#mermaid-svg-mOwn9ptkBFA0EBtj .node circle,#mermaid-svg-mOwn9ptkBFA0EBtj .node ellipse,#mermaid-svg-mOwn9ptkBFA0EBtj .node polygon,#mermaid-svg-mOwn9ptkBFA0EBtj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mOwn9ptkBFA0EBtj .rough-node .label text,#mermaid-svg-mOwn9ptkBFA0EBtj .node .label text,#mermaid-svg-mOwn9ptkBFA0EBtj .image-shape .label,#mermaid-svg-mOwn9ptkBFA0EBtj .icon-shape .label{text-anchor:middle;}#mermaid-svg-mOwn9ptkBFA0EBtj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mOwn9ptkBFA0EBtj .rough-node .label,#mermaid-svg-mOwn9ptkBFA0EBtj .node .label,#mermaid-svg-mOwn9ptkBFA0EBtj .image-shape .label,#mermaid-svg-mOwn9ptkBFA0EBtj .icon-shape .label{text-align:center;}#mermaid-svg-mOwn9ptkBFA0EBtj .node.clickable{cursor:pointer;}#mermaid-svg-mOwn9ptkBFA0EBtj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mOwn9ptkBFA0EBtj .arrowheadPath{fill:#333333;}#mermaid-svg-mOwn9ptkBFA0EBtj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mOwn9ptkBFA0EBtj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mOwn9ptkBFA0EBtj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mOwn9ptkBFA0EBtj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mOwn9ptkBFA0EBtj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mOwn9ptkBFA0EBtj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster text{fill:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj .cluster span{color:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mOwn9ptkBFA0EBtj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mOwn9ptkBFA0EBtj rect.text{fill:none;stroke-width:0;}#mermaid-svg-mOwn9ptkBFA0EBtj .icon-shape,#mermaid-svg-mOwn9ptkBFA0EBtj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mOwn9ptkBFA0EBtj .icon-shape p,#mermaid-svg-mOwn9ptkBFA0EBtj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mOwn9ptkBFA0EBtj .icon-shape .label rect,#mermaid-svg-mOwn9ptkBFA0EBtj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mOwn9ptkBFA0EBtj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mOwn9ptkBFA0EBtj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mOwn9ptkBFA0EBtj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
HDBSCAN 工作流程
1. 构建互达距离图考虑密度加权
2. 生成最小生成树Prim/Kruskal算法
3. 构建簇层次树不同ε值下的DBSCAN结果
4. 压缩层次树基于簇稳定性
5. 提取最稳定簇auto参数自动选择
HDBSCAN vs DBSCAN
不需要调ε只需min_cluster_size
能处理密度不均的数据
输出概率隶属度而非硬分配
更稳健的参数选择min_cluster_size有直觉含义
6.2 HDBSCAN 的核心参数
HDBSCAN 把 DBSCAN 的两个参数(ε 和 min_samples)简化为一个直觉参数:
- min_cluster_size:一个簇至少包含多少个点。这个参数有明确的业务含义——“少于 50 个用户的群体不值得单独成簇”。
- min_samples(可选):控制核心点的保守程度。值越大,聚类越保守(更多点被标为噪声)。默认等于 min_cluster_size。
from sklearn.cluster import HDBSCAN
hdb = HDBSCAN(
min_cluster_size=30, # 至少30个点才能成簇
min_samples=5, # 核心点的邻居数要求
)
labels = hdb.fit_predict(X)
# labels 中 -1 表示噪声点
# probabilities 表示每个点的隶属置信度
probs = hdb.probabilities_
6.3 HDBSCAN 的概率输出
DBSCAN 给出硬分配(点属于簇 0 或簇 1),HDBSCAN 额外输出概率隶属度——每个点属于某簇的概率。这在业务场景中很有价值:一个用户有 0.7 的概率属于"高价值"簇、0.3 的概率属于"成长型"簇,可以做更精细的运营策略。
7. 聚类评估:没有真值标签时怎么办
聚类评估是无监督学习最头疼的环节。没有真值标签,不能算准确率,不能画混淆矩阵。评估方法分为两类:内部指标(不需要真值)和稳定性评估。
7.1 内部评估指标对比
| 轮廓系数 | 簇内紧密度 vs 簇间分离度 | 球形簇 | 对非球形簇不公平 |
| Calinski-Harabasz | 簇间方差 / 簇内方差 | 紧凑且分离的簇 | 偏好大簇 |
| Davies-Bouldin | 簇内距离均值 / 簇间距离 | 紧凑且分离的簇 | 偏好球形簇 |
三个指标都基于距离度量,因此都隐含偏好球形簇——对 DBSCAN 发现的非球形簇可能给低分。评估非球形聚类时,内部指标只能作为参考,不能作为唯一标准。
from sklearn.metrics import (
silhouette_score,
calinski_harabasz_score,
davies_bouldin_score,
)
# 三种指标对比
for name, labels in [("KMeans", km_labels), ("DBSCAN", db_labels), ("HDBSCAN", hdb_labels)]:
# DBSCAN/HDBSCAN 可能有噪声点(-1),需要过滤
mask = labels != –1
if mask.sum() < 2 or len(set(labels[mask])) < 2:
print(f"{name}: 噪声过多,无法计算指标")
continue
sil = silhouette_score(X[mask], labels[mask])
ch = calinski_harabasz_score(X[mask], labels[mask])
db = davies_bouldin_score(X[mask], labels[mask])
print(f"{name}: Silhouette={sil:.3f}, CH={ch:.1f}, DB={db:.3f}")
7.2 稳定性评估
一个好的聚类结果应该对数据扰动有鲁棒性——给数据加点噪声、删掉一些样本,聚类结果不应该大幅变化。
from sklearn.utils import resample
from sklearn.metrics import adjusted_rand_score
def stability_score(model, X, n_boot=10, noise_level=0.01):
"""通过 bootstrap 采样 + 加噪声评估聚类稳定性"""
base_labels = model.fit_predict(X)
scores = []
for _ in range(n_boot):
# 重采样 + 加噪声
X_boot = resample(X, random_state=None)
X_boot += np.random.normal(0, noise_level, X_boot.shape)
labels_boot = model.fit_predict(X_boot)
# 只比较共同样本
# ARI (Adjusted Rand Index) 衡量两种聚类的一致性
# 这里简化处理,实际需要对齐样本
scores.append(adjusted_rand_score(base_labels[:len(labels_boot)], labels_boot))
return np.mean(scores), np.std(scores)
稳定性评估的核心逻辑:如果聚类结果在数据轻微扰动后就大幅变化,说明聚类结构不可靠——可能是过拟合了噪声。
7.3 业务可解释性:最终验收标准
在业务分群场景中,聚类结果的最终验收标准不是任何数学指标,而是业务可解释性。具体操作方法:
import pandas as pd
def cluster_profiling(df, labels, feature_names):
"""生成簇画像表"""
df_result = df.copy()
df_result['cluster'] = labels
# 每个簇的关键特征统计
profile = df_result.groupby('cluster')[feature_names].agg(['mean', 'median', 'std'])
# 簇大小和占比
cluster_sizes = df_result['cluster'].value_counts().sort_index()
cluster_pct = (cluster_sizes / len(df_result) * 100).round(1)
return profile, cluster_sizes, cluster_pct
8. 从聚类到半监督:无监督不是终点
聚类结果可以直接用于半监督学习——把聚类结果当作"伪标签",训练监督模型。
#mermaid-svg-eQnqChX5Sp78M3SW{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eQnqChX5Sp78M3SW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eQnqChX5Sp78M3SW .error-icon{fill:#552222;}#mermaid-svg-eQnqChX5Sp78M3SW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eQnqChX5Sp78M3SW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eQnqChX5Sp78M3SW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eQnqChX5Sp78M3SW .marker.cross{stroke:#333333;}#mermaid-svg-eQnqChX5Sp78M3SW svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eQnqChX5Sp78M3SW p{margin:0;}#mermaid-svg-eQnqChX5Sp78M3SW .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-eQnqChX5Sp78M3SW .cluster-label text{fill:#333;}#mermaid-svg-eQnqChX5Sp78M3SW .cluster-label span{color:#333;}#mermaid-svg-eQnqChX5Sp78M3SW .cluster-label span p{background-color:transparent;}#mermaid-svg-eQnqChX5Sp78M3SW .label text,#mermaid-svg-eQnqChX5Sp78M3SW span{fill:#333;color:#333;}#mermaid-svg-eQnqChX5Sp78M3SW .node rect,#mermaid-svg-eQnqChX5Sp78M3SW .node circle,#mermaid-svg-eQnqChX5Sp78M3SW .node ellipse,#mermaid-svg-eQnqChX5Sp78M3SW .node polygon,#mermaid-svg-eQnqChX5Sp78M3SW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eQnqChX5Sp78M3SW .rough-node .label text,#mermaid-svg-eQnqChX5Sp78M3SW .node .label text,#mermaid-svg-eQnqChX5Sp78M3SW .image-shape .label,#mermaid-svg-eQnqChX5Sp78M3SW .icon-shape .label{text-anchor:middle;}#mermaid-svg-eQnqChX5Sp78M3SW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eQnqChX5Sp78M3SW .rough-node .label,#mermaid-svg-eQnqChX5Sp78M3SW .node .label,#mermaid-svg-eQnqChX5Sp78M3SW .image-shape .label,#mermaid-svg-eQnqChX5Sp78M3SW .icon-shape .label{text-align:center;}#mermaid-svg-eQnqChX5Sp78M3SW .node.clickable{cursor:pointer;}#mermaid-svg-eQnqChX5Sp78M3SW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eQnqChX5Sp78M3SW .arrowheadPath{fill:#333333;}#mermaid-svg-eQnqChX5Sp78M3SW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eQnqChX5Sp78M3SW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eQnqChX5Sp78M3SW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQnqChX5Sp78M3SW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eQnqChX5Sp78M3SW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQnqChX5Sp78M3SW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eQnqChX5Sp78M3SW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eQnqChX5Sp78M3SW .cluster text{fill:#333;}#mermaid-svg-eQnqChX5Sp78M3SW .cluster span{color:#333;}#mermaid-svg-eQnqChX5Sp78M3SW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eQnqChX5Sp78M3SW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eQnqChX5Sp78M3SW rect.text{fill:none;stroke-width:0;}#mermaid-svg-eQnqChX5Sp78M3SW .icon-shape,#mermaid-svg-eQnqChX5Sp78M3SW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQnqChX5Sp78M3SW .icon-shape p,#mermaid-svg-eQnqChX5Sp78M3SW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eQnqChX5Sp78M3SW .icon-shape .label rect,#mermaid-svg-eQnqChX5Sp78M3SW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQnqChX5Sp78M3SW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eQnqChX5Sp78M3SW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eQnqChX5Sp78M3SW :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
无标注数据10万条
聚类发现数据结构
选取每簇代表性样本距质心最近的N个点
人工标注代表性样本每簇50-100条
训练监督分类器用标注样本训练
分类器预测全部数据获得高质量标签
迭代优化低置信度样本再标注
这个流程的核心价值是降低标注成本——不是随机抽 500 条标注,而是先聚类再从每簇抽代表样本标注,确保标注覆盖数据空间的所有区域。500 条有代表性的标注样本,比 2000 条随机标注样本训练出的模型更好。
import numpy as np
from sklearn.cluster import KMeans
def representative_sampling(X, n_clusters=10, n_per_cluster=50):
"""从每个簇中选取距质心最近的样本作为代表"""
km = KMeans(n_clusters=n_clusters, random_state=42)
labels = km.fit_predict(X)
selected_indices = []
for k in range(n_clusters):
mask = labels == k
cluster_points = X[mask]
cluster_indices = np.where(mask)[0]
# 计算到质心的距离
distances = np.linalg.norm(cluster_points – km.cluster_centers_[k], axis=1)
# 选最近的 n_per_cluster 个
top_indices = cluster_indices[np.argsort(distances)[:n_per_cluster]]
selected_indices.extend(top_indices)
return np.array(selected_indices), labels
9. 实战:电商用户分群全流程
9.1 RFM 特征构建
RFM(Recency, Frequency, Monetary)是电商用户分群的经典特征体系:
- R(Recency):最近一次消费距今天数,越小越活跃
- F(Frequency):消费次数,越多越忠诚
- M(Monetary):消费总金额,越多越高价值
import pandas as pd
import numpy as np
# 模拟电商订单数据
np.random.seed(42)
n_users = 5000
orders = pd.DataFrame({
'user_id': np.random.randint(1, n_users + 1, 50000),
'order_date': pd.date_range('2025-01-01', periods=50000, freq='2H'),
'amount': np.random.exponential(200, 50000)
})
# 计算 RFM
snapshot_date = orders['order_date'].max() + pd.Timedelta(days=1)
rfm = orders.groupby('user_id').agg(
Recency=('order_date', lambda x: (snapshot_date – x.max()).days),
Frequency=('order_id', 'count'),
Monetary=('amount', 'sum')
).reset_index()
# 标准化(K-Means 对尺度敏感)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm[['Recency', 'Frequency', 'Monetary']])
9.2 四种算法对比
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN, HDBSCAN
from sklearn.metrics import silhouette_score
import time
# K-Means(K=4)
t0 = time.time()
km = KMeans(n_clusters=4, random_state=42)
km_labels = km.fit_predict(rfm_scaled)
print(f"K-Means: {time.time()–t0:.2f}s, silhouette={silhouette_score(rfm_scaled, km_labels):.3f}")
# 层次聚类(Ward连接)
t0 = time.time()
agg = AgglomerativeClustering(n_clusters=4, linkage='ward')
agg_labels = agg.fit_predict(rfm_scaled)
print(f"Agglomerative: {time.time()–t0:.2f}s, silhouette={silhouette_score(rfm_scaled, agg_labels):.3f}")
# DBSCAN
t0 = time.time()
db = DBSCAN(eps=0.5, min_samples=10)
db_labels = db.fit_predict(rfm_scaled)
mask = db_labels != –1
if mask.sum() > 0 and len(set(db_labels[mask])) > 1:
print(f"DBSCAN: {time.time()–t0:.2f}s, silhouette={silhouette_score(rfm_scaled[mask], db_labels[mask]):.3f}")
else:
print(f"DBSCAN: 噪声过多或簇数不足")
# HDBSCAN
t0 = time.time()
hdb = HDBSCAN(min_cluster_size=50, min_samples=10)
hdb_labels = hdb.fit_predict(rfm_scaled)
mask = hdb_labels != –1
if mask.sum() > 0 and len(set(hdb_labels[mask])) > 1:
print(f"HDBSCAN: {time.time()–t0:.2f}s, silhouette={silhouette_score(rfm_scaled[mask], hdb_labels[mask]):.3f}")
else:
print(f"HDBSCAN: 噪声过多或簇数不足")
9.3 簇画像与业务解读
# 用 K-Means 结果做画像(RFM 场景下 K-Means 表现通常最稳定)
rfm['cluster'] = km_labels
profile = rfm.groupby('cluster').agg({
'Recency': 'mean',
'Frequency': 'mean',
'Monetary': 'mean',
'user_id': 'count'
}).rename(columns={'user_id': 'count'})
profile['pct'] = (profile['count'] / len(rfm) * 100).round(1)
print(profile)
典型的四簇画像结果及业务策略映射:
#mermaid-svg-sSfriaSjUGuCDp8q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sSfriaSjUGuCDp8q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sSfriaSjUGuCDp8q .error-icon{fill:#552222;}#mermaid-svg-sSfriaSjUGuCDp8q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sSfriaSjUGuCDp8q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sSfriaSjUGuCDp8q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sSfriaSjUGuCDp8q .marker.cross{stroke:#333333;}#mermaid-svg-sSfriaSjUGuCDp8q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sSfriaSjUGuCDp8q p{margin:0;}#mermaid-svg-sSfriaSjUGuCDp8q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-sSfriaSjUGuCDp8q .cluster-label text{fill:#333;}#mermaid-svg-sSfriaSjUGuCDp8q .cluster-label span{color:#333;}#mermaid-svg-sSfriaSjUGuCDp8q .cluster-label span p{background-color:transparent;}#mermaid-svg-sSfriaSjUGuCDp8q .label text,#mermaid-svg-sSfriaSjUGuCDp8q span{fill:#333;color:#333;}#mermaid-svg-sSfriaSjUGuCDp8q .node rect,#mermaid-svg-sSfriaSjUGuCDp8q .node circle,#mermaid-svg-sSfriaSjUGuCDp8q .node ellipse,#mermaid-svg-sSfriaSjUGuCDp8q .node polygon,#mermaid-svg-sSfriaSjUGuCDp8q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sSfriaSjUGuCDp8q .rough-node .label text,#mermaid-svg-sSfriaSjUGuCDp8q .node .label text,#mermaid-svg-sSfriaSjUGuCDp8q .image-shape .label,#mermaid-svg-sSfriaSjUGuCDp8q .icon-shape .label{text-anchor:middle;}#mermaid-svg-sSfriaSjUGuCDp8q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sSfriaSjUGuCDp8q .rough-node .label,#mermaid-svg-sSfriaSjUGuCDp8q .node .label,#mermaid-svg-sSfriaSjUGuCDp8q .image-shape .label,#mermaid-svg-sSfriaSjUGuCDp8q .icon-shape .label{text-align:center;}#mermaid-svg-sSfriaSjUGuCDp8q .node.clickable{cursor:pointer;}#mermaid-svg-sSfriaSjUGuCDp8q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sSfriaSjUGuCDp8q .arrowheadPath{fill:#333333;}#mermaid-svg-sSfriaSjUGuCDp8q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sSfriaSjUGuCDp8q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sSfriaSjUGuCDp8q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sSfriaSjUGuCDp8q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sSfriaSjUGuCDp8q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sSfriaSjUGuCDp8q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sSfriaSjUGuCDp8q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sSfriaSjUGuCDp8q .cluster text{fill:#333;}#mermaid-svg-sSfriaSjUGuCDp8q .cluster span{color:#333;}#mermaid-svg-sSfriaSjUGuCDp8q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sSfriaSjUGuCDp8q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sSfriaSjUGuCDp8q rect.text{fill:none;stroke-width:0;}#mermaid-svg-sSfriaSjUGuCDp8q .icon-shape,#mermaid-svg-sSfriaSjUGuCDp8q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sSfriaSjUGuCDp8q .icon-shape p,#mermaid-svg-sSfriaSjUGuCDp8q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sSfriaSjUGuCDp8q .icon-shape .label rect,#mermaid-svg-sSfriaSjUGuCDp8q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sSfriaSjUGuCDp8q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sSfriaSjUGuCDp8q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sSfriaSjUGuCDp8q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
电商用户分群结果
簇0: 低R高F高M= 高价值忠诚用户占比~15%
簇1: 高R低F低M= 流失风险用户占比~30%
簇2: 低R低F中M = 新用户/成长型占比~25%
簇3: 中R中F中M= 普通用户占比~30%
策略: VIP专属服务高价值保留
策略: 唤醒推送+优惠券流失挽回
策略: 新手引导+首购优惠培养习惯
策略: 满减活动+品类推荐提升频次
9.4 算法选型的实际建议
经过实战对比,RFM 用户分群场景下各算法的表现:
- K-Means:RFM 特征经过标准化后近似球形分布,K-Means 的球形假设恰好成立。调参简单(只需选 K),簇大小均匀,画像清晰。推荐首选
- 层次聚类:结果与 K-Means 接近(Ward 连接本质也在最小化 SSE),但计算量大,5000 样本以上速度明显变慢。适合需要树状结构展示的场景
- DBSCAN:RFM 数据密度均匀时表现尚可,但经常把大量边缘用户标为噪声,不适合"每个用户都要分到一类"的业务需求
- HDBSCAN:能自动发现簇数,但 min_cluster_size 的调整影响很大,且噪声点在用户分群中不可接受(不能告诉运营"这些用户不分群")
选型结论:数据近似球形分布 + 要求每个点都有归属 + 簇大小不要太极端 → K-Means 仍然是工业界最实用的选择。DBSCAN/HDBSCAN 的价值在于探索性分析和异常检测场景,而非业务分群。
10. 四种聚类算法的选型决策表
#mermaid-svg-COLQTuZBjGrm73IP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-COLQTuZBjGrm73IP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-COLQTuZBjGrm73IP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-COLQTuZBjGrm73IP .error-icon{fill:#552222;}#mermaid-svg-COLQTuZBjGrm73IP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-COLQTuZBjGrm73IP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-COLQTuZBjGrm73IP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-COLQTuZBjGrm73IP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-COLQTuZBjGrm73IP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-COLQTuZBjGrm73IP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-COLQTuZBjGrm73IP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-COLQTuZBjGrm73IP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-COLQTuZBjGrm73IP .marker.cross{stroke:#333333;}#mermaid-svg-COLQTuZBjGrm73IP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-COLQTuZBjGrm73IP p{margin:0;}#mermaid-svg-COLQTuZBjGrm73IP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-COLQTuZBjGrm73IP .cluster-label text{fill:#333;}#mermaid-svg-COLQTuZBjGrm73IP .cluster-label span{color:#333;}#mermaid-svg-COLQTuZBjGrm73IP .cluster-label span p{background-color:transparent;}#mermaid-svg-COLQTuZBjGrm73IP .label text,#mermaid-svg-COLQTuZBjGrm73IP span{fill:#333;color:#333;}#mermaid-svg-COLQTuZBjGrm73IP .node rect,#mermaid-svg-COLQTuZBjGrm73IP .node circle,#mermaid-svg-COLQTuZBjGrm73IP .node ellipse,#mermaid-svg-COLQTuZBjGrm73IP .node polygon,#mermaid-svg-COLQTuZBjGrm73IP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-COLQTuZBjGrm73IP .rough-node .label text,#mermaid-svg-COLQTuZBjGrm73IP .node .label text,#mermaid-svg-COLQTuZBjGrm73IP .image-shape .label,#mermaid-svg-COLQTuZBjGrm73IP .icon-shape .label{text-anchor:middle;}#mermaid-svg-COLQTuZBjGrm73IP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-COLQTuZBjGrm73IP .rough-node .label,#mermaid-svg-COLQTuZBjGrm73IP .node .label,#mermaid-svg-COLQTuZBjGrm73IP .image-shape .label,#mermaid-svg-COLQTuZBjGrm73IP .icon-shape .label{text-align:center;}#mermaid-svg-COLQTuZBjGrm73IP .node.clickable{cursor:pointer;}#mermaid-svg-COLQTuZBjGrm73IP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-COLQTuZBjGrm73IP .arrowheadPath{fill:#333333;}#mermaid-svg-COLQTuZBjGrm73IP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-COLQTuZBjGrm73IP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-COLQTuZBjGrm73IP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-COLQTuZBjGrm73IP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-COLQTuZBjGrm73IP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-COLQTuZBjGrm73IP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-COLQTuZBjGrm73IP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-COLQTuZBjGrm73IP .cluster text{fill:#333;}#mermaid-svg-COLQTuZBjGrm73IP .cluster span{color:#333;}#mermaid-svg-COLQTuZBjGrm73IP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-COLQTuZBjGrm73IP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-COLQTuZBjGrm73IP rect.text{fill:none;stroke-width:0;}#mermaid-svg-COLQTuZBjGrm73IP .icon-shape,#mermaid-svg-COLQTuZBjGrm73IP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-COLQTuZBjGrm73IP .icon-shape p,#mermaid-svg-COLQTuZBjGrm73IP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-COLQTuZBjGrm73IP .icon-shape .label rect,#mermaid-svg-COLQTuZBjGrm73IP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-COLQTuZBjGrm73IP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-COLQTuZBjGrm73IP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-COLQTuZBjGrm73IP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
球形/近似球形
知道大致K值
不确定K值
非球形/任意形状
密度均匀
密度不均
不确定形状
<10万
>10万
<20维
>20维
聚类算法选型
数据形状?
需要预设K?
K-Means简单高效, 工业首选
层次聚类树状图探索
密度是否均匀?
DBSCANε参数可调
HDBSCAN自适应密度
先用多种算法探索再用可视化判断
样本量?
标准K-Means
Mini-Batch K-Means
维度?
DBSCAN直接使用
先降维再DBSCAN或改用HDBSCAN
聚类算法速查表
| K-Means | 球形 | 是 | 无 |
O ( n K d ) O(nKd) O(nKd) |
用户分群、图像量化 |
| 层次聚类 | 任意 | 否(后切树) | 无 |
O ( n 2 log n ) O(n^2 \\log n) O(n2logn) |
小数据探索、分类体系 |
| DBSCAN | 任意 | 否 | 自动标记 |
O ( n log n ) O(n \\log n) O(nlogn) |
空间数据、异常检测 |
| HDBSCAN | 任意 | 否 | 自动标记 |
O ( n log n ) O(n \\log n) O(nlogn) |
密度不均、参数敏感场景 |
11. 避坑清单
坑1:忘记标准化。 K-Means 用欧氏距离,如果 RFM 中 Recency 的量级是 0-365 天、Monetary 是 0-10000 元,Monetary 会完全主导聚类结果。必须先 StandardScaler 或 RobustScaler。
坑2:用轮廓系数选非球形聚类的K。 轮廓系数偏好球形簇,用它在 DBSCAN 结果上选参数会得到错误的"最优"值。非球形聚类用稳定性评估更合适。
坑3:把 DBSCAN 的噪声点当"异常用户"。 DBSCAN 的噪声点可能是真异常,也可能只是密度低的边缘点。在用户分群中把 30% 的用户标为"异常"是不可接受的——需要调整参数或换算法。
坑4:高维数据直接聚类。 维度超过 20 时,欧氏距离的区分度急剧下降(维度灾难)。应先降维(PCA/UMAP)再聚类,或使用专门的高维聚类方法。
坑5:只看指标不看业务。 轮廓系数 0.6 的聚类不一定比 0.4 的好——如果 0.4 的结果能翻译成业务策略而 0.6 的不能,应该选 0.4 的。
如果觉得这篇文章对聚类算法选型和评估方法有帮助,点赞收藏支持一下。更多机器学习实战内容请关注专栏,前文相关阅读:
- 机器学习项目方法论:从业务问题到算法选型的系统决策框架
- 数据预处理与质量保障:清洗策略/缺失值处理/异常值检测/数据验证/泄漏防范
- KNN 与距离度量:最近邻算法的直觉/度量选择/计算瓶颈与近似加速
- 集成学习精讲:Bagging/Boosting/Stacking/Blending——从"三个臭皮匠"到算法竞赛王者
- 模型评估与验证体系:交叉验证策略/统计检验/校准曲线/多指标决策框架
- 神经网络入门:从感知机到 MLP——理解深度学习的算法根基
