核心思想:把扩散模型的骨干网络从 U-Net 换成 Transformer,在 latent patches 上做 self-attention。
传统扩散模型的骨干:U-Net
flowchart TD subgraph Encoder["编码器 (下采样)"] E1["Conv Block 1<br>256×256, 64ch"] E2["Conv Block 2<br>128×128, 128ch"] E3["Conv Block 3<br>64×64, 256ch"] E4["Conv Block 4<br>32×32, 512ch"] end B["Bottleneck<br>16×16, 1024ch"] subgraph Decoder["解码器 (上采样)"] D4["Conv Block 5<br>32×32, 512ch"] D3["Conv Block 6<br>64×64, 256ch"] D2["Conv Block 7<br>128×128, 128ch"] D1["Conv Block 8<br>256×256, 64ch"] end Input["输入图像<br>256×256"] --> E1 E1 -->|"MaxPool ↓2"| E2 E2 -->|"MaxPool ↓2"| E3 E3 -->|"MaxPool ↓2"| E4 E4 -->|"MaxPool ↓2"| B B -->|"UpConv ↑2"| D4 D4 -->|"UpConv ↑2"| D3 D3 -->|"UpConv ↑2"| D2 D2 -->|"UpConv ↑2"| D1 D1 --> Output["输出<br>256×256"] E4 -.->|"Skip Connection"| D4 E3 -.->|"Skip Connection"| D3 E2 -.->|"Skip Connection"| D2 E1 -.->|"Skip Connection"| D1
-
编码器:一路下采样,提取越来越抽象的特征
-
解码器:一路上采样,恢复分辨率
-
Skip Connection:跳跃连接,保留细节信息
-
整体形状像字母 U,所以叫 U-Net
DiT 的做法:用 Transformer 替代 U-Net
flowchart TD A["带噪 Latent<br>(从 VAE 编码器得到)"] --> B["切成 Patches<br>(类似 ViT)"] --> C["Patch Embedding<br>+ 位置编码"] C --> D["Transformer Block ×N<br>(Self-Attention + FFN)"] T["条件输入<br>(timestep, class label)"] --> D D --> E["Linear Decoder<br>还原 patch → latent"] E --> F["去噪后的 Latent"] F --> G["VAE 解码器 → 图像"]
DiT vs U-Net 对比
| U-Net(CNN) | DiT(Transformer) | |
|---|---|---|
| 感受野 | 局部,靠堆层数扩大 | 全局,self-attention 天然看全图 |
| 可扩展性 | 较差,加大模型收益递减 | 好,scaling law 明显 |
| 评估指标 | FID 2~3 | FID 2.27(ImageNet 256×256 SOTA) |
| 核心优势 | 成熟稳定 | 模型越大效果越好,scalability 强 |
Diffusion 数学公式拆解
式子 1:前向加噪过程
= ” 服从均值为、方差为 的高斯分布” ✅
给定原始干净图像 ,第 步的带噪图像 服从一个高斯分布:
-
均值 = (原图被缩小了一点)
-
方差 = (加了一些噪声)
是一个从 1 逐渐减小到 ~0 的系数:
-
t 很小时: → 均值 ≈ ,方差 ≈ 0 → 图像几乎没变
-
t 很大时: → 均值 ≈ 0,方差 ≈ 1 → 几乎是纯噪声
直觉: 控制”原图还剩多少”, 控制”噪声加了多少”,两者加起来恒为 1。
# 式子 1:采样噪声
epsilon = torch.randn_like(x0) # 采样 ε ~ N(0, I),shape 跟 x0 一样-
📌 和 是怎么来的?
先定义一组 (每一步加多少噪声),然后递推:
是所有 的累积乘积。因为每个 ,越乘越小。
举个具体例子(线性 schedule,总步数 , 从 0.0001 线性增长到 0.02):
(累乘) 1 0.0001 0.9999 ≈ 0.9999 100 0.003 0.997 ≈ 0.74 500 0.01 0.99 ≈ 0.05 1000 0.02 0.98 ≈ 0.00003 常见的 noise schedule:
-
Linear: 线性增长(DDPM 原版用的)
-
Cosine: 按余弦曲线衰减,前期衰减慢、后期快,效果通常更好(Improved DDPM 用的)
是超参数,不是模型学出来的。训练前把整条 schedule 算好,存成一个长度为 的数组,训练时直接查表用 ✅
-
式子 2:重参数化采样
式子 1 的等价操作形式 —— 直接告诉你怎么算:
-
先采一个跟图像同样大小的随机噪声
-
用 缩放原图(信号变弱)
-
用 缩放噪声(噪声变强)
-
两个加起来 = 第 步的带噪图像
这就是重参数化技巧(reparameterization trick):把”从分布采样”变成”确定性运算 + 一个固定的随机变量”,这样就能对 求梯度了(反向传播需要)。
# 式子 2:加噪 = 缩放原图 + 缩放噪声
x_t = sqrt_alpha_bar_t * x0 + sqrt_one_minus_alpha_bar_t * epsilon详细训练过程(原文拆解)
Diffusion 模型训练的是一个反转前向加噪过程的反向过程:
神经网络用来预测 的统计量(均值 和方差 )。
训练目标的推导:
反向过程模型通过 的对数似然的 变分下界(variational lower bound, VLB) 来训练,简化后为:
其中 是前向过程的后验分布(已知 和 时, 的真实分布)。因为 和 都是高斯分布,它们的 KL 散度可以用均值和方差直接计算。
-
📝 VLB 是怎么推导出来的?
目标:最大化数据的对数似然 ,但它没法直接算。
Step 1:引入边缘化
把所有中间步骤 引入:
这个积分维度太高,算不了。
Step 2:用 Jensen 不等式取下界
引入前向过程 作为辅助分布:
由 Jensen 不等式( 是凹函数):
这就是变分下界!因为 Jensen 不等式“压低”了真实值,所以是 lower bound。
Step 3:展开并分解
把 和 分别展开为每一步的乘积:
代入 VLB 并整理,最终得到:
每项的含义:
-
重建项 :模型从 还原 的能力
-
KL 散度项 :模型预测的 和真实后验 的差距
-
最后一项: 和 的差距, 足够大时这项趋近 0
Step 4:从 KL 到 MSE
因为 和 都是高斯分布,它们的 KL 散度可以用均值差的平方表示。再把均值 重参数化为噪声预测 ,就得到了:
从对数似然 → Jensen 不等式取下界 → 展开为 KL 散度之和 → 高斯 KL 简化为均值差 → 重参数化为噪声预测 MSE。这就是从 VLB 到 的完整链路 ✅
-
从 KL 散度到 MSE:
通过把 重参数化为噪声预测网络 ,模型可以用简单的 MSE 训练:
两个输出头的训练策略(沿用 Improved DDPM [36] 的做法):
-
Noise 头 :用 (MSE)训练 → 更稳定
-
方差头 :用完整的 (包含 项)训练 → 学习每步的不确定性
推理(采样生成):
训练完成后,从纯噪声开始采样:
-
初始化
-
逐步采样 (同样用重参数化技巧)
-
重复直到
式子 3:反向去噪过程
模型要学的就是这个 —— 给你一个噪声图 ,预测”退回一步”后 的分布:
-
:神经网络预测的均值(“我觉得去掉噪声之后应该长这样”)
-
:神经网络预测的方差(“我有多确定”)
这就是 DiT 输出的 Noise + Σ 的来源!
式子 4:训练损失(简化版)
训练目标超级简单:
-
取一张干净图 ,随机采噪声 ,用式子 2 算出
-
把 喂给模型,让它猜”加了什么噪声” → 输出
-
跟真实噪声 算 MSE(均方误差)
-
反向传播,更新参数
本质上就是一个预测噪声 → 跟真实噪声对比 → 缩小差距的回归任务。
串起来看
训练时:x₀ → 加噪得到 xₜ → 模型预测噪声 εθ → 跟真实 εₜ 算 loss → 更新参数
推理时:从纯噪声 x_T 开始 → 模型预测噪声 → 减掉 → 得到 x_{T-1} → 重复 → x₀概率论的部分说白了就是:用高斯分布建模每一步的”不确定性”,让加噪和去噪都有数学保证。但实际训练就是 MSE 回归,没那么玄 ✅