第3章 TM-score 数学公式详解:长度归一化、距离权重、缩放因子 d0 的计算

TM-score 这个指标,很多人第一眼看到公式就头大。其实它背后的逻辑非常朴素——说白了就是回答一个问题:

两个蛋白质结构叠在一起后,那些对齐的残基对,到底有多“近”?

但直接算平均距离肯定不行。小蛋白和大蛋白的尺度不一样,误差容忍度也不一样。所以 TM-score 用了三个巧妙的招数:长度归一化、距离权重和动态缩放因子 d0。把这三点搞明白,你就掌握了 TM-score 的核心。我当年第一次实现这个公式时,就是在 d0 的计算上踩了坑,折腾了好几天才调对。

3.1 长度归一化——把不同大小的蛋白拉到同一把尺子上

两个结构叠加后,先找出所有匹配的残基对。假设有 N 对,每对残基间的欧氏距离是 d_i(单位 Å)。

如果我们直接算平均距离,结果肯定被大蛋白“撑大”。举个例子:一个 100 残基的蛋白和一个 500 残基的蛋白,同样 RMSD = 2 Å,但后者的匹配对更多,天然就有更大的总距离值。

TM-score 的解决方法是——用 N 的 1/4 次方做归一化分母。公式里是这样写的:

TM-score = (1 / N) * Σ_{i=1}^{N} [1 / (1 + (d_i / d0)^2)]

其中 d0 = 1.24 * (N - 15)^(1/3) - 1.8
注意:这里的 N 是匹配的残基对数,不是蛋白全长。

为什么是 1/4 次方?其实 d0 里面已经隐含了 N 的 1/3 次方,再配合外面的 (1/N) 因子,整体效果等价于 长度归一化因子为 N^(1/4)。我个人习惯把这个因子理解为“把不同大小的蛋白压缩到同一个数值范围里”——好比把不同尺寸的图纸缩放到同一张标准纸上,便于比较。

我的经验: 如果你是自己写 TM-score 实现,千万注意 N 是匹配上的残基对数,不是被比对结构的全长。我第一次写的时候直接用输入序列长度当 N,结果算出来的 TM-score 全在 0.2 以下,排查了一天才发现这个坑。

3.2 距离权重——越近的残基贡献越大,远距离直接忽略

归一化之后,每个残基对的得分用权重函数计算:

w(d_i) = 1 / (1 + (d_i / d0)^2)

这个函数长什么样?你想想看:当 d_i = 0 时,w = 1;当 d_i = d0 时,w = 0.5;当 d_i = 3*d0 时,w = 0.1;当 d_i → ∞ 时,w → 0。

说白了,就是让距离很近的残基对贡献接近满分,距离超过 3 倍 d0 的直接被“砍掉”。这种设计非常实用——实际结构比对中,局部匹配的误差通常很小,而局部大偏差往往意味着该区域根本没叠好。权重函数自动把后者“降权”,避免它们干扰整体评分。

为什么不用指数衰减或高斯核? 因为 TM-score 的权重函数分母是二次型,计算简单且没有“长尾”效应。高斯核衰减太快,容易把中等距离的匹配也压得太低;而指数衰减又不够陡。二次型这波操作,算是个平衡的折中。

我在项目中遇到过一种情况:两个同源蛋白的核心区域 RMSD 只有 1 Å 出头,但 loop 区偏差很大。如果用 RMSD 评分,loop 区会把平均值拉得很高;而 TM-score 用权重函数后,loop 区得分几乎为零,核心区得分接近 0.95,最终 TM-score = 0.86——完美反映了两者实际的结构相似度。

3.3 缩放因子 d0——自适应阈值,大蛋白更“宽容”

d0 是 TM-score 里最精妙的设计。它不是一个固定值,而是根据匹配残基数 N 动态计算:

d0 = 1.24 * (N - 15)^(1/3) - 1.8

