Harmony算法原理(二):软聚类、RBF核与误差修正项
上一章我们把Harmony的整体框架讲清楚了。这一章,我打算深入三个核心机制——软聚类、RBF核和误差修正项。说白了,这三个东西才是Harmony真正「去批次效应」的杀手锏。
我自己刚开始用Harmony时,光调参数就折腾了好几周。后来把这三个机制拆开理解,才发现原来每个设计都有它的道理。咱们一个一个来看。
1. 从硬分配到软聚类:为什么非要「模糊」?
传统的k-means大家都很熟吧?每个细胞只能属于一个簇,非此即彼。这叫硬聚类。
但现实数据哪有这么干净?一个细胞可能处于两种细胞类型的过渡状态,也可能因为批次效应被压到了两个簇的边界上。你硬要给它贴一个标签,后续整合就会出问题。
Harmony用的是模糊C均值(Fuzzy C-Means, FCM)。每个细胞不是「属于某个簇」,而是「以多少概率属于每个簇」。
数学上看,我们有一个隶属度矩阵 U,维度是 n_cells × n_clusters。矩阵里每个元素 uik 表示细胞 i 属于簇 k 的概率。满足两个条件:
- 0 ≤ uik ≤ 1
- 每行之和为1(所有概率加起来等于100%)
这个初始化怎么做的?我习惯先用k-means跑一个粗糙的分簇,然后用距离的倒数来初始化隶属度。你想想看,离某个簇中心越近,初始隶属度就越高。这样迭代起来收敛更快。
核心思路:软聚类让Harmony在整合时「留有余地」。一个细胞可以同时参考多个簇的信息,不会因为硬性分错而带偏全局。
我的经验:nclust 参数一般设 3~8 就行。单细胞数据里细胞类型通常就这么多。设太大反而会让每个簇内细胞太少,隶属度估计不稳定。
2. RBF核:线性度量不够用了
好,现在有了隶属度矩阵,我们要算细胞之间的相似度。问题来了:批次效应经常是非线性的。
举个例子,两个相同类型的细胞,在不同批次里可能被测量系统扭曲成完全不同的分布。你用欧氏距离去算,它们「看起来」差得很远。但实际上它们应该是「相似」的。
怎么办?用核方法。
Harmony选的是高斯径向基函数核(RBF Kernel)。公式长这样:
K(x, y) = exp(-||x - y||² / (2 * σ²))
直观理解很简单:两个细胞在原始空间里距离越远,核函数输出的相似度就越低。但它是非线性衰减的——近的点给高权重,远的点权重迅速归零。这比简单地用欧氏距离做线性缩放要灵活得多。
这里面有个关键参数 σ(sigma),也就是带宽。它控制着「多近算近」。
- σ 太小:只有离得非常近的细胞才被当作相似,整合效果局部化,全局批次效应去不掉。
- σ 太大:所有细胞都被当作相似,相当于没做核变换,退化成线性度量。
Harmony内部其实做了一个自适应带宽——它会根据每个簇内细胞的离散程度,动态调整σ。我在项目中遇到过几次:用固定σ跑出来的整合结果总有点「拧巴」,后来开了自适应带宽,一下就顺了。
避坑指南:我曾经在一个人脑数据上直接用默认σ,结果神经元和胶质细胞被混在了一起。后来发现是σ设太大,导致跨类型的细胞也被误判为相似。所以sigma这个参数,我建议你先设一个较小的值(比如0.05),再逐步增大,观察分群变化。
3. 误差修正项:别让迭代跑偏了
有了软聚类的隶属度,有了RBF核算的相似度,接下来就是迭代优化了。但这里有个隐患——迭代次数多了,聚类可能会过度拟合局部结构,反而把批次效应「学」进去了。
Harmony加入了一个误差修正项,也叫正则化项。它的作用是惩罚那些「过于极端」的聚类结果。
具体来说,Harmony在目标函数里加了一项:
J = 原始聚类损失 + λ * 正则化项
其中 λ(lambda)控制修正的强度。λ 越大,聚类结果越「保守」,不容易被个别异常细胞带偏。
我自己的经验是:λ 设为 0.5~1.0 通常效果不错。如果数据里批次效应特别严重,可以适当加大到 2.0。但别超过 5.0,否则聚类结果会过于平滑,真实生物学差异也被抹掉了。
正则化项的具体形式是岭回归(Ridge)类型的惩罚——对聚类中心的偏移做限制。每次迭代更新时,中心不能跑得太远,相当于加了一个「阻尼器」。
收敛保证:误差修正项让目标函数在迭代过程中单调递减(至少不增加)。这意味着算法一定会收敛,不会出现振荡或发散。这在工程实现上非常重要——你不需要手动设置最大迭代次数,算法自己会停。
4. 三者的协同:一张图看清
好,三个机制都讲完了。它们不是各自独立的,而是串在一条流水线上。我画了一张图,把整个逻辑串起来:
这张图里,数据从PCA坐标进入,先经过软聚类得到隶属度矩阵,再用RBF核做非线性变换,然后通过误差修正项约束迭代过程。三个模块循环往复,直到目标函数收敛。我每次给团队讲这部分都直接拿这张图——比看十页公式都管用。
5. 代码里怎么体现?
说了这么多理论,咱们看看在R里怎么实际控制这三个机制。Harmony的核心函数就是 HarmonyMatrix,相关参数直接对应:
library(harmony)
result <- HarmonyMatrix(
data_mat = pca_coords, # PCA降维后的坐标
meta_data = sample_info, # 样本元数据(批次信息等)
vars_use = "batch", # 要矫正的批次变量
nclust = 5, # 软聚类的簇数量
lambda = 0.8, # 误差修正项的正则化强度
sigma = 0.1, # RBF核的带宽参数
max_iter = 10, # 最大迭代次数(通常5~10就收敛)
early_stop = TRUE # 早停:收敛后自动停止
)
调参建议:我一般先固定 nclust=5,sigma=0.1,lambda=0.8 跑一遍。然后看UMAP图里批次是否混在一起。如果混得不够好,先降 sigma(到0.05),再升 lambda(到1.2)。大部分数据集在这个范围内都能找到合适的组合。
你可以把 nclust、sigma、lambda 理解成三个旋钮。它们分别对应本章的三个核心机制:
| 参数 | 对应机制 | 作用 | 典型范围 |
|---|---|---|---|
nclust |
软聚类 | 控制分簇粒度,影响隶属度矩阵的维度 | 3 ~ 8 |
sigma |
RBF核 | 控制非线性相似度的衰减速度 | 0.05 ~ 0.5 |
lambda |
误差修正项 | 控制正则化强度,防止过拟合 | 0.5 ~ 2.0 |
嗯,到这里,Harmony的三个核心机制就全讲透了。你想想看,软聚类负责「模糊归属」,RBF核负责「非线性度量」,误差修正项负责「稳定收敛」——三者缺一不可。搞懂这些,你再去调参就不是盲人摸象了。
一个重要提醒:别一次性把三个参数都大幅改动。我习惯每次只调一个,跑完看结果再调下一个。三个一起动,你根本不知道是哪个起了作用,debug会非常痛苦。