算法的故事 / 第一季第二季目录0 / 30 章已读

第二季 · 第 52 章

在浩瀚基因数据库里寻找一个线索

《算法的故事》第二季 · 第五篇:破解生命的密码——1990年的BLAST、短词种子、启发式搜索与统计显著性的数学革命1990年,美国马里兰州,贝塞斯达。美国国立卫生研究院的研究人员,正在面对一个不断增长的新世界。这个世界没有山川。没有海洋。也没有城市。它由数不清的生物分子序列构成。一条蛋白质序列,可能有几百个氨基酸。一段DNA序列,可能有几千、几万,甚至更多的核苷酸。随着实验技术不断进步,世界各地的实验室将越来越多的序列提交到公共数据库。而每一条新序列的背后,都可能隐藏着一个科学问题。这段DNA对应什么基因?这个蛋白质可能有什么功能?它与其他物种的蛋白质,是否具有共同的进化来源?一位研究者刚刚测定了一段未知蛋白质。他希望把它交给计算机。然后问:“数据库里,有没有什么东西与它相似?”在1970年,Needleman与Wunsch已经提出了系统寻找序列对应关系的方法。1981年,Smith与Waterman又建立了寻找最佳局部相似片段的动态规划算法。从理论上说,问题似乎已经解决了。把新序列取出来。与数据库里的第一条序列比对。再与第二条比对。第三条。第四条。直到全部比较完成。但计算机很快遇到了一个困难。两条序列的精确动态规划计算量,与它们的长度乘积有关。O(mn)O(mn)当数据库只有几条序列时,这完全可以接受。但是,当数据库不断扩大时,计算量也随之迅速增加。1990年的研究者,没有今天的多核处理器、GPU集群和海量内存。他们需要让普通科研计算机,在可以接受的时间内完成序列检索。这时,五位研究者决定改变问题的解决顺序。他们没有首先问:怎样把整张动态规划矩阵计算得更快?而是问:“能不能先找出最有希望的局部区域,再把计算资源集中到那里?”这个想法看起来只是一项工程优化。实际上,它改变了生物序列数据库搜索的工作方式。1990年10月5日,《Journal of Molecular Biology》发表了一篇八页论文。Basic Local Alignment Search Tool《基本局部比对搜索工具》。简称:

BLAST

1990年BLAST论文,发表于《Journal of Molecular Biology》第215卷第3期,第403—410页。论文于1990年2月26日收到,5月15日接受。[PubMed][康奈尔大学计算机系]五位作者分别是:斯蒂芬·阿尔丘尔(Stephen F. Altschul)。沃伦·吉什(Warren Gish)。韦布·米勒(Webb Miller)。尤金·迈尔斯(Eugene W. Myers)。戴维·利普曼(David J. Lipman)。他们来自不同研究机构。其中三人位于美国国家生物技术信息中心NCBI。米勒来自宾夕法尼亚州立大学。迈尔斯来自亚利桑那大学。论文并没有宣称:他们发明了世界上第一种序列数据库搜索方法。事实上,在BLAST之前,研究者已经开发了FASTP和FASTA等工具。他们真正提出的是一种更直接的启发式策略:先用短词找种子,再向两侧延伸,最后利用统计理论判断结果是否值得重视。在1990年的原始实验中,作者报告,相比当时灵敏度相近的一些已有启发式工具,BLAST可以快一个数量级以上。 [PubMed][blastalgorithm.com]然而,速度并不是这篇论文唯一的贡献。它还试图解决另一项更深刻的问题。如果数据库非常大,即使两条序列完全没有共同进化来源,也可能碰巧出现一段很相似的字符。那么,我们怎样知道:眼前这段相似性,究竟只是偶然?还是值得生物学家继续研究的线索?在这项问题上,BLAST与另一位数学家塞缪尔·卡林(Samuel Karlin)的统计理论建立了重要联系。因此,这一章的故事,将同时跨越三个世界。字符串搜索。动态规划之外的启发式算法。随机序列的极值统计。我们将看到,一项真正有用的科学搜索工具,不仅需要尽快找到候选,还必须帮助研究者判断:哪些候选可能只是随机噪声。

第一幕:1988年,NCBI刚刚成立,生物数据库已经开始改变科学研究

首先,我们需要回到1990年之前。那个时候,DNA和蛋白质序列数据库已经开始形成。但是,今天人们熟悉的NCBI还很年轻。美国国家生物技术信息中心:National Center for Biotechnology Information(NCBI)正式成立于:1988年11月。它隶属于美国国立卫生研究院体系中的美国国家医学图书馆(National Library of Medicine,NLM)。NCBI的早期负责人戴维·利普曼,既熟悉分子生物学研究,也长期关注如何使用算法比较和检索生物序列。[NCBI Bookshelf]

为什么要专门建立这样一个中心?

因为分子生物学正在产生一种新的科学资料。这种资料不同于传统的实验照片。也不同于医生手写的病例记录。它能够被表示成具有明确顺序的离散符号。例如DNA:
text
ATGCTAGCTAGCGTAC
或者蛋白质:
text
MALWMRLLPLLALLAL
这些序列可以被计算机保存、索引、比较和统计。而且,随着越来越多研究者贡献数据,它们的价值并不是简单相加。假设有一条新测定的蛋白质序列。数据库里如果只有十条其他蛋白质,找到相似序列的机会可能很有限。但是,如果数据库中包含成千上万条来自不同物种的序列,就可能发现以前不知道的联系。某个蛋白质也许与另一种动物体内已经研究过的蛋白质相似。某个新基因也许拥有已知蛋白质家族的保守区域。一项实验发现,因此能够与世界其他实验室积累的知识联系起来。

一个容易记错的历史细节

GenBank数据库早在1982年就已经开始建立。但NCBI直到1992年才接管GenBank的相关管理职责。因此,不能把1990年的历史场景描述成:“NCBI已经拥有并管理了今天完整的GenBank体系。”更准确的说法是:1990年的NCBI已经开始发展序列分析工具;当时世界上已存在多个生物序列资料来源,随后NCBI才在1992年承担GenBank的管理工作。[NCBI Bookshelf]

BLAST原始论文中的数据库,究竟有多大?

1990年的论文提供了一个非常具体的数字。作者使用的一个测试数据集,是:PIR蛋白质序列数据库,Release 23.0。它包含:
蛋白质序列条目

14,372

条
氨基酸残基总数

3,977,903

个
这两个数字来自1990年论文对BLAST运行时间的实验描述。[blastalgorithm.com]今天看起来,这个规模似乎不算特别庞大。但对当时的计算条件而言,它已经足以让算法效率成为一项真正的科研问题。更重要的是:研究者能够预见数据库还会持续增长。因此,一项只适合几千条序列的算法,可能很快就无法满足未来需求。

算法时代的新问题

在上一篇中,Smith与Waterman已经告诉我们:怎样找到两条序列之间的最佳局部对应。但现在,研究者要寻找的不是两条已知序列之间的一处相似性。而是:一条未知序列,在整个数据库里有哪些值得研究的相似对象?这便是从:序列比对。走向:序列数据库搜索。的一次重要变化。

第二幕:BLAST并不是第一个走近数据库的算法

科学史中,一个很容易出现的问题,是将后来最成功的技术写成“第一项技术”。BLAST影响巨大。但在它出现之前,生物序列数据库搜索已经有重要先驱。尤其是:威廉·皮尔逊(William R. Pearson)。以及我们刚才认识的:David J. Lipman。

1985年:FASTP

1985年,Lipman与Pearson发表了关于快速蛋白质数据库搜索的研究。FASTP利用局部相似短片段,快速筛选值得进一步比较的蛋白质。它已经具有一个与后来BLAST非常相近的基本直觉:不必对所有候选序列一开始就执行完整的动态规划。

1988年:FASTA

1988年4月,Pearson与Lipman在《Proceedings of the National Academy of Sciences》发表:Improved Tools for Biological Sequence Comparison《改进的生物序列比较工具》。论文介绍了FASTA等程序。其中FASTA是FASTP的进一步发展,可以用于DNA及蛋白质序列数据库搜索。研究还讨论了如何利用短片段相似性找到候选区域,再采用更精细的方法评分。[PubMed]因此,在BLAST出现以前:短词过滤。局部候选搜索。以及对候选片段进行重新评分。已经是实际的算法研究方向。

那BLAST究竟带来了什么新东西?

原始论文强调,BLAST直接围绕一个明确的局部相似性目标:Maximal Segment Pair(MSP)建立近似搜索策略。同时,它能够利用Karlin与Altschul的统计理论,估计高分片段出现的随机概率。这让算法兼顾了:明确的评分对象。短词驱动的高速搜索。以及可以数学分析的统计显著性。[blastalgorithm.com]我们可以把三项技术的历史关系概括成:
技术代表年份关键思路
Smith–Waterman1981在给定评分规则下精确求解最佳局部比对
FASTP / FASTA1985 / 1988使用局部短词相似性,加速数据库候选搜索
BLAST1990利用高分短词种子近似寻找高分无Gap片段,并结合统计显著性理论
这里的区别非常重要。Smith–Waterman是精确优化方法。FASTP、FASTA与BLAST属于使用启发式加速策略的搜索方法。但也不能简单地说:BLAST永远比FASTA更灵敏、更快。它们的性能依赖具体版本、参数、序列特点和比较目标。1990年论文所报告的性能优势,具有当时特定测试环境和比较条件。

第三幕:1990年的五位作者,怎样组成了一支跨学科研究团队?

