Kraken2原理:基于k-mer的快速分类算法

Kraken2这东西,说白了就是用DNA短串来给序列贴标签。我自己在做项目时,常被新同学问:“它怎么就那么快?” 今天我来拆开讲讲。

k-mer是什么

把一条序列切成固定长度的小片段。比如 ATCGATCG,切成4-mer就是:ATCG、TCGA、CGAT、GATC。Kraken2默认k=31,也就是每31个碱基为一个单元。

你想想看,一条Illumina读长150bp,能切出120个31-mer。每个小段都有机会匹配到参考基因组。这就是Kraken2分类的基础——投票机制。

个人习惯:我一般不会动默认k-mer长度。太短会失去特异性,太长又容易匹配不上。31这个数字是作者反复调过的,挺靠谱。

建库过程:从基因组到哈希表

Kraken2建库分三步走:

  1. 提取参考基因组所有k-mer
  2. 给每个k-mer分配LCA(最低共同祖先)
  3. 构建哈希表

第二步最关键。如果一个31-mer只在单一种里出现,那就直接标为这个种。如果它出现在多个物种里,算法会回溯到它们的共同祖先。我配图说明一下:

Kraken2 分类核心流程 输入序列 (FASTQ) 分割成重叠 31-mer 片段 哈希表查找:每个k-mer匹配LCA 命中 未命中 记录匹配的物种LCA 跳过,不参与投票 投票:统计匹配最多的物种 输出分类结果 数据库中存储 (k-mer → LCA) 映射

哈希表里存的是(k-mer,LCA分类ID)对。查找时复杂度是O(1)——这就是 Kraken2 快的原因之一。我印象最深的是,有次跑一个1000万条reads的样本,它只用了不到8分钟就出结果。

分类过程:一次快速投票

当一条序列进来:

  1. 拆成31-mer:滑动窗口,每步移动1个碱基
  2. 哈希查找:每个k-mer查表,快速得到物种或LCA
  3. 投票决策:把所有k-mer的结果汇总,出现最多的分类胜出

举个实际例子。假设一条序列有120个31-mer命中,其中80个指向大肠杆菌,40个指向沙门氏菌。Kraken2会投票给大肠杆菌。我曾在项目里看到,有些序列表面看起来像某菌,但内部k-mer分布根本不是那么回事,投票结果一下子就暴露出来了。

为什么它能这么快

传统的方法(比如BLAST)要逐个比对,慢在搜索上。Kraken2干脆把匹配过程前置到建库阶段:

项目 传统比对(BLAST) Kraken2
查库方式 逐个序列比对,耗时 哈希表O(1)查找
数据库大小 动辄几十GB 压缩后~8GB(标准库)
10万条reads用时 数小时 数十秒
内存需求 通常大于50GB 约11GB(MiniKraken)
注意点:快是有代价的。Kraken2只能识别数据库里有的物种,新物种或变异菌株可能完全匹配不上。我遇到过一例罕见样品,80%的k-mer都落空,最后只能归到属水平。

k-mer长度与数据库的平衡

你把k调大,特异性上去了,但匹配概率下降了。我在实践中通常会检查一下数据库的构建参数。比如建库命令中其实可以设置k-mer长度:

kraken2-build --kmer-len 31 --db my_db --build

我个人习惯用默认31,除非是特殊项目。Kraken2还做了个优化——最小化k-mer存储。它把重复或冗余的k-mer去重,只保留代表性片段。这解释了为什么标准库能压缩到5GB大小。

核心优势总结:O(1)查找 + 最小化存储 + 投票决策 = 极速分类

跑起来什么样

命令行很简单,但背后是几百万个的哈希查找:

kraken2 --db /path/to/standard_db --threads 8 \
  --report my_report.tsv \
  --output my_output.txt \
  sample.fastq

输出的文件一般有两列:分类等级(U:未分类、R:根、D:域...到S:种)和reads数量。我在取结果的时候,一般会先看“U”那一行——未分类比例高的话,说明样本里有数据库未收录的新东西。

小结一下

Kraken2的原理就是把复杂的序列比对问题,转换成了简单的哈希表查找。做生物信息这么多年,我觉得在速度和资源受限的场景下,它依然是首选。当然,也有它搞不定的情况——下次讲MetaPhlAn时,你会发现两者恰好互补。