☰
扩散后验采样DPS:用生成先验求解计算成像逆问题
2026/10/20 7:13:46 网站建设 项目流程

开头得先说清楚一件事:很多人第一次接触成像逆问题,下意识觉得“不就是修复图片嘛”,拿个锐化滤镜、拉个对比度就完了。但真到了计算成像这个层面,问题本质完全不同。一张模糊的低分辨率图像、一次稀疏角度的CT扫描、一块随机掩膜后的光谱数据,我们面对的是一个从不完整甚至损坏的观测里反推原始场景的任务。这期是计算成像系列的第16篇,围绕扩散后验采样(Diffusion Posterior Sampling,DPS)展开,聊聊生成先验到底是怎么被用来求解这类逆问题的,以及我在复现、调参、对比实验过程中的一些真实体会。适合正在做超分辨率、去模糊、相位恢复、CT重建,或者想搞清楚扩散模型在图像恢复之外还能怎么用的读者。

DPS这个思路的诱人之处在于:它把扩散模型当成一个可微分的先验,在后验采样框架里自然地和任意前向测量模型结合。换句话说,你不用为每个任务重新训练一个模型,只要前向算子能算梯度,DPS就能在上边跑。这篇文章我会从逆问题为什么难、DPS的概率直觉、完整采样循环、不同任务的表现、与其他扩散逆问题方法的差异,以及实操中容易踩的坑这几个角度去拆。

1. 逆问题不是“修图”:前向算子、病态性与先验的进化

1.1 从成像模型说起

几乎所有成像逆问题都能写成同一个形式:

y = A(x) + n

其中 x 是我们想恢复的干净信号,y 是实际拿到的观测,A 是前向算子,n 是噪声。不同任务的区别基本都藏在 A 里边。

超分辨率任务里,A 是一个“模糊加下采样”的组合算子,它把一个高分辨率图像映射成低分辨率观测;去模糊任务里,A 是一个卷积算子,通常是运动模糊核或者高斯模糊核;压缩感知里,A 是随机采样矩阵;CT重建里,A 是Radon变换或者它的离散近似;相位恢复里,A 是非线性的,它丢掉相位信息,只保留傅里叶变换的幅度。

这里我想强调一个常被初学者忽视的点:A 的具体形式决定了问题的难度,也决定了最终解的性质。一个卷积模糊算子,它的频谱在高频段有大量零点,意味着高频信息在观测里直接丢失了,任何算法都变不回来,只能靠先验去“猜”。而下采样算子本质上是一个降维映射,丢失的信息更多。

1.2 三类“病态”来源

逆问题之所以难,通常有三层原因叠加。

第一层是欠定性。观测 y 的维度远小于 x 的维度,满足 A(x) ≈ y 的解有无穷多个。拿生活场景类比,好比你只有一张低分辨率照片,要还原出场景里每一根发丝的走向,这在数学上就是不唯一的。

第二层是信息丢失。A 的谱在部分频带是零或极小的数,即便观测是干净的,这部分信息也已经从数据里彻底消失了,恢复时只能靠猜测。去模糊问题最典型,模糊核在频率域的零点附近会放大噪声,这就是所谓的“噪声放大效应”。

第三层是噪声敏感性。真实观测总是带噪声的,n 的微小扰动会在逆运算中被大幅放大,直接求逆会得到一张布满椒盐噪声的图。我们常说的“病态性”,就是指 A 的最小奇异值趋近于零,导致 A 的逆操作不连续。

1.3 传统正则化的天花板

面对这些病态性,经典做法是加正则项:

x_hat = argmin_x || y - A(x) ||^2 + λ·R(x)

R(x) 是对解空间的约束,比如Tikhonov正则化约束解的 L2 范数,TV正则化约束梯度稀疏性。这类“手工先验”的优势是理论清晰、求解稳定,但缺陷也很明显:它们对自然图像复杂纹理的建模能力非常有限。TV正则化会带来阶梯效应,一块平滑区域可能被搞成一层一层的色块,在对细节要求高的场景里完全不够用。

