论文阅读|EZSpecificity:酶底物特异性预测准确率91.7%,颠覆生物催化与药物研发
🌞欢迎来到人工智能的世界
🌈博客主页:卿云阁
💌欢迎关注🎉点赞👍收藏⭐️留言📝
🌟本文由卿云阁原创!
🌠本阶段属于练气阶段,希望各位仙友顺利完成突破
📆首发时间:🌹2026年1月23日🌹
✉️希望可以和大家一起完成进阶之路!
🙏作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢!
目录
论文信息
酶和底物
酶
编辑
底物
酶和底物的对接
主要内容
第一部分:上面的蓝色通道——(序列分析)
第二部分:下面的蓝色通道——“搜身”(3D 结构分析)
第三部分:中间的红蓝连线
第四部分:右下角——“最终判决”(预测)
如何构建那个拥有 32 万条数据的 ESIBank 数据库
可视化页面
环境配置
准备工作(系统要求)
第一步:创建虚拟环境
第二步:安装图神经网络库 (PyTorch Geometric)
举个“做菜”的例子
需要的数据
数据集的地址
跑通整个示例代码
论文信息
[1] Cui, H.; Su, Y.; Dean, T. J.; Yu, T.; Zhang, Z.; Peng, J.; Shukla, D.; Zhao, H., Enzyme specificity prediction using cross attention graph neural networks. Nature 2025. (DOI: 10.1038/s41586-025-09697-2)
酶和底物
酶
酶最初只是一串写满氨基酸代号(MKFVK...)的软绳子,就像一堆没组装的乐高零件,没有任何
干活的能力(对应论文中 ESM-2 处理的一维数据 )。 这串绳子通过物理法则自动卷曲折叠,变
成了有固定形状、坚硬的 3D 实体,就像零件组装成了一把精密的锁(对应论文中 AlphaFold 生成
的 3D 结构 )。 这把锁上会自动形成一个独特的凹坑(活性位点),只有形状和电荷(指纹)完
全匹配的那个底物才能塞进去并解锁反应,这就是“特异性”的本质 。
一句话总结:酶就是把一维的“代码绳子”折叠成三维的“智能锁”,而 AI 的任务就是看一眼锁孔
(3D 结构),算出哪把钥匙(底物)能插进去。


底物
底物确实是分子(通常是糖、脂肪、药物前体等小分子),但在生物化学中,它特指被酶“选中”并
进行化学加工的那个原材料 。 “底物”是一个角色称呼。就像“面粉”本身是物质,但放在面包机
(酶)里时,它的身份就是“原料”(底物)。只有能精准卡进酶的“活性位点”里的分子,才配叫底
物 。 它是反应的输入端。它进入酶的口袋,发生化学键的变化,最后变身为产物(Product)被释
放出来 。一句话总结:底物就是那些能被酶的“锁孔”精准识别,并被催化成新物质的特定分子。

酶和底物的对接
AI 在电脑里把底物(钥匙)扔进酶的活性位点(锁孔),模拟两者相遇时的物理过程 。程序会像
玩魔方一样,让底物在酶的口袋里疯狂旋转、扭动、平移,尝试几百万种不同的结合姿态
(Poses),看哪种姿势卡得最严实 。通过物理公式计算每种姿势的结合能量 (Binding Affinity)。能
量越低(分数越低),说明结合越稳定、越舒服,也就是“对接成功” 。一句话总结:对接就是计算
机通过暴力计算,在分子层面帮酶和底物找到那个“最舒服、最稳定”的结合姿势。

主要内容
这个网络是一个酶 - 底物特异性预测模型(如 EZSpecificity),核心是融合序列、结构、相互
作用信息来判断酶与底物的匹配度,过程分为输入预处理、双编码模块、双向交叉注意力融合、特
异性预测四大部分,以下是详细拆解:

第一部分:上面的蓝色通道——(序列分析)
这张图展示了 “进化变换器编码” 模块的工作流程:它接收如 “MKFVRRIIA...” 的蛋白质序列,先
将其处理为单序列格式,再输入经掩码语言模型预训练的 ESM-2 蛋白质语言模型,该模型将序列
映射到 1,024 维的蛋白质序列潜空间以捕捉其进化等信息,最终输出与序列中各氨基酸残基对应的
高维特征向量(以不同颜色方块表示)。

输入 (Protein sequences):这里输入的是酶的氨基酸序列(那串枯燥的 MKFVK... 字母)。这就
像是输入了乘客的身份证号。
处理器 (ESM-2):这串字母被送进了一个叫 ESM-2 的模型。这是一个预训练好的“语言大师”,它
读得懂蛋白质的进化语言。
输出 (Amino acid residues representation):变成了一个粉红色的矩阵块。
第二部分:下面的蓝色通道——“搜身”(3D 结构分析)
左侧输入底物(如带羟基的苯环结构)与蛋白质三维结构,经 Vina-GPU2.0 工具完成分子对接
(模拟底物嵌入蛋白质活性口袋形成复合物),再通过 Internal MPNN 编码酶 / 底物各自的内部分
子环境、Interaction MPNN 编码酶与底物间的原子相互作用,最终将催化位点的三维空间与互作
信息转化为特征表示,实现催化微环境的编码。

