scikit-learn/AgglomerativeClustering · single linkage

按需计算距离,构建单链聚类层次

scikit-learn 的单链聚类使用 Prim 派生的 MST-LINKAGE-CORE,维护每个未加入样本到当前集合的最小距离,随后用并查集生成聚类层次。

1.6.1检索与计算核验 2026-10-12
相关概念

工程问题#

单链聚类以两个簇之间最近的样本距离决定合并顺序。若先保存全部 n 个样本之间的距离,需要 O(n²) 空间;对于可以直接计算距离的向量输入,完整距离矩阵并非必须。

实际实现#

未提供 connectivity、选择 single linkage 且使用内置可用距离度量时,linkage_tree 调用 mst_linkage_core。算法保存未加入样本到已处理集合的当前最小距离,每次纳入距离最小的样本,再用这个新样本更新其余距离。这沿用 Prim 的扩展规则,但仅记录足以恢复单链聚类的代表节点三元组;稳定排序后,single_linkage_label 用并查集给每次合并生成簇编号。

处理流程#

  1. 确认进入 single linkage 的向量距离优化路径,准备连续的 double 输入
  2. 按需计算新样本到其余样本的距离,更新到当前集合的最小距离
  3. 选取最小值对应的样本继续扩展,记录聚类所需的代表节点与距离
  4. 稳定排序记录,用并查集追踪已合并簇并生成 children_ 和合并距离

源码片段#

固定版本 · 原文摘录
sklearn/cluster/_agglomerative.py1.6.1 · L569–L588
查看原文件
569        if (570            linkage == "single"571            and affinity != "precomputed"572            and not callable(affinity)573            and affinity in METRIC_MAPPING64574        ):575            # We need the fast cythonized metric from neighbors576            dist_metric = DistanceMetric.get_metric(affinity)577578            # The Cython routines used require contiguous arrays579            X = np.ascontiguousarray(X, dtype=np.double)580581            mst = _hierarchical.mst_linkage_core(X, dist_metric)582            # Sort edges of the min_spanning_tree by weight583            mst = mst[np.argsort(mst.T[2], kind="mergesort"), :]584585            # Convert edge list into standard hierarchical clustering format586            out = _hierarchical.single_linkage_label(mst)587        else:588            out = hierarchy.linkage(X, method=linkage, metric=affinity)
linkage_tree · single linkage 优化分支

只有内置可用度量的 single 路径调用 mst_linkage_core;随后稳定排序并用 single_linkage_label 生成聚类树,其他输入走 scipy.cluster.hierarchy.linkage。

sklearn/cluster/_hierarchical_fast.pyx1.6.1 · L478–L499
查看原文件
478    for i in range(n_samples - 1):479480        in_tree[current_node] = 1481482        new_distance = INFINITY483        new_node = 0484485        for j in range(n_samples):486            if in_tree[j]:487                continue488489            right_value = current_distances[j]490            left_value = dist_metric.dist(&raw_data[current_node, 0],491                                          &raw_data[j, 0],492                                          num_features)493494            if left_value < right_value:495                current_distances[j] = left_value496497            if current_distances[j] < new_distance:498                new_distance = current_distances[j]499                new_node = j
mst_linkage_core · 更新距离并选下一个样本

将 current_node 加入集合;对未加入样本计算新距离,与已知最小距离比较,然后选择最小的候选。这是 Prim 派生的逐点扩展规则。

sklearn/cluster/_hierarchical_fast.pyx1.6.1 · L501–L506
查看原文件
501        result[i, 0] = current_node502        result[i, 1] = new_node503        result[i, 2] = new_distance504        current_node = new_node505506    return np.array(result)
mst_linkage_core · 记录聚类代表节点

保存 current_node、new_node 和当前最小距离,但不保存这个最小距离对应的真实来源端点;结果用于聚类,不能直接当作原图的 MST 边表。

