翻开一本泛黄的族谱,你会发现一个奇妙的事实:族谱从不直接告诉你“这个家族分成三房还是五房”,它只把所有人的血缘关系画成一棵大树。枝条底端是每一个单独的族人,往上走,夫妻合并成小家庭,小家庭合并成房头,房头再合并成整个宗族。如果你想按“三代亲”分人,就在族谱的相应高度画一条横线,横线下方每一根枝条就是一个家族;想再粗一点,就把线往上移;想再细一点,就把线往下移。同样地,有一类机器学习算法——层次聚类,就是给数据“修族谱”:它能把散落的样本点,按相似程度一层一层合并起来(凝聚),或者一层一层分裂下去(分裂),最终形成一棵完整的谱系树。你不需要提前告诉它“总共分几类”,它把从粗到细的所有可能都保存在这棵树里,供你随时取用。

岗位关联

掌握层次聚类能让你在探索数据阶段少走弯路:在不知道分几类时,用一棵树状谱系图把数据的嵌套结构一次看透,并支持任意粒度切割,是分析型AI工程师极好用的探索工具。

目录
  1. 可视化演示
  2. 谱系图思想
  3. 凝聚法原理
  4. 分裂法原理
  5. 距离度量
  6. 树状图切割
  7. 算法优劣
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示凝聚式自底向上合并、分裂式自顶向下拆分及四种连接方法,支持切割高度调节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

凝聚式层次聚类 紫色每个数据点初始自成簇,逐步合并最相似的簇对 青色谱系图记录合并历史,切割不同高度获得不同簇数 n points → merge → 1 cluster 自底向上构建嵌套聚类结构,无需预设簇数

谱系图思想

族谱的第一页,不会急着把族人分成几队,而是先把每个成员写进一张大网,标明谁在谁之下、谁与谁同支。层次聚类的产物也正是这样一张谱系图——树状图(Dendrogram)。它不像 K-Means 那样直接交给你“分成 3 堆”的答案,而是把“1 类、2 类、3 类……直到每个样本单独一类”的所有可能,全部以嵌套结构保存在同一棵树里。

在这个树状图中,最底层是原始样本,每个样本先是一根独立的枝条;枝条往上,相似样本两两接成小枝,小枝再汇成粗枝,最终汇成树干。整棵树就是一张完整的“家族关系图”:任意两个样本之间隔着几层节点,就能看出它俩亲疏几何。因为整个过程没有用到任何标签,所以它属于无监督学习。

层次结构可以用集合的递归嵌套来表达:

族谱的好处是“一次修成,世代受益”。层次聚类也一样:构建一次谱系图,以后想分粗分细,都只在这棵树上做文章,不必重跑算法。这就是它和“一次性分组”算法的根本区别。

Cnew=Ci∪Cj,Ci∩Cj=∅\mathcal{C}_{\text{new}} = \mathcal{C}_i \cup \mathcal{C}_j, \qquad \mathcal{C}_i \cap \mathcal{C}_j = \varnothing
一个高层簇,就是两个互不重叠的低层簇合并后的并集;一直套娃下去,就长成了族谱。
  1. 两个子簇$\mathcal{C}_i$ 和 $\mathcal{C}_j$ 是当前需要合并的两个簇,在族谱里相当于两房族人。
    Ci∩Cj=∅\mathcal{C}_i \cap \mathcal{C}_j = \varnothing
  2. 并集生新簇新簇 $\mathcal{C}_{\text{new}}$ 是两房合流,内部成员互不重叠。
    Cnew=Ci∪Cj\mathcal{C}_{\text{new}} = \mathcal{C}_i \cup \mathcal{C}_j

凝聚法原理

想象一个大家族刚刚从各地赶来过年:一开始每个人都是独立的个体,谁也不归谁管。随着拜年走动,关系最亲近的两个人先凑成一对小家庭;接着,来往最密切的两个小家庭又凑成一个大家庭;如此反复,最终全村拼成一张完整的族谱。这个“从底向上认亲”的过程,就叫凝聚法(Agglomerative),是层次聚类里最常用的路线。

算法开始时,把 $n$ 个样本都看成 $n$ 个只含一个成员的簇。然后反复执行:计算所有簇两两之间的距离,揪出距离最近的那对,把它俩合并成一个新簇;同时把旧的两个簇从名单上划掉。每轮合并,簇的数量就减一,一直循环到只剩一个大簇为止。