输入 (Substrates + Protein structures):这里输入的是底物(小分子)和酶的 3D 结构。
注意那个圆圈里的图: 这就是我们之前说的 Docking(对接)。图上显示把小球(底物)塞进了
黄色的带子里(酶的口袋)。
处理器 (Internal MPNN / Interaction MPNN):这里用了图神经网络 (GNN/MPNN)。
Internal MPNN: 只盯着底物(Substrate)这一个分子看,它通过图神经网络算法,把这些原子
特征汇聚起来,形成一个“底物特征向量”。
Interaction MPNN: 扫描底物和酶接触的那个“微环境”,看它们之间有没有拉手(化学键)、有
没有挤压。(原子间的距离(挤压)、空间位置、微环境)
输出 (Microenvironment representation):变成了一个蓝色的矩阵块。
第三部分:中间的红蓝连线
这张图展示的是 “双重交叉注意力(Double cross-attention)” 模块的工作流程:它接收氨基酸残
基表示、2D 底物特征、微环境表示(以及可选的 3D 复合物特征),通过将不同输入特征分别作
为注意力机制的 Q(查询)、K(键)、V(值),进行双向交叉计算(酶特征与底物特征互
作),生成 “加权催化核心嵌入” 与 “加权序列嵌入” 两种融合特征,最终将这些特征输入酶特异性
深度神经网络,实现酶 - 底物特异性的预测。

这是 AI 最聪明的地方。它没有把“户口信息”(粉色块)和“搜身信息”(蓝色块)简单加在一起,而
是让它们互相提问。这叫 Double Cross-Attention(双重交叉注意力)。这叫 Double Cross-
Attention(双重交叉注意力)。
第四部分:右下角——“最终判决”(预测)
融合 (Weighted Embeddings):经过“交叉审讯”后,得到了两个强化版的特征块(最右边的两个
网格)。
神经网络 (Deep Neural Network):把这两个块扔进一个多层感知机 (MLP)(画着黄色和绿色圆圈
的那个网络)。这就像大脑的最后决策区。输出:给出一个分数。高分: 结合!特异性强!

如何构建那个拥有 32 万条数据的 ESIBank 数据库
第一部分:图 2a —— “全网数据大挖掘” (Data Mining)
这部分展示了数据是从哪里来的。作者用了两条路来收集数据:
上面那条路(最硬核的):从旧纸堆里“挖金矿”
很多珍贵的实验数据藏在几十年前的论文 PDF 里,只有图片,没有电子文本。以前的 AI
读不懂。
Step 1-3 (Identification/Extraction/Translation): 作者开发了一套半自动化流程。
用 AI 视觉技术(Optical Structure Recognition,类似 OCR)去扫描论文里的化学反应图片。
把图片里的化学分子结构,自动翻译成计算机能读懂的代码(SMILES)。
Step 4 (Connection): 把挖出来的底物代码和对应的酶序列匹配起来。
这就像是把图书馆里发黄的旧书扫描一遍,把里面的“秘方”全部数字化。
下面那条路:从公共数据库“进货”
从 BRENDA 和 UniProt 这些现有的生物大数据库里,批量下载已知的酶和底物数据。
这两条路汇合,把原始数据存进了中间那个大桶里。

第二部分:图 2b (右侧流程图) —— “数字化流水线” (Data Processing)

这一部分是整张图的技术核心,展示了如何把“枯燥的文本数据”加工成“3D 训练数据”。对应了我
在前面解释的“如何把底物看成 3D 结构”的过程。
它分为两个车间(Section 1 和 Section 2):
车间 1:备料 (Input preparation)
这里的任务是把所有零件变成 3D 的。
处理底物 (上路):
输入:Oc1ccccc1(SMILES 代码,像你的身份证号)。
工具:RDKit 。
输出:3D Substrate(绿色的六边形模型,像充气后的气球)。

处理酶 (下路):
输入:酶的序列。
工具:AlphaFold(造酶的壳子) + AlphaFill(装辅因子)。
关键点: 注意那个 Cofactor(辅因子)。有些酶干活需要“电池”(辅因子),AlphaFold 以前只造
壳子不给电池,这里用 AlphaFill 把电池(那个彩色的分子)装进去了,这样的酶才是“活”的 。
输出:Active structure(带电池的完整 3D 酶模型)。
车间 2:组装与质检 (Docking and sorting)
这里的任务是把两个 3D 零件拼在一起。

工具: AutoDock-GPU 。
动作 (Various binding poses):
看那个杂乱的黄色线团图,这代表计算机尝试了成千上万种姿势,乱七八糟的。
质检 (Scoring):通过 Distance scoring(距离打分)和 Affinity scoring(亲和力打分)。
规则: 离活性位点太远的?扔掉!结合不紧密的?扔掉!
成品 (Reasonable binding pose):
最后只留下一张图——底物安安静静、舒舒服服地躺在酶的口袋里。这就是喂给 AI 的“标准答
案”。
第三部分:图 2b (左侧柱状图) —— “晒家底” (Statistics)
左边的蓝色柱子展示了战果:
Substrate (34,417): 收集了 3.4 万种不同的底物(钥匙)。
Enzyme (8,124): 收集了 8 千多种酶(锁)。
Data points sum (323,783): 总共构建了 32 万对 相互作用数据 。
总结这张图
这张图告诉你,EZSpecificity 这个 AI 之所以聪明,是因为作者做了一项巨大的基建工程:
图 2a: 他们不辞辛劳地从旧文献和数据库里“捡破烂”,凑齐了原料。
图 2b 右侧: 他们搭建了一条全自动化的“3D 加工流水线”,把文本原料变成了高质量的 3D 结构模型。
图 2b 左侧: 最终攒出了比以前大 25 倍 的数据库(ESIBank),用这个海量数据把 AI 喂饱了。
可视化页面
预发布演示 这一早期演示展示了EZSpecificity预测能力的预览。用户可以: 选择一种酶并查看特
异性预测,例如底物探索样本酶底物对的预测特异性评分和三维对接姿态可视化。
第一张图:选“锁”(酶输入界面)让用户选择要分析的目标酶。每个酶都配有 3D 结构预览图,并
标注了身份信息(EC 编号、UniProt ID),这对应了模型需要输入的 3D 结构和序列信息。

