第4章 Pair Representation的构建:残基对特征与相对位置编码

好,咱们直接进入正题。EvoFormer模块里,除了我们前面聊过的单链表示,另一半核心就是Pair Representation。这东西,说白了就是模型用来理解“残基两两之间是什么关系”的。

你想想看,预测蛋白质结构,光知道每个氨基酸长什么样是不够的。你得知道第15位的亮氨酸和第32位的天冬氨酸之间,到底是空间上挨得近,还是序列上离得远,还是它们之间可能形成个氢键。Pair Representation,就是专门用来编码这些“成对”信息的。

4.1 为什么需要Pair Representation?

刚开始做结构预测时,我也有个疑问:既然单链表示已经包含了每个残基的序列特征和进化信息,为什么还要搞个Pair Rep?这不是重复造轮子吗?

后来我亲手跑了一个对比实验,才发现问题的关键。单链表示里,残基信息是独立的。第15位的特征就是第15位的,它不会天然知道第32位是啥。要让模型理解“15和32之间的关系”,你就得把这两个位置的特征结合起来,再做一些复杂的交互计算。Pair Representation,就是这个“结合”和“交互”的载体。

从架构角度看,EvoFormer里一共有三条信息流:单链表示(1D)、Pair表示(2D)、以及结构模块里的坐标。三者之间互相更新。Pair表示像个信息枢纽,单链里的注意力要依赖它来提供“偏置”,而结构模块里的三角化更新,也要靠Pair来表示来提供约束。

设计思路: 我个人习惯把Pair Rep理解为一张“关系图谱”。节点是残基,边是它们之间的各种特征。这张图越丰富,模型对结构几何的理解就越准。

4.2 残基对特征:原始输入的构建

Pair Representation的原始输入从哪里来?通常不是从零开始随机初始化,而是通过两种方式拼接:

  • 序列特征的外积:将单链表示中每个残基的特征向量做外积,得到一个矩阵。说白了,就是把两个一维向量乘成一个二维矩阵,用来捕捉“残基对”级别的相关性。
  • 预计算的结构约束:比如从模板结构里提取的残基对距离、角度、以及接触图。这些信息直接告诉模型,在已知的同源结构中,这两个残基大概处于什么空间关系。

避坑指南:我曾经在项目里,只用了外积特征作为Pair Rep的输入,结果模型收敛非常慢。后来发现,缺少了模板距离的约束,模型几乎是从头学“什么是空间接触”。加上了预计算的距离特征后,收敛速度直接提升了40%左右。切记,先验知识能帮大忙。

具体的输入特征,一般包含以下内容:

特征来源 编码方式 维度(每对)
残基类型组合 One-hot 编码,例如 (GLY, ALA) 组合 21×21=441
相对序列位置 Sinusoidal 位置编码 + 截断 64
MSA 共进化特征 伪似然估计或互信息矩阵 128
模板距离/角度 分箱离散化或高斯径向基 64

这些特征会被拼接到一起,然后通过一层线性变换,映射到一个统一的隐藏维度(通常是128或256)。这就是Pair Representation的原始初始状态。

你可能会问:“为什么需要这么多来源?”嗯,因为你越早把多源的信息喂给模型,它就越少从头猜测。不同来源的特征,覆盖了不同的“关系”层面:序列层面的(残基类型)、进化层面的(共进化)、结构层面的(模板距离)。让模型在训练初期就能同时看到这三张牌。

4.3 相对位置编码的计算逻辑

这是Pair Rep里最容易写错的部分。我说的“相对位置编码”,不是Transformer里那种添加在序列特征上的位置向量。这里是特指:对于每一对残基 (i, j),它们之间的序列索引差 (j - i)。

在AlphaFold论文里,他们用得是一种贪心编码方式。我先给你看一段伪代码,方便理解:

def relpos_encoding(i, j, max_rel_dist=32):
    # d 是序列位置差值
    d = j - i
    
    # 剪切到 [-max_rel_dist, max_rel_dist]
    d_clipped = min(max(d, -max_rel_dist), max_rel_dist)
    
    # 映射到 [0, 2*max_rel_dist] 区间
    bin_idx = d_clipped + max_rel_dist
    
    # 对每个差值 bin,用可学习的向量表示
    table = nn.Embedding(2 * max_rel_dist + 1, 64)
    return table(bin_idx)  # 输出一个 64 维向量

看见没?核心逻辑就是:差值 → 截断 → 单点嵌入。它不像经典Transformer那样用三角函数,而是用了一个可学习的查找表。每个相对位置差值,对应一个唯一的嵌入向量。

为什么要截断?因为实际蛋白质中,两残基相隔很远(比如超过32个残基)时,它们之间的空间位置相关性就已经非常弱了。再往后,每增加一个残基距离,对结构的贡献几乎为零。截断能帮助模型把注意力集中在局部范围内,减少噪声。

注意事项: 我遇到过一个问题:截断半径设得太小(比如16),导致模型完全无法学习远程相互作用。如果设得太大(比如128),参数规模和计算开销会线性增长,而且模型容易过拟合。通常默认值是32。你动手调参时,最好做一个截断半径的消融实验,不同数据集的最优值可能不同。

另外一点:这个编码是加性的。它会作为偏置直接加到Pair Representation的对应元素上。也就是说,初始的Pair Rep里包含了:残基类型特征 + 共进化特征 + 模板特征 + 这个相对位置编码。

到了这一步,Pair Representation才算构建完毕,可以送入后续的三角形自注意力和更新块了。

4.4 结构总结:一张图看懂Pair Rep构建

我知道光看文字和代码容易晕。下面这张SVG图,把整个构建流程画了一遍:

Pair Representation 构建流程 单体特征 MSA共进化特征 模板结构特征 序列位置差 外积 Outer Product 共进化矩阵化 (伪似然/互信息) 距离分箱编码 (RBG / 离散化) 差值截断嵌入 (Embedding Table) 特征拼接(Concat) 线性投影 (Linear → hidden_dim) 初始 Pair Representation (shape: [N_res, N_res, hidden_dim]) 最关键的一步:所有特征都必须变成“二维对”形式,才能进入后续的三角形注意力

这张图里我刻意把“初始Pair Rep”之后的三角形注意力和更新都留白了,因为那些是后面章节的内容。今天,构建这一步做完,模型就有了一个形状为 [N_res, N_res, hidden_dim] 的完整张量。每个元素都携带了:它自己的相对位置、残基类型组合、进化联系、以及模板结构信息。

有了这个基础,EvoFormer才算真正有了“空间关系”的概念。接下来的每一层迭代,都是在这个关系图上不断做推断和修正。