每次究竟该合并哪两个簇,可以用一个很简单的选择规则来描述:

由于每轮只看“当前谁最亲近”,贪心地做局部最优,所以凝聚法实现简单、直觉清晰。就像过年拜年,大家不会刻意安排谁跟谁亲,而是顺着关系自然走到一起,最终关系网也就长成了族谱的样貌。

(i^{\*}, j^{\*}) = \arg\min_{i < j} \; d(\mathcal{C}_i, \mathcal{C}_j)
在所有簇对里,挑出距离最小的那对 $(i^{\*}, j^{\*})$,这就是下一次要合并的“最有缘分的两家”。
  1. 遍历所有簇对对当前名单中的任意两个簇 $\mathcal{C}_i$、$\mathcal{C}_j$ 都算一次距离。
    i<j,Ci,Cj∈Ci < j,\qquad \mathcal{C}_i, \mathcal{C}_j \in \mathcal{C}
  2. 取距离最小$\arg\min$ 表示“让括号里取值最小的那个下标组合”。
    (i^{\*}, j^{\*}) = \arg\min_{i < j} \; d(\mathcal{C}_i, \mathcal{C}_j)
  3. 合并两簇把选中的两簇合并,簇数量减一,然后进入下一轮。
    \mathcal{C}_{\text{new}} = \mathcal{C}_{i^{\*}} \cup \mathcal{C}_{j^{\*}}

分裂法原理

凝聚法是从零往一建族谱,而分裂法恰恰相反:族长摊开一本总谱,看着整个宗族,先按矛盾最深的裂缝把族人劈成两房,再在每个房里继续找裂缝往下劈,直到每个小家庭都单独成页。这种“自顶向下分家”的路线,就叫分裂法(Divisive)。

分裂法一开始把全体样本当成一个大簇。接着循环做两件事:先选中当前“最该拆”的簇,再把它分成两个子簇。最该拆的簇怎么选?常见策略是选直径最大、内部成员最分散的那个——就像闹矛盾最严重的房头先分家。把它拆完之后,新出现的两个子簇继续参与下一轮竞选,直到每个样本都成为独立小家庭为止。

选哪个簇来拆,可以写作:

分裂法思路很优雅,但“如何拆得最好”是一个很棘手的组合优化问题,计算成本通常比凝聚法高。它就像分家产:方案看似清楚,真要把每一房都分得明明白白,反而比从底往上拼族谱更费力。

C^{\*} = \arg\max_{C \in \mathcal{P}} \; \operatorname{diam}(C), \qquad \operatorname{diam}(C) = \max_{x, y \in C} d(x, y)
每轮都挑内部距离最大的那个簇开刀,先解决“矛盾最集中”的群体。
  1. 簇直径一个簇里相距最远的两个点之间的距离,就是这个簇的“家庭裂缝宽度”。
    diam⁡(C)=max⁡x,y∈Cd(x,y)\operatorname{diam}(C) = \max_{x, y \in C} d(x, y)
  2. 选最大直径簇$\arg\max$ 表示在所有簇中挑直径最大的那个。
    C^{\*} = \arg\max_{C \in \mathcal{P}} \; \operatorname{diam}(C)
  3. 拆分对 $C^{\*}$ 做二分,得到两个子簇,替代原来的位置。
    C^{\*} \to C^{\*}_1 \cup C^{\*}_2

距离度量

两家到底算不算“亲近”?族谱里没有统一答案。有人看最亲的那条血缘线,表亲也认;有人要求两家最生疏的人都能谈得来,才算真亲近;还有人看两家人平时平均走动多不多;更有人只看两家“当家人”的关系。同理,层次聚类合并两个簇时,也需要先定义“簇与簇之间的距离”——这个定义叫链接准则(Linkage)。

最常用的四种准则是:最小距离(单链)、最大距离(全链)、平均距离(均链)和质心距离。它们合并时的选择标准,可以用下面这个统一的公式来概括:

选距离度量就像选“亲疏标准”:单链容易把细长的家族串成一串(链式效应),全链会把松散家族无情拆开,均链最稳健,质心距离最直观。现实中应先画几张小图,看看哪种标准更符合你的业务直觉,再决定让族谱按哪套规则生长。