后来出现了数据驱动先验,也就是用大量干净图像训练卷积网络作为去噪器,插到迭代求解框架里,代表思路是plug-and-play和deep image prior。这类方法的进步在于先验不再是人手写的,而是从数据里学出来的。但它的表达方式仍然局限于单张图的局部平滑假设,很难表达“一张脸应该长什么样”这种全局语义信息。而扩散模型学的不是某个去噪函数,而是整个数据分布的得分函数,它知道一个完整自然图像应该满足的全局结构。这就是生成先验比传统先验高一个维度的原因。

DPS要做的事情,就是把这个强大的生成先验,接进贝叶斯后验采样的框架里。

2. DPS的概率直觉:为什么扩散模型能给后验“打底稿”

2.1 从MAP到后验采样

如果我们站在贝叶斯的角度看逆问题,目标不是求一个固定解,而是采样后验分布:

p(x | y) ∝ p(y | x) · p(x)

其中 p(y | x) 是似然项,由观测模型决定;p(x) 是先验项,由数据分布决定。传统方法求MAP估计,本质上是在后验分布里找一个峰值,但这样做有两个问题:第一,高维后验分布通常非常复杂,梯度优化很容易陷进局部峰值;第二,对很多任务来说,用户想要的不是“唯一答案”,而是一批合理解。比如从模糊人脸上恢复照片,可能存在多个都满足观测、但细节不同的人脸,只给一张“平均脸”反而显得不真实。

所以DPS选择了采样思路。我们要从 p(x|y) 里抽出足够多样、足够真实的样本,而不是只取一个极值点。

2.2 Tweedie公式:预先“去噪”的那一步

在理解DPS之前,需要先熟悉扩散模型的一个性质。给定带噪图像 x_t,DDPM训练的网络 ε_θ(x_t, t) 预测的是噪声,但我们能从这里得到一个对干净图像的估计:

x_0_hat = (x_t - sqrt(1 - ᾱ_t) · ε_θ(x_t, t)) / sqrt(ᾱ_t)

这个公式本质上是Tweedie公式的重新整理,它给出了给定 x_t 时 x_0 的条件期望估计。它不完美,但在高噪声水平下已经能勾勒出图像的大致轮廓。DPS的关键决策在于:采样过程中我们拿不到真实的 x_0,只能用这个估计去计算梯度,让观测约束起作用。

2.3 DPS的替代梯度

现在到了DPS最核心的近似。在反向扩散的每一步,我们面对的状态是 x_t,希望计算对数似然梯度:

∇_{x_t} log p(y | x_t)

这个梯度告诉我们:为了让当前状态更符合观测 y,应该往哪个方向调整。难点在于 p(y | x_t) 涉及到对 x_0 的积分,没法精确计算。DPS的做法很直接:用 x_0_hat 作为当前状态下对干净图像的“最佳猜测”,然后把条件分布替换成:

p(y | x_t) ≈ p(y | x_0_hat)

展开之后,梯度变成:

∇_{x_t} log p(y | x_t) ≈ -∇_{x_t} || y - A(x_0_hat) ||_2^2 / (2σ²)

实际实现时,通常用一个手动步长 ζ 来控制这个引导项的强度,写成:

x_{t-1} = 采样更新 + ζ · ∇_{x_t} || y - A(x_0_hat) ||_2^2

注意这里是损失梯度的负数方向,但因为实际代码里用梯度上升来增加似然,符号要看具体实现习惯。我第一次写代码时就在这个符号上栽过跟头,在超分实验里图像直接漂移成了纯色,排查了半天才发现是引导方向反了。

为什么这个近似可行?直觉上讲,x_0_hat 是一个点估计,它包含了从 x_t 出发的所有信息。用它代替潜在的真实 x_0,虽然会有偏差,但梯度方向通常是对的——如果当前图像和观测不符,x_0_hat 在观测域的计算结果也会偏离 y,梯度会推动 x_t 朝修正方向移动。这个“用点估计做条件近似”的技巧,让DPS能适用于任意可微的前向算子,不需要针对特定任务做专门设计,这是它最宝贵的通用性来源。

