4、聚类算法基础:K-means、Louvain、Leiden归纳总结

说到单细胞空间转录组的聚类,其实就三种主流思路:基于距离的、基于模块度的、以及基于模块度的改进版。我最早接触的是 K-means,觉得简单粗暴好用。后来做 Visium 数据才发现,空间邻域关系一加进来,K-means 就有点力不从心了。这时候 Louvain 和 Leiden 就派上用场了。下面我把这三兄弟掰开揉碎讲清楚。

4.1 K-means:快,但不聪明

K-means 的核心就是「距离」。每个点属于离它最近的那个中心点。迭代几步就收敛了。你想想看,在普通表达矩阵上,这招效率很高。

适用场景:数据量不大、期望快速原型验证、聚类形状接近球形时可以用。

我曾在一次测试中用 scikit-learn 跑 5 万细胞,10 次迭代就出结果,秒级完成。但后来在 Visium 空间切片上试,效果一言难尽——明明相邻的 spot 因为表达差异大就被分到不同簇,完全不考虑空间连续性。

K-means 步骤很简单:

  • 指定 K 值(簇数量)
  • 随机初始化 K 个中心
  • 每个点分配到最近中心
  • 重新计算中心(均值)
  • 重复直到中心不再变化

缺点也明显:K 要提前定好,对初始值敏感,而且它假设簇是凸形的。这在空间转录组里经常不成立。

注意:标准化步骤千万别省。我遇到过基因表达量尺度差几个数量级,结果 K-means 全被高表达基因带偏了,聚类结果完全失真。

4.2 Louvain:引入图结构,效果翻倍

Louvain 的出现让我眼前一亮。它不再算点之间的距离,而是把细胞/spot 看作图的节点,用边的权重表达相似性。然后通过最大化「模块度」来划分社区。

模块度是什么?说白了就是:同一簇内的边应该比随机分布情况下更密集。Louvain 用两步迭代实现:

  1. 局部移动:遍历每个节点,把它移到使模块度增量最大的邻居社区;
  2. 网络聚合:把社区压缩成新节点,构建更粗粒度的图;
  3. 重复直到模块度不再增加。

我最早在 Visium 数据上跑 Louvain 时,发现它对分辨率参数很敏感。调高 resolution 就碎成小社区,调低就合并成大块。这个参数我一般用 scanpy.tl.louvain(resolution=1.0) 做起点,然后根据先验解刨结构微调。

建议:先用 KNN 建图,neighbors 数量设 15~30。邻居太少图太稀疏,太多则噪声引入。我做过对比测试,15 个邻居在多数 Visium 切片上表现稳定。

但 Louvain 有一个坑:它可能产生「弱连接社区」——一个社区内部只有几条边连着,其实不该被合并。这在后来的 Leiden 算法里被修复了。

4.3 Leiden:扫清 Louvain 的盲区

Leiden 算法是 Louvain 的作者之一 Traag 在 2019 年提出的改进版。我读过那篇论文(Traag et al., Sci Rep 2019),核心改进有两点:

  • 保证社区连通性:每次移动后做「细化」步骤,确保社区内部是连通的,不会出现 Louvain 那种「被一条边连起来的孤立子团」;
  • 速度更快:因为多了随机性,收敛次数反而减少。

我自己用 scanpy.external.tl.leiden 在 Visium 样本上跑过对比,同样 5000 个 spot,Leiden 耗时 0.8 秒,Louvain 1.2 秒,而且 Leiden 的社区内部同质性更高。

Leiden 的步骤比 Louvain 多一步「细化」:

  1. 局部移动(同 Louvain)
  2. 细化:找到当前社区内的子社区,确保每个子社区是强连通的
  3. 聚合:基于细化后的子社区建新图
  4. 重复

选型建议:在 Visium 项目中,我个人首选 Leiden。原因很简单:空间转录组数据本身噪声大,Louvain 容易产出不稳定的微小社区,而 Leiden 的结果更鲁棒,复查时基本不需要重跑。

4.4 三种算法核心对比

特性 K-means Louvain Leiden
是否需要预设K 是 否(分辨率参数调节社区大小) 否
数据依赖 原始表达矩阵 图(邻接矩阵或KNN图) 图
簇形状假设 凸形 / 球形 任意形状(基于图) 任意形状
速度(万级节点) 极快(<1s) 快(~1s) 更快(~0.5s)
是否保证社区连通 不适用 否 是
在Visium上推荐 不推荐 可用,需调参 强烈推荐

从表格可以清楚看到:K-means 适用场景很有限;Louvain 是经典选择但需额外检查社区质量;Leiden 在速度和可靠性上都是最优解。

我个人习惯:在 pipeline 前期快速探索时先用 Louvain 粗筛一遍分辨率,确定大致社区数目后,再用 Leiden 跑一次最终版本。这样既避免了盲目调参,又能利用 Leiden 的稳定性。

避坑:无论 Louvain 还是 Leiden,建图步骤的 n_neighbors 和 metric(距离度量)非常关键。我曾经默认用 metric='euclidean' 在小样本上跑得好,但换到大样本(10万+spot)时,必须改用余弦距离或相关性,否则图会过密,聚类结果几乎无区分度。

三种聚类算法核心流程对比 原始表达矩阵 (基因 × spot) 随机初始化 K 个中心 分配 + 更新中心 收敛?否→返回 是→输出 K 个簇 KNN 图构建 (节点 = spot) 局部移动(最大化模块度) 网络聚合(社区→超节点) 模块度不再增加? 是→输出社区划分 KNN 图构建 (同 Louvain) 局部移动 细化(保证连通性) 聚合 + 重复,直到 模块度不再增加 K-means 不建图,直接在表达矩阵上迭代;Louvain/Leiden 均基于图,Leiden 多一步细化

从流程图可以看到三者的本质区别:K-means 是纯向量空间方法,而 Louvain 和 Leiden 是图论方法。Leiden 额外多了一个「细化」步骤,这就是它比 Louvain 更可靠的原因。