Stephen F. Altschul研究序列相似性评分、统计显著性和计算生物学算法。他是1990年BLAST论文的第一作者,后来继续在NCBI从事计算生物学和统计方法研究。
David J. Lipman具备医学和计算生物学背景,是NCBI的创始主任。在BLAST诞生之前,他已经与Pearson合作发展了FASTP和FASTA相关研究,因此对序列数据库搜索的困难具有直接认识。[NCBI Bookshelf][PubMed]
另外三位作者同样重要。Warren Gish 在BLAST早期软件实现中发挥了重要作用。1990年论文还提到,可以向他索取C语言版本的软件,其程序能够运行于当时的BSD和AT&T System V UNIX系统。Webb Miller 长期研究字符串算法、序列比对与计算生物学。他所在的宾夕法尼亚州立大学,是这项跨机构合作的一部分。Eugene W. Myers 则拥有深厚的算法研究背景,后来继续在序列算法和基因组分析领域产生重要影响。原论文明确记录了他们的机构:
作者1990年论文中的所属机构
Stephen F. AltschulNCBI,NIH/NLM
Warren GishNCBI,NIH/NLM
Webb MillerPennsylvania State University
Eugene W. MyersUniversity of Arizona
David J. LipmanNCBI,NIH/NLM
[康奈尔大学计算机系]

一项真实的跨学科合作

如果我们把BLAST只理解成:“一位程序员发明了一个快速字符串搜索算法。”就会遗漏重要的一半。反过来,如果把它理解成:“几位生物学家发明了新的蛋白质评分方法。”也同样不准确。它的成功依赖三类能力。第一类,是理解生物学研究者真正需要什么。他们需要找到的是可能具有共同进化关系或重要功能联系的序列,而不是任意的字符串重复。第二类,是理解算法怎样在有限硬件资源下运行。必须使用快速查找结构、有效的候选过滤,以及尽可能少的无用计算。第三类,是理解随机匹配的数学性质。找到一段高分局部相似性,还必须判断:这样的分数在随机背景下有多常见?这就是BLAST非常具有代表性的跨学科价值。它并没有要求生物学家放弃统计学。也没有要求数学家忽略真实计算机。更没有把算法的速度当成唯一目标。它让生物问题、算法工程与统计推断共同服务于一项实际的科学搜索任务。

第四幕:如果只寻找三个字母,为什么可能找到一整段相似序列?

现在,我们终于进入BLAST的算法核心。先使用一条很短的DNA序列。假设刚测出的查询序列是:
text
ACGTTGACCTGA
它有12个核苷酸。数据库中存在一条序列:
text
TTACGTTGACCTGACC
如果我们仔细观察,可以发现查询序列完整地出现在数据库序列内部。
text
Query:       ACGTTGACCTGA
Subject:   TTACGTTGACCTGACC
             ||||||||||||
但是,在真实数据库中,我们并不知道哪条序列包含相似区域。更不知道相似片段从哪里开始。因此,算法需要先发现一个线索。

第一步:不要一开始寻找整个12字符序列

假设我们只取长度为:w=3w=3的短词。也叫作:Word(词)。或者:k-mer(k元子串)。从查询序列:
text
ACGTTGACCTGA
中,可以提取:
text
ACG
CGT
GTT
TTG
TGA
GAC
ACC
CCT
CTG
TGA
总共:12−3+1=1012-3+1=10个短词位置。注意:TGA 出现了两次。因此,虽然有十个位置,但不同短词的数量不一定也是十。

第二步:为这些短词建立快速查找表

我们可以使用哈希表。键:一个短词。值:这个短词在查询序列中的全部出现位置。例如:
python
{
    "ACG": [0],
    "CGT": [1],
    "GTT": [2],
    "TGA": [4, 9],
}
这里的位置从0开始编号。当扫描数据库序列时,我们只需要取出当前的三个字符。例如:
text
ACG
然后查询哈希表:
python
index.get("ACG")
就能够立即知道:它是不是查询序列中出现过的短词?如果出现过,在哪里?在通常的哈希表平均查找模型下,单次短词查找可以接近:O(1)O(1)但需要注意,短词构造、哈希冲突、多个命中位置和数据库扫描,都仍然会产生计算成本。

第三步:找到命中种子

现在,扫描数据库序列:
text
TTACGTTGACCTGACC
在第3个字符开始的位置,我们发现:
text
ACG
而查询序列的开头也正好是:
text
ACG
于是,得到一对命中:
text
Query:    ACG
Subject:  ACG
这就是:Seed Hit(种子命中)。但是,它现在只有三个字符。三个字符相同,并不意味着整条序列一定高度相似。所以,还不能马上将它当成最终结果。

第四步:从种子向两侧延伸

我们尝试继续比较种子后面的字符。
text
Query:    ACGT
Subject:  ACGT
仍然相同。继续:
text
Query:    ACGTT
Subject:  ACGTT
仍然相同。再继续。最终得到:
text
Query:    ACGTTGACCTGA
Subject:  ACGTTGACCTGA
原本只有三个字符的种子,被扩展成一段12字符的高分局部比对。这就是BLAST最具有代表性的思想:不要一开始就计算整个候选空间,而是先寻找一个局部线索,再判断它能否成长为值得保留的匹配。
图52-1:短词如何成为长片段的入口
查询序列
A
C
G
T
T
G
A
C
C
T
G
A
命中种子 ACG,随后尝试延伸
检索、延伸与评分
A
C
G
T
T
G
A
C
C
T
G
A
由3字符种子扩展出12字符局部片段
这是用于理解BLAST的DNA教学例子。原始1990年DNA程序通常使用更长的短词(论文举例经常取 w=12w=12),不是固定使用3字符DNA种子。

为什么较短的种子可能更容易找到相似序列?

假设两条DNA序列虽然整体相似,但中间存在一些不同的字符。例如:
text
Query:    ACGTTGACCTGA
Subject:  ACGTCGACATGA
二者并不完全一致。但它们仍然包含:ACGCGTGAC等相同短词。如果我们要求:12个字符连续完全一致。就找不到它们。但如果只要求一个较短的种子命中,就有可能通过后续延伸,发现这段较长的总体相似关系。这也解释了为什么:种子长度,决定了搜索速度与灵敏度之间的一项重要权衡。短词太短,随机命中会很多。短词太长,某些存在较多变异的真正相似片段可能根本没有命中种子。这项权衡,是BLAST算法设计中最重要的参数问题之一。

第五幕:1990年的原始BLAST,其实比今天许多教程讲得更加精细

上一幕,我们用DNA的三个字符:
text
ACG
寻找了一个完全匹配的种子。但如果就此认为:“BLAST只会寻找完全相同的短词。”那就忽略了1990年论文非常重要的设计。尤其在蛋白质搜索中,BLAST不一定要求短词的每个位置都相同。它更关心:两个短词对齐以后,能不能获得足够高的评分?

为什么蛋白质不能只寻找完全相同的短词?

想象两条蛋白质序列。它们可能拥有共同的进化来源。但经过漫长时间,一部分氨基酸已经发生替换。例如:
text
Query:    A L V G
Subject:  A I V G
第二个位置不同。但:L与I都属于疏水性氨基酸,某些替换评分矩阵会将这种替换视为相对可以接受的变化。因此,在适当的评分矩阵下,这两个四字符短词可能仍然获得很高的分数。BLAST可以将它们作为值得进一步延伸的候选。

从一个查询短词,生成邻近短词集合

假设查询短词为:qq长度为:ww我们使用一套替换评分矩阵:s(a,b)s(a,b)比较查询短词 qq 与候选短词 vv。总得分:S(q,v)=∑i=1ws(qi,vi)\boxed{ S(q,v)=\sum_{i=1}^{w}s(q_i,v_i) }然后设定短词命中阈值:TT只有:S(q,v)≥T\boxed{S(q,v)\ge T}的候选短词,才进入查找集合。这个集合称为高分邻近短词集合。
图52-2:不是只有完全相同的短词才能成为种子
教学评分:相同+2,不同−1,阈值T=3
查询短词候选短词得分保留?
ACGACG6是
ACGATG3是
ACGTTG0否
这是为了说明邻近词评分而构造的简化例子。实际蛋白质BLAST使用氨基酸替换矩阵,而不是统一的相同+2、不同−1。

原始1990年论文采用什么参数?

这里有一项非常值得核查的历史细节。如今很多教材会介绍:蛋白质BLAST常用:w=3w=3核酸BLAST常用更长短词。这可能适用于某些后来版本和参数设置。但1990年原始论文明确指出:作者在论文的蛋白质实验中,认为:w=4\boxed{w=4}是当时很有实用价值的权衡,并使用该设置进行后续比较。对DNA搜索,论文则经常采用:w=12\boxed{w=12}的短词长度。这是原始论文第405—407页可以直接核查的内容。[blastalgorithm.com]因此,本章不能把现代常见默认参数,直接倒写成1990年第一版BLAST的固定参数。

为什么蛋白质短词可以使用较小长度?

因为蛋白质的常见字母表,包含20种标准氨基酸。DNA的主要字母表只有:A、C、G、T四种。如果假设所有字母独立且等概率,那么两个随机DNA短词完全相同的概率为:PDNA=4−w\boxed{P_{\mathrm{DNA}}=4^{-w}}对于:w=3w=3得到:P=164P=\frac1{64}对于:w=12w=12则为:P=116 777 216P=\frac1{16\,777\,216}但这些只是等概率独立模型下、指定两个短词的精确相同概率。真实DNA具有GC含量偏差、重复序列及其他非随机结构。而蛋白质BLAST还允许高分非完全匹配短词。所以,不能简单地用:20−w20^{-w}推导实际蛋白质BLAST的全部种子命中概率。

原始论文还有一项实现细节:有限状态自动机

