🏠 总目录📚 本教程 03 · 蛋白质语言模型 ← →
📑 本页目录(点开跳转)

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,它高出四十倍,看起来和真接触一模一样。

5–15 接触、15–25 接触,而 5 和 25 离得很远(15,25)3.579真接触(5,15)3.528真接触(5,25)2.973⚠️ 并不接触(4,18)0.085背景(5,7)0.080背景假接触是背景的 35 倍,只比真接触低 16%相关性顺着 5 → 15 → 25 传过去了
只比第三根和上面两根的长度:红色那对根本不接触,却几乎和真接触一样高,而背景只有 0.08。⭐ 这就是「直接耦合分析」被发明的原因 ——要问的不是「相关吗」,是「扣掉其他位置的中介之后还直接相关吗」。

⭐ 这就是「直接耦合分析」(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 · 模型怎样看懂一句话 注意力为什么天然在做联合建模(第三节最后那个论点的机制)

✅ 检查点

  1. 掩码语言建模搬到蛋白序列上,什么没变、什么变了?
  2. 什么是共进化?为什么完形填空这个目标被迫学会它?
  3. 实验里三对真实接触的 MI 是多少?背景是多少?差多少倍?
  4. 💀 互信息会怎么撒谎?实验里那个假接触拿了多少分、排第几?
  5. 这个问题催生了哪一类方法?深度模型为什么在这件事上有优势?
  6. 蛋白语言模型有哪三件事做不了?第三件为什么在平均指标上看不出来?
👀 答案
  1. 目标函数没变,原样搬过来就能用。变的是输入:词表只有 20 出头、没有词边界(切在哪都是人为的)、长度几百到几千。⭐ 最关键的是「语法」的来源变了 —— 语言里是人类习惯,序列里是物理和演化的约束(配不上的个体活不下来,从来没进过数据库)。
  2. 同一个蛋白在几千个物种里各有版本,摞成 MSA 之后,如果两个残基在空间上挨着,一个变了另一个必须跟着变,于是这两列呈现强相关。⭐ 完形填空要把遮住的位置猜准,没有别的办法,只能学会这种成对约束。
  3. 三对真实接触 MI 约 3.5 bit(3.566 / 3.531 / 3.494),整齐排在 1、2、3;其余 777 对背景平均 0.066 bit —— 五十倍以上的分离。
  4. 间接相关:5-15 接触、15-25 接触,而 5 和 25 并不接触,但相关性顺着传递过去 —— (5,25) 拿到 2.973 bit,排第三,比背景 0.085 高四十倍,看起来和真接触一样。
  5. 催生了直接耦合分析(DCA)这一类方法:问的不是「i 和 j 相关吗」,而是「扣掉其他位置的中介之后还直接相关吗」。⭐ 深度模型的优势在于注意力让所有位置在同一层互相看,天然是联合建模而不是逐对统计,间接相关会被其他位置解释掉。
  6. ①不理解能量学(学的是「出现过什么」不是「自由能变多少」)②对设计序列不可靠(数据库全是演化产物,新设计的序列在分布外、高分没校准过)③embedding 带着数据库的采样偏差(热门家族表示好、冷门差)。⭐ 第三件看不出来是因为平均指标会把冷门家族的差表现摊平,除非你按家族分组去看。

🛑 可以停在这里

到这里你已经知道这类模型学到的是什么(共进化 = 结构约束)、怎么用(冻结取特征)、 以及不能指望它什么。⭐ 光是「互信息会因为间接相关而撒谎」这一条, 就够你在读任何一篇讲接触预测的东西时不被绕进去。

⚠️ 什么时候看下一页:你想知道从「哪些残基挨着」到「三维坐标」中间还差什么。

⚡ 走神救援

先记住这几件事

下一节 👉 04-从序列到结构.md

打卡记录保存在你的浏览器里,首页能看到总进度