数据输入:喂给 EvoFormer 的三种特征

咱们今天聊聊 EvoFormer 吃进去的到底是什么。说白了,就是三类原始数据——序列、MSA、模板。这是整个结构预测的原料,原料不好,后面模型再强也白搭。

我刚开始接触 AlphaFold 的时候,觉得输入不就是一条氨基酸序列吗?结果一看代码才明白,EvoFormer 的输入层远比想象中复杂。它把序列、多序列比对、同源模板结构分别编码成特征张量,然后再拼到一起。嗯,这里每一步都有讲究。

序列特征:最基础的那条线

单个蛋白质序列,我们一般叫它 target sequence。模型会把它编码成两个东西:一个是 one-hot 表示,一个是位置编码。

具体怎么做?我直接上代码示意,这是我从 AlphaFold 源码里摘出来的简化逻辑:

# 伪代码:序列特征构建
num_res = len(sequence)
aatype = one_hot_encoding(sequence, alphabet=20)
# shape: [num_res, 21]  (20种AA + 未知)
position = positional_encoding(num_res, max_rel_pos=32)
# shape: [num_res, num_dim]

位置编码这个东西,你想想看,Transformer 本身是不感知顺序的,得靠它告诉模型“第5个残基在第10个残基前面”。AlphaFold 用的是相对位置编码,范围限制在 -32 到 32 之间。为什么要限制?因为远距离残基的位置信息其实不太重要,切一刀能省不少计算量。

小技巧: 我在项目里跑预测时发现,如果序列长度超过 2000,位置编码的截断窗口明显影响局部结构的精度。长序列建议分段跑,或者至少检查一下截断半径够不够。

MSA 特征:进化信息怎么编码

MSA(多序列比对)是 AlphaFold 的杀手锏。它把同源序列的进化保守性转换成模型能用的数值特征。这个过程我花了两周才完全吃透——它比看上去要复杂得多。

MSA 特征主要分两块:行注意力 和 列注意力。前者捕捉序列间的进化关系,后者捕捉每个残基位点的保守模式。

先看具体编码方式。MSA 中的每条序列都会被处理成一个 22 维的 one-hot 向量(20种氨基酸 + 缺口 + 未知),再加上一些辅助特征:

特征名称 维度 含义
msa_aatype 22 氨基酸类型 + 缺口/未知
msa_deletion 1 该位置是否缺失(gap)
msa_profile 21 该位置氨基酸出现频率
msa_has_deletion 1 二进制标记是否曾发生缺失
msa_deletion_value 1 缺失概率(归一化后)

有些同学会问:这些特征怎么得到的?其实大部分是从 MSA 原始比对里统计出来的。举个例子,profile 就是每列每个氨基酸的出现频率——你算一下总共有多少条序列,数一数 Aln 占了几个,Gln 占了几个,除一下就行。

避坑提醒: 我曾经在一个项目中用了不平衡的 MSA,因为序列库没有更新,很多同源序列被截断了。结果模型预测的结构局部区域像“面条”一样塌掉了。所以 MSA 质量远比数量重要,垃圾进垃圾出。

模板特征:从已知结构中偷师

模板特征是最容易理解的部分——直接用已知的蛋白质结构去辅助预测目标结构。但这些已知结构不能直接用 PDB 坐标,得转成模型能理解的表示。

AlphaFold 用的模板特征主要有:

  • 模板残基类型:one-hot 编码,20维
  • 模板主链二面角:phi、psi,各一维,标准化到 [-π, π]
  • 模板距离图:CB-CB 距离(Gly 用 CA),分 bin 编码
  • 模板单位向量:描述主链方向,用于相对方位编码

其中距离图编码这步比较有意思。AlphaFold 不是直接丢距离值进去,而是先做分桶——从 2.0Å 到 20.0Å 分 15 个 bin,每个距离落在对应 bin 里就激活那个通道。这东西我一开始觉得多余,后来测试时发现,没有分桶的话模型对距离的泛化能力差很多。

# 伪代码:模板距离图编码
dist = cdist(CB_coords, CB_coords)  # [N, N]
bins = [2.0, 2.5, 3.0, ..., 20.0]
dist_bin = digitize(dist, bins=bins)  # 0~14 共15个bin
feat = one_hot(dist_bin, num_bins=15)
# shape: [N, N, 15]
关键点: 模板特征从结构数据库取出来之后,还得和目标序列做结构比对。这一步会生成一个“结构同源度”权重,质量太差的模板会被模型自动忽略。我个人的经验是,当模板序列一致度低于20%时,基本不贡献有效信息,别浪费计算资源。

三种特征的拼接方式

这三种特征不是简单堆叠的。EvoFormer 的输入层把它们分别嵌入到不同的子空间,然后在后面的 Attention 模块里通过 轴向注意力 交互。

具体流程看图:

EvoFormer 输入特征构建流程 序列特征 one-hot + 位置编码 shape: [N, 256] MSA特征 one-hot + profile shape: [S, N, 256] 模板特征 距离图 + 二面角 shape: [N, N, 128] 特征拼接层 维度对齐 + 归一化 + 线性投影 EvoFormer 主干网络 行注意力 | 列注意力 | 三角乘法更新 特征维度需对齐:序列[N], MSA[S,N], 模板[N,N] → 统一隐藏层维度 d_model=256

你看,整个流程是从左到右,三种特征分别编码,然后在拼接层对齐到统一的 hidden size(256维)。我这里用了 S 表示 MSA 的序列数(deep 维度),N 表示目标序列的残基数。

维度的对齐在实际实现里是个坑。序列特征是 2D 的 [N, d],MSA 是 3D 的 [S, N, d],模板又是 3D 的 [N, N, d]——它们怎么拼?答案是:不拼!我一开始也以为会拼成一个超级张量,但实际 EvoFormer 用的是“保持各自维度,通过交叉注意力交互”。具体来说,MSA 特征做行注意力时,会 Query 序列特征的信息;模板特征则作为 pair representation 的初始化。

实际经验: 我自己调试时发现,MSA 的 S 维度(序列数量)如果太大(超过 1024),显存会暴涨。这时可以按序列同源度或覆盖度做截断——保留最相关的 512 条就够了,多了模型也学不进去。

总结一下我对这三种特征的理解:序列是骨架,MSA 是肌肉,模板是参考线。模型没有骨架会乱跑,没有肌肉会软塌塌,没有参考线会找不到方向。三者缺一不可。