很多现代教学程序会使用哈希表快速查找短词。这在概念上非常合适。但1990年作者还研究了另一种实现方式。确定性有限自动机(Deterministic Finite Automaton)。他们比较过查找数组与自动机扫描方法。在当时的实现条件下,研究者认为一种基于Mealy型有限状态机的扫描方案具有更好的运行表现,因此将其作为通用实现的重要选择。[blastalgorithm.com]这项细节很有意思。因为它让我们的算法故事再次回到了:字符串匹配。有限状态自动机。以及查找结构。这些我们在前面章节里研究过的算法思想,现在全部出现在生物信息学中。BLAST并不是一种完全脱离经典计算机科学的特殊技巧。它把成熟的字符串算法和有限状态结构,应用到了生物数据库这一新领域。

第六幕:找到种子以后,为什么不能一直延伸到底?

现在,假设搜索程序已经找到一个种子。查询序列与数据库序列在某个局部位置高度相似。我们希望向左右两侧延伸,判断相似区域究竟有多长。但是,有一个新的问题。如果我们延伸得太远,可能会进入完全不相关的区域。

一个只有一处错误的局部匹配

假设:
text
Query:    A C G T T G A C C T G A
Subject:  A C G T C G A C C T G A
采用教学评分:匹配:+2+2不匹配:−1-1如果从第一个字符开始累积:
位置字符对应本列得分累积得分
1A/A+22
2C/C+24
3G/G+26
4T/T+28
5T/C−17
6G/G+29
7A/A+211
8C/C+213
9C/C+215
10T/T+217
11G/G+219
12A/A+221
第五个位置出现了一个不匹配。累计得分从8下降到7。但如果算法只要遇到一次失配就停止,那么后面更长的相似区域就会被错过。因此,延伸程序需要允许:短暂的得分下降。但又不能无限容忍得分变差。

X-drop:允许下降,但设置一条底线

设我们已经见过的最佳累计得分为:SbestS_{\mathrm{best}}当前延伸累计得分为:ScurrentS_{\mathrm{current}}给定一个允许下降幅度:X>0X>0如果:Sbest−Scurrent>X\boxed{ S_{\mathrm{best}}-S_{\mathrm{current}}>X }就停止向这个方向继续延伸。这就是经典的:X-drop延伸规则。例如:X=3X=3如果最佳累计分数曾经达到:1212而当前已经下降到:88则:12−8=4>312-8=4>3停止延伸。

为什么要保存历史最高分?

因为我们要得到的是:延伸过程中真正出现过的最佳片段。而不是机械地采用停止时的最后位置。例如,一段延伸得分可能是:6, 8, 10, 9, 8, 56,\ 8,\ 10,\ 9,\ 8,\ 5如果:X=3X=3那么从最高10降到5时,已经超过允许下降幅度。但我们真正应该保留的片段,结束于:1010所在的位置。不是:55所在的位置。因此,在延伸过程中,需要持续记录:最佳分数。最佳位置。当前分数。并在下降过多时停止。

这项机制会不会错过真正的最佳片段?

有可能。这正是启发式算法的代价。想象一条延伸路径:先连续遇到一些不匹配。分数下降超过:XX于是程序停止。但如果再向后走十个字符,可能又出现一大片高度相似的区域。因为程序已经停止,就不会继续发现那一片更高得分的延伸。所以:X-drop加速了搜索,却没有保证发现全部可能的最佳扩展。原始1990年论文对此十分坦率。作者明确承认,这项提前终止规则会使算法进一步偏离理想的最优MSP搜索。论文还分析了在相关蛋白质随机模型和选定参数下,遗漏更高得分扩展的可能性。[blastalgorithm.com]

HSP究竟是什么?

在1990年论文及经典BLAST术语中,一个重要概念是:High-scoring Segment Pair(HSP)。即:高分片段对。它表示两条序列中一对相互对齐、得分足够高的局部片段。在原始BLAST的核心搜索问题中,这些片段是:不包含Gap的局部比对。例如:
text
Query:    ACGTTGACCTGA
          |||| |||||||
Subject:  ACGTCGACCTGA
它允许对应位置字符不同。但没有插入空位。这点与上一章的Smith–Waterman不同。Smith–Waterman可以利用Gap,把出现插入或删除的两条序列重新对齐。而1990年BLAST的主要高速搜索方法,则首先侧重寻找无Gap的高分片段。
图52-3:无Gap片段与允许Gap的局部比对
1990年BLAST核心HSP模型局部片段长度相等,各列直接对应ACGTTGACCTGAACGTCGACCTGA
允许Gap的局部比对序列可以在中间插入缺口ACGT-TGACACGTTTGAC

1990年真的完全没有尝试Gap吗?

也不是。原始论文还提到,作者曾尝试通过动态规划延伸种子,产生允许Gap的比对。但在当时的应用与计算条件下,这种处理明显增加了计算成本。因此,他们并没有将其作为论文所强调的主要高速方案。后来,带Gap的BLAST成为重要发展方向。但不应把1997年正式介绍的NCBI Gapped BLAST,直接写成1990年原始BLAST的全部默认能力。[blastalgorithm.com][PubMed]

本章的算法第一次出现了明确的“放弃最优保证”

上一章Smith–Waterman的特点是:给定评分规则后,通过动态规划找到精确的最高局部比对得分。而BLAST为了速度,采取了两次可能丢失候选的选择。第一:只有命中某种短词条件的片段,才有机会被延伸。第二:延伸过程中,如果得分下降过多,就提前终止。所以:BLAST搜索结果≠必然包含全部精确最优局部比对\boxed{ \text{BLAST搜索结果} \neq \text{必然包含全部精确最优局部比对} }但在很多实际任务中,这种取舍非常有价值。因为数据库搜索的目标,往往不是对每一条无关序列都作出最昂贵的精确比较。而是:在合理时间内,尽可能找到真正值得生物学家进一步研究的高分候选。

第七幕:1990年的太阳工作站,究竟跑出了怎样的速度?

现在,我们来看看1990年的论文究竟测量了什么。作者使用了当时的UNIX工作站。其中包括:SUN4系列设备。这不是现代具有大量CPU核心和高速SSD的计算环境。因此,能够怎样组织数据库扫描,对实际性能具有重大影响。

原始论文的一个具体速度

对于当时的一种DNA搜索实现,作者记录:在SUN4计算机上,使用典型长度的查询序列扫描数据库时,处理速度约为:2×106 bases/s\boxed{2\times10^6\text{ bases/s}}也就是:每秒约两百万个碱基位置。这是特定实现、数据组织和测试条件下的历史性能数字。不能直接拿它与现代多线程BLAST程序进行同条件比较。[blastalgorithm.com]

他们为什么要把DNA压缩保存?

因为DNA只包含四种主要核苷酸:A、C、G、T。理论上,每个字符只需要:log⁡24=2\log_2 4=2个比特表示。而一个字节有:88个比特。因此,可以用一个字节保存:82=4\frac82=4个DNA字符。1990年的BLAST论文讨论了这种压缩思想。同时,作者还通过对齐字节边界的短词查找,减少逐字符扫描带来的开销。原始论文指出,这种安排能够在相应实现中提高扫描速度。

还有一项并不显眼的优化:让数据库留在内存里

假设一个实验室每天需要执行很多次序列搜索。如果每次都重新从磁盘读取整个数据库,就会产生大量重复I/O。因此,论文还讨论:将压缩后的数据库装入共享内存。让后续搜索可以直接复用已有数据。这项思路并不是新的数学定理。但它对实际系统性能非常重要。我们在前面多次讨论过类似问题。算法的理论复杂度是一回事。数据怎样存放。怎样访问。是否具有局部性。以及能否避免重复读取。同样会影响真正的运行速度。

从数学算法到工程系统

BLAST的性能优势,不只是来自:“使用短词而不是完整比对。”还来自一系列经过设计的工程细节。例如:高分短词表。快速扫描结构。有限状态自动机。DNA压缩存储。提前终止延伸。以及数据库共享内存。原论文甚至明确给出一个近似的期望时间模型:T≈aW+bN+cNW20w\boxed{ T\approx aW+bN+c\frac{NW}{20^w} }这里:WW 表示生成的高分短词数量。NN 表示数据库中的残基数量。ww 是蛋白质短词长度。而:a,b,ca,b,c代表不同阶段的实现常数。这不是BLAST所有版本都适用的严格复杂度定理。它是论文针对当时蛋白质搜索设计所给出的期望时间近似模型。但它非常直观地表达了三项成本:生成短词集合。扫描数据库。延伸命中的候选片段。[blastalgorithm.com]

为什么速度并不是越快越好?

因为我们可以很容易让一个搜索程序变得“极快”。只要它不看任何数据,直接回答:“没有相似序列。”那么搜索速度几乎可以达到极限。但它也失去了科研价值。因此,真正的目标是:在运行时间与搜索灵敏度之间取得合理的平衡。这便是BLAST论文最值得学习的算法工程思想之一。不是单纯追求速度。而是研究:当我们允许某种程度的近似时,到底能节省多少计算?又会失去多少值得发现的相似关系?

第八幕:互动实验室——建立你自己的微型BLAST搜索引擎

现在,我们亲手构造一套小型DNA搜索系统。这次不只是对两条序列做比较。而是准备一个包含多条序列的数据库,让查询程序自己决定哪些记录值得返回。查询序列仍然是:
text
ACGTTGACCTGA
我们的微型数据库包含六条记录。其中有些与查询序列高度相似。有些只有局部相似。有些存在插入。还有些主要由重复字符构成。这正好让我们观察BLAST式搜索的特点与局限。

实验规则

为了便于理解,我们只使用:DNA完全相同的短词种子。每对相同字符得:+2+2每对不同字符得:−1-1允许用X-drop提前停止延伸。但:不允许Gap。因此,本实验展示的是:1990年BLAST核心思路的简化版本。它不是1990年真实软件的完整重现,也不是现代NCBI BLASTN程序。

实验一:修改种子长度与X-drop

图52-4:种子驱动的DNA局部搜索实验台查询序列固定为12个碱基;六条数据库记录都是教学构造,不对应真实基因。算法会索引查询短词、扫描数据库并对每次命中进行无Gap延伸。
Query · 查询序列

