📑 本页目录(点开跳转)
03 · 蛋白质语言模型:它到底学到了什么
⏱ 24 分钟 | ⭐ 掩码语言建模被迫学会的是共进化,而共进化就是结构约束
🎯 一句话
把序列喂给一个 Transformer 做完形填空,它学到的不是「语法」,是哪些位置必须一起变。 而「必须一起变」在三维空间里就意味着这两个残基挨着 —— ⭐ 这一条就是从序列一路通到结构的那座桥。
🔁 一、目标可以照搬,别的都变了
掩码语言建模的目标函数原样搬过来就能用:遮住一部分,让模型猜。 但 02 章已经说清楚,输入这一侧和自然语言完全不同:
| 自然语言 | 蛋白序列 | |
|---|---|---|
| 词表 | 几万到几十万 | ⭐ 20 出头 |
| 词边界 | 有(空格、BPE) | ⚠️ 没有,切在哪都是人为的 |
| 长度 | 几百 token | 几百到几千个残基 |
| 「语法」从哪来 | 人类用法 | ⭐ 物理和演化的约束 |
⭐ 最后一行才是重点。语言里「猫追老鼠」比「老鼠追猫」常见是习惯; 而序列里某两个位置必须配合,是因为配不上的那些个体活不下来 —— 它们从来没进过数据库。数据集本身就是一台筛选器。
🧬 二、共进化:这一章的题眼
同一个蛋白在几千个物种里都有自己的版本。把它们对齐摞成一张表(这叫 MSA,多序列比对), 列与列之间的相关性就是结构信息:
如果位置 i 的残基变大了一号,而位置 j 必须跟着变小一号才不至于把结构撑坏, 那么在这几千条序列里,i 和 j 这两列就会呈现出很强的统计相关。
⭐ 完形填空被迫学会这件事 —— 想把遮住的位置猜准,模型没有别的办法。
下面这段把它跑出来:造 4000 条序列,只让三对位置真正耦合, 然后用互信息把它们找出来。
import numpy as np
L, Q, N = 40, 20, 4000 # 40 个位置,20 种氨基酸,4000 条序列
TRUE_PAIRS = [(3, 27), (11, 34), (18, 22)] # ⭐ 只有这三对在三维空间里挨着
rng = np.random.default_rng(0)
msa = rng.integers(0, Q, size=(N, L)) # 先随机
# 让配对的两个位置强相关:j 的取值跟着 i 走(否则"结构塌了",这条序列不会存在)
for i, j in TRUE_PAIRS:
keep = rng.random(N) < 0.9 # 90% 的序列遵守约束
msa[keep, j] = (Q - 1 - msa[keep, i]) # 一个简单的"互补"规则
def mutual_info(a, b):
"""两列之间的互信息,单位 bit。"""
joint = np.zeros((Q, Q))
np.add.at(joint, (a, b), 1.0)
joint /= joint.sum()
pa, pb = joint.sum(1, keepdims=True), joint.sum(0, keepdims=True)
nz = joint > 0
return float((joint[nz] * np.log2(joint[nz] / (pa @ pb)[nz])).sum())
scores = []
for i in range(L):
for j in range(i + 1, L):
scores.append((mutual_info(msa[:, i], msa[:, j]), i, j))
scores.sort(reverse=True)
print("互信息最高的 6 对位置:")
for mi, i, j in scores[:6]:
tag = " <== 真实接触对" if (i, j) in TRUE_PAIRS else ""
print(" (%2d,%2d) MI = %.3f bit%s" % (i, j, mi, tag))
bg = np.mean([s[0] for s in scores[len(TRUE_PAIRS):]])
print("\n背景(其余 %d 对)平均 MI = %.3f bit" % (len(scores) - len(TRUE_PAIRS), bg))
真实输出:
互信息最高的 6 对位置:
(11,34) MI = 3.566 bit <== 真实接触对
( 3,27) MI = 3.531 bit <== 真实接触对
(18,22) MI = 3.494 bit <== 真实接触对
( 4,18) MI = 0.085 bit
(22,28) MI = 0.081 bit
(13,15) MI = 0.080 bit
背景(其余 777 对)平均 MI = 0.066 bit
⭐ 三对真实接触整整齐齐排在 1、2、3,MI 约 3.5 bit, 而其余 777 对的背景是 0.066 bit —— 五十倍以上的分离。 结构信息真的躺在序列统计里,不需要任何标签就能捞出来。
⚠️ 三、但互信息会撒谎:间接相关
⭐⭐ 这是这一章最值得记住的一个坑。 假设 5 和 15 接触、15 和 25 接触, 而 5 和 25 离得很远。互信息会怎么说?实测:
互信息最高的 5 对:
(15,25) MI = 3.579 bit <== 真实接触
( 5,15) MI = 3.528 bit <== 真实接触
( 5,25) MI = 2.973 bit <== ⚠️ 间接相关,并不接触
( 4,18) MI = 0.085 bit
( 5, 7) MI = 0.080 bit
💀 (5,25) 拿到 2.973 bit、排名第三,而它根本不接触 —— 相关性顺着 5→15→25 传递过去了,背景才 0.085,它高出四十倍,看起来和真接触一模一样。
⭐ 这就是「直接耦合分析」(DCA)这一整类方法被发明出来的原因: 它们要回答的不是「i 和 j 相关吗」,而是「扣掉所有其他位置的中介之后,i 和 j 还直接相关吗」。
⭐⭐ 而这也正是深度模型的优势所在:注意力机制在同一层里让所有位置互相看, 天然是在联合建模而不是逐对统计,间接相关会被其他位置解释掉。 ⚠️ 有工作观察到某些注意力头的高分位置和真实残基接触高度重合 —— 机制是清楚的,但具体重合到什么程度依模型和层而异,这里不给数字。
🧊 四、拿它当特征用
对绝大多数人来说,蛋白语言模型的用法不是自己训一个,而是:
拿一个训好的主干 → 冻结 → 取出 embedding → 上面接一个小模型。
⭐ 这是小数据场景的默认做法,理由和实测在 06 章: 20 条标签时,冻结表示把 R² 从 0.374 抬到 0.696,而那个表示一个标签都没用。
🗓️ 模型选型会过期,这里不给推荐。 ESM 系列、ProtTrans 系列是常见的起点, 但具体哪一代、哪个尺寸最合适,请去查最新的。⭐ 不变的是用法: 先冻结、先量一下它比 one-hot 好多少,再决定要不要投入微调。
🚧 五、它不能做什么
⚠️ 这一节比上面几节更重要,因为高估它的代价很大。
① 它不理解能量学。 模型学的是「什么序列在数据库里出现过」, 不是「这个突变会让自由能变化多少」。两者相关但不等价。
② 对设计出来的序列不可靠。 数据库里全是演化产物; 你新设计的序列可能落在分布之外,⚠️ 模型给出的高分在那里没有校准过。
③ embedding 里带着数据库的采样偏差。 被研究得多的家族样本多、表示好; 冷门家族表示差。⚠️ 而这个差别不会体现在你的平均指标上 —— 除非你按家族分组去看。⭐ 这和 07 章是同一件事的两面。
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 02 · 序列不是文本 | 第一节那张对照表的完整版:词表、词边界、长度各自带来什么后果 |
| 04 · 从序列到结构 | ⭐ 共进化捞出来的接触信息,下一章讲怎么把它变成三维坐标 |
| 06 · 小数据这一关 | 「冻结 + 小头」的实测账:20 条标签时 0.374 → 0.696 |
| 07 · 评测的坑 | ⚠️ 第五节第三条的展开:平均指标看不见冷门家族的表现 |
| 机器学习的数学原理 01b · KL 散度 | 第二节用的互信息就是一个 KL 散度 —— 联合分布和「假装两列独立」之间的距离 |
| 大模型全景导论 主线 2 · 模型怎样看懂一句话 | 注意力为什么天然在做联合建模(第三节最后那个论点的机制) |
✅ 检查点
- 掩码语言建模搬到蛋白序列上,什么没变、什么变了?
- 什么是共进化?为什么完形填空这个目标被迫学会它?
- 实验里三对真实接触的 MI 是多少?背景是多少?差多少倍?
- 💀 互信息会怎么撒谎?实验里那个假接触拿了多少分、排第几?
- 这个问题催生了哪一类方法?深度模型为什么在这件事上有优势?
- 蛋白语言模型有哪三件事做不了?第三件为什么在平均指标上看不出来?
👀 答案
- 目标函数没变,原样搬过来就能用。变的是输入:词表只有 20 出头、没有词边界(切在哪都是人为的)、长度几百到几千。⭐ 最关键的是「语法」的来源变了 —— 语言里是人类习惯,序列里是物理和演化的约束(配不上的个体活不下来,从来没进过数据库)。
- 同一个蛋白在几千个物种里各有版本,摞成 MSA 之后,如果两个残基在空间上挨着,一个变了另一个必须跟着变,于是这两列呈现强相关。⭐ 完形填空要把遮住的位置猜准,没有别的办法,只能学会这种成对约束。
- 三对真实接触 MI 约 3.5 bit(3.566 / 3.531 / 3.494),整齐排在 1、2、3;其余 777 对背景平均 0.066 bit —— 五十倍以上的分离。
- 间接相关:5-15 接触、15-25 接触,而 5 和 25 并不接触,但相关性顺着传递过去 —— (5,25) 拿到 2.973 bit,排第三,比背景 0.085 高四十倍,看起来和真接触一样。
- 催生了直接耦合分析(DCA)这一类方法:问的不是「i 和 j 相关吗」,而是「扣掉其他位置的中介之后还直接相关吗」。⭐ 深度模型的优势在于注意力让所有位置在同一层互相看,天然是联合建模而不是逐对统计,间接相关会被其他位置解释掉。
- ①不理解能量学(学的是「出现过什么」不是「自由能变多少」)②对设计序列不可靠(数据库全是演化产物,新设计的序列在分布外、高分没校准过)③embedding 带着数据库的采样偏差(热门家族表示好、冷门差)。⭐ 第三件看不出来是因为平均指标会把冷门家族的差表现摊平,除非你按家族分组去看。
🛑 可以停在这里
到这里你已经知道这类模型学到的是什么(共进化 = 结构约束)、怎么用(冻结取特征)、 以及不能指望它什么。⭐ 光是「互信息会因为间接相关而撒谎」这一条, 就够你在读任何一篇讲接触预测的东西时不被绕进去。
⚠️ 什么时候看下一页:你想知道从「哪些残基挨着」到「三维坐标」中间还差什么。
⚡ 走神救援
先记住这几件事
- 序列预训练利用共现与演化约束学习表示,无标签数据也可能携带结构信息。
- 相关不等于直接接触,间接关联需要单独辨别。
- 小数据任务可先用冻结表示建立基线,并检查家族覆盖和分布外限制。
下一节 👉 04-从序列到结构.md