dlink(Ci,Cj)={min⁡x∈Ci,y∈Cjd(x,y),单链:最近邻点决定亲疏max⁡x∈Ci,y∈Cjd(x,y),全链:最远邻点一票否决1∣Ci∣∣Cj∣∑x∈Ci∑y∈Cjd(x,y),均链:所有点对距离求平均∥μi−μj∥,质心:两簇中心点的距离d_{\text{link}}(\mathcal{C}_i, \mathcal{C}_j) =\begin{cases}\displaystyle \min_{x \in \mathcal{C}_i, y \in \mathcal{C}_j} d(x, y), & \text{单链:最近邻点决定亲疏} \\[6pt]\displaystyle \max_{x \in \mathcal{C}_i, y \in \mathcal{C}_j} d(x, y), & \text{全链:最远邻点一票否决} \\[6pt]\displaystyle \frac{1}{|\mathcal{C}_i||\mathcal{C}_j|} \sum_{x \in \mathcal{C}_i} \sum_{y \in \mathcal{C}_j} d(x, y), & \text{均链:所有点对距离求平均} \\[6pt]\displaystyle \| \mu_i - \mu_j \|, & \text{质心:两簇中心点的距离}\end{cases}
同一个合并过程,换一种“亲疏定义”,族谱的形状就完全不同。
  1. 单链(最小距离)取两个簇之间最近的一对样本距离,作为簇间距离。
    dmin⁡(Ci,Cj)=min⁡x∈Ci,y∈Cjd(x,y)d_{\min}(\mathcal{C}_i, \mathcal{C}_j) = \min_{x \in \mathcal{C}_i, y \in \mathcal{C}_j} d(x, y)
  2. 全链(最大距离)取两个簇之间最远的一对样本距离,喜欢紧凑家族的选它。
    dmax⁡(Ci,Cj)=max⁡x∈Ci,y∈Cjd(x,y)d_{\max}(\mathcal{C}_i, \mathcal{C}_j) = \max_{x \in \mathcal{C}_i, y \in \mathcal{C}_j} d(x, y)
  3. 均链(平均距离)把所有点对距离加起来取平均,最常用的折中方案。
    davg(Ci,Cj)=1∣Ci∣∣Cj∣∑x∈Ci∑y∈Cjd(x,y)d_{\text{avg}}(\mathcal{C}_i, \mathcal{C}_j) = \frac{1}{|\mathcal{C}_i||\mathcal{C}_j|} \sum_{x \in \mathcal{C}_i} \sum_{y \in \mathcal{C}_j} d(x, y)
  4. 质心距离先求两簇各自的质心 $\mu_i$、$\mu_j$,再算两个质心的欧氏距离。
    dcen(Ci,Cj)=∥μi−μj∥d_{\text{cen}}(\mathcal{C}_i, \mathcal{C}_j) = \| \mu_i - \mu_j \|

树状图切割

族谱最妙的地方在于:不需要重新修谱,就能得到任意粗细的家族划分。想在“三代以内”认亲戚,就在族谱对应高度画一道横线;想只看大宗族层面,就把横线往上抬。树状图切割(Cutting the Dendrogram)正是这个操作:水平切一刀,所有被这条线穿过的枝条,各自成为一个簇。

切割高度 $h$ 和簇数之间有一种天然的默契:线切得越高,越靠近树顶,穿过的枝条越少,得到的簇数量越少;线切得越低,越靠近树叶,得到的簇数量越多。换句话说,谱系图把“簇数 $k$”这个超参数,变成了“切割高度 $h$”这个更直觉化的旋钮。

设 $V$ 表示谱系图上的全部节点,$h_v$ 为节点 $v$ 的合并高度,则分割后簇的数量可以写成:

于是在实践中,你完全不需要提前拍板“分几类”:先构建出完整族谱,再反复移动横线观察不同粒度下的分组是否合理。这种“先种树、后切果”的用法,让层次聚类成为探索性分析中最灵活的工具之一。

k(h)=#{v∈V∣hv≤h<hparent(v)}k(h) = \#\left\{ v \in V \mid h_v \le h < h_{\text{parent}(v)} \right\}
高度 $h$ 像一道闸门,凡是“已经合并完成、但还没被更高层合并”的节点,就是一次完整聚类得到的簇。
  1. 节点合并高度每个内部节点 $v$ 都有一个合并高度 $h_v$,代表它形成时的距离。
    hv=该节点合并时的距离h_v = \text{该节点合并时的距离}
  2. 父子高度关系节点合并后,还要等更高层合并才会被吞并,所以它夹在两代高度之间。
    hv≤h<hparent(v)h_v \le h < h_{\text{parent}(v)}
  3. 计数为簇数数一数满足这个条件的节点个数,就得到切割高度 $h$ 对应的簇数。
    k(h)=#{v∈V∣hv≤h<hparent(v)}k(h) = \#\{ v \in V \mid h_v \le h < h_{\text{parent}(v)} \}

算法优劣

族谱虽好,修谱却是件费时费力的事。探访每一户人家、厘清每一段血缘都要大量的比对;到了应用层,层次聚类同样有得必有失。它的核心理念——无需预设簇数、输出完整嵌套结构——既是最大优点,也是“贵”的来源。

要构建谱系图,首先得维护一张两两之间的距离表:$n$ 个样本就要存 $n \times n$ 的距离矩阵。每轮合并后还要更新这张表并重新找最小值,朴素实现的复杂度约为 $O(n^3)$,即使使用优先队列优化,也仍有较高的开销。空间上,存储距离矩阵本身就需要 $O(n^2)$ 的内存。所以当样本量很大时,直接给全部数据修族谱会非常吃力。

所以在实际工程里,若数据量很大,通常先抽样或先用其他聚类跑一版,再用层次聚类在关键子集上精修。族谱的价值在于看得全、切得细,代价则是“修谱时出力多、族谱里怕混入外人”。理解了这套得与失,你才能真正驾驭它。

T(n)=O(n3)(朴素凝聚法),S(n)=O(n2)(距离矩阵)T(n) = O(n^3) \quad \text{(朴素凝聚法)}, \qquad S(n) = O(n^2) \quad \text{(距离矩阵)}
时间复杂度和空间复杂度都贴着 $n$ 的平方或立方走,样本一多,代价就迅速膨胀。
  1. 时间开销$n$ 个样本,每轮都要扫描距离矩阵找最小值,一共合并 $n-1$ 轮,朴素实现是 $O(n^3)$。
    T(n)=O(n3)T(n) = O(n^3)
  2. 空间开销为快速取用任意两簇的距离,需要存下一整张距离表。
    S(n)=O(n2)S(n) = O(n^2)
  3. 噪声影响一个孤立的噪声点会被拖到很晚才合并,成为族谱里突兀的长枝,干扰切割结果。

AI 应用场景

用户画像探索

阶段:探索性数据分析与特征工程

解决的问题:产品团队拿到上万条用户行为数据,却完全不知道应该分成几类用户。用传统方法只能靠经验先猜 $K$,猜错了全盘皆输。层次聚类能先构建一棵用户“族谱”,再从多个高度切割,快速判断最合理的用户群体数量。

具体案例:某电商平台对用户的下单频率、客单价、活跃时长做标准化后跑凝聚法,得到树状图。把横线放在高度 3.0 时得到“高频高价”“高频低价”“低频沉睡”三类人群,业务方觉得太粗;下移到 2.0 后,“高频低价”又分出“促销敏感型”和“忠实薅毛型”,直接改写了运营策略。

基因表达谱聚类

阶段:模型构建与生物信息学分析

解决的问题:基因表达数据通常只有样本标签,没有“该分几组”的先验知识。层次聚类输出的谱系树本身就有生物学意义——基因调控层级天然呈树状。

具体案例:生物信息团队对 500 个基因的表达谱做平均链接凝聚聚类,谱系图清晰分出几个大枝;再结合已知功能注释,发现每个大枝恰好对应一条信号通路。后续论文中的“基因家族树”就直接采用树状图展示。

异常检测辅助

阶段:数据质量监控与预处理

解决的问题:层次聚类的树状图对“落单者”极其敏感。一个离群点往往要到最后才会被合并进大簇,在谱系图上表现为一根又长又细的独枝,肉眼可辨。

具体案例:某风控系统用层次聚类分析登录设备特征,发现一个设备簇的合并高度始终高出其他簇一大截,顺着这条“长枝”追查,果然挖出一批机器批量注册的异常账号。

Python 演示

python37 行
import numpy as np def agglomerative_single(X):    n = len(X)    groups = [[i] for i in range(n)]    merges = []    for _ in range(n - 1):        best_d = float("inf")        best_pair = (0, 1)        for a in range(len(groups)):            for b in range(a + 1, len(groups)):                d = min(                    np.linalg.norm(X[i] - X[j])                    for i in groups[a]                    for j in groups[b]                )                if d < best_d:                    best_d = d                    best_pair = (a, b)        a, b = best_pair        merges.append((groups[a].copy(), groups[b].copy(), round(best_d, 3)))        groups[a] = groups[a] + groups[b]        groups.pop(b)    return merges if __name__ == "__main__":    X = np.array([        [0.0, 0.0],        [0.1, 0.2],        [2.0, 2.0],        [2.1, 1.9],        [5.8, 6.0],        [6.0, 5.9],    ])    result = agglomerative_single(X)    for left, right, dist in result:        print(left, "<->", right, "距离:", dist)
目的:用纯 NumPy 实现凝聚法中的单链(最小距离)版本,逐步打印每个簇的合并顺序与合并距离。
  1. groups = [[i] for i in range(n)]最初每个样本自立门户,单独成一簇。
  2. d = min(np.linalg.norm(X[i] - X[j]) for i in groups[a] for j in groups[b])用最小点对距离作为两个簇之间的亲疏。
  3. if d < best_d:贪心地记住当前最亲近的一对簇。
  4. groups[a] = groups[a] + groups[b]把两个簇合并成一个新簇。
  5. groups.pop(b)从族谱名单中划掉已被合并的旧簇,簇数量减一。
  6. result = agglomerative_single(X)执行全部合并,返回每一次“认亲”的记录。

常见误区

误区我以为是像 K-Means 一样,先要给算法一个参数 $K$,它才开始聚类。

正解层次聚类的核心优势恰恰是不需要预设 $K$。算法只负责把数据建成一棵树,$K$ 是你事后用“切族谱”的方式自己决定的。你把横线放在不同高度,就能得到不同数量的簇,完全不用重跑算法。

为何常见:初学者先学了 K-Means,“先定簇数再聚类”的思维惯性太强,容易把这种印象套到所有聚类算法上。

误区反正都是同一棵族谱树,从底往上拼和从顶往下拆,最后长得应该一模一样。

正解两者虽然在理论上都能得到树状结构,但贪心策略不同:凝聚法每步只盯“最近的两个簇”,分裂法每步要处理“最难拆的簇”,中间步骤的误差分叉会让最终树形明显不同。而且分裂法通常因为每步都要做复杂的二分搜索,计算开销更大。

为何常见:看到两张图都像树,就误以为只是方向不同、结果等价,忽略了中间决策路径对结果的深远影响。

误区反正都是算距离,单链、全链、平均链差别应该不大,用默认的就完事了。

正解不同的链接准则会塑造出完全不同的族谱。单链容易产生“链条型”的细长簇;全链倾向生成紧凑的球形簇;平均链则是一个常用折中。实际工作中至少应对比两三种准则,再结合业务解读选型。

为何常见:所有公式都长得很像,看起来只差一个 $\min$、$\max$ 或平均值,初学者很难预感到这会造成结构性的差异。

误区反正聚类效果这么好,我有 100 万条数据,直接丢进去跑不就行了。

正解层次聚类需要维护两两距离矩阵,空间复杂度 $O(n^2)$,朴素合并复杂度可达 $O(n^3)$,百万级数据会直接撑爆内存。正确姿势是先采样、降维或先用快速聚类粗筛,再在关键子集上构建谱系树。

为何常见:很多人只看到谱系图直观强大,却没算过“修整本族谱”背后的计算代价。

面试题

Q1. 层次聚类最终输出的核心结果是什么?

单选题 · easy

  • A.一棵树状谱系图← 正确答案
  • B.一张平面散点图
  • C.一组数值标签表
  • D.一条回归方程线

答案:A。层次聚类的核心输出是像族谱一样的树状谱系图,它保留从底到顶的全部嵌套关系。平面散点图是可视化工具,数值标签是 K-Means 式输出,回归方程则完全无关。

Q2. 凝聚法开始时,每个簇的初始状态是什么?

单选题 · easy

  • A.每个样本单独一簇← 正确答案
  • B.全体样本合成一簇
  • C.随机拆成两个簇
  • D.先假设簇数为 K

答案:A。凝聚法是自底向上的“认亲”过程,初始时每个样本都自立门户。全体样本合成一簇是分裂法的起点,随机拆簇和预设 K 都不属于凝聚法。

Q3. 下列哪一项是“单链距离”(最小距离)的含义?

单选题 · mid

  • A.两个簇中最接近的两个点距离← 正确答案
  • B.两个簇中最远的两个点距离
  • C.两个簇的质心之间距离
  • D.两个簇所有点对距离均值

答案:A。单链距离看的是两个簇之间“最近的两个人”的距离,像族谱里最亲近的那条血缘线。最远点距离是全链,质心距离是两簇中心点的距离,均值则是平均链。

Q4. 树状图中,切割线高度越高,得到的簇数通常会?

单选题 · mid

  • A.越少← 正确答案
  • B.越多
  • C.保持不变
  • D.完全不确定

答案:A。切割线越靠近树顶,穿过的枝条越少,簇数越少;越靠近底部,穿过的枝条越多,簇数越多。这就是“高度决定粒度”的直观体现。

Q5. 关于层次聚类的优势,下列说法正确的有?

多选题 · hard

  • A.无需预设簇数← 正确答案
  • B.输出完整嵌套谱系← 正确答案
  • C.可按多种粒度分析← 正确答案
  • D.一定比平面聚类快

答案:ABC。A、B、C 都是族谱核心理念带来的优点:不用提前猜 K、保留所有层级信息、一次建树多次切割。D 错误,因为层次聚类的复杂度通常远高于简单平面聚类。

Q6. 分裂法(Divisive)的基本思路是哪一种?

单选题 · mid

  • A.自顶向下拆分← 正确答案
  • B.自底向上合并
  • C.先随机后迭代
  • D.贪心不做拆分

答案:A。分裂法对应“分家”:从整簇出发,反复拆分成更小的簇。自底向上合并是凝聚法,随机迭代和贪心不做拆分都不是分裂法的定义。

Q7. 凝聚法中,当前应该合并的两个簇是如何选出的?

单选题 · easy

  • A.距离最近的两个簇← 正确答案
  • B.距离最远的两个簇
  • C.随机抽取两个簇
  • D.簇内最分散的两组

答案:A。凝聚法每轮都挑距离最近的两个簇合并,这是“贪心认亲”的核心。距离最远或随机选择都会破坏算法的基本逻辑。

Q8. 以下关于层次聚类过程的说法,正确的有?

多选题 · hard

  • A.凝聚法从单点簇开始← 正确答案
  • B.分裂法从整簇开始← 正确答案
  • C.凝聚法会删除已合并簇← 正确答案
  • D.分裂法不断合并近簇

答案:ABC。凝聚法从单样本簇开始向上合并,分裂法从整体簇开始向下拆分,两者方向相反。凝聚法每次合并后会把旧簇从名单中删除,而分裂法做的是拆分而非合并,所以 D 错。

Q9. 对 n 个样本做层次聚类,存储两两距离矩阵所需的空间复杂度约为?

单选题 · hard

  • A.O(n^2)← 正确答案
  • B.O(n log n)
  • C.O(n^3)
  • D.O(1)

答案:A。距离矩阵要记录任意两两样本的距离,共约 $n^2/2$ 个数值,所以空间复杂度是 $O(n^2)$。$O(n^3)$ 是朴素凝聚法的时间复杂度,$O(n \log n)$ 和 $O(1)$ 都不符合。

Q10. 关于“树状图切割”,下列表述正确的有?

多选题 · mid

  • A.高度越高簇越少← 正确答案
  • B.高度越低簇越多← 正确答案
  • C.一次构建多次切割← 正确答案
  • D.每次切割必须重算

答案:ABC。树状图把不同粒度全部保存在树中,切割高度决定簇数,且一次建树后可以随意在各种高度切割,不需要重新训练。D 恰恰相反,这正是树状图的价值所在。