4. Kraken2原理详解:k-mer索引、数据库构建、分类算法

我第一次用Kraken2的时候,被它的速度震惊了。同样是做宏基因组分类,MetaPhlAn跑个样本要几个小时,Kraken2十几分钟就出结果了。这背后的秘密,就是今天要聊的核心——k-mer索引。

4.1 k-mer索引:核心思想

Kraken2的核心思路,说白了就是“查字典”。

它先把参考基因组里的所有序列,拆成固定长度的短串。这个短串就叫k-mer。然后把这些k-mer建成一个巨大的哈希表。分析样本时,再把测序读段也拆成k-mer,去哈希表里匹配。

匹配到哪个物种的k-mer多,读段就归谁。
关键参数:k-mer长度
Kraken2默认k=35。这是我试过很多次后的推荐起点。
太短(比如k=20):特异性差,容易误匹配。
太长(比如k=50):数据库膨胀,内存吃紧。
k-mer长度 特异性 数据库大小 运行速度 适用场景
k=20 低 小(约4GB) 快 快速筛查、低资源环境
k=31(Minikraken默认) 中 中等(约8GB) 较快 日常分析、教学演示
k=35(Kraken2默认) 高 大(约12GB+) 中等 标准科研、高精度要求
k=50 极高 超大(20GB+) 慢 近缘物种区分、特殊需求
避坑指南:
我曾经在集群上跑Kraken2,直接用了默认的k=35,结果数据库建到一半把内存撑爆了。后来才摸清楚——数据库大小跟k值、基因组数量、重复序列都有关。建议你先用Minikraken试水,确认环境没问题再上完整库。

4.2 数据库构建

Kraken2的数据库构建分为两步:

  1. k-mer提取与哈希:把所有参考基因组拆成k-mer,每个k-mer对应一个物种ID。这一步最耗内存。
  2. 构建最小化哈希表:Kraken2用了最小化哈希(Minimizer)技巧。不存所有k-mer,只存每个窗口内“最小”的那个k-mer,大幅压缩内存。

为什么要用最小化哈希?你想想看,人类基因组有30亿碱基,拆成k=35的k-mer,数量接近30亿个。存到内存里,128GB都不够用。用了最小化哈希,大概能压缩到原来的1/10左右。

我个人习惯:
对于新手的第一个数据库,我建议直接用官方预构建的Minikraken2_v2(8GB内存即可)。跑通了再考虑自定义库。

构建命令长这样:

kraken2-build --download-taxonomy --db mydb
kraken2-build --download-library bacteria --db mydb
kraken2-build --build --db mydb --threads 8
--threads 8 表示用8个CPU核,并行构建
参数 作用 说明
--download-taxonomy 下载分类学树 NCBI的taxonomy,约500MB
--download-library 下载参考基因组库 可选bacteria/fungi/viral等
--build 执行构建 最耗时的一步,约2-4小时
--threads 并行线程数 多核能明显加速
警告:
构建完整数据库(所有微生物基因组)需要约120GB内存和大量时间。我建议你先确认服务器资源再动手,否则构建中途OOM(内存溢出)会很崩溃。

4.3 分类算法:LCA与打分

Kraken2的分类算法,核心是最低共同祖先(Lowest Common Ancestor, LCA)。什么意思呢?

一个读段被拆成多个k-mer,每个k-mer可能匹配到不同的物种。比如:

  • k-mer_A → 大肠杆菌
  • k-mer_B → 大肠杆菌+痢疾杆菌(两个物种共有)
  • k-mer_C → 志贺氏菌

LCA算法会找到这些物种在分类树上的“最低共同祖先”。在这个例子里,可能是肠杆菌科这个层级。Kraken2就把这个读段分到肠杆菌科。

但问题来了——如果匹配到的k-mer太少,或者来源太杂,LCA会抬得太高,精度就下降了。Kraken2引入了置信度打分来解决这个矛盾。

置信度打分怎么做?

  1. 每个读段统计匹配到各个分类层级的k-mer数量。
  2. 计算每个层级的“支持度”:该层级k-mer数 / 总匹配k-mer数。
  3. 如果某个层级的支持度超过阈值(默认0.5),就接受这个结果;否则退回到上一级。

举个例子:

分类层级 匹配k-mer数 支持度 是否通过(阈值0.5)
大肠杆菌(种) 12 12/20 = 0.6 ✔ 通过,归为种级
肠杆菌科(科) 8 8/20 = 0.4 ✘ 不通过,继续上溯
细菌(界) 20 1.0 ✔ 通过,但太宽泛
实际运行时的默认行为:
Kraken2先做LCA,然后根据打分决定是否“往下压”。
如果支持度低于阈值,就退回到更高层级,直到满足条件。

嗯,这里要注意:置信度阈值不要设太高。我见过有人设成0.9,结果大部分读段都退回到“界”或“门”级别,根本没法用。

图4-1:Kraken2分类流程 输入读段 拆分为k-mer(默认k=35) k-mer → 哈希表匹配(物种ID) LCA最低共同祖先计算 置信度打分 → 最终分类判决 分类结果输出 说明: Kraken2将读段拆为 k=35的k-mer,匹配 哈希表得到物种ID。 LCA找共同祖先, 置信度打分做最终判决。

实际运行中的常见问题

  • 读段太短:比如50bp的单端读段,拆出来的k-mer太少(50-35+1=16个),统计意义不足,分类容易退回到高级分类。我建议读段长度至少75bp以上。
  • 高度保守区域:比如16S rRNA基因的保守区,很多物种共享相同的k-mer,LCA会抬得很高。Kraken2对这种区域的区分度较差。
  • 新物种/未知物种:数据库中不存在,k-mer匹配不到,读段被归为“未分类”。这时候可以改用Bracken做丰度重估计,或者用MetaPhlAn的标记基因策略。
我的经验:
对于混合感染样本(比如肠炎患者的粪便),我习惯把k值降到31,同时打开Kraken2的--quick模式。速度能再快一倍,而且能抓到更多低丰度物种。

最后说一下底层实现,Kraken2用了近似最近邻哈希(Approximate Nearest Neighbor Hashing),而不是精确匹配。这带来了速度优势,但也牺牲了一点精度。记住这个取舍就够了——大部分场景下,速度比那1%的精度提升更重要。