第二张图:配“钥匙”(底物列表界面)展示了待测试的底物清单。以表格形式列出了各种小分子化
合物。 每一行包含底物的 2D 化学结构图、名称、SMILES 代码(即图片中翻译为“微笑”的那一
列,代表分子的文本代码)以及数据库链接。

这是 AI 给酶(CAS1)列出的“相亲对象排行榜”,告诉你在这么多底物里,谁跟它最般配。L-
arginine (L-精氨酸)。你看第一名的分数是 -2,第五名的分数是 -4.44。在这个模型里,分数越高
(越接近正数或负得越少),代表匹配度越高。

环境配置
先配置基础环境(Conda),再安装核心的图神经网络库(PyTorch Geometric)。
准备工作(系统要求)
首先,官方推荐的“舒适区”配置如下,如果你的环境不一样,可能需要微调版本号:
操作系统:Ubuntu 22.04
Python版本:3.10.12
CUDA版本:12.1 (显卡驱动)
第一步:创建虚拟环境
使用 Conda 一键创建环境,这一步会把大部分基础包(比如 PyTorch, NumPy 等)装好。在终端
输入:
conda env create -f environment.ymlconda activate ezspecificity第二步:安装图神经网络库 (PyTorch Geometric)
这是最关键的一步,因为 GNN 的库对版本非常敏感。请按顺序执行以下命令:
pip install torch_geometric安装依赖库(Scatter, Sparse, Cluster, Spline_conv): 注意:这里的 URL (.../torch-2.2.0+cu121.html) 对应的是 PyTorch 2.2.0 和 CUDA 12.1。如果你的 CUDA 版本不同,需要去 PyG 官网找对应的链接。
pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.2.0+cu121.html验证安装(跑个 Demo)
装好后,可以试着跑一下官方提供的“卤化酶(Halogenase)”案例,看看报不报错。
测试训练(Train):
python main_specificity_ss.py --config_path $LOCAL_ROOT_DIR/src/Configs/Train/random_0.yml注意:把 $LOCAL_ROOT_DIR 换成你实际的项目路径。
测试预测(Test/Inference):
python main_specificity_ss_eval.py --log_dir $LOCAL_ROOT_DIR/saved_model/finetune_halogenase_random_0/run_0E2E 流程: 真正用的时候,你需要先按照论文方法生成 3D 复合物结构(PDB文件),然后参照
example.ipynb 来提取特征并进行预测。
第一关:准备“3D 数据包” (最麻烦的一步)
模型代码里明确说了,它需要的输入不是简单的字符串,而是酶和底物结合在一起的 3D 模型文件
(通常是 PDB 格式)。但这东西通常不存在,你得自己造。怎么造?你需要按照论文里的“笨办
法”一步步来:
造酶的 3D 模型:
你有酶的序列(字符串),得去跑 AlphaFold 软件,算出它长什么样。
如果有辅因子(比如 FAD),还得跑 AlphaFill 把它装上去。
造底物的 3D 模型:
你有底物的 SMILES(字符串),得跑 RDKit 代码,把它吹成立体的小球。
强行合体(Docking):
最后,你得用 AutoDock-GPU 软件,把上面两个 3D 模型在电脑里“撞”在一起,生成一个复合物文件 (Complex PDB)。
这一步做完了,你才刚刚拿到了 AI 模型的“入场券”。
第二关:喂给 AI 预测 (参照 example.ipynb)
这时候你手里有了一个 .pdb 文件(里面存了原子坐标)。
提取特征:
你需要运行 example.ipynb 里的代码。
这段代码会读取你的 .pdb 文件,把里面的原子坐标转换成 AI 能看懂的数学矩
阵(比如图神经网络需要的节点和边)。
AI 打分:
代码把这些矩阵喂给训练好的 EZSpecificity 模型。
模型输出一个分数(比如 0.9 或 0.1)。
举个“做菜”的例子
你的原始数据(酶/底物名) = 刚买回来的带泥土豆和生牛肉。
EZSpecificity 模型 = 微波炉。
问题是: 微波炉不能直接塞带泥的土豆进去,会炸。
第一关(生成 3D 结构):
你得洗土豆、削皮、切块、把牛肉腌好(AlphaFold/Docking)。
做成一盘“半成品菜”(PDB 文件)。
第二关(example.ipynb):
你把这盘“半成品”放进微波炉(模型),按下开关。微波炉“叮”一声,吐出熟菜(预测结果)。
简单来说:
我们需要准备的数据如下:
1. 男嘉宾名单 (enzymes.csv)
这是什么: 酶的花名册。
内容要求: 必须有一列叫 "Protein sequence"(蛋白质序列)。
作用: 告诉 AI 我们有哪些酶(锁)。
第 0 号男嘉宾: 序列是 MKFV...
第 1 号男嘉宾: 序列是 MLVT...
2. 女嘉宾名单 (substrates.csv)
这是什么: 底物的花名册。
内容要求: 必须有一列叫 "Substrate_SMILES"(SMILES 代码)。
作用: 告诉 AI 我们有哪些底物(钥匙)。
第 0 号女嘉宾: SMILES 是 CCO...
第 1 号女嘉宾: SMILES 是 c1ccc...
3. 合影照片库 (Structure 文件夹)
这是什么: 存放所有 3D 对接文件(PDB) 的文件夹。
内容要求: 里面的文件名必须是纯数字,比如 0.pdb, 1.pdb, 2.pdb...
作用: 这是最核心的“熟食”。每一个 .pdb 文件都是你用 AutoDock 算出来的一对“酶+底物”的 3D 复合物。
4. 只有 AI 能看懂的“红娘记录本” (data.csv)
这是什么: 这是最重要的索引表,用来把上面三个东西连起来。
内容要求: 它必须包含三列数字:Dock Index (d), Enzyme Index (e), Substrate Index (s)。
它的逻辑是:
“请注意,文件夹里的第 d 张照片(d.pdb),是第 e 号男嘉宾(来自 enzymes.csv 的第 e 行)和
第 s 号女嘉宾(来自 substrates.csv 的第 s 行)的合影。”
举个具体的例子(大白话版)
假设你的 data.csv 里有一行是这样的:10, 5, 3
这就代表告诉 AI:
去 Structure 文件夹里找: 文件名为 10.pdb 的那个 3D 模型文件。
这个模型里是谁?
酶: 是 enzymes.csv 表格里的第 5 行那个酶。
底物: 是 substrates.csv 表格里的第 3 行那个底物。
总结:你要做什么?
如果你想跑通 example.ipynb,你不能乱起文件名。你必须:
把酶和底物分别填进两个 CSV 表格,记住它们的行号(这就是 ID)。
把你生成的 PDB 文件重命名为数字(比如 0.pdb)。
在 data.csv 里写下一行记录,把文件名数字、酶的行号、底物的行号对应起来。
通过存放所有 3D 对接文件(PDB) 的文件夹。我难道不是已经知道了酶和底物的结合概率嘛,
为啥还要用这个模型去预测呐?
Docking(对接)得到的 PDB 文件只是一个“摆拍的照片”,而 AI 模型是用来判断“这张照片是不是 P 的(假的)”。
你可能会问:“AutoDock 不是也会给一个能量打分(Score)吗?分低不就是能结合吗?”
现实是: 传统的物理打分(Docking Score)非常粗糙。它只计算原子有没有撞车、电荷吸不吸,
但它经常看走眼。假阳性(False Positive): 很多时候,AutoDock 觉得“哇!这俩卡得真严实,
分数很高!”,但在生物体里,因为酶的微小形变或者进化上的原因,这俩压根不反应。
所以整个任务的本质就是一个二分类的问题?
AI 的核心任务是做一个判别器。你给它一对(酶 + 底物),它需要判断:“这对组合是真的(能反
应),还是假的(不能反应)?”虽然任务是二分类(0 或 1),但模型输出的通常是一个连续的分
数(比如你之前看到的 -2, -4.44)。为什么不直接输出 0 或 1? 因为科学家更需要“排名”。虽然都
是“正类”,但 Rank 1 的底物可能比 Rank 2 的结合得更好、更优先。
需要的数据
My_Data/
├── enzymes.csv <-- 1. 酶名单(男嘉宾)
├── substrates.csv <-- 2. 底物名单(女嘉宾)
├── data.csv <-- 3. 配对索引表(红娘记录本)
└── Structure/ <-- 4. 存放 PDB 文件的文件夹(合影照片)
├── 0.pdb
├── 1.pdb
├── 2.pdb
└── ...
enzymes.csv
| 列名 (Header) | 示例内容 (Example) | 含义与作用 (Meaning & Function) |
| Protein sequence | MQDTKFKVAVVQ... | 这是酶的“身体密码”(氨基酸序列)。 这是模型最核心的输入数据。这一长串字母(M, Q, D, T...)代表构成该蛋白质的每一个氨基酸。模型会读取这个序列,利用 ESM-2 来分析它的进化特征,并利用 AlphaFold 预测它的 3D 结构。 |
| uniprots | nitr_Enzyme_654 | 这是酶的“身份证号”(唯一标识符)。 尽管列名叫 "uniprots"(通常指 UniProt 数据库编号),但看示例内容 它的作用是方便你(和程序)区分这 18 个酶谁是谁。 |

