批次效应矫正:那些默认值背后的秘密
你知道 Harmony 的
θ默认值是 2,scVI 的n_latent默认是 10——但你知道为什么吗?知道什么时候调它们会让结果变好,什么时候会让一切崩掉吗?
这篇文章不讲"Harmony vs scVI vs Seurat MNN 谁更好"——那类文章已经很多了。这篇讲的是:当你拿到一个新数据集,那些关键参数在数学上意味着什么,它们的默认值为何在大多数情况下管用,以及哪些边界条件会让它们失效。
先建立直觉:批次效应是什么在数学上在说什么
批次效应的本质是:同一细胞类型,在不同实验条件下测出来的基因表达向量,在高维空间里系统性偏移。
矫正方法大体分两类思路:
- 对齐型(Harmony、Seurat MNN):把不同批次的细胞嵌入拉到一起
- 生成模型型(scVI、scANVI、totalVI):显式建模"批次是噪声变量",学一个无批次的潜在表示
两者参数逻辑完全不同。下面分开讲。
一、scVI / scANVI 参数深潜
scVI 的核心是一个变分自编码器(VAE),目标函数是:
$$\mathcal{L} = \underbrace{\mathbb{E}{q(z|x,s)}[\log p(x|z,s)]}{\text{重建损失}} - \underbrace{\beta \cdot D_{KL}(q(z|x,s) | p(z))}_{\text{正则化损失}}$$
其中 $x$ 是观测到的基因计数,$s$ 是批次标签,$z$ 是潜在空间向量。理解这个公式,你就理解了 scVI 的所有核心参数。
1.1 n_latent:潜在空间维度
默认值:10
$z \in \mathbb{R}^{n_latent}$,是编码器学到的"无批次"细胞表示。
为什么默认 10 够用?
一般 scRNA-seq 数据集,细胞的生物学变异主要由有限数量的细胞类型/状态驱动。10 个维度足以区分 PBMCs 的 8-12 个主要细胞类型,以及它们之间的状态连续体。研究表明,对于 10k–50k 的典型数据集,前 10 个 PCA 成分能解释 > 70% 的方差——n_latent=10 的选择与这个经验规律对应。
什么时候应该调大?
| 场景 | 建议 |
|---|---|
| 数据集 > 100k 细胞、细胞类型极多(如全器官图谱) | 调到 20–30 |
| 包含罕见细胞亚群(< 0.5%),需要精细区分 | 调到 15–20 |
| 发育轨迹数据(连续细胞状态) | 调到 15–20 |
| 仅做批次矫正、不做下游分型 | 保持 10,不必大 |
什么时候会崩?
n_latent过大(如 50+):模型参数过多,在小数据集(< 5k 细胞)上容易过拟合——训练损失漂亮,但 UMAP 中批次点仍然分开(模型把批次信息"藏"进了多余的维度)n_latent过小(如 5):信息瓶颈太窄,不同细胞类型的表示开始重叠,下游聚类精度下降
快速判断法: 训练完后跑 model.get_latent_representation(),然后做 PCA,看前 n_latent 个成分的解释方差曲线。如果曲线很快趋于平坦(比如第 6 个成分后基本为 0),说明你的 n_latent 可以更小,模型在浪费容量。
1.2 n_hidden:隐藏层神经元数
默认值:128
编码器/解码器每个隐藏层的神经元数。这控制的是非线性映射的容量。
为什么 128 够用: 对于一般的 2000–5000 个高变基因输入,128 个神经元的隐藏层能捕捉足够的非线性特征。增大它在大多数数据集上没有显著提升,只会增加训练时间。
例外: 输入基因数非常大(如全基因组转录组,10k+ 基因),建议同步调大 n_hidden=256,否则第一层压缩步幅过大,容易丢细节。
1.3 dispersion:负二项分布的离散参数
选项:"gene" | "gene-batch" | "gene-label" | "gene-cell"
默认:"gene"
scVI 用负二项(NB)或零膨胀负二项(ZINB)建模基因计数:
$$p(x_{ng} | \mu_{ng}, \theta_g) = \text{NB}(\mu_{ng}, \theta_g)$$
$\theta_g$(有时记为 dispersion)控制每个基因计数分布的"散布程度":$\theta_g$ 越小,分布越分散(尾部越重);$\theta_g \to \infty$ 时趋近于泊松分布。
| 选项 | 含义 | 适用场景 |
|---|---|---|
"gene" |
每个基因一个 $\theta_g$,跨所有细胞/批次共享 | 绝大多数场景,默认首选 |
"gene-batch" |
每个(基因 × 批次)一个 $\theta$,允许批次间离散度不同 | 批次间测序深度差异巨大(如 10x vs Smart-seq2 混合) |
"gene-label" |
每个(基因 × 细胞类型)一个 $\theta$ | 有强先验知识,知道不同细胞类型对同一基因离散度差异大 |
"gene-cell" |
每个(基因 × 细胞)各一个 $\theta$ | 最灵活,但参数最多,需要大数据集(> 50k 细胞) |
为什么 "gene" 默认会崩的场景:
当你混合了单端测序(10x)和全长测序(Smart-seq2)时,同一基因的计数分布差异极大——10x 产生稀疏的计数,Smart-seq2 产生更连续的高计数。此时用同一个 $\theta_g$ 建模两个批次,等于强迫一个参数拟合两个完全不同的分布。改用 "gene-batch" 能显著改善这类数据集的 ELBO。
1.4 gene_likelihood:基因计数的概率分布
选项:"zinb" | "nb" | "poisson"
默认:"zinb"(零膨胀负二项)
ZINB 在 NB 基础上加了一个伯努利门控,用来建模技术性零值:
$$p(x_{ng} = 0) = \pi_{ng} + (1 - \pi_{ng}) \cdot \text{NB}(0 | \mu_{ng}, \theta_g)$$
什么时候用 "nb" 而非 "zinb":
- 数据集较新、测序深度较高(> 3000 UMI/cell),零值较少
- 数据已经过质控过滤,低质量细胞和 doublets 已去除
- 用
"zinb"时 UMAP 出现奇怪的细胞簇分裂——这通常是零膨胀参数在过拟合噪声
1.5 β(kl_weight):KL 散度的权重
默认值:1.0
这是最容易被忽视、影响最大的参数之一。
回看损失函数:
$$\mathcal{L} = \text{重建损失} - \beta \cdot D_{KL}(q | p)$$
- KL 项的作用:把后验 $q(z|x,s)$ 拉向标准正态先验 $p(z) = \mathcal{N}(0, I)$,强迫潜在空间平滑、连续、可插值。正是这个约束让不同批次的细胞在潜在空间里"混"在一起。
- 重建项的作用:确保解码器能从 $z$ 还原出原始计数,保留生物学信息。
两者是拮抗关系:
| β 值 | 效果 |
|---|---|
| β = 0 | 纯自编码器,没有正则化,批次效应完全保留在 z 中 |
| β < 1 | 轻正则化,重建好,批次矫正弱,适合批次效应很小的数据 |
| β = 1(默认) | 标准 VAE,平衡点,大多数场景适用 |
| β > 1(β-VAE) | 强正则化,批次混合好,但生物学细节可能被压缩丢失 |
| β >> 10 | 潜在空间坍缩(posterior collapse),所有细胞 z 趋同,聚类失去意义 |
实际调参建议:
- 默认
kl_weight=1.0,先跑,看 scVI 的 UMAP - 如果批次分离仍然明显:试
kl_weight=1.5–2.0 - 如果细胞类型之间边界模糊:适当降低到
kl_weight=0.5–0.8 - scVI 有
kl_weightwarm-up 机制(前几个 epoch 从 0 线性增加到目标值),这个 warm-up 本身很重要,不要禁用
注意:scVI 的 Python API 在 train() 里通过 kl_weight 或 plan_kwargs={"kl_weight": ...} 传入,而不是模型初始化时。不同版本 API 位置略有不同,查文档确认。
1.6 alpha(scANVI 特有):监督信号强度
scANVI 在 scVI 基础上加入了细胞类型标签的监督:
$$\mathcal{L}{scANVI} = \mathcal{L}{scVI} + \alpha \cdot \mathcal{L}_{classifier}$$
alpha 控制分类器损失的权重。默认 alpha=1。
- 调大
alpha:分类器更强,标注细胞的表示更规整,但无标注细胞更依赖分类器预测(过拟合风险) - 调小
alpha:更多依赖无监督部分,适合标注细胞比例 < 10% 的场景
二、Harmony2 参数深潜
Harmony 的核心思路是迭代软聚类 + 质心移动:
- 在 PCA 空间中,对所有细胞做 K-means 软聚类,得到每个细胞属于每个簇的隶属度
- 计算每个簇内各批次细胞的质心偏差
- 将每个细胞的嵌入向各自批次质心的反方向平移(消除批次偏差)
- 重复直到收敛
Harmony2(最新版本)引入了软余弦矫正,允许矫正方向在空间中连续变化,而非对每个簇做硬性平移。
2.1 theta(θ):批次多样性惩罚
默认值:2(可以是一个向量,每个批次协变量一个值)
$\theta$ 控制 Harmony 在计算软聚类质心时,对批次不均匀性的惩罚强度:
$$\text{簇内惩罚} \propto \theta \cdot \sum_b \frac{n_b}{n} \log \frac{n_b / n}{R_{kb}}$$
其中 $R_{kb}$ 是批次 $b$ 的细胞对簇 $k$ 的隶属度,$n_b/n$ 是批次 $b$ 的细胞比例。
直觉理解:$\theta$ 越大,Harmony 越努力让每个簇内各批次细胞的比例接近全局比例,即强制混合。
| θ 值 | 效果 |
|---|---|
| θ = 0 | 不惩罚,等于不做批次矫正 |
| θ = 2(默认) | 适中矫正,适合批次效应中等的数据 |
| θ = 4–6 | 强矫正,批次混合好,但可能将不同细胞类型强行压在一起 |
| θ > 8 | 过矫正风险——生物学差异开始被当成批次效应去掉 |
什么时候调高 θ:
- 批次差异来源非常强(不同测序平台)
- 数据集有经验证的同种细胞类型,UMAP 里却分成了两团
什么时候降低 θ:
- 你的"批次"其实是真实的生物学差异(如疾病 vs 正常,时间点不同)
- 矫正后发现一些本来应该分开的细胞类型被合并了
多个协变量时的 θ 向量:
harmony_kwargs = {
"theta": [2, 1], # 第一个协变量(测序批次)矫正强,第二个(供体)矫正弱
}
2.2 sigma(σ):软聚类带宽
默认值:0.1
控制 K-means 软聚类的"模糊度"。$\sigma$ 越小,每个细胞越确定地只属于一个簇(趋向硬聚类);$\sigma$ 越大,隶属度分布越均匀。
为什么 0.1 通常够: 这个值使得 PCA 空间中距离最近簇的细胞隶属度 >> 其他簇,同时又不完全变成硬分配。
崩的场景: 数据分布非常不均匀(细胞类型比例差异极大),小 $\sigma$ 时稀有细胞类型可能被划入相邻大簇,导致稀有群体在矫正后被"同化"。这时适当增大 $\sigma$(如 0.2)给稀有群体更多"缓冲"。
2.3 nclust:软聚类数目
默认值:min(100, N_cells / 30)(大约)
Harmony 内部进行 K-means 软聚类的簇数。这个参数经常被忽视,但影响显著。
nclust太小:每个"超簇"混入了太多不同细胞类型,矫正时把真实异质性平均掉了nclust太大:超过实际生物学群体数,收敛慢,且每簇细胞数太少时质心估计不稳定
建议: 至少是你预期细胞类型数的 5–10 倍。50k 细胞的数据集,nclust=100–200 通常是合理的。
2.4 block_size:每次更新使用的细胞比例
默认值:0.05(即每次用 5% 的细胞)
Harmony 每次迭代并不用所有细胞更新参数,而是采样一个 block。这是一个随机梯度思路,作用是:
- 降低内存占用
- 为收敛过程引入随机性,避免局部极小值
什么时候调:
- 数据集非常小(< 5k 细胞):
block_size=0.05每次只用 250 个细胞,随机性过大,收敛不稳定。调到0.2–0.5。 - 数据集非常大(> 500k 细胞):默认 5% 仍然很多(25k 细胞每次),可适当降到
0.01–0.02加速每步。
2.5 max_iter_harmony 和 eps_harmony:收敛控制
默认:max_iter_harmony=10,eps_harmony=1e-4
Harmony 运行到目标函数变化 < eps_harmony 或达到最大迭代次数停止。
10 次迭代通常够用,但有时不够: 批次效应复杂、协变量多时,观察 Harmony 的 objective function 曲线(harmony_object.objective_harmony),如果到第 10 次还没收敛(曲线还在下降),调大 max_iter_harmony=20。
三、totalVI:多模态的额外参数
totalVI 同时处理 RNA 和蛋白质(CITE-seq / ASAP-seq)数据,在 scVI 基础上加了:
3.1 蛋白质背景先验
CITE-seq 的蛋白质信号里有非特异性背景噪声,totalVI 用混合模型建模:
$$p(\text{protein}) = \pi \cdot \mathcal{N}(\mu_{bg}, \sigma_{bg}) + (1-\pi) \cdot \mathcal{N}(\mu_{fg}, \sigma_{fg})$$
protein_background_prior_mean(默认-10,log-normal 空间):背景信号均值的先验protein_background_prior_scale(默认0.5):背景先验的不确定性
什么时候这两个参数会崩:
如果你的蛋白质 panel 含有很多不在目标细胞上表达的蛋白(阴性对照大量),背景均值会系统性偏高。此时 protein_background_prior_mean 应该调得更高(如 -5),否则模型会把真实的低表达蛋白也判为背景。
3.2 protein_dispersion
类似 scVI 的 dispersion,控制蛋白质计数的离散度建模。通常保持默认("protein",每个蛋白一个参数),但当你有多个测量批次时,"protein-batch" 更合适。
四、通用调参流程
不管用哪个工具,建议按这个顺序:
1. 先跑默认参数
→ 看 UMAP:批次分离?细胞类型边界清晰?
→ 有批次分离 → 考虑加强矫正参数(θ↑ 或 kl_weight↑)
→ 细胞类型边界模糊 → 考虑降低矫正强度
2. 调一个参数,固定其他
→ 对比 LISI 分数(iLISI 衡量批次混合,cLISI 衡量细胞类型保留)
→ 两个分数要同时看,不能只追求高 iLISI
3. 检查生物学合理性
→ marker gene 是否在正确的簇里高表达?
→ 稀有细胞类型有没有被"同化"消失?
速查表
| 问题 | 可能原因 | 调参方向 |
|---|---|---|
| UMAP 里批次仍然分开 | 矫正强度不足 | Harmony: θ↑;scVI: kl_weight↑ |
| 不同细胞类型被合并 | 过矫正 | Harmony: θ↓;scVI: kl_weight↓ |
| 稀有细胞类型消失 | nclust 太小 / σ 太小 | Harmony: nclust↑, sigma↑ |
| 训练不收敛 | 学习率 / warm-up 问题 | 检查 kl_weight 曲线,加 warm-up |
| 大数据集 OOM | block_size 太大 | Harmony: block_size↓;scVI: batch_size↓ |
| ZINB 出现奇怪小簇 | 零膨胀参数过拟合 | 换 gene_likelihood="nb" |
| scANVI 标注迁移差 | alpha 太小 | alpha↑,或增加有标注细胞比例 |
小结
批次效应矫正参数的选择,本质上是在两个对立目标之间找平衡:
- 批次混合(integration) vs 生物学保留(biology preservation)
- 模型容量(capacity) vs 泛化能力(generalization)
默认值是经过大量数据集验证的合理起点,但它们不是万能的。理解每个参数在数学上控制的是什么,你才能在默认值失效时知道往哪里走——而不是盲目网格搜索。
相关工具文档:
- scVI-tools: docs.scvi-tools.org
- Harmony (harmonypy): github.com/immunogenomics/harmony
- totalVI: scvi-tools.org/en/latest/api/reference/scvi.model.TOTALVI.html