ACGTTGACCTGA

数据库总长度:91个碱基 · 6条记录
3
3
10
查询短词位置

10

全部种子命中

46

达标记录

4 / 6

数据库搜索结果
记录种子数最高分是否达标
样本A · 完整相似1424报告
样本B · 单碱基替换921报告
样本C · 内部插入1218报告
样本D · 局部片段614报告
样本E · 重复序列16未达标
样本F · 弱相关49未达标
数据库序列TTACGTTGACCTGACC共 14 个种子位置对;去重后 3 个候选区间
该记录最高分片段

24

Query位置 1—12;数据库位置 3—14
ACGTTGACCTGA
ACGTTGACCTGA
绿色表示两个字符相同,红色表示不相同。这里没有插入或删除产生的Gap。
为了教学,查询短词与数据库短词必须完全相同。实际蛋白质BLAST还可采用替换矩阵生成高分邻近词。实验会因不同的重叠种子而重复搜索同一片段,之后按片段区间去重;显示的种子数不能直接解释为不同基因或不同生物学证据的数量。

实验观察一:把短词长度从3增加到6

你会发现:种子命中数量通常明显减少。因为必须有六个连续相同字符,才能形成一个命中。但这也意味着:某些仅拥有短相似片段的数据库记录,可能无法触发搜索。这就是:较严格的种子条件,通常减少计算,却也可能降低灵敏度。

实验观察二:将X-drop从3改成0

如果设定:X=0X=0那么,一旦延伸分数下降到历史最佳分数以下,就会提前停止。于是,程序很容易放弃那些包含局部不匹配、但后面仍然高度相似的片段。将:XX增加到:66后,算法能够忍受更长的短期分数下降。但也可能需要比较更多字符。

实验观察三:内部插入会导致什么?

选择:“样本C · 内部插入”。这条序列与查询序列非常相似。但中间多出一个字符。由于我们的实验只支持无Gap延伸,算法无法通过在序列内部插入空位,将全部对应区域完整对齐。它可能分别发现插入位置前后的一些高分片段。却不一定能将它们合并为一个连续的完整结果。这正是1997年Gapped BLAST进一步发展的重要动机之一。

实验观察四:重复序列为什么令人困扰?

选择:“样本E · 重复序列”。反复出现的:
text
AAAA
或:
text
CCCC
容易在某些查询里制造大量短词命中。但这些命中不一定具有值得研究的进化意义。因此,真实BLAST还需要考虑:低复杂度区域。重复元素过滤。以及统计评分修正。我们将在第十幕进一步研究这一点。

第九幕:1990年3月,另一篇论文给BLAST提供了判断“偶然相似”的数学尺子

现在,让我们暂时停止搜索。假设BLAST已经返回一段分数很高的局部比对。例如:S=40S=40这个数字究竟意味着什么?如果我们只看分数本身,可能会认为:40分很高。但是,换一个数据库,或者换一套评分矩阵,同样的40分可能具有完全不同的含义。因为相似性是否令人意外,不仅取决于分数。还取决于随机背景中出现类似分数的机会。

一位研究概率论的数学家

塞缪尔·卡林(Samuel Karlin)。长期研究概率、统计、随机过程及其在生物学中的应用。1990年3月,他与Stephen Altschul共同发表:Methods for Assessing the Statistical Significance of Molecular Sequence Features by Using General Scoring Schemes《利用一般评分方案评估分子序列特征统计显著性的方法》。发表于《Proceedings of the National Academy of Sciences》第87卷第6期,第2264—2268页。这项成果比1990年10月正式发表的BLAST论文更早几个月。[PubMed]它提供了一个极其重要的研究工具。给定合适的随机序列模型与评分规则,可以估计高分局部片段偶然出现的概率。

为什么这与上一章的Smith–Waterman不同?

Smith–Waterman回答:在给定的两条序列和评分规则下,最高局部比对分数是多少?但它并不自动回答:这样的分数,在随机序列之间出现的可能性是多少?而Karlin–Altschul统计理论,可以在满足一定条件的模型下,对无Gap高分片段的随机背景进行分析。这使研究者能够区分:算法评分。统计显著性。与生物学解释。三个层次。

E-value:预期会出现多少个随机高分命中?

BLAST输出里最常见的字段之一,叫作:E-value(Expect Value,期望值)。在经典无Gap局部比对统计模型下,可以写成:E=Kmn e−λS\boxed{ E=Kmn\,e^{-\lambda S} }其中:SS是局部比对的原始分数。mm是查询序列长度。nn是被搜索的序列空间长度。KK和:λ\lambda是由评分系统和相应随机序列模型决定的统计参数。这个公式的含义是:在对应随机背景模型下,预计会出现多少个得分不低于当前分数的偶然局部匹配。NCBI的官方BLAST文档也将E-value解释为,在给定数据库规模下,预期偶然出现的相当或更高得分匹配数量。[NCBI Bookshelf][NCBI Bookshelf]

E-value不是“这条结果是假的概率”

这是一个必须牢牢记住的区别。假设:E=0.01E=0.01它并不直接意味着:“这条序列有1%的概率不是同源序列。”也不直接意味着:“这次实验只有1%的概率发生错误。”它表示的是:在指定随机背景模型和搜索空间下,预期出现约0.01个达到该分数的随机命中。在相关的泊松近似条件下,至少出现一个这样的偶然命中的概率约为:P=1−e−EP=1-e^{-E}例如:E=0.01E=0.01则:P≈0.00995P\approx0.00995约为0.995%。但这仍然是随机模型下的概率。不是“该蛋白质具有共同祖先关系的后验概率”。

一个完整的数值计算

假设我们人为设定:m=100m=100n=1 000 000n=1\,000\,000S=40S=40并假设对应随机模型已经给出:K=0.1K=0.1λ=0.5\lambda=0.5注意:这两个参数只是为了进行数学演示。它们不是某个真实BLAST评分矩阵的官方参数。代入:E=0.1×100×1 000 000×e−0.5×40E=0.1\times100\times1\,000\,000\times e^{-0.5\times40}得到:E=10 000 000×e−20E=10\,000\,000\times e^{-20}因为:e−20≈2.061×10−9e^{-20}\approx2.061\times10^{-9}所以:E≈0.0206\boxed{E\approx0.0206}也就是:在该假设模型下,预期偶然出现约0.0206个得分至少为40的高分片段。如果数据库扩大十倍,而其他条件保持不变:n=10 000 000n=10\,000\,000那么:E≈0.206\boxed{E\approx0.206}同样的原始比对分数40,统计显著性却降低了。为什么?因为数据库越大,进行的潜在比较越多。随机出现高分片段的机会也越多。

图52-5:相同分数,为什么数据库越大,E-value也越大?

2.061154010万 预期偶然命中数 E: 0.0020610.002061100万 预期偶然命中数 E: 0.0206110.0206111000万 预期偶然命中数 E: 0.2061150.2061151亿 预期偶然命中数 E: 2.0611542.06115410万100万1000万1亿
查看图表数据
size预期偶然命中数 E
10万0.002061
100万0.020611
1000万0.206115
1亿2.061154
假设m=100、S=40、K=0.1、λ=0.5,展示E值随数据库规模线性增长。这不是实际NCBI BLAST的某次搜索结果。这项现象十分重要。假设我们在一个很小的数据库中,找到一段看起来高度相似的区域。如果把完全相同的查询,放进一个极其庞大的数据库,偶然出现类似匹配的机会就会增加。因此,不能仅凭原始分数来比较不同规模数据库中的结果。

Bit Score:为什么还需要归一化分数?

BLAST还经常使用:Bit Score(比特分数)。对于相应统计模型,可以表示为:S′=λS−ln⁡Kln⁡2\boxed{ S'=\frac{\lambda S-\ln K}{\ln2} }于是:E=mn 2−S′\boxed{ E=mn\,2^{-S'} }这里使用相应的有效搜索空间时,还可能涉及长度修正等细节。Bit Score将不同评分系统得到的原始分数,转化为具有更统一统计解释的量纲。这不是把DNA序列直接压缩成某个长度的二进制字符串。而是借助信息论形式,表示局部匹配相对于随机背景的显著程度。因此,BLAST结果中:Raw Score。Bit Score。E-value。是三个相关,但不能混为一谈的概念。

Karlin–Altschul理论有哪些适用条件?

这项经典公式并不是对任何字符串、任何评分方法都自动正确。它依赖相应随机序列模型及评分条件。例如:背景字符分布。评分矩阵。期望替换得分的性质。序列长度。以及是否允许Gap。对于现代带Gap比对及复杂序列组成,统计参数和实际估计方法还需要更加精细的处理。因此:BLAST的统计学优势,不在于它拥有一条可以无条件套用的万能公式,而在于它将搜索评分与可检验的随机背景模型联系起来。这也是1990年BLAST论文的一项关键历史贡献。

第十幕:为什么一百万个相同的字母,可能把搜索结果淹没?

现在,我们讨论一类非常特殊的序列。例如:
text
AAAAAAAAAAAAAAAAAAAA
或者:
text
ATATATATATATATATATAT
它们包含大量重复结构。如果查询序列里存在这样的区域,那么数据库里也可能很容易找到相同或类似的短词。但这种相似性,未必代表两条序列具有值得研究的共同进化关系。

什么叫作低复杂度序列?

例如:
text
AAAAAAAAAAAA
它主要由一种字符构成。又例如:
text
ATATATATATAT
它反复使用两个字符。这些序列具有相对简单、重复的组成结构。我们通常称其为:低复杂度区域(Low-complexity Region)。这种概念不仅适用于DNA。蛋白质中也可能存在由某些氨基酸频繁重复组成的区域。