DPS也有一些局限性。最明显的是当图像维度很高、观测缺失严重时,x_0_hat 的估计误差变得很大,引导信号会不稳定。另外在有噪声场景下,ζ 如果取固定值,后期容易出现过度依赖引导导致的纹理伪影,这部分我在后面实操章节会细讲。

3. 一次DPS采样循环的完整拆解:公式、伪代码与步长直觉

3.1 一个最小可跑的DPS循环

DPS的完整推断算法其实不复杂,核心是一个反向扩散循环,每一步都往采样更新里注入一次观测引导。下面这个伪代码可以直接映射到PyTorch实现,DDIM采样和DPS论文里的DDPM变体差别不大,只是更新规则略有不同。

def dps_sampling(y, A, eps_theta, alpha_bar, T, zeta, eta=0.0): # y: 观测图像,A: 前向算子,eps_theta: 预训练扩散模型 # alpha_bar: 累积噪声调度,T: 总步数,zeta: 引导步长 x_t = torch.randn_like(y_shape_high_res) * sqrt(alpha_bar[T]) # 初始化高维噪声 for t in range(T, 0, -1): eps = eps_theta(x_t, t) # 1. 用Tweedie公式估计干净图像 x0_hat = (x_t - sqrt(1 - alpha_bar[t]) * eps) / sqrt(alpha_bar[t]) # 2. 计算观测一致性梯度 grad = gradient_of_loss(x_t, y, A, x0_hat) # 3. 反向采样更新,这里用简化DDIM形式 x_prev = ddim_update(x_t, eps, t, eta) # 4. 加上引导项 x_t_prev = x_prev + zeta * grad x_t = x_t_prev return x_t

其中ddim_update对应经典的DDIM迭代式,η 控制是否加入额外随机噪声。判断梯度方向是否正确的快速方法是:把zeta设成0,先跑一遍不带引导的采样,输出是一张随机生成的图像;把zeta设成一个很小的数,输出开始出现观测里的大致结构;继续增大,结构越来越清晰。如果方向反了,图像会朝着完全无关的方向跑。

3.2 x0_hat、梯度和更新三者的顺序

这个循环的每一步里,信息流是“从 x_t 推 x0_hat,从 x0_hat 推梯度,再拿梯度和 x_t 一起推 x_prev”。需要注意实现里有两个容易写错的细节。

第一个是gradient_of_loss应当对 x_t 求导,而不是对 x0_hat 求导。如果我们对 x0_hat 求导,梯度就会被截断,无法反向传播到当前状态,整个引导就失效了。实际上 x0_hat 是 x_t 和 εθ 的函数,所以对 x_t 的求导会自动穿过这条链路。

第二个是计算量。为了保证引导梯度完整传播,我们需要对前向算子 A 做一次前向传和一次反向传播,这比单纯生成图像多了一次A算子的开销。当 A 本身是一个大型矩阵(比如CT系统矩阵),这一步的内存占用会非常可观。我的做法是尽量用算子分解的形式代替显式矩阵,能省不少显存。

3.3 步长ζ:DPS里最敏感的超参数

DPS论文里默认设置 ζ 在1.0附近,但这是针对像素值归一化到 [-1, 1]、图像分辨率在256左右的场景。如果你的数据尺度不同、或者前向算子的能量不同,zeta必须重新标定。

一个实用标定思路是先算一下 ||y - A(x0_hat)|| 的初始量级,保证引导项的贡献和DDIM更新本身的量级接近。经验公式大概是:

有效引导强度 ≈ zeta · || A^T (y - A(x0_hat)) || / (图像norm)

zeta太小,生成的图像虽然真实但完全不管观测内容,相当于纯先验采样;zeta太大,图像会明显出现棋盘格状的高频伪影,虽然观测残差很低,但视觉上非常糟糕。这个“残差低但视觉差”的现象是DPS调参时最常见的陷阱,后面我在实验章节会具体讲。

