scVI 概述:单细胞 RNA-seq 中的批次效应
大家好,今天我们来聊聊单细胞数据分析里一个绕不开的坎——批次效应。
我个人习惯把批次效应称为“实验室的指纹”。你想想看,同一个生物学样本,在周一做实验和周五做实验,最终测出来的基因表达谱可能天差地别。但这不代表细胞本身变了,而是实验操作、试剂批号、测序通道、甚至当天的室温造成的系统性偏差。
如果不管这批差,直接扔进聚类算法里,结果往往是:不同批次的细胞各自抱团,而真正的生物学差异却被淹没了。我在很多项目里都见过这种情况,新手很容易被“假阳性”的聚类结果误导,以为发现了新的细胞亚群,结果一复查,不过是批次在作怪。
批次效应到底长什么样?
我们先看一个典型的数据结构。单细胞 RNA-seq 实验通常会产出多个样本或多次上机测序的数据。假如你有两个批次的 CD4+ T 细胞数据:
| 批次 | 细胞数 | 基因数 | 测序深度 | 环境条件 |
|---|---|---|---|---|
| Batch A | 5,000 | 20,000 | 高(50K reads/cell) | 文库试剂版本 v2 |
| Batch B | 5,000 | 20,000 | 低(30K reads/cell) | 文库试剂版本 v3 |
这两批细胞理论上应该是同一群细胞,但测序深度的差异会直接反映在 count 矩阵上——Batch A 的许多基因表达量整体偏高。传统归一化方法只能部分缓解,但很难彻底消除这种结构性的偏移。
避坑指南: 我曾经遇到过一个项目,用标准 Seurat 流程跑的聚类结果,Batch A 和 Batch B 的细胞被分成了两个独立的簇。组里同学兴奋地以为发现了“疾病特有亚群”,我让他们先做批次校正再跑一次聚类,结果那两群细胞就合在一起了。嗯,这就是批次效应最典型的迷惑性——它很容易让你“发现”不存在的生物学差异。
传统处理方法为什么不够?
目前最常用的批次校正方法包括 ComBat、MNN Correct、Harmony 等。它们各有各的长处,但本质上都是基于线性或近线性假设来处理批次影响。
举个例子,ComBat 假设每个基因的表达量服从一个位置-尺度模型:
Y_{ij}^k = μ_j + γ_{ij} + δ_{ij} * ε_{ij}^k
其中 μ_j 是基因 j 的整体均值,γ_{ij} 和 δ_{ij} 是批次 i 对该基因的均值和方差调整项。这个模型在线性尺度上工作得还可以,但一旦遇到细胞类型之间的复杂非线性关系,就有点力不从心了。
说白了,批次效应不仅仅是均值平移,它会影响基因之间的关系网络。我见过很多案例,两个批次在低表达基因上的噪声模式完全不同,这些东西是无法通过简单的线性变换拉齐的。
另一个重要问题是,传统方法往往需要先明确批次标签,并且只能处理已知的离散批次。但在现实项目中,你可能会碰到连续型的批次变量,比如测序仪的不稳定漂移,或者样本的采集时间跨度。这时候 Harmony 或 MNN 的效果就会打折扣。
scVI 的核心思路:用深度生成模型学习数据分布
那 scVI 是怎么解决这个问题的呢?它不再假设批次效应是线性或可分离的,而是把整个数据生成过程建模为一个 深度生成模型。
简单来说,scVI 假设每个细胞的基因表达是由两组隐变量共同决定的:一组是 生物学隐变量 z,用于捕捉细胞类型、状态等真正的生物学信息;另一组是 批次的指示变量(batch index),用于解释实验技术带来的系统偏差。
然后用一个变分自编码器(VAE)去推断这个隐变量的后验分布。在推理阶段,我们可以忽略批次信息,只保留 z 来进行下游分析,这样就达到了“抵抗批次干扰”的效果。
核心优势: scVI 能同时处理离散批次和连续协变量(比如测序深度、样品质量指标)。而且在它的框架里,我们不需要显式地“校正”原始表达矩阵,而是直接在隐空间中完成对齐。这是一个质的变化——从“数据清洗”转向“建模中的隐式消除”。
为什么是 scVI 而不是其他?
我估计你会问:VAE 有那么多变体,为什么 scVI 在单细胞领域最受欢迎?我的理解是,它专门针对单细胞数据的稀疏性和过离散分布做了优化。
常规的 VAE 假设数据服从高斯分布,但单细胞数据是 count 数据,而且零值特别多。scVI 使用了 零膨胀负二项分布(ZINB)来建模 UMI count,这个设计更贴合真实的测序过程——一个基因在某个细胞里没有被检测到,可能是因为它根本没表达,也可能是因为测序没捕获到。
同时,scVI 引入了一个条件变分自编码器(Conditional VAE)的结构,把批次变量作为条件输入到解码器中。这样模型就能“看到”批次信息,但却能学着把批次信息与生物学信息分开,而不是混在一起编码。
模型输入:
- X: 基因表达矩阵(counts 或 normalized values)
- B: 批次标签(one-hot 编码)
编码器输出:
- 隐变量 z 的均值和方差(N(μ, σ²))
解码器输入:
- 从后验采样的 z
- 批次的 one-hot 向量 B
解码器输出:
- ZINB 分布的参数(均值 μ, 离散度 θ, 零膨胀概率 π)
你看,这个框架很灵活。训练结束后,我们可以直接用 encoder 的输出去做降维、聚类、差异表达分析,因为 z 已经不包含批次信息了。
一个简单的流程图
为了更好地理解 scVI 怎么“抵抗”批次干扰,我画了一张流程图:
从这张图里你可以看到,scVI 把批次信息当成了“条件”,而不是编码进隐变量。模型在学习过程中会不断分配哪些变异来源于生物学(z),哪些来源于批次(B)。最终我们只需要保留 z,下去跑聚类或差异表达,就不会被批次带偏了。
一个小技巧: 我发现很多同学在调参时容易忽略一个细节——scVI 的 n_latent 参数不要设得太大。一般 10~20 就够了。如果设到 50 以上,模型可能把噪声也学进去,导致隐空间里的批次结构反而更难消除。我习惯先用 10 跑一次初步结果,再根据重构损失和聚类效果微调。
scVI 的局限与边界
当然,scVI 并不是万能药。我总结了几个它不太擅长的场景:
- 批次与生物学完全混杂: 比如 Batch A 全是病患组,Batch B 全是正常对照组。这时候没有重叠的生物信息可供模型学习,scVI 也分不清哪些差异是生物学、哪些是批次。这种实验设计本身就有问题,再好的算法也救不回来。
- 极低的细胞数: 如果某个批次里只有几百个细胞,变分推断的方差会很大,z 的后验估计可能不稳定。我对这种数据通常会先用 Harmony 做个粗校正,再用 scVI 做精调。
- 对陌生批次的外推能力弱: 如果训练时没见过某种批次模式的细胞,scVI 无法自动泛化。所以如果你计划要对新数据做预测,最好把新数据跟原有数据一起 re-train 而不是直接 infer。
说到这,你可能已经感觉 scVI 的平衡性做得不错:它保留了深度学习的表达能力,又通过条件 VAE 的结构把批次效应隔离出去。在后面的章节里,我们会一步步搭建 scVI 模型,并亲手验证它对批次干扰的抵抗力到底有多强。