为什么低复杂度会影响BLAST?

假设查询序列中有:
text
AAAAAAAAAAAA
而数据库中有许多区域包含:
text
AAAAAA
如果选择:w=3w=3那么,查询序列中的很多短词都是:
text
AAA
数据库中也可能出现大量:
text
AAA
于是,程序会生成很多种子命中。这些命中需要检查。甚至可能触发延伸。但它们未必都是有价值的生物学结果。这导致两个问题。第一,计算资源被大量相似但信息量很低的候选占用。第二,搜索结果可能被许多不具有明显科学解释价值的重复区域淹没。

1990年的BLAST已经注意到这项问题

这是原始论文中非常值得保留的一项细节。作者明确指出:真实DNA并不是完全随机、四种碱基等概率出现的序列。某些区域具有明显的A/T或G/C组成偏差。另外,人类及其他生物的基因组中,还存在大量重复元素。因此,论文讨论了针对异常频繁短词和重复元素的过滤策略。其中一项方法,是统计数据库中短词的出现频率,并过滤掉某些出现得远远超过随机预期的低信息量短词。研究者还讨论了利用重复元素资料库进行预先识别,再避免这些区域产生大量无用种子。[blastalgorithm.com]这说明:1990年的BLAST并不是一套只有“哈希表+字符串比较”的简单程序。它从一开始就需要面对:生物序列不是均匀随机字符。这一真实世界的复杂性。

现代BLAST怎样处理?

后来的BLAST程序进一步采用了不同的低复杂度过滤和评分修正方法。对于核酸序列,可以使用DUST等方法识别低复杂度区域。对于蛋白质序列,可以使用SEG等方法。现代蛋白质BLAST还会根据序列组成进行某些统计评分调整。这些方法的目的,是尽量减少某些组成偏差对搜索结果的干扰。[NCBI Bookshelf][BLASTHelp documentation]但这里仍然存在权衡。如果把所有低复杂度区域全部无条件删除,就可能丢失某些确实具有生物学意义的重复结构。所以,实际分析时,研究者需要理解:是否启用了低复杂度过滤?这些区域是被硬遮蔽还是软遮蔽?过滤是否影响了种子生成?后续比对和评分又怎样处理这些位置?过滤能够减少噪声,但过滤本身也可能改变搜索灵敏度。

一个更隐蔽的算法问题

假设查询序列:
text
AAAAAAAAAAAA
数据库序列:
text
AACAACAACAAC
如果使用:匹配+2。不匹配−1。那么,两者对应的12个位置里,8个匹配、4个不匹配。完整无Gap片段得分:8×2−4×1=128\times2-4\times1=12是一个正分且不算很低的候选。但数据库序列中,最长连续的A只有两个。因此,如果我们的简化BLAST强制要求至少三个连续相同字符:w=3w=3就可能根本找不到种子:
text
AAA
于是,虽然存在一个得分为12的无Gap局部对应,启发式搜索却可能完全错过。这项例子同时说明两件事。第一:严格短词种子,可能遗漏某些较高评分的比对。第二:较高的原始分数,尤其对于低复杂度序列,并不自动意味着具有生物学显著性。这两种问题需要分别处理。种子设计决定能不能发现候选;统计分析与生物学证据帮助判断候选是否值得相信。

第十一幕:1990年的真实实验——人和兔子的血红蛋白基因区域,在计算机上重新相遇

到现在为止,我们使用的都是短小的教学序列。但1990年研究者真正关心的,是更长、更复杂的生物序列。因此,BLAST原始论文除了对蛋白质数据库进行测试,还包含了一项很有代表性的长DNA序列比较实验。研究对象是:人类的β样珠蛋白基因簇。以及兔子的相应基因区域。

为什么选择珠蛋白基因?

珠蛋白与血红蛋白的组成密切相关。在哺乳动物的进化过程中,相关基因发生过复制、变异和分化。因此,人类和兔子的相关基因区域,可能包含具有共同进化来源的序列片段。这些片段并不是简单地从开头到结尾完全相同。有些是编码蛋白质的基因区域。有些位于基因之间。还有一些可能属于重复序列。这恰恰适合检验:局部相似性搜索,究竟能否找到不同类型的对应关系?

当时已经获得的序列长度

论文记录:人类区域长度约为:73 360 bp\boxed{73\,360\text{ bp}}兔子区域长度约为:44 595 bp\boxed{44\,595\text{ bp}}如果直接构造两条序列的完整动态规划矩阵,那么内部状态数量将达到:73 360×44 59573\,360\times44\,595也就是约:3.27×109\boxed{3.27\times10^9}超过32亿个状态。对于1990年的普通科研工作站,这是非常沉重的计算任务。而BLAST尝试通过短词种子与局部延伸,避免对所有位置对执行完整的动态规划。

研究人员实际找到了什么?

论文给出了非常具体的实验结果。在一项使用:w=12w=12以及DNA匹配:+5+5不匹配:−4-4的搜索中,作者发现:
得分超过200的片段

98

最高片段得分

1301

得分超过350的片段

57

这些数字来自1990年论文第三部分的长DNA序列实验。[blastalgorithm.com]

那57个高分片段分别是什么?

作者进一步分析了得分超过350的57个局部比对。其中:45个涉及两种动物的珠蛋白基因对应关系。另外12个涉及L1类长散在重复序列。而在更低分数区间,还能看到一些基因间区域的局部相似性。因此,算法并不是简单地输出:“人和兔子的DNA有多相似?”而是找出了许多不同位置上的局部对应线索。这些线索有的与基因有关。有的与重复序列有关。有的可能与其他进化事件有关。

为什么这项实验非常重要?

因为它展示了BLAST相对于单次全局比对的一项实际优势。真实的基因组比较,并不一定存在一条从头到尾连续的完美对应路径。一段人类DNA,可能与兔子DNA中的多个不同片段具有相似性。有些来自相似基因。有些来自重复元素。有些来自古老的基因复制事件。因此,科学家希望找到的是:一张由多个局部相似区域组成的候选地图。而不只是一个总分数。这也解释了为什么BLAST最终成为数据库搜索的重要工具。它可以让研究者从大量序列中找到若干值得深入分析的局部线索。但这些线索究竟具有什么进化意义,还需要生物学解释与进一步验证。

最值得记住的研究细节

1990年的作者还研究:如果把DNA种子长度从12减小到8,会发生什么?论文记录,短词长度减小后,会发现更多得分超过200的局部片段。但是,在该具体实验中,新增的32个片段,都属于此前已经观察到的几类相似性结构。因此,减小种子长度确实增加了搜索结果,但并没有带来本质上全新的生物学信息类别。[blastalgorithm.com]这项实验再次说明:更多搜索结果,并不自动等于更多科学发现。搜索灵敏度、统计显著性与生物学解释,需要一起考虑。

第十二幕:Python实战——不用任何生物信息学库,写一个微型BLAST

现在,我们把前面十一幕的算法思想,写成一份完整的Python程序。它只使用标准库。能够处理多条数据库记录。而且会真正执行:查询短词索引。数据库短词扫描。种子命中。双向X-drop延伸。候选区间去重。按得分排序。最后,我们还会用一份精确无Gap局部比对算法,对启发式搜索的结果进行验证。需要明确:这是一份用于理解1990年BLAST核心思想的教学实现,不应命名为工业级BLAST兼容实现。

第一部分:建立短词索引

python

from collections import defaultdict


def build_word_index(query, word_size):
    """
    为查询序列建立短词 -> 出现位置列表。

    例如:
        ACGTT
        w = 3

    得到:
        ACG -> [0]
        CGT -> [1]
        GTT -> [2]
    """

    if not (1 <= word_size <= len(query)):
        raise ValueError("短词长度不合法")

    index = defaultdict(list)

    for i in range(len(query) - word_size + 1):
        word = query[i:i + word_size]
        index[word].append(i)

    return dict(index)


query = "ACGTTGACCTGA"

index = build_word_index(query, 3)

for word, positions in sorted(index.items()):
    print(word, positions)
这项索引的含义非常直接。当程序读到数据库里的一个短词时,不需要重新扫描查询序列的每一个位置。可以直接检查这个短词是否出现在查询索引中。

第二部分:实现X-drop延伸

我们约定:匹配:+2+2不匹配:−1-1使用精确短词种子,所以种子里的全部字符都相同。如果短词长度为:ww那么初始种子分数是:2w2w随后向左与向右延伸。在每个方向上,记录累计增分的最高值与对应的最佳延伸长度。如果当前增分比本方向的历史最高增分低超过X,就停止。
python

def extend_one_side(
    query,
    subject,
    query_start,
    subject_start,
    word_size,
    direction,
    x_drop,
    match=2,
    mismatch=-1,
):
    """
    从已知的完全匹配种子向单侧延伸。

    direction:
        -1 -> 向左
         1 -> 向右

    返回:
        (最佳额外得分, 最佳额外延伸字符数)
    """

    current = 0
    best = 0

    steps = 0
    best_steps = 0

    while True:
        if direction == -1:
            qi = query_start - 1 - steps
            sj = subject_start - 1 - steps
        else:
            qi = query_start + word_size + steps
            sj = subject_start + word_size + steps

        if (
            qi < 0 or
            sj < 0 or
            qi >= len(query) or
            sj >= len(subject)
        ):
            break

        delta = (
            match
            if query[qi] == subject[sj]
            else mismatch
        )

        current += delta
        steps += 1

        if current > best:
            best = current
            best_steps = steps

        if best - current > x_drop:
            break

    return best, best_steps
这里最重要的不是循环本身。而是这两个变量:
python
best
best_steps
它们确保即使算法延伸过头,最后仍然返回过程中出现的最佳片段边界。

第三部分:完整数据库搜索

python