从模型表现来看,DPS对ζ的敏感性在不同任务上差异很大。超分任务是相对温和的,ζ在0.5到2.0之间都能出不错的结果;高噪声CT重建就不同,ζ稍微大一点,重建图像边缘立刻出现一圈“光晕”,那是因为引导过于相信观测,把噪声也当成信号来拟合了。

4. DPS在不同成像任务上的实战表现:超分、去模糊、相位恢复

4.1 超分辨率:最容易验证的场景

超分大概是DPS最容易上手的验证场景。把一张256×256的清晰图像做4倍下采样得到64×64的输入,前向算子是“高斯模糊加双线性下采样”,对DPS来说是一个标准逆问题。我在自己的模拟项目X上跑完的感受是:恢复出来的图像纹理非常自然,草地的细节、皮肤的毛孔、建筑的窗户边缘,看起来像是真实拍摄出来的,而不是锐化过度的结果。

但这里有一个很有意思的现象:用PSNR做指标时,DPS并不占优,甚至可能比一些经典方法低。原因是DPS追求的是分布一致性,它采样的样本是有合理随机性的,而PSNR这种逐像素指标会惩罚任何偏离Ground Truth的生成。相同实验下,LPIPS这类感知指标DPS通常表现更好。所以评估DPS时一定要想明白自己到底要什么:是要像素级逼近原图,还是要视觉上合理可信的图像。

4.2 去模糊:对核估计非常敏感

在运动去模糊任务里,DPS的表现比超分波动大。问题不在先验本身,而在前向算子的精确性。DPS假设我们已知模糊核A,如果A估计不准——比如真实模糊核是7像素运动轨迹,你给了9像素——引导梯度会不断把错误的结构注入采样过程,生成图像看起来清晰,但细节是幻觉出来的。

我从这里得到一个经验:用DPS之前,先花时间把模糊核标定准确,这比调任何超参数都值钱。模糊核误差导致的伪影,在采样图像上表现为“重影”,因为DPS倾向于沿着错误A的方向反复强调特征,最终形成类似多帧合成失败的边缘。

4.3 相位恢复:非线性前向的分水岭

相位恢复是一个很有意思的非线性逆问题:观测是全息图的强度,也就是傅里叶变换的幅度,相位信息全部丢失。传统上这个问题需要用迭代相位恢复算法,比如Gerchberg-Saxton,但这些方法对初始值非常敏感,容易收敛到局部最优。

DPS处理非线性前向算子的方式很优雅:它完全不关心A是不是线性的,只要你能写出 A(x0_hat) 并求出对 x_t 的梯度就行。相位恢复里,A(x) = |F(x)|²,梯度是容易计算的。我在实验里用小尺寸图像验证,DPS确实能从随机初始化出发,逐步生成和观测幅度匹配的清晰图像。代价是收敛速度慢,需要更多采样步数和更小的ζ,因为非线性算子放大了引导噪声。

4.4 稀疏视角CT与高缺失率场景:短板暴露

当观测缺失率非常高的时候,DPS的优势开始减弱。比如稀疏视角CT重建,只有20个投影角度,信息量太少,扩散先验即便再强大,也难以无中生有地补充全部解剖结构。这时候DPS生成的结果往往是“看起来像一张真实CT的图”,但和真实的解剖结构对不上,这是生成先验类方法的共同风险。

另一个短板是高噪声场景。带引导的采样和纯采样不一样,纯采样在后期噪声水平低,生成稳定;而带引导的采样潜在地把观测里的噪声也当成信息拟合进去了。我实验里遇到的情况是:加性噪声越大,DPS输出图像表面越容易出现一种“油彩感”,类似低分辨率照片强行放大的纹理失真。

下面这个表粗略总结了我在不同任务上的实测感受。注意这是针对我自己的模拟实验和文献汇总,具体效果取决于先验模型质量和超参数设置。

