简介:IRDiff 完整评测文档与可运行项目代码,面向从事分子生成、扩散模型及基于结构药物设计的研发者与研究生。内容聚焦 ICML 2024 论文 IRDiff 的复现落地,针对官方代码缺失模块、难以跑通、评估环节不完整等实际问题给出可用方案。压缩包为单个 PDF,约 12.23MB,内含训练好的模型、修正后的项目代码、报错位置与修改方法、缺失模块文件及测试案例,已有 121 人学习。读者可据此在指定蛋白/口袋体系上直接运行分子生成,并计算 vina_score、vina_docking_score、qvina_score、QED、SA 等指标,也可基于自定义数据集做微调与训练。文档另附个人分析标注,梳理 PMINet 检索增强与自增强机制的设计逻辑,便于在此基础上改造模型结构、替换参考配体集或调整扩散采样流程,适合希望跨过环境与代码门槛、把精力放在方法改进与实验对比上的读者。
1. 环境还没建完就报错:分子生成项目复现的开场白
拿到一份 IRDiff 完整评测文档加可运行项目代码,多数人第一件事就是把环境跑起来,结果conda env create -f irdiff.yml敲下去,屏幕上滚出一屏LibMambaUnsatisfiableError,环境连创建都没走完。这不是个例。扩散模型类的分子生成项目天然依赖重,torch、pyg、rdkit、openbabel、vina 一层套一层,药物设计场景又对可复现性要求极高——同一份权重,版本差半个小号,Vina 分数就能漂出零点几。这份资源把训练好的模型、修正后的代码、每处报错的位置和改法、缺失的模块文件、测试案例都摊开了,但落到自己机器上,仍然会撞上几个高度固定的坑:conda 的版本冲突、autodocktools-py3 装不上、einops 缺失、路径写死、评测跑完只留下一个 pt 文件却找不到分子构象。下面按我实际踩过去的顺序,一条条拆。
2. 环境创建就卡死:LibMambaUnsatisfiableError 与 cryptography/openssl 的版本死锁
这是复现这套代码最先撞上的墙,也是最容易被误判的一道。很多人看到报错第一反应是「conda 坏了」「换 mamba 试试」「重装 miniconda」,折腾两小时才发现问题写在 yml 的第 42 行。
2.1 先把报错读明白:├─和└─到底在说什么
完整的报错大致是这样:
LibMambaUnsatisfiableError: Encountered problems while solving: - package cryptography-37.0.1-py38h9ce1e76_0 requires openssl <1.1.2a, Could not solve for environment specs The following packages are incompatible ├─ cryptography ==37.0.1 py38h9ce1e76_0 is installable and it requires │ └─ openssl <1.1.2a , which can be installed; └─ openssl ==3.1.0 hd590300_2 is not installable because it conflicts with the above constraintlibmamba 的报错格式比老 solver 友好得多,只要认准两个符号:├─开头的是被同时选进环境的包,└─缩进的是它拉出来的约束。上面这段翻成大白话就是——你要求装cryptography 37.0.1,它只认openssl < 1.1.2a;但 yml 里另一处又钉死了openssl == 3.1.0。两个约束不可能同时满足,solver 直接放弃。
solving environment: failed之后那一长串Verifying transaction、Executing transaction是干扰信息,那是上一次残留日志或者并行输出的内容,不用管。真正要看的只有The following packages are incompatible这一段。
提示:conda 冲突类报错,90% 的情况不需要搜索,只看
incompatible段落的最后两行就能定位到具体是哪两个包在打架。
2.2 改一行就够:放开 cryptography 的版本钉死
问题的根源是irdiff.yml里cryptography被写成了带 build string 的精确 pin:
cryptography=37.0.1=py38h9ce1e76_0这种写法是conda env export的直接产物。它在导出那台机器上是精确可用的,但换一台机器、换一个 python 小版本、换一个 channel,就必然冲突——build string 里的py38h9ce1e76本身就是平台与编译环境相关的标识,跨环境没有可移植性。
处理方式是去掉版本号和 build string,只留包名,交给 conda 自己解:
# 修改前(irdiff.yml 第 42 行附近) cryptography=37.0.1=py38h9ce1e76_0 # 修改后:只留包名,让 solver 自己挑一个和 openssl 3.1.0 兼容的版本 cryptography为什么不反过来把openssl == 3.1.0降下去?因为 openssl 在这个环境里是被上游链条锁死的——python 3.8 的若干 wheel、urllib3、requests 都依赖较新的 openssl ABI,强行降版会引发第二波、第三波冲突,属于典型的拆东墙补西墙。而cryptography在这里只是一个被动依赖,它真正被用到的地方(ssl 相关)对版本并不敏感,放开后 solver 会自动选一个 41.x 左右的版本,整个环境一次通过。
改完重新执行:
conda env create -f irdiff.yml这一步会顺利很多,但别急着庆祝,后面还有一个 pip 阶段的坑在等着。
2.3 环境建好后的验收动作:别等训练跑崩了才回头查
环境创建成功只是「能 import」的最低标准。我的习惯是每次建完先做三件事,五分钟换后面几小时不返工:
# 1. 激活环境,确认 python 与 torch 正常 conda activate IRDiff python -c "import torch; print(torch.__version__, torch.cuda.is_available())" # 2. 确认 pyg 版本是 2.0.4,这个后面会单独讲 python -c "import torch_geometric; print(torch_geometric.__version__)" # 3. 导出快照,日后出问题直接 diff 对比 conda list --export > env_snapshot.txt第二条要特别留意。这套代码的等变网络层是按torch-geometric 2.0.4的接口写的,如果 solver 给你解出了 2.3 或 2.4,训练阶段会冒出一些看起来完全不相关的错误(后面 3.3 会展开)。至于第三条的env_snapshot.txt,是我被坑出来的习惯——分子生成项目的环境一旦崩了,重装一次要四十分钟,有快照就能直接conda create --file env_snapshot.txt复刻回去,算是一颗后悔药。
3. autodocktools-py3 装不上:Pip subprocess error 与 CondaEnvException 的双重夹击
环境创建走到 pip 阶段时,会冒出第二个报错。这个报错看着吓人,信息量其实很集中。
3.1 报错原文与根因:PyPI 上根本没有这个包
Installing pip dependencies: \ Ran pip subprocess with arguments: ['/workspace/anaconda3/envs/IRDiff/bin/python', '-m', 'pip', 'install', ... Pip subprocess error: ERROR: Could not find a version that satisfies the requirement autodocktools-py3==0+unknown ERROR: No matching distribution found for autodocktools-py3==0+unknown failed CondaEnvException: Pip failed注意那个==0+unknown。这是setuptools_scm在没有 git tag 的情况下自动生成的本地版本号,说明 yml 的作者是在源码目录里直接pip install .导出的依赖清单,导出时把本地版本号一起写进去了。而 PyPI 上从来没有发布过autodocktools-py3的正式发行版——它只以 GitHub 仓库源码的形式存在。所以无论你怎么换镜像源、怎么加--index-url,都会报No matching distribution found。
镜像站那行Looking in indexes: https://mirrors.cloud.tencent.com/pypi/simple是干扰项,换个源一样失败。
3.2 手动从源码装:进入环境,绕开 yml 的 pip 段
conda env create在这个包上失败后,环境本身其实已经建好了大半,只是 pip 阶段中断,退出码非零。这时候不要删环境重来,直接激活进去手动补:
# 激活已建好的环境 conda activate IRDiff # 从 GitHub 源码安装,注意用 python -m pip 而不是裸 pip python -m pip install git+https://github.com/Valdes-Tresanco-MS/AutoDockTools_py3装成功会看到Successfully built AutoDockTools-py3和Successfully installed AutoDockTools-py3-1.5.7.post1+...。这里有两个细节:用python -m pip能确保装进当前激活的环境,避免 pip 指向系统 python 的老问题;git+https://前缀告诉 pip 走 git clone 而不是索引查询。
紧接着把同一批被跳过的依赖补齐:
# 一次性补齐 AutoDock 工具链相关的四个包 pip install meeko==0.1.dev3 pdb2pqr==3.6.1 vina==1.2.2 propka==3.5.0 mmcif-pdbx==2.0.1这几个包各自负责什么,值得说清楚,因为后面算指标全靠它们:
| 包 | 版本 | 作用 |
|---|---|---|
| meeko | 0.1.dev3 | 配体 PDB/SDF 转 AutoDock 的 pdbqt,对接前的必备步骤 |
| pdb2pqr | 3.6.1 | 受体质子化、加氢、补缺失原子 |
| vina | 1.2.2 | Python 绑定,计算 vina_score、vina_docking_score |
| propka | 3.5.0 | 预测残基质子化状态,影响口袋的电荷分布 |
| mmcif-pdbx | 2.0.1 | 读取 mmCIF 格式的结构文件 |
装完顺手验一下:
python -c "import AutoDockTools, meeko, vina; print('docking stack ok')"报ModuleNotFoundError就说明前面的 pip 装到了别的环境里,回退检查which python。
3.3 隐性雷区:pyg 必须是 2.0.4,这是训练阶段的定时炸弹
这条在 yml 里看不出来,装完也一切正常,直到你开始训练才爆。IRDiff 的等变图神经网络层是按torch-geometric 2.0.4的MessagePassing接口实现的,2.1 之后官方调整了aggr、flow这些参数的默认行为和签名,表现是:代码能 import,前向也能跑,但 loss 在第几十步开始发散,或者直接抛一个TypeError: forward() got an unexpected keyword argument。
检查与锁定:
# 先看当前版本 python -c "import torch_geometric; print(torch_geometric.__version__)" # 不是 2.0.4 就重新装 conda install pyg=2.0.4 -c pyg注意:
pip install torch-geometric默认拿最新版。补装其他依赖时如果顺手升级了 pyg,前面跑通的采样会重新变回不可用状态。装任何新包之后,都把这条版本检查再过一遍。
4. 采样脚本一跑就 ImportError:einops 缺失、路径写死与显存预估
环境齐了,执行python sample_split.py时撞上第三个坑。这个坑本身很小,但它背后连着两个更容易被忽略的问题:路径和资源。
4.1 ModuleNotFoundError 的定位链条
Traceback (most recent call last): File "sample_split.py", line 17, in <module> from models.molopt_score_model import ScorePosNet3D, log_sample_cate File "/workspace/xxxx/projects/IRDiff/models/molopt_score_model.py", line ... from models.attn import CrossAttention, RetAugmentationLinearAttention File "/workspace/xxxx/projects/IRDiff/models/attn.py", line 4, in <module> from einops import rearrange, repeat ModuleNotFoundError: No module named 'einops'Python 的 import traceback 是从下往上读的:最后一行才是真正缺失的模块,上面几层只是「谁把它引进来的」路径。这里einops是检索增强模块里做张量重排用的,属于开发时漏写进 yml 的依赖。
conda install einops装完直接重跑,一般还能连着撞上下一个缺失模块(常见是torch_scatter、rdkit)。我的处理方式是先把所有 import 走一遍,一次性补齐,别一个报错跑一次:
python -c " import torch, torch_geometric, einops, rdkit, meeko, vina from torch_scatter import scatter_mean print('all imports clean') "4.2 三处写死的绝对路径,必须改成相对路径
项目从原始作者的机器上导出来时,路径是/path/to/...这种占位符,不改会直接FileNotFoundError。要动的地方有三处,缺一处都跑不通:
# cal_metrics_from_pt.py 第 8 行:评测结果目录 # 修改前 eval_path = '/path/to/eval_results/' # 修改后 eval_path = './eval_results/'# configs/training.yml 第 4-5 行:数据集路径 # 修改前 path: /path/to/crossdocked_v1.1_rmsd1.0 split: /path/to/crossdocked_pocket10_pose_split.pt # 修改后 path: ./data/crossdocked_v1.1_rmsd1.0 split: ./data/crossdocked_pocket10_pose_split.pt第三个是configs/sampling.yml里的checkpoint字段,默认指向./pretrained_models/pretrained-IRDiff.pt。权重文件必须真的在这个位置,否则报错会变成一句很含糊的Restored from ... with 0 missing and 0 unexpected之后卡住,让人以为模型加载成功——其实并没有。
数据侧要准备两个文件,都放进./data:crossdocked_pocket10_pose_split.pt(划分文件)与crossdocked_v1.1_rmsd1.0_pocket10_processed_final.lmdb(预处理好的口袋数据)。这两个文件的体积都不小,下载和解压留足空间。
提示:路径改完后,先用
ls -lh ./data ./pretrained_models确认文件名和大小。分子生成项目里最常见的「玄学失败」,一半以上是文件放错目录或者 lmdb 解压不完整。
4.3 采样命令与参数:每个 flag 到底改什么
内置测试案例是测试集 index 为 0 的体系(PDB 2Z3H 对应的口袋),命令如下:
python sample_split.py \ --start_index 0 \ --end_index 0 \ --batch_size 25 \ --test_prompt_indices_path ./src/test_prompt_ligand_indices_top3.pt \ --result_path ./results/result_0| 参数 | 取值示例 | 含义与调法 |
|---|---|---|
--start_index | 0 | 起始体系编号,测试集里的索引 |
--end_index | 0 | 结束体系编号,与 start 相同表示只跑这一个口袋 |
--batch_size | 25 | 单批并发生成的分子数,直接决定显存占用 |
--test_prompt_indices_path | ./src/test_prompt_ligand_indices_top3.pt | 检索到的参考配体索引,检索增强的核心输入 |
--result_path | ./results/result_0 | 生成结果落盘目录,建议带体系编号便于区分 |
真正的生成超参在configs/sampling.yml里,不看这份文件会误以为命令就是全部:
| 配置项 | 默认值 | 说明 |
|---|---|---|
checkpoint | ./pretrained_models/pretrained-IRDiff.pt | 扩散模型权重路径 |
seed | 2021 | 随机种子,复现实验时不要改 |
num_samples | 100 | 每个口袋生成的分子数 |
num_steps | 1000 | 扩散采样步数,减少会掉质量 |
pos_only | False | 是否只生成坐标不生成原子类型 |
center_pos_mode | protein | 质心归零的参照,取 protein 表示按蛋白质心对齐 |
sample_num_atoms | prior | 原子数采样策略,prior 表示按训练集分布先验采 |
4.4 显存与耗时基线:先按实测值规划,别凭感觉设 batch
batch_size = 25、num_samples = 100、num_steps = 1000这组参数实测下来的表现是:显存占用约 19 GB,单卡跑完 100 个分子耗时约 50 分钟。这个数字很有参考价值——它意味着 24 GB 显存的卡是这条配置的舒适区,16 GB 的卡必须往下调。
显存不够时的处理顺序:先把batch_size降到 10 或 5,num_samples保持不变。降到 5 的时候显存大概能压到 6~7 GB 区间,代价是采样总时长显著拉长,因为步数没变、批次数变多了。不要靠降num_steps来省显存,那个参数省不了多少显存,只会让生成分子的结构质量明显下降。
跑起来之后日志长这样,重点是看Restored from ... with 0 missing and 0 unexpected这行,0 missing / 0 unexpected 说明权重和模型结构完全对齐:
[INFO] Successfully load the dataset [INFO] Restored from ./pretrained_models/PMINet with 0 missing and 0 unexpected [INFO] Successfully load the model! sampling: 100%|██████████████████████████████████████| ... [INFO] Sample done!5. 评测跑完却找不到分子:eval_split.py 不落盘构象,以及四个高频避坑点
这是最让人上火的一类问题:采样跑完了,评测也跑完了,eval_results里只有几个metrics_*.pt,翻遍目录找不到一个 SDF 或 mol 文件。指标是数字,但你没法打开任何一个分子去看它长什么样。
5.1 现象与原因:指标存了,构象没存
原版eval_split.py的收尾逻辑是这样的:
if __name__ == '__main__': parser = argparse.ArgumentParser() ... if args.save: torch.save({ 'stability': validity_dict, 'bond_length': all_bond_dist, 'all_results': results }, os.path.join(result_path, f'metrics_{args.eval_step}_{eval_steps}.pt'))torch.save只把字典序列化了。results里其实带着原子坐标和原子类型,能还原结构,但没人替你做这一步。所以 Vina 分数、QED、SA 全都算出来了,分子本身却还躺在内存里,进程一结束就没了。这就是典型的「有指标没结构」。
5.2 在脚本结尾补一段 SDF 落盘
改法是在torch.save之后追加一段,把results里的坐标和原子类型还原成 RDKit 分子写出去:
import os import torch from rdkit import Chem def save_generated_sdf(results, out_path): """把 eval 结果里的原子坐标/类型还原为 RDKit 分子并写成 SDF。 字段名以本地 results 中实际的键为准,先 print(results[0].keys()) 看一眼。 """ writer = Chem.SDWriter(out_path) for i, item in enumerate(results): # 坐标与原子类型:按项目实际字段名取值,常见为 pred_pos / pred_v pos = item['pred_pos'].detach().cpu().numpy() atom_logits = item['pred_v'].detach().cpu().numpy() # 只保留真实原子,过滤掉 padding 位,否则会写出几十个幽灵原子 n_atoms = int(item['num_atoms'].item()) pos = pos[:n_atoms] atom_types = atom_logits[:n_atoms].argmax(-1) mol = Chem.RWMol() for t in atom_types: mol.AddAtom(Chem.Atom(int(t))) # 原子类型编号需与训练集映射表一致 conf = Chem.Conformer(n_atoms) for idx, xyz in enumerate(pos): conf.SetAtomPosition(idx, (float(xyz[0]), float(xyz[1]), float(xyz[2]))) mol.AddConformer(conf, assignId=True) # 键的推断沿用项目内的成键逻辑,不要在这一步临时自己写距离阈值 writer.write(mol.GetMol()) writer.close()调用位置就放在原有的保存逻辑后面:
if args.save: torch.save({ 'stability': validity_dict, 'bond_length': all_bond_dist, 'all_results': results }, os.path.join(result_path, f'metrics_{args.eval_step}_{eval_steps}.pt')) # 追加:生成构象落盘,路径固定为 ./eval_results/Generated_molecules.sdf sdf_path = os.path.join(result_path, 'Generated_molecules.sdf') save_generated_sdf(results, sdf_path)几个参数上的坑说清楚:num_atoms那行过滤必须留,不然 padding 位会被当成真实原子写出去,SDF 里出现一堆坐标在原点重叠的幽灵原子,RDKit 读取时直接报Sanitization error。原子类型的编号映射要和训练集一致,通常是 C/N/O/F/P/S 之类的顺序,如果写出来发现全是碳,就是映射表对错了。至于成键,别图省事用 1.5 Å 距离阈值临时拼,直接复用项目里已有的成键函数,否则 QED 和 SA 会在你手写的键级上算出一堆诡异值。
5.3 常见问题与避坑:五条踩坑记录
现象一:训练跑几十步 loss 突然发散或抛 TypeError。原因:pyg 被升到了 2.1 以上,等变层的消息传递接口签名变了。 解决:conda install pyg=2.0.4 -c pyg锁回原版本,装完用python -c "import torch_geometric; print(torch_geometric.__version__)"复核。
现象二:采样日志显示权重加载成功,但生成分子全部挤在口袋质心一个点上。原因:center_pos_mode与口袋坐标不对齐,蛋白质质心归零没生效,或者换了自定义口袋但没同步更新 pocket 坐标。 解决:检查configs/sampling.yml里center_pos_mode: protein,自定义体系先把口袋中心平移到原点再喂进去。
现象三:vina_score 一列全是 NaN。原因:受体 pdbqt 转换失败,通常是 pdb2pqr 质子化环节缺少 force field 或者 meeko 版本不匹配。 解决:单独抽一个体系手动跑一次受体准备,把 pdb 转 pdbqt 的命令逐条执行,看哪一步退出码非零,而不是在批量评测里盲猜。
现象四:pip 补装依赖后,torch 或 numpy 被顶到新版本,训练报 CUDA 相关或 ABI 相关错误。原因:pip install不看 conda 的环境约束,遇到版本冲突直接覆盖。 解决:pip 装完立刻conda list | grep -E "torch|numpy|pyg"和env_snapshot.txt对一遍,发现被顶版本就conda install 包名=原版本压回去。
现象五:自定义数据集微调时 dataloader 报 lmdb 相关错误。原因:新的 lmdb 不是按原预处理脚本生成的,键名和索引格式对不上。 解决:用项目自带的预处理脚本重新处理一遍自定义数据,别手工拼 lmdb,格式细节比想象的多。
6. 快速定位这类复现故障:三层剥离法与我压箱底的一张对照表
复现这类扩散模型项目,报错位置和真实根因之间往往隔着两三层。我的做法是把问题按「环境层 → 导入层 → 数据层」剥开,逐层确认,不在层与层之间来回跳。
环境层的判定标准很简单:conda list --export的快照和预期一致,python -c "import torch; print(torch.cuda.is_available())"返回 True。这一层不过,后面所有报错都没有分析价值。导入层用一个脚本把所有模块一次性 import 完,缺什么一目了然,不要一个ModuleNotFoundError跑一次。数据层检查三件事:文件在不在、大小对不对、路径是不是相对路径。
| 报错关键词 | 首查位置 | 一条验证命令 |
|---|---|---|
LibMambaUnsatisfiableError | yml 里带 build string 的精确 pin | grep -n "==" irdiff.yml |
No matching distribution found | yml 的 pip 段是否写了本地版本号 | grep -n "+unknown" irdiff.yml |
ModuleNotFoundError | 缺失包是否在 yml 里 | python -c "import 缺失包名" |
FileNotFoundError | 三处写死路径是否改完 | ls -lh ./data ./pretrained_models |
Sanitization error(SDF 打不开) | padding 原子是否过滤 | grep -c "num_atoms" 保存脚本 |
采样的验证方法则是拿内置的 index 0 体系做基线:batch_size 25、num_samples 100,显存约 19 GB、耗时约 50 分钟。如果你机器上的数字和这个差出一倍,先怀疑权重没加载对或者数据文件不完整,别急着调超参。跑完打开./eval_results/Generated_molecules.sdf,用任意分子可视化工具看一眼,原子不该重叠、不该全是碳、不该挤成一个球——这三条过了,指标数字才有意义。
说个血泪经验:我以前复现这类项目,习惯一上来就改代码调参数,结果环境里三个包的版本已经错位了,改到半夜才发现是在给一个坏掉的地基刷墙。从那以后我每次动手前都强制走一遍「快照 → 逐层 import → 内置案例跑通」这三步,慢十分钟,少返工一整天。这份资源里已经把每处报错的位置、改法和实测数字都标了出来,配套的模型权重、修正后代码、缺失模块和测试案例都在,照着走基本能绕开上面这些坑。希望帮到你。
本文还有配套的精品资源,点击获取