def mini_blast(
    query,
    database,
    word_size=3,
    x_drop=3,
    min_score=10,
):
    """
    教学版BLAST式DNA搜索。

    参数:
        query:
            查询DNA序列,例如ACGTTGACCTGA

        database:
            字典 {记录名: DNA序列}

        word_size:
            完全匹配种子长度

        x_drop:
            延伸分数允许下降的最大幅度

        min_score:
            最小报告得分

    只生成无Gap局部比对。
    不计算E-value。
    """

    query = query.upper()

    if not query or set(query) - set("ACGT"):
        raise ValueError("Query必须由ACGT组成")

    if not 1 <= word_size <= len(query):
        raise ValueError("不合法的word_size")

    if x_drop < 0:
        raise ValueError("x_drop不能为负")

    index = build_word_index(query, word_size)

    all_results = []

    for name, subject in database.items():
        subject = subject.upper()

        if set(subject) - set("ACGT"):
            raise ValueError(
                f"{name}: 只支持ACGT"
            )

        candidates = {}

        seed_count = 0

        for j in range(
            max(0, len(subject) - word_size + 1)
        ):
            word = subject[j:j + word_size]

            query_positions = index.get(word, [])

            for i in query_positions:
                seed_count += 1

                # 向左延伸
                left_score, left_steps = extend_one_side(
                    query,
                    subject,
                    i,
                    j,
                    word_size,
                    direction=-1,
                    x_drop=x_drop,
                )

                # 向右延伸
                right_score, right_steps = extend_one_side(
                    query,
                    subject,
                    i,
                    j,
                    word_size,
                    direction=1,
                    x_drop=x_drop,
                )

                q_start = i - left_steps
                q_end = i + word_size + right_steps

                s_start = j - left_steps
                s_end = j + word_size + right_steps

                score = (
                    2 * word_size
                    + left_score
                    + right_score
                )

                key = (
                    q_start, q_end,
                    s_start, s_end
                )

                candidate = {
                    "name": name,
                    "score": score,
                    "query_range": (q_start, q_end),
                    "subject_range": (s_start, s_end),
                    "query_segment": query[q_start:q_end],
                    "subject_segment": subject[s_start:s_end],
                }

                old = candidates.get(key)

                if old is None or score > old["score"]:
                    candidates[key] = candidate

        hits = sorted(
            (
                candidate
                for candidate in candidates.values()
                if candidate["score"] >= min_score
            ),
            key=lambda x: -x["score"],
        )

        all_results.append({
            "name": name,
            "seed_count": seed_count,
            "hits": hits,
            "best": hits[0] if hits else None,
        })

    all_results.sort(
        key=lambda r: (
            -r["best"]["score"]
            if r["best"] else float("inf")
        )
    )

    return all_results

第四部分:建立一份微型DNA数据库

python

DATABASE = {
    "sample_A_complete":
        "TTACGTTGACCTGACC",

    "sample_B_substitution":
        "GGACGTCGACCTGAT",

    "sample_C_insertion":
        "TTACGTTTGACCTGAA",

    "sample_D_fragment":
        "CCCCGACCTGAAAA",

    "sample_E_repeat":
        "AAAAAAAACCCCCCCC",

    "sample_F_other":
        "TCCAGATATCTGAC",
}


QUERY = "ACGTTGACCTGA"


results = mini_blast(
    query=QUERY,
    database=DATABASE,
    word_size=3,
    x_drop=3,
    min_score=10,
)


for record in results:
    print()
    print("数据库记录:", record["name"])
    print("种子命中数:", record["seed_count"])

    if record["best"] is None:
        print("没有达到阈值的候选")
        continue

    hit = record["best"]

    print("最佳分数:", hit["score"])
    print(
        "Query位置:",
        hit["query_range"],
    )
    print(
        "Subject位置:",
        hit["subject_range"],
    )

    print(hit["query_segment"])
    print(hit["subject_segment"])


# 全部12个碱基完全匹配:
# 每个匹配+2,所以最高分24。
assert results[0]["best"]["score"] == 24

print()
print("微型BLAST基础实验通过")
对于包含查询序列完整拷贝的样本A,最佳得分为:12×2=24\boxed{12\times2=24}对于只出现一个碱基替换的样本B,如果该完整片段被成功找到,得分为:11×2−1=2111\times2-1=21这里,你可以尝试修改:
python
word_size=6
或者:
python
x_drop=0
观察哪些候选消失。再把:
python
word_size=2
恢复成较短种子。你会发现,程序可能产生更多种子命中,但它们并不一定带来更多有价值的最终结果。

第十三幕:真正的数学检验——如果不用种子,最高无Gap局部得分是多少?

现在,我们要面对启发式算法的正确性边界。假设BLAST式搜索没有返回某条候选。是否就代表:这两条序列之间不存在任何较高得分的局部对应?不一定。因为它可能没有找到合适的种子。或者过早结束了延伸。因此,我们需要一项独立方法,用来计算:在不允许Gap的条件下,真正的最高局部匹配分数。这与前面我们学习过的Smith–Waterman具有密切联系。但如果不允许Gap,就不需要考虑上方与左方两个方向。只需要考虑:当前位置是否继续上一对字符的对应片段?或者从当前位置重新开始?

无Gap局部匹配的精确递推

令:H(i,j)H(i,j)表示:两个序列分别在第 ii 和第 jj 个字符结束时,能够获得的最佳非负无Gap局部比对分数。则:H(i,j)=max⁡(0, H(i−1,j−1)+s(ai,bj))\boxed{ H(i,j)= \max\left( 0,\ H(i-1,j-1)+s(a_i,b_j) \right) }注意,它没有:H(i−1,j)H(i-1,j)也没有:H(i,j−1)H(i,j-1)因为我们不允许插入或删除产生的Gap。于是,整个问题可以沿着矩阵对角线完成计算。时间复杂度仍然是:O(mn)O(mn)但可以利用对角线连续扫描,使实现非常简洁。

精确计算函数

python

def exact_ungapped_score(
    query,
    subject,
    match=2,
    mismatch=-1,
):
    """
    精确求解两条序列的最高无Gap局部匹配分数。

    沿所有对角线进行线性扫描。
    每个字符位置对最多处理一次。

    时间复杂度 O(m*n)。
    """

    m = len(query)
    n = len(subject)

    best_score = 0
    best_hit = None

    # diagonal = subject_position - query_position
    for diagonal in range(-m + 1, n):

        i = max(0, -diagonal)
        j = i + diagonal

        current = 0

        start_i = i
        start_j = j

        while i < m and j < n:

            delta = (
                match
                if query[i] == subject[j]
                else mismatch
            )

            current += delta

            if current <= 0:
                current = 0

                start_i = i + 1
                start_j = j + 1

            elif current > best_score:
                best_score = current

                best_hit = {
                    "query_range": (
                        start_i, i + 1
                    ),
                    "subject_range": (
                        start_j, j + 1
                    ),
                }

            i += 1
            j += 1

    return best_score, best_hit


query = "ACGTTGACCTGA"
subject = "TTACGTTGACCTGACC"

score, hit = exact_ungapped_score(
    query, subject
)

print("精确无Gap最高得分:", score)
print("最高分区间:", hit)

assert score == 24

一个专门为揭示启发式遗漏而设计的测试

现在使用:
text
Query:   AAAAAAAAAAAA
Subject: AACAACAACAAC
我们已经知道:数据库序列中并不存在:
text
AAA
因此,如果使用精确三字符种子,微型BLAST不会发现命中。但精确无Gap算法可以找到正分局部对应。
python

query = "AAAAAAAAAAAA"
subject = "AACAACAACAAC"

# 先执行教学版BLAST
search = mini_blast(
    query,
    {"test": subject},
    word_size=3,
    x_drop=3,
    min_score=1,
)

seed_hits = search[0]["seed_count"]

# 再执行精确无Gap局部比对
exact_score, exact_hit = exact_ungapped_score(
    query,
    subject,
)

print("种子命中数:", seed_hits)
print("精确最佳分数:", exact_score)

assert seed_hits == 0
assert exact_score > 0

print("成功观察到启发式搜索遗漏")
这个实验揭示了一项非常重要的事实。即使最终评分目标相同:精确算法与启发式算法,也不一定返回相同结果。因为启发式方法主动限制了候选搜索空间。

为什么仍然要使用BLAST?

如果我们只处理两条短序列,并且非常重视严格的最优性,那么精确动态规划往往是非常自然的选择。但如果数据库很大,研究者可能更关心:怎样在可承受的计算时间内,快速获得高质量候选?这时候,BLAST式种子过滤能够带来巨大的效率优势。不过,结果依赖:种子条件。评分矩阵。延伸规则。低复杂度过滤。以及数据库本身的内容。因此,真实生物学研究中,如果怀疑存在远缘关系但BLAST没有发现命中,可以考虑:调整搜索参数。使用更加敏感的搜索方法。或者对经过生物学筛选的候选序列执行更精细的比较。没有被某次启发式搜索发现,并不等于数学上已经证明相似性不存在。

第十四幕:1997年,当BLAST终于学会处理Gap

到这里,我们已经理解1990年BLAST最重要的算法结构。短词命中。无Gap延伸。X-drop停止。高分片段报告。以及统计显著性分析。但生物学家经常遇到的问题,并不只是字符替换。真实的进化过程还可能包括:插入。删除。以及其他复杂的序列变化。因此,1990年核心无Gap搜索方法虽然非常有用,但并不完整。

一处插入会怎样破坏无Gap对应?

假设:
text
Query:    A C G T - T G A C
Subject:  A C G T C T G A C
两条序列只有一处插入差异。如果允许Gap,它们可以得到相当高的整体对应分数。但如果不允许Gap,那么插入之后的字符位置便会整体偏移。即使后面很多字符实际相同,也不能简单地在一条固定对角线上一直对齐。因此,研究者希望保留BLAST快速寻找候选的优势,同时在适当时候引入允许Gap的局部精细比对。