任务 | 前向算子 | 难度来源 | DPS实测表现 | 主要失败模式 超分辨率 | 模糊+下采样 | 高频信息缺失 | 感知质量高,纹理拟真 | 细节不忠实,PSNR偏低 去模糊 | 卷积核 | 核估计误差放大 | 对核精度敏感 | 重影、边缘幻觉 相位恢复 | 非线性幅度 | 相位丢失、局部最优 | 能从随机初始化恢复 | 收敛慢、需调小ζ 稀疏CT | Radon子集 | 角度缺失严重 | 重建结果全局合理 | 结构偏离真实解剖

5. 和Score-SDE、Rediff、DDNM摆在一起:DPS的定位与短板

5.1 扩散逆问题方法的三个流派

DPS并不是唯一一个把扩散模型用于逆问题求解的方法。过去一段时间,围绕“生成先验+观测约束”的思路,大家大致分成了几个流派。

Score-SDE类方法走的是连续时间路线,直接在反向SDE里加入一个测量梯度项,让采样过程沿着观测约束方向漂移。这类方法理论干净,但实现起来要处理SDE求解器的离散误差,速度慢,采样质量对步数的依赖性很强。

Rediff流走的是“测量一致性投影”路线。它在每一步采样之后,额外做一个“向测量子空间投影”的操作,确保生成结果和观测在测量域严格一致。这类方法通常对无噪声场景非常有效,因为它假设观测是精确的、不可违背的,但在有噪声场景下,严格投影会把噪声也锁进结果里,导致伪影。

DDNM流的思路更聪明一点,它把信号分解成“在测量零空间里的分量”和“与测量相关的分量”。扩散模型只编辑零空间部分,测量部分直接用伪逆保持。这样得到的结果保真度极高,但它天生假设观测无噪声,而且需要设计零空间分解,对非线性前向算子处理起来很别扭。

5.2 DPS在其中的位置

DPS则属于“用点估计做近似后验采样”的一类,它的特点可以总结为通用、简单、但对超参数敏感。它不需要设计专门的投影算子,只需要前向算子和梯度;能处理有噪声观测;能从一个后验里采样多个不同结果,保持生成多样性,这是在图像修复、人脸重建这类任务里的重要优势。

代价是每次迭代都要做一次反向传播,算力开销显著大于DDNM和Rediff这类只需要前向计算的方法。而且因为引导是有偏近似,它生成的结果在保真度(和观测匹配程度)上可能不如DDNM,在视觉真实性上又会因为ζ调不好而翻车。

下面是我在不同维度上对这几类方法的粗略对比:

对比维度 | DPS | Score-SDE | Rediff | DDNM 处理有噪观测 | 支持 | 支持 | 勉强 | 无噪假设 非线性前向 | 只要可微即可 | 需要SDE适配 | 难实现 | 难实现 多样性采样 | 强 | 强 | 弱 | 弱 计算开销 | 高 | 高 | 中 | 低 调参难度 | 高 | 高 | 中 | 低

5.3 怎么选方法

我的建议是看你的任务属性。如果观测噪声比较明显,前向算子是线性的,而且你希望输出可信度优先于保真度,DPS和Score-SDE都值得试。如果前向算子是非线性的,DPS因为“只需要可微”这个特点,实现成本最低。如果观测很干净,且保真度直接决定结果能否使用,DDNM值得优先尝试。如果只是想快速生成一个看起来合理的重建结果,并没有严格的工程保真要求,DPS既快又省心。

另外还有一个务实维度:如果项目需要向别人展示可解释的迭代过程,DPS有天然的叙事优势。每一次采样迭代里,都能看到 x0_hat 从“一团噪声里的轮廓”逐步清晰化,这种渐进式的图像生成比黑盒输出更有说服力。

6. 复现DPS最值得注意的坑:步长调参、Time Travel与指标陷阱

6.1 ζ的动态调整策略

