欢迎光临
我们一直在努力

无监督学习实战:聚类算法选型/层次聚类/密度聚类/评估方法

文章目录

    • 前言
    • 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=1KxiCkxiμk2

其中

μ

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 的概率选择下一个质心(距离越远越可能被选)

  • 重复直到选满 K 个
  • 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噪声处理复杂度最佳场景
    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——理解深度学习的算法根基
    赞(0)
    未经允许不得转载:171主机测评 » 无监督学习实战:聚类算法选型/层次聚类/密度聚类/评估方法
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址