2. 输入特征:MSA与Pair特征的生成原理

坦白讲,EvoFormer 最让我着迷的地方,就是它怎么处理输入数据。你不把原始序列喂进去就完事了——它需要两种非常特殊的特征:MSA 特征和Pair 特征。你可以把它们想象成蛋白质的“社交关系”和“空间关系”。

我第一次调 AlphaFold2 的时候,总觉得 MSA 特征就是个序列对齐嘛,能有多大差别?直到有一次跑一个孤儿蛋白,MSA 深度不够,预测结果直接崩了。嗯,从那以后我才真正重视起这个输入环节。

2.1 MSA 特征:从序列到进化协方差

为什么需要 MSA?说白了,单个序列的信息太少了。你拿一条氨基酸序列,根本看不出哪些位置在空间上靠近。但你找来几千条同源序列一比对——那些共变的位置,极有可能在三维结构里是挨着的。

特征维度 含义 形状示例
目标序列(query) 你要预测的那条蛋白序列 L × 1
同源序列(hit) 数据库中搜索到的相似序列 N × L
序列权重(weight) 每条序列的可靠程度 N × 1
删除率(deletion) 该位置在序列比对中缺失的概率 N × L

MSA 的构建流程,我一般走这几步:

  1. 搜索同源序列——用 HHblits、jackhmmer 这类工具,从 UniRef 或 BFD 数据库里捞。
  2. 多轮迭代对齐——每次迭代都会扩展序列库,我习惯跑 3 轮,再深就过拟合了。
  3. 去冗余 & 重加权——太相似的序列会拉低有效深度,我一般设 80% 的序列相似度阈值。
  4. 计算序列权重——每条序列的重要性不一样,越独特的序列权重越大。

原始特征怎么编码?我直接贴一段我在项目中用过的伪代码逻辑:

# MSA 原始特征编码示意
def encode_msa(sequences):
    # sequences: list of strings, length = N
    # 1. 独热编码(one-hot)
    residues = "ARNDCQEGHILKMFPSTWYV-X"
    one_hot = np.zeros((N, L, len(residues)))
    for i, seq in enumerate(sequences):
        for j, aa in enumerate(seq):
            one_hot[i, j, residues.index(aa)] = 1.0

    # 2. 有无缺失标记(has_deletion / deletion_value)
    has_del = (sequences == '-').astype(float)
    del_val = ...  # 从对齐质量中提取

    # 3. 拼接后投影到 c_m 维(通常 256)
    features = np.concatenate([one_hot, has_del, del_val], axis=-1)
    return features  # shape: (N, L, 22+2) -> 投影后 (N, L, c_m)

你想想看,这个 c_m 维度是个关键参数。我踩过坑——一开始设太小,信息丢失;设太大,又浪费显存。实践下来 256 是个好选择。

2.2 Pair 特征:空间关系的起点

Pair 特征解决的,是“残基 i 和残基 j 之间是什么关系”这个问题。乍一看你可能觉得 MSA 已经包含共变信息了,但 MSA 是二维的(序列 × 位置),还需要一个显式的“残基对”表示。

Pair 特征的来源有两个:

  • 相对位置编码——残基 i 和 j 在序列上的距离差,我用正弦余弦函数嵌入。
  • 外积均值(Outer Product Mean, OPM)——从 MSA 的行维度上提取协方差。

这里有个细节:OPM 的输入不是原始 MSA,而是经过单层 MLP 投影后的 MSA 表示。我当初写这个模块的时候,手滑把投影层忘了,导致梯度一直不收敛……后来追了三天 bug 才发现。

Pair 特征的最终形状:L × L × c_z,其中 c_z 通常设为 128。你看,它是对称的——一条对角线天然就是自己和自己配对。

关键理解: MSA 特征是“序列间”的信息,而 Pair 特征是“位置间”的信息。EvoFormer 的核心机制,就是在双流结构中让这两条信息互相补充、互相精炼。

2.3 特征生成的完整流水线

我把整个流程画了张图,方便你对照着理解。你注意到没——MSA 和 Pair 在初期就产生了交互,这也是 EvoFormer 比传统方法聪明的地方。

EvoFormer 输入特征生成流程 原始氨基酸序列 Query Sequence (长度 L) MMseqs2 / HHblits 同源序列搜索 MSA 构建与去冗余 多轮迭代 + 重加权 MSA 特征编码 (N, L, c_m) 投影 Pair 特征生成 相对位置编码 + OPM Pair 特征 (L, L, c_z) MSA 特征 (N, L, c_m) 双流特征 进入 EvoFormer

2.4 避坑指南:我踩过的三个坑

在特征生成这块,我总结过几个实战中容易出问题的点:

坑一:MSA 深度不够,强行跑 Pair 特征
我曾经接过一个任务,目标蛋白只有 30 条同源序列。按默认配置跑,Pair 特征里的 OPM 项几乎全是噪声。后来我加了模板特征做补充,效果才起来。
技巧:MSA 深度 < 100 时怎么办?
我一般会在搜索阶段放宽 E-value 阈值,或者把迭代轮次从 3 增加到 5。如果还不行,就别依赖 OPM 了,Pair 特征里多靠相对位置编码撑着。
坑二:序列权重没加,MSA 特征退化
最初我懒得算权重,把所有序列一视同仁。结果 MSA 特征里重复的保守区域权重过大,共变信息被淹没了。嗯,后来老老实实加了重加权,收敛速度直接翻倍。
技巧:试试序列重采样的效果
我建议你每次训练时,对 MSA 做随机裁剪——只采样 512 条序列,而不是全量。这样既能降低显存消耗,又能防止模型过拟合某些冗余序列。

2.5 小结:这两组特征有多重要?

说白了,如果你把 EvoFormer 看作一个推理引擎,那 MSA 和 Pair 就是它的燃料。燃料质量不好,引擎再强也跑不起来。我见过太多人把注意力都放在 Transformer 模块上,却忽略了输入端的处理——结果练出来的模型,泛化能力一塌糊涂。

你回头看看这幅流程图,重点抓住两件事:

  1. MSA 特征是 (N, L, c_m),承载进化共变信息。
  2. Pair 特征是 (L, L, c_z),表达残基对空间关系。

这两者缺一不可,也互相校准。接下来的章节,我会详细拆解它们如何通过“成对注意力”和“三角乘法”进行交互。——不过那是后话了,先把今天的特征生成原理吃透再说。