为什么会这样?

  • 当 N 很小时(比如 N = 30),d0 ≈ 1.24 * 15^(1/3) - 1.8 ≈ 1.24 * 2.47 - 1.8 ≈ 1.27 Å。小蛋白允许的误差很小,比较“严格”。
  • 当 N 很大时(比如 N = 400),d0 ≈ 1.24 * 385^(1/3) - 1.8 ≈ 1.24 * 7.28 - 1.8 ≈ 7.23 Å。大蛋白的允许误差变大,比较“宽松”。

我个人觉得这个公式的灵感来自概率统计——蛋白质残基对的距离分布大致服从一种长尾分布,d0 的立方根关系刚好让相同 TM-score 值对应不同大小蛋白的“同等统计显著性”。换句话说,TM-score = 0.5 在小蛋白和大蛋白里代表的相似程度在统计上是一致的。

曾经我犯过的错: 我用 Python 实现时,直接写 d0 = 1.24 * (N - 15)**(1/3) - 1.8,结果在 N < 15 时 (N-15) 为负数,开立方根导致复数。实际上 TM-score 原作者要求 N 最小为 15(少于 15 个残基的比对不计算 TM-score)。我后来加了判断:if N < 15: return None 或者返回 0。记住这个边界条件!

3.4 完整公式与计算流程

把上面三部分组合起来,TM-score 的完整计算公式:

TM-score = (1 / N) * Σ_{i=1}^{N} [1 / (1 + (d_i / d0)^2)]
其中 d0 = 1.24 * (N - 15)^(1/3) - 1.8,且 N ≥ 15

计算步骤很清楚:

  1. 输入两个蛋白质的三维坐标,通过叠合算法得到刚性变换,计算所有匹配残基对的欧氏距离 d_i。
  2. 统计匹配残基对数 N。
  3. 根据 N 计算 d0。
  4. 对每一个 d_i,计算 w_i = 1 / (1 + (d_i / d0)^2)。
  5. 求所有 w_i 的平均值,得到 TM-score。

下面这张图把整个流程串起来,方便你对照理解:

TM-score 计算流程 结构 A & B 刚性叠合 计算 d_i, N 计算 d0 1.24·(N-15)^(1/3)-1.8 计算权重 Σ 1/(1+(d_i/d0)^2) TM-score 关键点: • 长度归一化 = 1/N,配合 d0 中的 N^(1/3) 实现整体 N^(1/4) 效果 • 权重函数确保近距离贡献大,远距离贡献小 • d0 自适应大蛋白的较大误差容忍度,保持统计显著性一致

3.5 一个计算实例

假设我们比对两个结构,得到 N = 100 对匹配残基,各距离 d_i 如下(简化数据):

d_i 范围 (Å)频次权重因子 (d0≈4.0Å)加权和
0.5 ~ 1.5301/(1+(1/4)^2) ≈ 0.9428.2
1.5 ~ 3.0401/(1+(2.25/4)^2) ≈ 0.7630.4
3.0 ~ 5.0201/(1+(4/4)^2) = 0.5010.0
5.0 ~ 8.0101/(1+(6.5/4)^2) ≈ 0.282.8

总和 = 28.2 + 30.4 + 10.0 + 2.8 = 71.4

TM-score = 71.4 / 100 = 0.714

这个值落在“结构显著相似”区间(通常 TM-score > 0.5 认为两个结构属于同一折叠类型)。你想想看,如果直接用 RMSD 算,这些距离的平均值大约是 2.8 Å,但两个蛋白大小不同,单看 RMSD 很难判断相似程度。而 TM-score 直接给了一个 0~1 之间的数值,非常直观。

我的建议: 在实际做跨批次比较时,不要只盯着 TM-score 的数值。我习惯同时看两个辅助指标:匹配残基对数 N 和对应的 d0 值。如果 N 很小(比如 < 30),即使 TM-score = 0.7 也不一定可靠,因为匹配太少了。另外,观察权重函数的衰减曲线也能帮你理解“什么样的误差是可以接受的”——记住,当 d_i > 3*d0 时,权重已经低于 0.1,差不多可以认为这些残基对没匹配上。