sklearn/cluster/_hierarchical_fast.pyx1.6.1 · L338–L348
查看原文件
338    @cython.wraparound(True)339    cdef intp_t fast_find(self, intp_t n) noexcept:340        cdef intp_t p341        p = n342        # find the highest node in the linkage graph so far343        while self.parent[n] != -1:344            n = self.parent[n]345        # provide a shortcut up to the highest node346        while self.parent[p] != n:347            p, self.parent[p] = self.parent[p], n348        return n
UnionFind.fast_find

查找当前簇代表,并沿途压缩父节点路径,供后续合并查询复用。

sklearn/cluster/_hierarchical_fast.pyx1.6.1 · L376–L395
查看原文件
376    result_arr = np.zeros((L.shape[0], 4), dtype=np.float64)377    U = UnionFind(L.shape[0] + 1)378379    for index in range(L.shape[0]):380381        left = <intp_t> L[index, 0]382        right = <intp_t> L[index, 1]383        delta = L[index, 2]384385        left_cluster = U.fast_find(left)386        right_cluster = U.fast_find(right)387388        result_arr[index][0] = left_cluster389        result_arr[index][1] = right_cluster390        result_arr[index][2] = delta391        result_arr[index][3] = U.size[left_cluster] + U.size[right_cluster]392393        U.union(left_cluster, right_cluster)394395    return np.asarray(result_arr)
_single_linkage_label · 合并簇编号

按已排序记录查找两端的当前簇,记录簇编号、合并距离和大小,再创建新的合并关系。这是生成模型 children_ 前的实际层次表示。

五段连续原文串起公开估计器使用的内部调用、Prim 派生内核和并查集合并。原论文明确说明该简化内核不维护完整 MST,因此案例保留这一边界。

收益与取舍#

工程分析

带来的收益

  • 距离按需生成,无须保留 O(n²) 的全点对距离矩阵
  • 以线性规模的距离数组和合并记录组织计算,并查集跟踪不断变化的簇编号

付出的代价

  • n 个样本需要 O(n²) 次距离比较;如果每次距离计算为 O(d),距离阶段为 O(n²d),并未因此消除二次时间成本
  • 此内核除输入外使用 O(n) 级工作数组和输出;Python 层转换连续 double 输入可能额外复制 O(nd) 数据
  • 单链方法容易沿局部近邻产生链状合并;这是聚类准则本身的特点,和距离矩阵是否保存无关

可比较的选择

预先保存完整距离矩阵适合已有距离数据或需要反复使用全部距离的场景,但保存 n×n 距离需要 O(n²) 空间。

提供稀疏 connectivity 约束限制可合并的邻接关系,并进入另一条实现路径;计算规模和可获得的聚类结果也可能改变。

版本与适用范围#

对应 scikit-learn 1.6.1:connectivity=None、linkage="single"、affinity 非 precomputed 且非 callable,并在 METRIC_MAPPING64 中。高层公开参数为 metric,内部 linkage_tree 沿用 affinity 命名。MST-LINKAGE-CORE 是 Prim 派生的聚类算法;它省略了恢复真正最小生成树所需的端点记录,不能把返回三元组逐行当作原图的 MST 边。这里的并查集为新簇创建父节点,含路径压缩但不是 LLVM 案例中的按秩合并。

参考来源#

源码、官方文档与原始论文
01
scikit-learn · linkage_tree · 优化路径调用

sklearn/cluster/_agglomerative.py · linkage_tree · 优化路径调用

源码1.6.1
02
scikit-learn · mst_linkage_core · 按需距离与集合扩展

sklearn/cluster/_hierarchical_fast.pyx · mst_linkage_core · 按需距离与集合扩展

源码1.6.1
03
scikit-learn · UnionFind / single_linkage_label

sklearn/cluster/_hierarchical_fast.pyx · UnionFind / single_linkage_label

源码1.6.1

相关实现与比较#

1 个相关案例
19 个匹配案例