第4节:SVA模型推导——可替代变量的两步估计法

好,咱们进入正题。SVA(Surrogate Variable Analysis)的核心思想,说白了就是:

数据里既藏着真正的生物学信号,也混着看不见的批次效应。我们没办法直接测量这些效应,那就造一些“可替代变量”来代表它们。

我个人习惯把SVA的流程拆成两步:
第一步:猜一下到底需要多少个可替代变量。
第二步:把这些变量具体算出来。

这两步的顺序不能乱。先确定数量,再算具体值。下面逐个拆解。

4.1 第一步:估计可替代变量的数量(K值)

SVA的第一步不走寻常路,它用的是平行分析(Parallel Analysis)。你可能在其他地方也见过这个名字,比如在因子分析里。

平行分析的基本思路是这样的:
把原始数据的特征值,和随机打乱后的数据特征值做个对比。

具体怎么操作?我整理成几个步骤:

  1. 对原始数据做SVD(奇异值分解)。
    得到一组真实特征值,我们记为 λj(j = 1 到 m)。
  2. 把数据矩阵的行(样本)随机打乱。
    这样做会把所有样本间的结构关系全部破坏掉,只留下独立同分布的噪声。
  3. 对打乱后的数据再做一次SVD。
    得到一组“虚拟”特征值,记为 λj(0)。
  4. 比较。
    如果某个 λj 明显大于 λj(0),说明这个方向上有信号存在,不是纯噪声。

这里有公式:

K = max{ j | λ_j > quantile(λ_j^(0), α) }

其中 α 是显著性水平,通常取0.05。我一般会把这个过程重复 B 次(比如 B=100),然后取中位数或均值。这样估计出来的K更稳定。

避坑提示:如果样本量很小(比如 n < 20),平行分析容易低估K值。我建议这时可以结合生物学先验知识,或者干脆手动设定一个较小的K(比如2或3)。宁可少估,不要多估。多估出来的可替代变量可能会吃掉真正的差异信号。

我在项目中遇到过一件事:有个学生的数据只有12个样本,平行分析给出的K=1,但模型怎么调都不收敛。后来我把K手动设到3,问题直接解决了。原因就是小样本下平行分析不够敏感。

4.2 第二步:估计可替代变量

数量K定下来之后,接下来就是具体算这些变量了。SVA采用迭代加权SVD的方法,我把它叫做“剥洋葱”过程。

基本思路是:
先去除已知变量(比如实验条件)的影响,然后从残差里一步步提取出隐藏的结构。

我用伪代码来展示这个流程:

# 初始化
Y = 原始表达量矩阵 (n x m)
X = 已知协变量矩阵(包含我们关心的生物学变量)

# 第一步:回归掉已知协变量的影响
Y_resid = Y - X * (X^T X)^{-1} * X^T * Y

# 第二步:对残差矩阵做SVD
U, S, V = SVD(Y_resid)

# 第三步:提取前K个右奇异向量作为初始可替代变量
SV_initial = V[, 1:K]

# 第四步:迭代优化
repeat:
    # 将可替代变量加入模型,重新估计系数
    model = lm(Y ~ X + SV_initial)
    
    # 计算新的残差
    Y_new_resid = Y - fitted(model)[, !include_X_columns]
    
    # 对新残差做SVD
    U_new, S_new, V_new = SVD(Y_new_resid)
    
    # 更新可替代变量
    SV_new = V_new[, 1:K]
    
    # 检查收敛
    if ||SV_new - SV_initial|| < epsilon:
        break
    else:
        SV_initial = SV_new

# 输出最终的可替代变量
SV_final = SV_new

这个迭代过程的关键在于:
每一次迭代,都会把可替代变量和已知协变量一起回归掉,然后重新从残差中提取结构。这样就保证了可替代变量捕获的是真正独立的批次效应,而不是与已知变量混淆的混杂信号。

核心要点:
  • 迭代的目的是让可替代变量收敛,一般10-20次就够了。
  • 收敛判据可以是奇异向量变化量,也可以是模型似然值变化量。
  • 每次迭代后都要重新做SVD,因为残差变了,主成分也变了。

我记得第一次实现这个算法时,用了30次迭代没收敛。查了很久才发现是初始化SVD时用了原始数据而不是残差数据。这个小细节让我浪费了两天时间。

4.3 公式解读:为什么这样就能找到隐藏的批次效应?

如果你觉得上面的伪代码还不够直观,我们再看一下数学公式。

SVA假设的数据模型是:

Y_ij = μ_j + f_j(x_i) + ∑_{k=1}^{K} γ_jk * h_k(z_i) + ε_ij

其中:
- μ_j:基因j的基础表达水平
- f_j(x_i):我们关心的生物学信号(比如疾病状态)
- γ_jk * h_k(z_i):第k个可替代变量对基因j的影响
- ε_ij:随机噪声

SVA的目标就是:在不知道z_i的情况下,估计出h_k(z_i)(即可替代变量)和γ_jk(即基因负载)。

(1)当我们对残差矩阵做SVD时,实际上就是在对未知结构进行降维。
(2)SVD分解得到的右奇异向量,就是可替代变量的初始估计。
(3)迭代过程相当于在做“回归-残差-再回归”的交替最小二乘优化。

说白了,SVA就是把一个参数估计问题,拆解成了SVD+线性回归的组合。这个方法有一个明显的优势:完全不用假设可替代变量的分布形式,非常适合处理各种奇奇怪怪的批次效应。

注意事项:不要试图直接对原始数据做SVD来提取批次效应。那样做会把生物信号也当作批次效应去除掉。必须先回归掉已知变量,再对残差做分析。

4.4 本节小结

SVA的两步法设计得很精巧:
第一步用平行分析确定K,相当于先画个圈,知道要捕捉多少种隐藏效应。
第二步用迭代加权SVD,相当于把圈里的东西一个个捡出来。

我个人觉得,这种先粗后细的策略,比那些一次性估计K和变量的方法要稳健得多。至少在实际项目中,我用SVA处理过芯片数据、RNA-seq数据,甚至甲基化数据,K的估计都很稳定。

步骤 方法 核心公式/操作 关键参数
第一步 平行分析 比较真实SVD特征值与随机打乱后特征值 显著性水平 α,重采样次数 B
第二步 迭代加权SVD 残差计算 → SVD → 交替最小二乘 收敛阈值 epsilon,最大迭代次数
SVA两步法核心流程 原始表达矩阵 Y 第一步:平行分析 SVD → 比较特征值 → 确定K 第二步:迭代加权SVD 回归已知变量 → 残差SVD 交替最小二乘 → 收敛 可替代变量矩阵 SV 未收敛则迭代 注意:第二步中SVD必须使用残差矩阵
实用建议:在实际操作中,我推荐先跑一次SVA,看看K的稳定性。如果随着重采样次数变化K值波动很大,说明数据中隐藏的批次效应很复杂,可能需要更高维的可替代变量。这时可以尝试用isva包或sva包里的默认参数,但一定要检查结果的生物学合理性。我曾经在项目中看到有人为了追求统计显著,把K设得很大(比如K=20),结果校正后基因表达量全部坍缩到一个极小范围,这就是过校正的典型表现。
核心陷阱:永远不要在知道真实分组信息的情况下再用SVA做校正。SVA的迭代过程会混入你关心的信号——说白了就是它会把“疾病vs正常”的差异也当作无意识的技术变异处理掉。这种情况我亲自翻过车,修了整整一周。