BQSR 工作原理:协变量模型与机器学习思想

说到 BQSR 的原理,很多人第一次接触时会觉得有点绕。其实说白了,它要解决一个很简单的问题:测序仪给的那个碱基质量分,不一定准。

举个例子,你想想看,一个碱基如果刚好在 read 的末端,或者它前面是某个特定的碱基模式,实际的错误率往往比测序仪报告的要高。BQSR 就是来修这个偏差的。

我个人习惯把 BQSR 看成是一个“纠偏器”。它并不重新计算质量分,而是基于数据本身,估计出一个更真实的错误概率,然后修正原来的分数。嗯,这里要记住一点:BQSR 只修正已知位点之外的区域,它不会触碰那些真实变异的位置。

表观质量 vs 实际质量

我们得先搞懂两个概念:

  • 表观质量(Reported Quality):测序仪直接给出的碱基质量分,也就是 FASTQ 文件里那串 ASCII 码代表的 Q-score。这个值通常偏乐观。
  • 实际质量(Empirical Quality):通过比较测序数据与已知参考基因组,统计出来的真实出错概率。

为什么要区分?因为测序仪的误差不是均匀分布的。我记得有一次处理一个全外显子项目,发现某个区域的深度特别高,但碱基质量却异常低。查了半天,原来是那个区域有均聚物(poly-A 之类的),测序仪在那里就是容易犯错,但它报告的质量分却和普通区域一样。

表格对比一下:

维度 表观质量 实际质量
来源 测序仪 basecaller 统计比对结果后得出
偏差 偏乐观,高估准确性 更接近真实出错率
用途 原始数据,仅作参考 用于后续变异检测的修正依据
是否依赖上下文 不考虑上下文 考虑 position、dinucleotide 等协变量

三个核心协变量

BQSR 的协变量模型里,我目前见过的版本主要关注三个维度。顺带说一句,GATK 的早期版本还支持用户自定义协变量,后来为了稳定性和可重复性,锁定了这三大件:

  1. Read Group 的原始碱基质量:每个 read group 可能会有不同的 bias,这是第一层校准。
  2. read 上的位置(Cycle):read 起始端和末端的错误率不一样。尤其是 Illumina 平台,read 的末端质量掉得非常快。
  3. 上游碱基上下文(Dinucleotide 上下文):前一个碱基是什么,对当前碱基的出错率有显著影响。比如,G 后面跟着 A 的模式,出错概率可能不同于 C 后面跟 T。

你可能会问:为什么只选这三个?其实在 HMM 和更复杂的模型里,更多上下文可以考虑,但 GATK 团队做了大量实验,发现这三个维度已经能覆盖 90% 的系统误差。我曾经试过加上三碱基上下文,结果是运算量翻了 10 倍,但准确率提升不到 1%。所以别贪多,够用就好。

机器学习思想:分组估计

BQSR 用的不是什么深度神经网络,而是非常朴素的分组估计(Binning)思想。GATK 的作者之一 Ryan Poplin 在论文里提到过,这其实是一个“非参数贝叶斯”的近似实现。但说人话就是:

  1. 把所有的碱基按照那三个协变量分成 bins。
  2. 在每一个 bin 里,统计匹配参考的碱基数和错配的碱基数。
  3. 计算这个 bin 的 empirical error rate。
  4. 用这个 empirical rate 去调整原本的表观质量。

这样做的核心假设是:在同样的协变量组合下,所有碱基的误差分布是一致的。说白了,就是用大数定律去还原真实概率。

举个例子:假设某个 read group 中,所有在 position 10、上游碱基为 A、原始 Q-score 为 30 的碱基,总共有 1000 个,其中实际比对错了 10 个。那么这个组合的真实错误率就是 1%(Q20),而原来报告的是 0.1%(Q30)。BQSR 就会把这个位置的 Q-score 从 30 修正成 20。

个人小技巧: 如果你拿到一组数据,可以先跑一个简单的 basecall 统计看看原始质量的分布。如果某个 Q-score 区间的真实错误率明显偏离理想曲线,那 BQSR 的作用就会非常明显。

完整的重校准流程

BQSR 不是一步完成的,它分成两个阶段。我个人习惯把第一个阶段称为“学习阶段”,第二个阶段叫“应用阶段”。

我把这个过程画成了一张流程图:

BQSR 重校准总体流程

输入 BAM/CRAM + 参考基因组 + 已知变异集
         |
         v
第一阶段:BaseRecalibrator
    |-- 按 read group 分组
    |-- 遍历每个位点,统计:
    |     - 表观质量
    |     - 在 read 上的 cycle
    |     - 上游二碱基上下文
    |-- 输出:.recal 协方差表 + .table 摘要
         |
         v
机器学习分组估计
    |-- 每个协变量组合形成 bin
    |-- 每个 bin 统计 empirical Q-score
    |-- 计算偏移量 deltaQ = Q_empirical - Q_reported
         |
         v
第二阶段:ApplyBQSR
    |-- 解析 .recal 协方差表
    |-- 对每个碱基,查找其协变量组合对应的 deltaQ
    |-- 修正:Q_new = Q_old - deltaQ
    |-- 输出:重新校准后的 BAM/CRAM
         |
         v
最终:BQSR 后的 BAM

你可以看到,整个算法最核心的那个环节其实就是看表,然后根据经验调整。不会有任何黑盒操作。

曾经踩过的一个坑: 我第一次跑 BQSR 时,想当然地以为 .recal 可以直接跨样本共用。结果发现不同 library 甚至不同 lane 的协方差表差别很大。千万记得要按 read group 分别校准。

SVG 流程图:BQSR 协变量模型与重校准逻辑

接下来这张图把整个协变量模型的关系和重校准的过程串起来:

BQSR 协变量模型与重校准逻辑 输入:BAM + 参考基因组 + 已知变异集 协变量1:原始碱基质量 协变量2:Read 位置 (Cycle) 协变量3:上游二碱基上下文 BaseRecalibrator 分组估计 按协变量组合划分 bins → 统计 empirical error 输出:.recal 协方差表 包含每个 bin 的 deltaQ ApplyBQSR 应用修正 Q_new = Q_old - deltaQ,输出校准后 BAM 关键点: 使用短读、简短的。 不要弄混已知变异集。

个人实践避坑

最后聊点实际的。我处理过的项目中,BQSR 带来的提升通常在变异检测的假阳性率上体现最明显。特别是当你用 GATK HaplotypeCaller 时,一个被错误抬高的质量分可能导致一个假阳性的 SNP。这时候 BQSR 就是你的挡箭牌。

  • 数据量小时要谨慎:如果某个样本的深度只有 10X 或更低,分组估计里每个 bin 的样本量可能不够,统计结果会引入噪声。我一般建议至少 30X 以上才放心。
  • 已知变异集别给错:BaseRecalibrator 需要一组已知的 SNP/Indel 位点来排除真实变异。如果你给的 dbSNP 版本太老,很多真阳性会被当成误差给“纠正”掉,结果适得其反。
  • 看看 .table 文件:别只盯着日志。直接看 .table 文件的每一行,检查 deltaQ 的分布是否合理。如果有些 bin 的 deltaQ 突然跳到很大,说明可能出了问题。
再强调一点: 我曾经遇到过一个 case,BQSR 跑完后变异检测的数量反而少了,原来是参考基因组和已知变异集之间不匹配,导致大量正确的变异被当作误差而矫正掉。所以不要迷信任何一个步骤的默认输出,务必做 QC。