简介:IRDiff 完整评测文档与可运行代码资源包,面向分子生成、扩散模型及药物设计方向的研究者。IRDiff 是基于蛋白质-配体相互作用的检索增强 3D 分子扩散模型,由清华深圳国际研究生院与鹏城实验室团队提出,发表于 ICML 2024,通过精心挑选的参考配体集合引导扩散模型生成目标感知的分子,在 CrossDocked 2020 数据集上取得领先的结合亲和力表现。资源包含 1 个 PDF 文档,压缩包约 12.23MB,文档完整呈现项目背景、模型框架(PMINet)、评测过程与结果,并详细标注代码报错位置及修改方法、缺失模块文件、测试案例与个人分析注释。修正后的代码可针对特定蛋白口袋体系进行分子生成,计算 vina_score、QED、SA 等指标,也支持自定义数据集的微调与训练,便于读者理解算法细节、复现实验并开展后续改进。目前已有 121 人学习下载。
1. 接手 IRDiff 完整评测文档加可运行项目代码,先搞清楚它到底是什么
大多数人第一次接触 IRDiff,是带着一份完整评测文档和一套可运行项目代码来的,第一反应往往是:这跟我跑过的图像修复网络有什么区别。IRDiff 是一套统一图像修复框架,核心思路是用预训练的扩散模型当图像先验,再用一个可学习的退化算子去模拟噪声、雨纹、模糊和下采样,在采样过程中用观测图把生成结果逐步拉回到退化约束上。它跟 DPS、DDRM 这类免训练修复方法不同:退化算子不是手工假设的线性模型,而是训练出来的。跟 Restormer 这类专用修复网络不同:它不需要针对每个任务训练一个端到端模型。这套方案最合适的人群有两种,一是复现扩散修复基线做对比实验的研究者,二是想借扩散先验做自定义退化任务、又没有资源重训大模型的工程师。这份文档加代码能帮你绕开的,正是从头训扩散模型和写评测脚本这两件最耗时的事。
2. IRDiff 原理拆解:冻结的扩散先验、可学习退化算子与采样引导
2.1 骨架:冻结的扩散先验与可学习退化算子
先解释为什么修复可以交给扩散模型。扩散模型的生成过程本质上学到的是“一张自然图像长什么样”,而修复任务可以重新理解为带约束的生成问题:结果既要落在自然图像流形上,又要满足退化约束。IRDiff 把这两件事拆成两个模块,各管一摊。
第一个模块是预训练的无条件扩散模型。所谓无条件,指它训练时只学了从噪声生成自然图像,没有专门学过按退化图还原。IRDiff 在整条修复链路里冻结它,不更新任何权重。这么设计最直接的好处是:自然图像分布是跨任务共享的,去噪、去雨、超分、去模糊最终都要落到“生成一张可信的干净图像”上,一个通用先验就够用,不用每个任务重训生成模型。
第二个模块是可学习退化算子。它以一张干净图为输入,输出一张与观测图同尺寸的退化结果。超分任务是先下采样再加模糊,去雨任务是叠加雨纹,去噪任务是加噪声。算子用一个带下采样结构的小网络实现,训练时用一批干净图像和对应合成退化图像做监督,目标函数可以写成:
L_deg = || h(x; φ) - y_syn ||^2其中 h 是退化算子,φ 是它的参数。更完整的做法是给这个算子额外输入一个退化条件向量,把任务类型、超分倍数、噪声强度这些信息传进去,这样多任务可以共用一个算子网络,这也是评测文档里“统一模型”这个表述的来源。
这里有一个最容易看走眼的地方:退化算子学习的方向是从干净图到退化图,不是从退化图到干净图。很多第一次看代码的人会在训练脚本里找“修复网络”,结果找不到,因为真正被训练的小模块只有这个退化算子,真正干修复活的是那个冻结的扩散先验。
2.2 采样引导:残差是怎么回传到生成过程的
推理阶段是这套框架最不直观的部分。扩散模型采样时从随机噪声出发,逐步去噪到干净图。每走一步,当前状态会经过去噪网络,网络会预测一张“当前估计的干净图”。IRDiff 的关键动作就发生在这里,每一步做四件事:
- 把当前估计的干净图 x̂0 送进退化算子,得到 h(x̂0);
- 计算 h(x̂0) 与观测图 y 的均方误差;
- 求这个误差对当前采样状态的梯度,沿梯度方向修正这一轮的去噪结果;
- 进入下一步采样。
这样走完整个采样链,生成图会同时满足两个条件:看起来自然(扩散先验保证),又和观测图对得上(引导梯度保证)。这里有个重要认知:推理时所有网络参数都冻结,只有采样状态在动。换任务时不需要重新训练扩散先验,只需要重新训练那个小的退化算子,几十到一百个 epoch 通常就能收敛,这是 IRDiff 这类方案工程上最划算的地方。
有一个参数值得单独说:引导强度,代码里一般叫 guide_scale 或 guidance。它控制引导残差对采样结果的修正比例。这个值太小,结果像普通扩散生成图,和观测对不上;太大,结果被硬拉回观测,先验失效,出现噪点和伪影。常见调试手法是从 1.0 往下试,每次除以 10,观察修复结果在“约束满足”和“图像自然度”之间的变化。
2.3 评测文档的三张表:PSNR、SSIM、LPIPS 该信哪个
一份完整的 IRDiff 评测文档,核心常常是几张指标表。横向是任务,去噪、去雨、超分、去模糊;纵向是 PSNR、SSIM、LPIPS;对比对象一般分成两类,一类是专用修复模型,一类是 DPS、DDRM 这类免训练引导方法。
读这张表要抓三件事。第一,看它是不是一模型打全场。IRDiff 类方法的价值主张是统一性:固定扩散先验,换任务只换退化算子。如果专用模型在单项任务上 PSNR 更高,这是正常的,不代表框架失败,还要看综合维度和部署成本。第二,看它用了几步采样。扩散修复里采样步数是质量与耗时的最大杠杆,1000 步和 50 步差一个量级的时间,复现时不看这一行,比较结论就不公平。第三,LPIPS 比 PSNR 更能反映扩散先验的价值。PSNR 对像素级误差敏感,对纹理细节不敏感;LPIPS 在感知层面更接近人眼。扩散修复的典型特点就是像素指标略逊、感知指标领先,评测文档里两类指标并列不是没道理的。
3. 把 IRDiff 跑起来:环境、数据准备与两条最小命令
3.1 依赖与硬件门槛:先看 requirements,再决定拿什么卡跑
跑 IRDiff 这类扩散修复工程,依赖可以分成三组:核心计算用 PyTorch 和 torchvision;数据与图像处理用 numpy、opencv、Pillow、scikit-image;评测工具用 lpips 和 pytorch-fid。最后一组最容易缺,评测脚本跑到最后一步报 ModuleNotFoundError 的,十有八九是它没装。
PyTorch 版本搭配 CUDA 版本,以自己机器的驱动为准,但我建议优先按仓库 requirements 里锁定的版本装,没锁再选当前稳定的 2.x。常见做法是新建一个独立环境,避免和别的项目互相污染依赖:
conda create -n irdiff python=3.9 -y conda activate irdiff pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt硬件方面,12GB 显存是比较现实的起步线。原因是采样时的引导需要对当前状态回传梯度,计算图长度和采样步数成正比,远比训练一个小分类头占显存。8GB 卡跑 50 步很容易 OOM,正确做法是先用 20 步把流程跑通,再往上加步数。CPU 不是不能跑,但只能用来验证代码逻辑,完整采样在 CPU 上是灾难,别浪费时间。
3.2 数据准备与配置文件:在线生成退化对,统一像素范围
数据目录我一般按“一个任务一个子目录”组织,每个任务下面分 train、val、test。以去噪为例:
data/ denoise/ train/ # 干净图像,训练时现加噪声 val/ test/ deblur/ train/这里要注意一个实现上的差别:有些工程在数据准备阶段就生成退化图像,离线固化到硬盘;有些则在训练循环里现算。IRDiff 训练退化算子时,推荐用在线生成。原因是退化算子要学习的就是“干净到退化”的映射,如果训练数据是离线固定的,算子学到的只是这批噪声实例的拟合,泛化能力差。在线生成时,每个 epoch 的噪声强度、雨纹方向、模糊核都不一样,退化算子见到的输入分布更宽,学出来的映射更稳定。
配置用 YAML 管理。典型训练配置的关键字段如下,我按调试频率从高到低排:
data_root: ./data/denoise save_dir: ./logs/denoise batch_size: 8 lr: 1e-4 max_epochs: 100 sample_steps: 50 # 训练退化算子时用不到,推理时才生效 guide_scale: 0.5 # 引导强度,推理时最常调 seed: 42 pixel_range: [-1, 1] # 所有 tensor 的像素范围约定pixel_range 这个字段值得重点盯。它决定你读图后把像素归一化到什么范围,后面一大堆翻车都跟它有关。seed 也重要,评测文档多数会写明随机种子,配置里不设的话,采样噪声每次不同,PSNR 能差 1dB 以上,复现就无从谈起。
3.3 两条最小命令:训练退化算子,再采样修复
拿到代码后,我习惯先把入口整理成两条命令:一条训练退化算子,一条采样修复。不同仓库的脚本名不一定一致,但职责基本都是这两个角色。
# 训练退化算子(超分 ×4 任务示例) python train_degradation.py --config configs/sr4.yaml # 测试修复(单张图推理) python sample.py \ --config configs/sr4.yaml \ --checkpoint ./logs/sr4/best.pt \ --input ./data/test/hr_001_lr.png \ --output ./results/sr4/hr_001_sr.png \ --sample_steps 50 \ --guide_scale 0.5train_degradation.py 负责训练那个可学习退化算子,--config 指向的 YAML 决定数据路径、学习率和训练轮数。训练产物 checkpoint 里只有退化算子网络的权重,不要拿它当修复模型直接输出结果。sample.py 是完整推理入口:读入退化图、初始化噪声、跑采样循环,在循环里调用冻结的扩散先验和训练好的退化算子,最后写出修复图。
--sample_steps 是采样步数,直接决定推理耗时。50 步是质量和速度的常见折中,20 步能看个大概但细节粗糙,100 步适合最终评测出指标。--guide_scale 就是引导强度,这是全项目最值得单独调试的参数。这两条命令能跑通,代码主链路就跑通了,剩下的事都是参数微调。
4. 复现评测文档里的指标:先抄默认值,再算 PSNR/SSIM/LPIPS
4.1 实验设置速查表:从评测文档里抄默认值
复现评测文档的第一步不是跑代码,而是建一张实验设置速查表。评测文档的正文或脚注里通常藏着这些默认值,要逐项摘出来:
| 项目 | 默认值 | 配置位置 |
|---|---|---|
| 随机种子 | 通常是 42 或 0 | 配置 seed 字段 |
| 测试图像数 | 100 张/任务 | 评测文档测试集说明 |
| 采样步数 | 50 或 100 | 采样脚本参数 sample_steps |
| 引导强度 | 0.1~1.0 之间 | 采样脚本 guide_scale |
| 图像尺寸 | 256×256 或 512×512 中心裁剪 | 配置 preprocess |
这张表的价值在于:代码仓库里的 config 默认值和评测文档不一致的情况经常发生。我遇到过不止一次,仓库里 sample_steps 写 20,评测文档写 100,直接跑出来的结果当然对不上。以评测文档为准,改配置,再跑,这是复现的第一条规矩。
4.2 跑完怎么评:评测脚本的三个隐藏错误点
评测脚本是整条链路里最容易复制错的部分。下面是一个我反复使用的三指标评估脚本:
import numpy as np from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim import lpips import torch def evaluate(restored, target, net='alex', pixel_range=1.0): # restored/target: [H,W,C] or [C,H,W], RGB, 0~1 restored = np.clip(restored, 0, 1).astype(np.float32) target = np.clip(target, 0, 1).astype(np.float32) psnr_val = psnr(target, restored, data_range=pixel_range) # 图像是 [H,W,C] ssim_val = ssim(target, restored, data_range=pixel_range, channel_axis=-1) # LPIPS 需要 [B,C,H,W],输入范围是 [-1,1] loss_fn = lpips.LPIPS(net=net) t1 = torch.tensor(restored).permute(2, 0, 1).unsqueeze(0) * 2 - 1 t2 = torch.tensor(target).permute(2, 0, 1).unsqueeze(0) * 2 - 1 lpips_val = loss_fn(t1, t2).item() return psnr_val, ssim_val, lpips_val这个脚本有三个容易出错的地方。第一,ssim 的通道参数在不同版本里写法不同,旧版 skimage 是 multichannel=True,新版是 channel_axis=-1,版本不对会直接报错或算错。第二,LPIPS 期望输入范围是 [-1,1],不是 [0,1],忘了乘 2 减 1,LPIPS 数值会整体被放大。第三,data_range 必须和像素范围对应,图是 0~1 就传 1.0,传 255 会让 PSNR 和 SSIM 整体偏低。
评测文档里如果提到“边缘裁剪”,意思是评估前把图像四周裁掉若干像素,因为扩散模型的边界填充会带边缘伪影。不做这步,PSNR 会被边缘区域拉低,在小尺寸图像上差距能被放大到 0.5dB 以上。看文档里有没有这一句,有就照做。
4.3 数字对不上论文:三个自检方向
跑完的数字比评测文档低,是这个方向最常见的情况。别急着怀疑代码,按下面三个方向查。
第一,退化算子对不对。每个任务有独立的退化算子 checkpoint,用去噪的去跑超分,结果一定会崩。先确认 checkpoint 指向的是当前任务对应的训练产物。第二,采样设置对不对。把 sample_steps、seed、guide_scale 三个值和速查表逐一对照。seed 最容易被忽略,因为采样噪声随机,PSNR 会跳;guide_scale 差一个数量级,修复结果不是“不贴合观测”就是“过度贴合观测”,指标都低。第三,数据加载和配对的顺序对不对。输出文件名如果命名不统一,脚本按字符串排序配对,结果图和标签对不上,指标会惨不忍睹。先存一张并排对比图肉眼确认配对无误,再上指标。
5. IRDiff 落地避坑:显存 OOM、像素范围与偏色问题排查
5.1 显存不够:梯度回传,计算图和采样步数成正比
现象:batch size 已经调到 1,采样步数降到 20,显存还是溢出。
原因:IRDiff 的引导需要对当前采样状态回传梯度,这要求保留从采样状态到干净图估计、再到退化算子的完整计算图。如果代码没有手动截断每步的图,整条采样链的计算图会逐层叠加,显存占用随步数线性上涨。
解决:优先做两步。第一,在采样循环里对引导传播路径做 detach 控制,只保留当前步需要的梯度,这一步通常能省一半以上显存。第二,降低引导频率,每 5 步做一次引导而不是每步都做,对最终指标影响有限,显存和时间都明显下降。另外,开启 fp16 混合精度在这个场景收益明显,12GB 显存基本可以跑 50 步以内,但要注意反向传播的数值稳定性。
5.2 退化算子不收敛:先查像素范围是否统一
现象:训练 loss 几十个 epoch 几乎不降,或者输出的退化图像整体偏亮或偏暗一个固定倍数。
原因:退化算子的输出范围和监督信号的像素范围不一致。典型情况是扩散先验内部用 [-1,1],而训练脚本读图后停留在 [0,255] 或 [0,1] 就送进算子。梯度方向没问题,但数值尺度差了几十倍,优化过程在小学习率下原地踏步,大学习率下震荡。
解决:在训练脚本开头加一个断言,检查输入 tensor 和标签 tensor 的范围一致,并把 pixel_range 写进配置。我习惯统一用 [-1,1]:读图、归一化、进网络、退化算子输出,全部保持在同一数值约定下。这样扩散先验、退化算子、引导 loss 三段代码的数值基础完全一致,后续一切调参才有稳定前提。
5.3 修复结果发灰、偏色:先怀疑通道顺序和归一化
现象:修复结果整体饱和度低,像蒙了一层灰,或者颜色通道错位。
原因:两个高频来源。第一是 OpenCV 读图默认返回 BGR,而扩散先验训练时用的是 RGB,通道顺序不一致,生成结果颜色必然错乱。第二是引导强度过大,把生成结果硬拉到观测约束上,而观测图和先验分布范围不一致,结果被拉出伪影和灰色。
解决:读图后立刻转成 RGB,并维护一条固定输入链路:读图、转 RGB、归一化、送采样脚本,所有任务共用。排查发灰问题时,先把 guide_scale 降到原来的十分之一跑一张,如果颜色恢复,问题在引导强度;如果还是灰的,去查通道顺序和归一化链路。
5.4 指标比评测文档低一截:先对速查表,再对评测口径
现象:PSNR 比文档低 2dB 以上,SSIM 也低,但修复结果肉眼看上去没问题。
原因:大多数时候不是模型坏了,是复现条件不一致。采样步数、随机种子、测试集条数、边缘裁剪,任何一项不同都能造成这个量级的偏差。尤其是测试集条数,文档用 100 张求平均,你只跑 10 张,几张难例就能把均值拖下来。
解决:按 4.1 的速查表一字不差核对设置,再单独检查评测脚本是否做了边缘裁剪、图像尺寸是否一致。最后把生成结果和标签存成并排图,肉眼确认不是系统性偏暗偏糊。对比图正常但指标就是低,基本可以锁定是评测脚本的口径问题,回到 PSNR 的 data_range 和通道顺序上检查。
6. 进阶:把 IRDiff 接进自己的退化任务
6.1 自定义任务只改一个模块:写一个可微的退化算子
接自定义退化任务时,最稳妥的路径是只替换退化算子。需要提供的接口极其简单:
class MyDegradation(nn.Module): def forward(self, x_clean): # x_clean: [B, C, H, W],数值范围与配置的 pixel_range 一致 # 返回与观测 y 同尺寸的退化图 return degraded只要这个 forward 是可微的,不要在里面用随机采样、硬阈值这类不可导操作,采样引导的逻辑完全不用动。写完算子先做静态验证:拿一张干净图过算子生成 y,再跟真实观测图对比,看退化风格是否贴近。这一步能提前暴露算子设计上的方向性错误。
6.2 每个新任务都先跑单图冒烟测试
我的习惯是每个新任务只跑一张图:干净图、自己做退化、走修复、看能否回到干净图。这一张图同时验证了退化算子、采样链路、评测脚本三段代码,跑通了再上完整测试集,能避免在 100 张图上反复试错。
最后说一个自己的教训。早期我热衷于把退化算子做得复杂,认为越贴近真实物理退化越好,结果在复杂算子上反复调参,效果反而不如一个简单算子配合理引导强度。后来我意识到,扩散先验的分布范围和退化算子的数值约定保持一致,比算子本身的复杂度更影响最终效果。先跑通最简单版本,再逐步加复杂度,每一步都守着观测对比图验证,是这个方向最省时间的做法。希望帮到你。
本文还有配套的精品资源,点击获取