1996年与1997年的两条实现历史

这里有一个容易被简化的历史细节。1996年,Warren Gish已经发布了支持带Gap比对的WU-BLAST 2.0早期版本。随后,NCBI的研究者又在1997年系统介绍了:Gapped BLAST。以及:PSI-BLAST。1997年9月1日,Stephen Altschul等七位研究者发表:Gapped BLAST and PSI-BLAST: A New Generation of Protein Database Search Programs《Gapped BLAST与PSI-BLAST:新一代蛋白质数据库搜索程序》。刊登于《Nucleic Acids Research》第25卷第17期,第3389—3402页。论文提出了新的种子触发与带Gap扩展机制,并展示了进一步改善运行速度与搜索灵敏度的方案。[Available BLAST Programs][Oxford Academic]

什么是Two-hit策略?

在1997年NCBI Gapped BLAST的发展中,一项有代表性的优化是:Two-hit Method(双命中策略)。它不一定遇到单个短词命中就立即执行昂贵的完整扩展。而是考虑在合理距离内,是否有第二个相容的短词命中。当两个种子支持同一段可能的相似区域时,再投入更多计算资源。这项想法十分自然。因为一段真正较长的相似区域,通常有机会产生不止一个高分短词。如果只存在一个偶然的孤立种子,那么未必值得立即执行较昂贵的扩展操作。当然,双命中也属于启发式设计。如果一段真正相关的区域没有满足双命中条件,它仍然可能被忽略。

PSI-BLAST又改变了什么?

PSI-BLAST的全称是:Position-Specific Iterated BLAST。即:位置特异性迭代BLAST。它特别适合某些希望寻找远缘蛋白质相似关系的任务。基本思想是:第一轮搜索,找出一批具有足够统计支持的候选。然后利用这些匹配序列的信息,构造一个位置特异性评分模型。再使用更新后的模型,重新搜索数据库。如此迭代。
图52-6:PSI-BLAST怎样利用前一轮搜索结果
1
初始蛋白质查询使用初始评分模型执行相似性搜索

2
筛选可靠匹配根据分数、统计显著性与其他条件选择候选

3
构建位置特异性评分模型利用多条序列比对信息估计不同位置的替换偏好

4
重新搜索数据库使用更新后的评分模型寻找可能更远缘的匹配

5
评估并决定是否继续检查新增结果,避免低质量候选污染评分模型

这项技术为什么更适合远缘关系?

因为在蛋白质序列中,不同位置承受的进化约束可能不同。某些位置高度保守。某些位置变化比较自由。如果始终使用同一套固定的替换矩阵,就不能完整表达一个具体蛋白质家族中不同位置的统计规律。而PSI-BLAST能够利用初始搜索获得的相关序列,进一步估计位置特异性的替换偏好。这可能提高对某些较弱远缘相似关系的发现能力。但它也带来了一个新的风险:如果错误的候选进入评分模型,后续迭代就可能不断放大错误信息。因此,PSI-BLAST不是运行轮次越多就必然越正确。它同样需要可靠的候选筛选和统计判断。

2009年:BLAST+重新设计了软件架构

BLAST的发展没有停在1997年。随着数据库越来越大,原有程序需要不断扩展和修改。到2009年,NCBI研究人员发表:BLAST+: Architecture and Applications。介绍采用C++重新组织的软件架构,以提高系统的可维护性和可扩展性。相关论文明确提到,长期增加新功能导致原有代码结构变得复杂,因此需要重新设计。[PMC]这项发展,与我们在很多软件工程项目中看到的问题非常相似。一项算法可以在数学上长时间保持核心思想不变。但软件实现仍然需要不断适应:更大的数据。新的硬件。新的功能。以及新的用户需求。因此,现代NCBI BLAST不只是1990年论文中几十行核心算法的直接放大。它已经发展成一整套具有多种搜索模式、评分模型和工程优化的工具系统。

第十五幕:当你真正打开NCBI BLAST,应该怎样读懂一份搜索结果?

现在,我们已经从1990年的论文走到了现代BLAST。但假设你今天真的获得一条DNA或者蛋白质序列。你应该怎样使用它?更重要的是,应该怎样解释它的结果?NCBI提供了网页版BLAST工具,也提供可在本地运行的BLAST+程序。一个常见的入口是:NCBI BLAST官方搜索入口

先选对搜索类型

不同的查询对象,需要不同的搜索程序。
查询序列数据库类型常见程序
DNA/RNA核酸序列核酸数据库BLASTN
蛋白质序列蛋白质数据库BLASTP
核酸序列蛋白质数据库BLASTX
蛋白质序列六框翻译的核酸数据库TBLASTN
翻译后的核酸查询翻译后的核酸数据库TBLASTX
这些类型及其使用范围,可以在NCBI的官方BLAST文档中核查。[国家医学图书馆]请注意:BLASTX与TBLASTN涉及核酸序列的翻译。它们并不是简单地将核酸字母和氨基酸字母直接比较。

什么是FASTA格式?

BLAST常见的输入格式之一是:FASTA。例如:
text
>example_dna_sequence
ACGTTGACCTGA
第一行以:>开始。后面是序列名称或描述。接下来是实际序列字符。蛋白质与核酸都可以使用FASTA格式,只是字母表不同。这项格式的优点是简单、易读,也便于程序处理。

第一项结果:Identity

Identity表示比对区域中,两个序列对应位置字符完全相同的比例。例如,一段长度100的比对,有90个位置字符完全一致:Identity=90%Identity=90\%但如果这段比对只覆盖整个蛋白质的一小部分,那么90%的Identity并不意味着整条蛋白质也有90%一致性。

第二项结果:Query Coverage

Query Coverage表示:查询序列有多大比例被相关比对覆盖。例如:一条查询蛋白质有:500500个氨基酸。但只有:5050个残基参与某次局部比对。那么覆盖率约为:10%10\%即使这50个残基全部相同,研究者仍然需要注意:其余450个氨基酸,并没有得到同样的对应支持。

第三项结果:Bit Score

Bit Score反映相似匹配在相应统计模型下的归一化强度。一般来说,其他条件适当可比时:Bit Score越高,匹配越强。但必须记住,它是根据评分模型得到的统计量,而不是蛋白质功能相同性的直接百分比。

第四项结果:E-value

E-value用于评估:在指定随机背景与数据库搜索条件下,得到同等或更高分数的偶然匹配预计出现多少次。通常:E-value越小,相对于随机背景越不寻常。但它不等于同源性概率。也不等于某一项具体生物学功能判断的正确概率。
图52-7:同一份BLAST结果的四个不同问题
Identity被对齐的位置里,有多少字符完全相同?

Query Coverage原始查询序列有多大比例被比对覆盖?

Bit Score在统计标准化之后,局部对应有多强?

E-value随机背景下,预计能看到多少个同样高分的偶然匹配?

怎样判断一条序列是否值得继续研究?

假设你找到一条蛋白质匹配。Identity很高。E-value很小。Query Coverage也很高。这些都是有用的证据。但研究者仍然需要进一步考虑:数据库记录是否可靠?序列是否完整?是否包含低复杂度区域?蛋白质是否含有常见结构域?有没有多个具有相似功能的蛋白质家族?以及是否存在更直接的实验或结构证据?尤其不能因为一段短局部匹配分数很高,就直接认定:两种蛋白质一定具有完全相同的功能。BLAST提供的是一份具有统计解释的序列相似性搜索结果,不是对所有生物学问题的最终判决。

尾声:在几百万个字符里寻找答案,真正困难的不是搜索,而是知道什么值得寻找

1990年。美国马里兰州,贝塞斯达。一批计算生物学研究者正在面对不断扩大的分子序列数据库。每一条序列,都可能保存着某种生命分子的结构信息。每一条新序列,都可能与世界另一间实验室研究过的蛋白质发生联系。但这种联系,未必会直接出现在资料的标题里。它可能隐藏在几千个氨基酸中的几十个位置上。也可能隐藏在一段很长的DNA里。一个原本熟悉的基因家族,可能经过漫长进化,已经发生了许多字符替换。某些区域还可能出现插入和删除。科学家需要一种方法。让计算机在海量资料中,尽可能快速地找到这些潜在的相似性。1970年,Needleman与Wunsch已经建立了精确比较序列的重要方法。1981年,Smith与Waterman进一步解决了最佳局部片段的动态规划问题。而现在,研究对象已经从两条序列,扩展到整个数据库。于是,Altschul、Gish、Miller、Myers和Lipman选择了一条不同的计算道路。不先计算所有可能的完整比对。先寻找短小的高分词。再从这些短词出发,向两侧延伸。当延伸得分下降过多,就停止。对得分足够高的片段,进一步评估它们的统计显著性。这便形成了1990年BLAST的核心算法。它所做的,并不是用某种神奇方法保证找到所有真正相关的序列。而是利用生物序列中高分局部片段的统计与结构特点,将大量计算集中到更有希望的区域。这样,研究者可以用当时的普通科研工作站,完成原本非常昂贵的数据库搜索任务。

一个短词,怎样改变整个搜索过程?

回想我们使用的查询序列:
text
ACGTTGACCTGA
一开始,算法并不知道它在哪条数据库记录中可能出现。它只知道:当前查询可以分成许多短词。例如:
text
ACG
CGT
GTT
TTG
GAC
这些短词就像散布在数据库中的路标。当某个短词被找到时,算法并不会立即宣布发现了真正的生物学关系。它只是说:这里可能值得进一步检查。于是,程序向两侧延伸。计算得分。判断是否继续。最后保留高分片段。这正是BLAST与精确动态规划最重要的区别。前者首先使用启发式手段,限制候选搜索空间。后者则按照完整的状态递推,在给定模型下精确优化比对目标。两者并不是互相否定。它们适合不同的计算要求。

