DDPM详解:理论、损失与架构
我第一次看 DDPM 论文的时候说实话有点懵。什么马尔可夫链、变分下界、重参数技巧… 一套组合拳下来,差点把我劝退。后来才发现,这东西的核心思想其实特别纯粹。你把它拆开看,就俩过程:前向加噪,反向去噪。就这么简单。
咱们先把 DDPM 的骨架搭起来,再往里填肉。
前向扩散过程:给数据“下毒”
这个过程说白了,就是一步步往干净数据里加高斯噪声。加够 T 步,原始数据完全变成一个标准正态分布的白噪声。
你想象一下:一张清晰图片,我加一点噪点,再加一点… 最后啥也看不见了。道理一样。
关键公式定义:
- q(xₜ | xₜ₋₁) = N(xₜ; √(1-βₜ) · xₜ₋₁, βₜI) → 单步加噪过程
- q(xₜ | x₀) = N(xₜ; √ᾱₜ · x₀, (1 - ᾱₜ)I) → 直接从 x₀ 跳到 xₜ,不用一步步走
这里 ᾱₜ = ∏(1-βₛ) 是累计乘积。βₜ 是噪声调度表,控制每一步加多少噪声。我记得最早一批工作里,βₜ 从 0.0001 线性增加到 0.02。后来发现 cosine 调度更好用,我在分子生成项目里试过,确实收敛更稳。
反向去噪过程:从噪声中“复活”数据
反向过程就是学一个神经网络 p_θ,把纯噪声一步步变回干净数据。每一步都在预测:给定 xₜ,xₜ₋₁ 应该是什么。
公式长这样:
p_θ(xₜ₋₁|xₜ) = N(xₜ₋₁; μ_θ(xₜ, t), Σ_θ(xₜ, t))
但现实中,我们一般不直接预测 xₜ₋₁ 的分布参数。Ho 他们在 DDPM 里做了一个聪明的简化:固定方差,只让网络预测 均值。更狠的是,预测均值可以等价转换成预测噪声 ε。
嗯,这里要注意:预测噪声比预测原始数据更稳定。我刚开始复现时,非要预测 x₀ 试一下,结果模型训练怎么都不收敛。改成预测噪声,效果立竿见影。
损失函数:一个简化的变分下界
原论文推导的损失函数很复杂,有很多 KL 散度项。但作者发现把这些项去掉,只保留一个简单的 均方误差,效果反而最好。
核心损失函数:
L_simple = E_{t, x₀, ε} [ || ε - ε_θ(√ᾱₜ · x₀ + √(1-ᾱₜ) · ε, t) ||² ]
翻译成人话:
1. 随机采样一个时间步 t
2. 随机采样一个高斯噪声 ε
3. 用公式生成 xₜ
4. 让网络预测这个噪声 ε
5. 算预测噪声和真实噪声的 MSE
6. 反向传播更新网络
我在项目中遇到过一个问题:直接拿标准 MSE 训练,分子生成的质量总差一口气。后来我把损失函数里加了 坐标加权——也就是对原子位置的损失权重设得高一些——效果才提上来。所以别死套公式,懂原理,灵活改。
为什么不预测 x₀ 而预测噪声?
因为预测噪声的梯度更平滑。你想想,x₀ 的尺度一直在变,而噪声永远是标准正态分布,网络更容易学。
采样过程:从噪声逆袭成数据
训练完模型,怎么生成样本?采样过程就是把反向去噪从头到尾跑一遍。伪码如下:
1. x_T ~ N(0, I) // 从纯噪声开始
2. for t = T 到 1:
a. z ~ N(0, I) // 如果 t > 1
b. xₜ₋₁ = 1/√αₜ · (xₜ - (1-αₜ)/√(1-ᾱₜ) · ε_θ(xₜ, t)) + σₜ · z
3. 返回 x₀
这里面有三个要点:
- 系数缩放:每一步都有个 1/√αₜ 因子,把去噪后的分布拉回正确的尺度
- 随机项 z:除了最后一步,每一步都要加一点随机噪声,保证生成多样性
- σₜ 控制去噪随机性:默认 σₜ² = βₜ,也可以设成 0
我早期调试时犯过一个错:忘了给中间步加 z,结果生成的结果全是一模一样的分子。折腾了两天才发现,就是缺了这步噪声注入。所以,每一步的 z 不是可选项,是必需品。
采样速度 vs 质量权衡
T=1000 步生成的样本质量最好,但慢得让人抓狂。后来有不少工作(DDIM、DPM-solver)能把步数降到 50-100,速度提升 10-20 倍。不过这是后话,你现在先把标准流程跑通。
模型架构:U-Net 加时间嵌入
DDPM 官方实现用了 U-Net 作为骨干网络。为什么选 U-Net?因为 U-Net 天然有编码器-解码器结构,能保留多尺度信息,这对恢复分子结构里的局部和全局特征都很有用。
核心设计要素:
- 时间步嵌入 (Time Embedding):把 t 编码成一个向量,通过自适应层归一化或加法注入到各层
- 残差块 + 自注意力:每个下采样/上采样块包含残差连接和空间注意力
- 跳跃连接 (Skip Connection):编码器每层输出直接拼接到对应解码器层
对于分子生成,输入通常是 原子类型 + 坐标 的张量。我习惯把分子表示成 3D 网格或者点云,U-Net 里的卷积层对这两种输入形式都适用。
架构参数示例(参考 DDPM 原论文):
- 通道数基数:128,然后 128→256→512→1024
- 每个分辨率 2 个残差块
- 自注意力层放在 16×16 特征图上
- Dropout: 0.1
- 总共参数量约 35M
不过老实说,这个架构并不是不能改的。我最近做分子构象生成,发现把自注意力换成 graph transformer,模型对分子拓扑的建模能力更强。所以,U-Net 是个好起点,但也只是起点。
下面这张图帮你看清整体流程:
总结一下 DDPM 最让我舒服的地方:原理清晰,实现简单,效果惊艳。你只要把加噪公式和去噪损失写对,剩下的交给数据和算力。下手的第一个项目,我建议你跑通一个 2D 分子生成,看看模型能不能从噪声里画出分子结构来。那个过程真的很爽。