前面说过固定ζ容易出现后期过度引导的问题。我在实验里发现,一个简单有效的改进是让ζ随采样时间衰减。早期去噪步中 x0_hat 很模糊,引导梯度噪声大,ζ应该小一些;中期结构逐渐显现,ζ可以加大;后期高频细节占比高,ζ又需要回落到比较小的值。

我常用的调度是分段常数或线性衰减:

zeta_t = zeta * (0.5 + 0.5 * t / T) # 或者线性衰减到0.25倍

这个技巧并没有在DPS原始思路里详细讨论,但实测对高噪声任务有明显帮助。本质是去平衡“先验可信度”和“观测可信度”,早期先验不可靠时少信观测,后期图像结构基本锁定后也少信观测以免注入噪声。当然,这个调度函数本身也是一组超参数,需要在验证集上试几个组合。

6.2 Time Travel:把一条路走出多条路的效果

另一个经常被实验者用到、但很少有人系统性解释的工具是Time Travel。具体来说,就是在反向采样过程中,当 t 降到某个阈值时,把状态“打回”更早的噪声水平,重新再走一遍。DPS每次重走的过程中,因为随机噪声不同,x0_hat 也会略有不同,相当于在一次采样里多次让扩散模型重新“思考”观测约束。

我在超分实验里使用两次Time Travel,PSNR大约能提升0.5到一个分贝,感知质量提升更明显。代价是采样时间翻了2到3倍。这提示了一个现象:DPS的采样质量不只是由ζ决定,还由采样轨迹的“长度”决定。多走几遍,引导信息被分摊到更多差点上,整体约束更稳定。如果项目对推理时间敏感,可以先不走Time Travel,把ζ调对之后再用它做质量增强。

6.3 评估指标为什么容易骗你

DPS场景下,我最想提醒的一点是:不要只用PSNR评价模型好坏。PSNR对DPS的惩罚是不公平的,因为DPS生成的是分布样本,它的任何一次采样都不一定和Ground Truth像素对齐。同一个观测下采样两次,得到的看起来合理但细节不同的图,像素级差异会很大。用PSNR评估这类方法,容易得出“差很远”的结论。

我自己的评估习惯是把指标分成两组。第一组是保真度指标:PSNR、SSIM,用于衡量和观测/参考图的一致性。第二组是感知质量指标:LPIPS、FID,用于评估生成图像的纹理真实性。DPS的典型表现是第一组中等、第二组非常靠前。如果一篇工作只汇报PSNR,你心里要有数,它在真实图像细节上的优势可能被这个指标掩盖了。

还有一个容易踩的坑是多样性评估。DPS主打后验采样,就要看多个样本的方差。有些复现为了追求PSNR,故意降低ζ、减少随机性,导致所有样本几乎一样,这等于放弃了DPS的核心价值。真正使用DPS做重建的系统,应该允许用户拿到多个合理候选,再由下游任务进行选择,这才是生成先验和判别先验的本质区别。

6.4 我的复现心得

最后分享一点个人经验。最让我印象深刻的DPS实验是一次8倍超分,输入是一张模糊到几乎认不出人脸的图。使用DPS跑出来的多个采样结果,脸型轮廓高度一致,但每张图的皮肤纹理、眼睛细节都不一样,可每张单独看都自然得像是真实拍摄的。反观一个经典判别式方案给的结果,脸部细节平滑到“塑料感”非常明显。那一刻我真正理解了“生成先验”的意义:它不只是补全信息,而是给你一个符合该领域分布的结果。

如果你想上手DPS,我的建议是不要一上来就做复杂任务,先在一个公开数据集上把超分场景跑通,把“观察x0_hat的变化轨迹”变成一种直觉,再逐步增加噪声、改变前向算子类型。最花时间的永远不是写代码,而是调ζ、确认梯度方向、判断指标结论是否和视觉一致。这些坑踩过一轮之后,你再回头看DPS的实现,会发现它本质上只是“一个先验很强大的反向扩散循环,加了一行很简单的观测引导”,但这一行引导,恰好就是把生成模型变成成像工具的关键一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询