substrates.csv
一共39个数据,基本都是腈类化合物(含 - C≡N 官能团)
| 列名 (Header) | 示例内容 (Example) | 含义与作用 (Meaning & Function) |
| Substrate_SMILES |
| 这是底物的“化学密码”。 SMILES 是一种用 ASCII 字符表示化学分子结构的语言。例如 模型代码 ( |

data.csv
一共有121个数据
| 列名 (Header) | 这一行的数据 (Row 1) | 它的含义 (Translation) |
| Substrate Index | 19 | “女嘉宾编号” 去 |
| Enzyme Index | 9 | “男嘉宾编号” 去 |
| Label | 0 | “牵手成功了吗?”
这里全是 0,说明这是一组负样本(或者你在做预测时,暂时填 0 占位)。 |
| Dock Index | 549 | “合影照片编号” 这是最关键的!它告诉程序:“请去 这个 PDB 文件就是上面那个 9 号酶和 19 号底物的对接结构。 |
| positive_reactions | 19 | “原配是谁?”(辅助信息) 这列通常用于训练时的对比学习。它的意思是:“虽然这一行可能是假配对,但这个 9 号酶真正的底物是 19 号。” (注:在你的图里,这列和 Substrate Index 一样,可能意味着这行本身就是正样本,或者只是格式上的冗余。做预测时这列通常不影响核心运行) |
Structure
用txt文件打开之后的样子
| 数据 | 含义 (What is it?) | 解释 (Explanation) |
| ATOM | 记录类型 | 告诉程序:“这行描述的是一个原子”。 |
| 1 | 原子序号 | 这是整个文件里的第 1 个原子。 |
| N | 原子名称 | 这是一个氮原子 (Nitrogen)。 |
| MET | 氨基酸名称 | 这个原子属于甲硫氨酸 (Methionine)。 |
| A | 链 ID | 它属于蛋白质的 A 链。 |
| 1 | 氨基酸序号 | 它是整条链上的第 1 个氨基酸。 |
| -24.657 | X 坐标 | 🔴 最重要的数据! |
| 5.405 | Y 坐标 | 🔴 AI 依靠这三个数字定位它在 |
| 3.760 | Z 坐标 | 🔴 3D 空间中的确切位置。 |
| 1.00 | 占有率 | (物理参数,通常为 1.00) |
| 63.49 | 温度因子 | 原子震动的剧烈程度(数值越大越不稳定)。 |
| N | 元素符号 | 再次确认这是氮元素。 |
第二层:关键的“分割线” (The Splitter)
位置: 也就是你文件中段那几行看起来不一样的字:
TER 5202 GLU A 344 <-- 蛋白质结束标志
END <-- 结构结束
REMARK SMILES N#Cc1ccccn1 <-- 底物的化学式(氰基吡啶)
COMPND nitrilase_best_405.pdbqt <-- ⭐【代码的信号灯】⭐
AUTHOR GENERATED BY OPEN BABEL...
第三层:底物的“姿态” (Ligand Pose)
位置: 文件最底部的 ATOM 记录(注意看残基名变成了 UNL)。
| 原子序号 | 元素类型 | 残基名称 | X 坐标 (Å) | Y 坐标 (Å) | Z 坐标 (Å) | 含义 |
| 1 | C (碳) | UNL | -2.062 | 15.106 | -5.503 | 底物的第1个碳原子 |
| 2 | N (氮) | UNL | -3.210 | 15.449 | -6.138 | 底物的氮原子 |
| 3 | C (碳) | UNL | -3.704 | 14.722 | -7.162 | 碳原子 |
| 4 | C (碳) | UNL | -3.080 | 13.579 | -7.638 | 碳原子 |
| 5 | C (碳) | UNL | -1.909 | 13.211 | -7.007 | 碳原子 |
| 6 | C (碳) | UNL | -1.421 | 13.970 | -5.961 | 碳原子 |
| 7 | C (碳) | UNL | -1.549 | 15.898 | -4.409 | 碳原子 |
| 8 | N (氮) | UNL | -1.155 | 16.540 | -3.528 | 另一个氮原子 |
| 主原子 (我是谁?) | 连接对象 (我拉着谁的手?) | 化学解读 (推测) |
| 原子 1 | 2, 6, 7 | 原子1 是中心,连接了2号、6号和7号原子。 |
| 原子 2 | 3, 1 | 氮原子2 连接了 3号和1号。 |
| 原子 3 | 4, 2 | 原子3 连接了 4号和2号。 |
| 原子 4 | 3, 5 | 原子4 连接了 3号和5号。 |
| 原子 5 | 4, 6 | 原子5 连接了 4号和6号。 |
| 原子 6 | 5, 1 | 原子6 连接了 5号和1号。 |
| 原子 7 | 1, 8 | 原子7 连接了 1号和8号。 |
| 原子 8 | 7 | 氮原子8 只连接了 7号 (通常是末端基团,如氰基)。 |
UNL (Unknown Ligand) 是被塞进去的那个底物。
ATOM 表 告诉了它底物现在的 姿势(3D 坐标)。
CONECT 表 告诉了它底物的 骨架(谁连着谁)。
example_out.csv
| 列名 (Header) | 示例内容 (Row 0) | 含义与作用 (Meaning & Interpretation) |
| Substrate | 12 | 底物编号 对应 |
| Enzyme | 9 | 酶编号 对应 |
| Label | 0 | 原始标签 这是从输入文件 |
| Dock Index | 405 | 结构编号 代表这对组合对应的是 |
| positive_reactions | 12 | 参考底物(冗余列) 同样是从输入文件复制过来的。在预测模式下,它通常和 |
| dataset_id | 12 | 数据条目ID 这是代码内部用来追踪是“第几条测试数据”的编号。通常和底物编号一致,可以忽略。 |
| tag | 0 | 实验标签 用于标记这批数据属于哪个数据集(比如 0 代表测试集,1 代表验证集)。对结果分析无影响。 |
| score | -2.0953... | ⭐ 预测分数(最重要!) 这是 AI 给出的特异性打分(Logits)。 怎么看: 1. 分数越大(越接近正数)越好。 2. 分数越小(越负)越差。 例如: |
数据集的地址
Zenodo
src.zip (源码):
包含模型的核心代码(可能是 Python/PyTorch 格式)。主要模块应包括:数据加载器(处理分子
图)、SE(3) GNN 层的实现、交叉注意力模块的定义、训练循环和推理脚本。
saved_model.zip (预训练模型):
包含已经训练好的权重文件(如 .pth 或 .ckpt 文件)。使用这个文件,你可以直接进行推理(预
测),而不需要花费大量时间重新训练模型。
data.zip (数据):
包含用于训练或测试的数据集。通常包括酶的序列或结构文件(如 .pdb)、底物的分子式(如
SMILES 字符串)以及它们的结合亲和力或活性标签。

EZSpecificity Dataset - Google 云端硬盘
Additional Data/Figure S17_SSN_Node_data.xlsx
| 列名 (Column Name) | 描述 (Description) |
|---|---|
| Cluster Number | 蛋白质所属的群集编号。 |
| Cluster Node Count | 该蛋白质所属群集中的节点总数。 |
| Enzyme Name | 酶或蛋白质的名称/标识符,例如 1-A09(F)、AbeH(FTry5)、KtzQ(FTry7) 等。名称中包含 Ftry、FPheA、FPheB、FPyrA、FPyrB 等后缀,可能指代催化的底物或酶的类型。 |
| Protein Sequence | 蛋白质的氨基酸序列。 |
| Sequence Length | 蛋白质序列的长度。群集 1 的序列长度范围在 485 到 692 个氨基酸之间,群集 2 的序列长度范围在 409 到 1022 个氨基酸之间。 |
| Singleton Number | 未在提供的文件中找到足够信息来描述此字段的含义。 |
ESIBank
“ESIBank”文件夹是一个结构化的生物信息学或化学信息学数据集的容器,其主要目的是为酶分类
(Enzyme Classification)相关的机器学习任务提供数据。
brenda: 强烈暗示该数据集来源于或受到了 BRENDA (BRaunschweig ENzyme DAtabase) 的启
发。BRENDA 是目前最全面的酶功能信息数据库之一。
enzyme_split: 表示对原始酶数据或酶-反应关联数据进行了划分(split)操作。
“brenda”文件夹包含超过 19 个文件,其中大多数是 CSV 文件(10 个)。基于对 5 个文件的审
查,该文件夹是一个用于生物信息学或酶功能预测的全面数据集,特别是侧重于酶-反应关联、酶
序列、反应化学结构和机器学习模型训练。
酶-反应关联数据集 (data.csv 和拆分文件)
酶序列信息 (enzymes.csv)
反应化学信息 (reaction.csv 和 reaction_smiles.csv):
| 文件名称 | 祖先路径 | 主要内容 | 关键字段(共同) | 特殊观察 |
|---|---|---|---|---|
| data.csv | 原始酶-反应关联数据 | enzyme, reaction, label, ecnumber, difficulty, fake_ecnumber, structure_index | 包含 EC 编号、难度评分和结构索引。 | |
| testing_datas_0.csv | enzyme_split | 用于模型测试的数据子集 | enzyme, reaction, label, ecnumber, difficulty, fake_ecnumber, structure_index | 用于评估模型在未见过数据上的泛化能力。 |
| enzymes.csv | 酶序列和 UniProt ID | sequences, uniprots | 提供酶的氨基酸序列信息。 | |
| reaction.csv | 反应SMILES和底物信息 | reactions, substrates | 提供化学反应的详细 SMILES 字符串。 | |
| reaction_smiles.csv | 底物 SMILES 信息 | substrates | 包含参与反应的底物的 SMILES 结构。 |
data.csv文件
| Enzyme (酶编号) | Reaction (底物编号) | Label (标签) | EC Number (真EC号) | Difficulty (难度) | Fake EC (假EC号) | Structure Index (结构索引) |
| 15164 | 3312 | 0 | 3.5.2.6 | 1 | 3.2.1.3 | 2039 |
A. 三大核心索引(必须对上号)
这是把三个文件串起来的关键。
Enzyme (15164):
含义:去 enzymes.csv 表格里的第 15164 行找那个酶的序列。
Reaction (3312):
含义:去 substrates.csv 表格里的第 3312 行找那个底物的 SMILES。
Structure Index (2039):
含义:去 Structure 文件夹里找 2039.pdb 这个文件。
注意:这个数字非常重要!它是物理世界的连接点。AI 会去读 2039.pdb,看看这俩分子在空间上
是怎么挤在一起的。
B. 训练信号(告诉 AI 对错)
Label (0):
含义:这是标准答案。
0 代表 Negative(不结合/假):告诉 AI,“记住了,这俩货凑一对是没戏的,下次看到类似的情况要打低分。”
1 代表 Positive(结合/真):告诉 AI,“这俩是真爱,要打高分。”
注:如果你是做预测(Inference),这一列通常填什么都行(因为你在等 AI 给你算结果),或者直接留空。
C. 辅助信息(用来提升 AI 智商)
EC Number (3.5.2.6):
含义:这是酶的真实身份(比如它是个“酰胺酶”)。AI 会结合这个分类信息来判断底物是否合理。
Fake EC Number (3.2.1.3):
含义:这是负样本采样(Negative Sampling) 的策略。
大白话逻辑:这行数据是个错配(Label=0)。作者是故意拿一个“酰胺酶”(3.5.2.6)去配一个本该属于“糖苷酶”(3.2.1.3)的底物。通过这种“张冠李戴”,训练 AI 识别出跨家族的错误。
Difficulty (1):
含义:题目难度。
0 可能是“送分题”(比如完全不搭边的两个分子)。
1 可能是“陷阱题”(比如结构很像但就是不能结合)。用来在训练时给 AI 加大强度。
跑通整个示例代码
(1)在本地大致看一下相关的代码,知道每段代码的输入和输出
(2)开始租用服务器,完成训练
ssh -p 13203 [email protected]
/rz6Xmfwrw2e
(3)远程连接传送文件和远程管理服务
如果出现Downloading: "https://dl.fbaipublicfiles.com/fair-esm/models/esm2_t33_650M_UR50D.pt" to /root/.cache/torch/hub/checkpoints/esm2_t33_650M_UR50D.pt ,说明正在下载文件,不过这里更建议在,可以通过wget命令下载并查看实时速度(替代 PyTorch 自动下载):
# 先进入缓存目录 cd /root/.cache/torch/hub/checkpoints/ # 用wget下载,能看到实时速度和剩余时间 wget https://dl.fbaipublicfiles.com/fair-esm/models/esm2_t33_650M_UR50D.pt
(4)
进入到路径下
请在f“{src_root_dir}/src/other_softwares/grover_software”中执行以下命令。
第一步:设置环境变量
这一步是为了让 Python 知道 Grover 软件的源代码在哪里,防止报 ModuleNotFoundError。
export PYTHONPATH=/root/enzyme_specificity_public/src/other_softwares/grover_software:$PYTHONPATH 第二步:生成基础化学特征 & 构建词汇表
这两行命令是 Grover 运行的前置准备。
- 提取官能团特征 (
save_features.py):计算分子的基础化学属性(如官能团)。 - 构建词汇表 (
build_vocab.py):建立分子原子和键的字典,供后续深度学习模型查阅。
# 1. 提取基础特征 (生成 .npz 文件) python /root/enzyme_specificity_public/src/other_softwares/grover_software/scripts/save_features.py \ --data_path /root/enzyme_specificity_public/data/toy_example/grover_substrates.csv \ --save_path /root/enzyme_specificity_public/data/toy_example/grover_substrates.npz \ --features_generator fgtasklabel \ --restart # 2. 创建词汇表文件夹 mkdir -p /root/enzyme_specificity_public/data/toy_example/grover_vocab # 3. 构建词汇表 (生成 vocab 文件) python /root/enzyme_specificity_public/src/other_softwares/grover_software/scripts/build_vocab.py \ --data_path /root/enzyme_specificity_public/data/toy_example/grover_substrates.csv \ --vocab_save_folder /root/enzyme_specificity_public/data/toy_example/grover_vocab \ --dataset_name test 第三步:生成 Grover 指纹 (核心推理)
这是最耗时的一步。它加载预训练好的 grover_large.pt 模型,读取刚才生成的 CSV 和 NPZ 文件,计算出最终的深度特征向量,并保存为 LMDB 数据库。
python main.py fingerprint \ --data_path /root/enzyme_specificity_public/data/toy_example/grover_substrates.csv \ --features_path /root/enzyme_specificity_public/data/toy_example/grover_substrates.npz \ --checkpoint_path /root/enzyme_specificity_public/data/pretrain_model/grover_large.pt \ --fingerprint_source both \ --output /root/enzyme_specificity_public/data/toy_example/grover_fingerprint.npz \ --save_lmdb_path /root/enzyme_specificity_public/data/toy_example/grover_fingerprint.lmdb ✅ 执行后的检查
运行完这些命令后,请去 /root/enzyme_specificity_public/data/toy_example/ 目录下检查是否生成了以下文件:
grover_substrates.npz(基础特征)grover_vocab/文件夹 (词汇表)grover_fingerprint.lmdb(⭐最重要的最终特征库)
如果这三个都有了,这一步就大功告成了!
ImportError: cannot import name '_TrimmedRelease' from 'packaging.version' (/root/miniconda3/lib/python3.12/site-packages/packaging/version.py)
!pip install "packaging==23.2"ModuleNotFoundError: No module named 'pyximport'
pip install CythonMisconfigurationException: `Trainer(strategy='ddp')` is not compatible with an interactive environment. Run your code as a script, or choose one of the compatible strategies: `Fabric(strategy=None|'dp'|'ddp_notebook')`. In case you are spawning processes yourself, make sure to include the Trainer creation inside the worker function.
import sys import os import glob import pandas as pd import pytorch_lightning as pl from easydict import EasyDict as edict import warnings from rdkit import RDLogger # ================= 配置路径 (自动修正版) ================= ROOT_DIR = "/root/enzyme_specificity_public" SRC_DIR = f"{ROOT_DIR}/src" if SRC_DIR not in sys.path: sys.path.append(SRC_DIR) from Datasets.brenda import Singledataset from Models.ss import SS from utils import load_config RDLogger.DisableLog('rdApp.*') warnings.filterwarnings("ignore") def run_inference(): features_dir = f"{ROOT_DIR}/data/toy_example" structure_dir = f"{features_dir}/Structure" test_csv_path = f"{features_dir}/data.csv" predict_dict = { "data": { "tag": "example", "representer": "structure_sequence", "log_dir": [f"{ROOT_DIR}/saved_model/model"], "train_data_path": test_csv_path, "val_data_path": test_csv_path, "test_data_path": test_csv_path, "enzyme_lmdb_path": f"{features_dir}/enzyme_features.lmdb", "reaction_lmdb_path": f"{features_dir}/reaction_features.lmdb", "grover_path": f"{features_dir}/grover_fingerprint.lmdb", "morgan_path": f"{features_dir}/morgan_fingerprint.npy", "structure_processed_path": f"{features_dir}/structure_features.lmdb", "sequence_processed_path": f"{features_dir}/str_features.lmdb", "pdb_dir": f"{structure_dir}/str_tmp_data/pocket", "ligand_dir": f"{structure_dir}/str_tmp_data/ligand", "high_quality_id_path": f"{features_dir}/high_quality_id.txt", "full_data": True, "fake_sequence_ratio": 0, "sample_weight": [1.0, 1.0], "batch_size": 16, "max_substrate_length": 280, "max_enzyme_length": 1450, "features": ["morgan", 1024, "grover_mean", 4885], "atom_features": ["grover", 2400] } } # 动态搜索配置文件 config_dir = f"{ROOT_DIR}/saved_model/model/run_0" yaml_files = glob.glob(f"{config_dir}/*.yml") if not yaml_files: raise FileNotFoundError(f"在 {config_dir} 下找不到 .yml 配置文件!") config_path = yaml_files[0] # 自动取第一个找到的yml print(f"Loading config from: {config_path}") config = load_config(config_path) config.data = edict(predict_dict).data # 强制单进程加载 config.num_cpus = 0 log_dirs = config.data.log_dir search_path = f"{log_dirs[0]}/run_*/models/*.ckpt" ckpt_files = glob.glob(search_path) if not ckpt_files: ckpt_files = glob.glob(f"{log_dirs[0]}/*.ckpt") if not ckpt_files: raise FileNotFoundError(f"No checkpoint found in {log_dirs[0]}") resume_checkpoint_path = ckpt_files[0] if "-v" in resume_checkpoint_path: resume_checkpoint_path = ckpt_files[-1] print(f"🚀 Using Checkpoint: {resume_checkpoint_path}") dm = Singledataset(config) model = SS.load_from_checkpoint(resume_checkpoint_path, config=config) trainer_paras = { 'gpus': 1, 'num_nodes': 1, 'accelerator': 'gpu', 'logger': False, } trainer = pl.Trainer(**trainer_paras) print("Starting prediction...") trainer.test(model, datamodule=dm) logits_dict = model.logits_dict test_prediction_df = dm.test_prediction_df for key in logits_dict: scores = [_.item() for _ in logits_dict[key]] test_prediction_df["score"] = scores break output_csv = "example_out.csv" test_prediction_df.to_csv(output_csv, sep=',') print(f"\n✅ Prediction saved to {output_csv}") print(test_prediction_df.head()) if __name__ == "__main__": run_inference() import os # ================================================================= # 终极方案: 用纯 PyTorch 手写 KNN 替换掉报错的库 # ================================================================= transform_file = "/root/enzyme_specificity_public/src/Datasets/Structure/transforms.py" print(f"🔧 正在执行‘换心手术’ (替换 KNN 实现): {transform_file}") # 1. 定义我们的纯 PyTorch KNN 函数 (写入到文件顶部) # 这是一个不依赖任何外部 C++ 库的 KNN 实现" import torch def safe_knn_graph(x, k): # 计算所有点对之间的距离 # x: [N, 3] dist = torch.cdist(x, x) # 找到距离最近的 k+1 个点 (包含它自己) # largest=False 表示找最小的距离 _, indices = dist.topk(k + 1, dim=1, largest=False) # 去掉第一列 (因为它自己到自己的距离是0,肯定是最近的,要排除) # indices: [N, k] neighbors = indices[:, 1:] # 构建 edge_index [2, Num_Edges] num_nodes = x.size(0) # 目标节点 (中心点) target = torch.arange(num_nodes, dtype=torch.long, device=x.device).repeat_interleave(k) # 源节点 (邻居) source = neighbors.flatten() # flow='target_to_source' 意味着边是: 邻居 -> 中心点 # 即: source -> target return torch.stack([source, target], dim=0) """ # 2. 读取原文件 with open(transform_file, "r") as f: lines = f.readlines() new_lines = [] inserted_function = False patched_call = False for i, line in enumerate(lines): # 2.1 在 import 区域之后插入我们的自定义函数 if not inserted_function and ("class " in line or "def " in line): new_lines.append(custom_knn_code) inserted_function = True print("✅ 已注入纯 PyTorch KNN 算法代码") # 2.2 替换原本调用 knn_graph 的地方 # 寻找类似 knn_index = knn_graph(...) 的行 if "knn_index = knn_graph(" in line: # 获取缩进 indent = line.split("knn_index")[0] # 替换为调用我们的 safe_knn_graph # 注意:我们去掉了 flow 参数,因为我们的函数内部已经硬编码了逻辑 # 还要确保 x 是 float 类型 new_call = f"{indent}knn_index = safe_knn_graph(pos.float(), k=self.k)\n" new_lines.append(new_call) patched_call = True print("✅ 已替换出错的 knn_graph 调用") else: new_lines.append(line) # 3. 保存修改 if patched_call: with open(transform_file, "w") as f: f.writelines(new_lines) print("💾 transforms.py 已重写,彻底移除 torch_cluster 依赖。") else: print("⚠️ 未找到目标代码行,请检查文件是否已被过度修改。") # ================================================================= # 步骤 2: 再次尝试预测 # ================================================================= print("\n🚀 正在重新启动预测 (Native PyTorch Mode)...") # 这里的脚本依然使用 num_cpus=0 (单进程) 以保万无一失 !python run_prediction_script_safe.py原始的分数是0.3,我们程序跑出来的是0.5
原始的是0.6,我们跑出来的是0.8
原始的是-14,我们跑出来的是-16
如果是整数说明是匹配的,因为我观察到了他的损失函数用的是BCEWithLogitsLoss
其实到这里的话应该是复现成功的
核心原因:单模型 vs 集成模型 (Single vs Ensemble)