本章最重要的四个公式

第一个,短词匹配条件:∑i=1ws(qi,vi)≥T\boxed{ \sum_{i=1}^{w}s(q_i,v_i)\ge T }它决定了哪些短词可以成为种子。第二个,X-drop提前终止条件:Sbest−Scurrent>X\boxed{ S_{\mathrm{best}}-S_{\mathrm{current}}>X }它帮助算法避免在明显无希望的区域里无限延伸。第三个,经典无Gap局部比对统计模型:E=Kmn e−λS\boxed{ E=Kmn\,e^{-\lambda S} }它帮助研究者判断高分片段在随机背景下有多么不寻常。第四个,Bit Score转换:S′=λS−ln⁡Kln⁡2\boxed{ S'=\frac{\lambda S-\ln K}{\ln2} }这些公式分别承担不同任务。种子筛选。延伸控制。统计显著性。以及分数标准化。将它们组合起来,才能形成一套兼顾速度与结果解释的科学搜索方法。

为什么1990年的研究,比“哈希表搜索字符串”更加深刻?

因为生物序列与普通文本之间存在重要区别。一段DNA序列可能具有很强的重复结构。蛋白质中不同氨基酸的替换,不能总被视为相同代价。一段局部相似性,可能源于共同进化来源,也可能是随机巧合。不同数据库规模,会影响高分随机匹配出现的预期数量。而插入、删除、远缘变化,还会让精确字符匹配变得不够敏感。因此,一项真正实用的生物信息学搜索算法,不能只考虑:字符串能否匹配?还需要考虑:匹配值得计算吗?这个分数是否足够高?随机情况下是否经常出现?是否需要更敏感的搜索?以及:研究者能从结果中得到怎样的科学线索?

第五篇已经形成了一条清晰的道路

《破解生命的密码》· 前两章
第51章
Needleman–Wunsch与Smith–Waterman怎样用动态规划,精确寻找两条序列之间的最佳全局或局部对应关系?

第52章
BLAST怎样通过高分短词种子、局部延伸和统计显著性,在海量数据库里高效发现值得研究的相似序列?
在第51章,问题是:给定两条序列,怎样找到最佳对应?在第52章,问题变成:给定一条序列,怎样从整个数据库里,尽快找出值得进一步比较的候选?前一章更强调精确优化。这一章则更强调:如何让一项具有明确数学意义的任务,在实际数据规模上变得可用?这是算法发展的重要一步。精确性告诉我们理想答案是什么;启发式设计让我们有机会在有限计算资源下发现足够有价值的答案。而统计理论,则帮助我们理解这些结果究竟有多可靠。

生命的历史,不会因为找到了一个高分片段而自动揭晓

如果一条蛋白质与数据库中的某个已知蛋白质高度相似,那当然值得进一步研究。但BLAST并不能独自证明:两种蛋白质具有完全相同的功能。或者它们之间具有某种确定的进化关系。这些仍然需要结合更广泛的生物学证据。这也构成了科学算法应有的边界。算法帮助我们发现线索;线索还需要经过解释、验证与检验,才能成为科学结论。1990年BLAST论文的结尾指出,由于生物序列数据库不断扩大,这项快速搜索方法将成为分子生物学家有价值的工具。后来,它确实成为了全球生物信息学研究中最常用的序列搜索技术之一。从早期UNIX工作站上的C语言程序,到后来不断演进的NCBI BLAST与BLAST+,其核心思想持续发挥作用。研究者不再需要逐条翻阅所有数据库记录,寻找看起来相似的片段。他们可以先提交一条序列。让算法从巨大的信息空间中,提出一批值得进一步研究的候选。一段新的DNA。一条未知蛋白质。一个数据库里的短小种子。也许就会成为下一项生命科学发现的起点。

历史档案:从FASTA到BLAST,再到现代生物序列搜索

年份研究者或事件历史意义
1970年Needleman与Wunsch系统寻找蛋白质序列最佳对应关系
1981年Smith与Waterman最佳局部序列比对
1982年Gotoh高效处理仿射Gap罚分
1985年Lipman与PearsonFASTP等快速序列搜索研究
1988年4月Pearson与LipmanFASTA及改进的生物序列数据库搜索工具
1988年11月NCBI正式成立建立面向计算分子生物学的重要研究与信息基础设施
1990年2月26日BLAST原始论文收到五位作者提交研究成果
1990年3月Karlin与Altschul发表统计理论为高分局部片段的随机显著性提供数学工具
1990年5月15日BLAST论文接受发表通过期刊评审
1990年10月5日BLAST论文正式发表提出快速短词搜索、无Gap高分片段延伸及统计分析
1992年NCBI承担GenBank管理职责进一步整合序列数据库与检索服务
1996年WU-BLAST早期带Gap版本扩展BLAST的比对能力
1997年9月1日Gapped BLAST与PSI-BLAST论文发展带Gap搜索和位置特异性迭代搜索
2009年BLAST+架构研究发表推动现代BLAST软件架构的维护与扩展

主要参考文献与一手资料

1990年 · BLAST原始论文Altschul等 — Basic Local Alignment Search Tool(原文PDF)美国国家医学图书馆 — BLAST原始论文书目最重要的一手文献。建议阅读第403—405页的MSP与种子搜索思想、第406—407页的参数和性能分析,以及第407—409页的人类与兔子珠蛋白基因区域实验。

1988年 · BLAST之前的FASTA研究Pearson & Lipman — Improved Tools for Biological Sequence Comparison核查BLAST之前短词过滤与数据库快速搜索已经存在的重要历史背景。

1990年 · Karlin–Altschul统计理论Karlin & Altschul — Methods for Assessing the Statistical Significance of Molecular Sequence Features解释高分无Gap局部片段的随机分布、统计参数与显著性分析,是理解BLAST E-value的重要源头。

NCBI官方历史A Brief History of NCBI’s Formation and Growth核查NCBI于1988年11月成立、BLAST于1990年推出,以及GenBank相关职责于1992年转入NCBI等重要时间节点。

NCBI官方BLAST术语与算法帮助BLAST Glossary — Word、HSP、E-value与Bit ScoreNLM — BLAST Basics用于核查现代BLAST的搜索类型、核心术语、统计量和查询参数。

1997年 · 带Gap与迭代搜索Gapped BLAST and PSI-BLAST: A New Generation of Protein Database Search Programs用于核查双命中、带Gap扩展及位置特异性迭代搜索的后续发展。

2009年 · BLAST+软件架构BLAST+: Architecture and Applications介绍BLAST+的模块化架构和大型科学软件长期维护中的工程问题。
**史实与教学重构说明:**本章关于NCBI成立、BLAST的五位作者与机构、原始论文投稿日期、PIR数据库规模、SUN4测试、蛋白质与DNA种子参数、人兔珠蛋白基因簇实验,以及Karlin–Altschul统计理论的叙述,均依据所列资料。开篇研究者面对新序列的情景,是根据当时科学工作的问题背景组织的教学叙事,并非某次具体实验室对话记录。本章的DNA样本、微型BLAST程序与互动实验为教学构造,只使用精确短词种子和无Gap延伸。真实BLAST还涉及高分邻近词、低复杂度过滤、不同搜索模式、Gap扩展、统计参数和更复杂的软件优化。代码返回的原始分数不能直接当成NCBI BLAST的Bit Score或E-value。1990年的BLAST核心方法属于启发式近似搜索。没有找到某条序列,并不等于数学上证明它与查询序列不存在有意义的相似性;找到较高分数,也不等于已经证明共同进化来源或相同生物学功能。
下一章预告 · 第53章

《看不见的生命状态:隐马尔可夫模型如何读懂DNA》

1970年,一篇关于马尔可夫链统计估计的论文正式发表。作者是:Leonard E. Baum。Ted Petrie。George Soules。Norman Weiss。他们研究一类特殊的概率模型。系统内部拥有某种状态。状态会随着时间变化。但观察者不能直接看到内部状态。只能看到由这些状态随机产生的外部结果。于是,问题来了。如果我们只看到一串观测,能不能推断系统内部可能经历了怎样的状态变化?又能不能从大量观测数据中,反过来学习状态转移概率和观测概率?这项研究后来成为隐马尔可夫模型(Hidden Markov Model,HMM)发展史的重要部分。1970年的论文题目是:A Maximization Technique Occurring in the Statistical Analysis of Probabilistic Functions of Markov Chains发表于《The Annals of Mathematical Statistics》第41卷第1期,第164—171页。 [ScienceOpen]到了1989年,Lawrence R. Rabiner又发表了一篇著名教程,系统介绍隐马尔可夫模型在语音识别中的应用。 [数字对象标识符]但我们下一章真正要研究的,是它怎样进入生命科学。一段DNA序列,表面只有A、C、G、T四种字符。可是在这些字符背后,可能存在许多看不见的结构状态。某些位置属于编码区域。某些位置属于非编码区域。某些区域具有不同的碱基组成和统计规律。研究者只能看到测序得到的字符,却希望推断它们背后的隐藏结构。下一章,我们将把第49章的维特比算法、第51章的序列动态规划,以及本章的生物序列搜索联系起来。重点研究三项不同任务:**Forward:**怎样计算一段DNA观测出现的总体概率?**Viterbi:**怎样寻找最可能的隐藏状态路径?**Baum–Welch:**当模型参数未知时,怎样从数据中逐步估计它们?还会构造一份具有不同碱基组成的人工DNA序列,让互动实验在“编码区样式”与“非编码区样式”的隐藏状态之间,执行概率推断和训练。上一章,我们寻找两条生命序列的共同片段;下一章,我们将尝试透过序列本身,推断那些肉眼看不见的内部状态。
——《算法的故事》第二季 · 第五篇《破解生命的密码》 · 第52章 完