Diffusion|DDPM 理解、数学、代码
Diffusion
论文:Denoising Diffusion Probabilistic Models
参考博客;参考 paddle 版本代码: aistudio 实践链接
该文章主要对 DDPM 论文中的公式进行小白推导,并根据 ppdiffuser
进行 DDPM 探索。读者能够对论文中的大部分公式如何得来,用在了什么地方有初步的了解。
本文将包括以下部分:
- DDPM 总览
- Forward process: 包括论文中公式推导,以及其在 ppdiffusor 中代码参考
- Reverse process: 包括论文中公式推导,以及其在 ppdiffusor 中代码参考
- 优化目标推导:包括论文中公式推导,以及简单的伪代码描述
- 探索与思考:通过打印,修改 ppdiffusor ddpm 代码,探索 DDPM 模型。
DDPM 总览
扩散模型在 2015 年已经被提出(Deep Unsupervised Learning using Nonequilibrium Thermodynamics),而 DDPM 将扩散模型应用在了图像生成领域上。
DDPM 的大致思想是:用 AI 构建一个模型,相比于一步到位生成图像,我们让这个模型每次生成一小步,经过 T 步后,图像就完成了。

如上图,给定原图片
而后我们训练模型,使其能根据带有噪声的图片
Forward Process
给定原图片
其中
因为
因此得出论文的前向扩散公式
在该步骤中,[1e-4, 0.02]
之间,随着时间步 t
线性变换,这也极大的简化了训练时的优化目标推理。此外,DDPM 设置了 T=1000
,在加噪 1000 步之后,图像就完全变成了无信号的电视画面。
在 ppdiffusor 中,公式 DDPMScheduler.add_noise()
。
如果你参考了 DDPM 官方文档,那么公式 q_sample()
函数。
Reverse process
Reverse process 的目的是 能根据带有噪声的图片
首先我们能够通过
因此图像采样过程可以定义为
公式推导 公式推导
把上式对应到正态分布公式当中,可以得到论文中的公式
公式推导
由于在采样过程中我们不知道真实的
公式推导
Reverse process 该部分对应的为 ppdiffusor.DDPMScheduler.step
。DDPM 论文提供的 Tensorflow 版代码链接为 link。
- 上文公式 (5) 在官方代码中对应
predict_start_from_noise
,在 paddle 中对应ppdiffusor.DDPMScheduler.step
的
pred_original_sample = (sample - beta_prod_t**(0.5) * model_output) / alpha_prod_t**(0.5)
- 上文公式
在官方代码中对应q_posterior_mean_variance
,在ppdiffusor.DDPMScheduler.step
对应。
pred_prev_sample = pred_original_sample_coeff * pred_original_sample + current_sample_coeff * sample
细心的朋友们会发现官方给的代码中,sampling 方式分为:
但其实这等价于:
- 上文公式
上文公式 ppdiffusor.DDPMScheduler.step
中将采样过程全部替换为:
prev_sample = (sample - model_output * self.betas[t]/(1-self.alphas_cumprod[t]) **0.5)/self.alphas[t]** (
0.5) + variance
那么结果会是一样的,我们将在之后的代码探索中尝试验证它。
优化目标
在训练过程中,我们只需要对每个 pred_noise
参数,用于选择模型的预测目标为噪声
从论文的变分边界角度出发
我们的目标是获得
公式推导
因此我们得到了论文中的公式
公式推导
因此我们得到了论文中的公式
公式推导
其中
因此:
所以我们得到了论文中的公式
由于我们在前面假设了
公式推导
因此我们得到了论文中的公式
从优化像素的角度出发
生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼 从优化像素的角度出发进行了推理,得到了与论文相似的优化函数形式。
大致思路是直接对图像进行优化:
由于在预测时候我们不知道原先噪声,因此使用预测的噪声
当然以上只是进行了大致流程概括,实际推理过程还需要考虑方差过大等细节问题,详细请参考 生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼。
模型优化过程
根据上述的公式,我们只需要建立模型,对噪声进行拟合即可。以下伪代码参考了 DDPM 论文提供的代码,展示 DDPM 优化过程逻辑:
def train_losses(self, model, x_start, t):
noise = torch.randn_like(x_start)
x_noisy = self.q_sample(x_start, t, noise=noise)
predicted_noise = model(x_noisy, t)
loss = F.mse_loss(noise, predicted_noise)
# 部分网友提到此处使用 mse 可能导致 loss 太小,在低精度训练情况下,模型先收敛后发散
return loss
DDPM 论文中采用的 model 为 UNET(并做了一些优化配置),我们不展开讨论。其中
for epoch in range(epochs):
for step, (images, labels) in enumerate(train_loader):
optimizer.zero_grad()
batch_size = images.shape[0]
images = images.to(device)
# sample t uniformally for every example in the batch
t = torch.randint(0, timesteps, (batch_size,), device=device).long()
loss = gaussian_diffusion.train_losses(model, images, t)
if step % 200 == 0:
print("Loss:", loss.item())
loss.backward()
optimizer.step()
探索与思考
为什么 DDPM 效果更好?
笔者猜想是否因为优化目标从图片像素便到了噪声,优化目标变小,更好拟合了??此外,DDPM 相比于单步的 VAE 效果更好,可能因为:
VAE 同样假设建模对象符合正态分布,对于微小变化来说,可以用正态分布足够近似地建模,类似于曲线在小范围内可以用直线近似,多步分解就有点像用分段线性函数拟合复杂曲线,因此理论上可以突破传统单步 VAE 的拟合能力限制。 -- 引用来源 生成扩散模型漫谈(二):DDPM = 自回归式 VAE
代码(torch 版本)
参考代码 TF-DDPM torch-DDPM :
其中函数分别以及对应的公式:
q_sample
对应本文公式 : .predict_start_from_noise
对应本文公式 : .q_posterior_mean_variance
对应本文公式 :
p_mean_variance
对应本文公式 .p_sample
对应p_mean_variance
+ 本文公式 .
细心的朋友们会发现官方给的代码中,sampling 方式分为:
但其实这等价于:
经过测试,将 p_sample
部分的代码换成上面这个公式后,采样生成图片的结果是一样的。
模型方面 DDPM 采用了 UNET 作为 backbone,在传播过程中加入了三角函数位置编码,用于传递采样步骤 [-1, 1]
的区间进行模型学习,在预测编码的时候映射回到 [0, 255]
。
此外论文中的 UNET 还加入了 attention 等操作,能够提高打榜分数,但如果采用基础的自编码器效果也是够好的。
训练过程
根据官方的代码,优化时直接对噪声进行优化,即:
def train_losses(self, model, x_start, t):
noise = torch.randn_like(x_start)
x_noisy = self.q_sample(x_start, t, noise=noise)
predicted_noise = model(x_noisy, t)
loss = F.mse_loss(noise, predicted_noise)
# 部分网友提到此处使用 mse 可能导致 loss 太小,在低精度训练情况下,模型先收敛后发散
return loss
其中
for epoch in range(epochs):
for step, (images, labels) in enumerate(train_loader):
optimizer.zero_grad()
batch_size = images.shape[0]
images = images.to(device)
# sample t uniformally for every example in the batch
t = torch.randint(0, timesteps, (batch_size,), device=device).long()
loss = gaussian_diffusion.train_losses(model, images, t)
if step % 200 == 0:
print("Loss:", loss.item())
loss.backward()
optimizer.step()
代码(ppdiffuser 版本)
查看采样过程中的渐变图片
我们需要在 DDPMScheduler.step
中,将 prev_sample
打印出来,首先运行一个图片采样过程:
import sys
sys.path.append("ppdiffusers")
sys.path.append("ppdiffusers/ppdiffusers")
from ppdiffusers import DDPMPipeline
# 加载模型和 scheduler
pipe = DDPMPipeline.from_pretrained("google/ddpm-celebahq-256")
pipe.scheduler.config.clip_sample =False
# 执行 pipeline 进行推理
output = pipe(seed=777)
images = output[0].images
image = images[0]
# 保存图片
all_images = output[1] # 保存了所有预测过程中的 x_t
all_x_0 = output[2] # 保存了所有预测过程中的 x_0,参考本文公式 5
image.show()
我们打印所有过程图片
from matplotlib import pyplot as plt
plt.figure(figsize=(10,5))
count = 0
for i in range(1,1000,50):
img = all_images[i][0].resize((64,64))
count += 1
plt.subplot(5,10,count)
plt.imshow(img)
plt.axis("off")
plt.show()

接下来我们打印所有中间预测过的
plt.figure(figsize=(10,5))
count = 0
for i in range(1,1000,50):
img = all_x_0[i][0].resize((64,64))
count += 1
plt.subplot(5,10,count)
plt.imshow(img)
plt.axis("off")
plt.show()

验证将
替换为:
后的结果(参考本文 Reverse process 部分)
将 ppdiffusers/ppdiffusers/schedulers/DDPMScheduler.step
中 pred_prev_sample
预测方式改为
pred_prev_sample = (sample - model_output * self.betas[t]/(1-self.alphas_cumprod[t]) **0.5)/self.alphas[t]** (
0.5) + variance
得出与原先相近的图片。由于采样过程中存在对预测的 DDPMScheduler
中的 config.clip_sample
参数)。因此两者在代码上来说,并不是完全等价的。这个影响在 DDIM (DENOISING DIFFUSION IMPLICIT MODELS)中会相对严重,笔者将在下一个笔记中一起来探讨 DDIM。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
2021-07-27 NLP(三十):BertForSequenceClassification:Kaggle的bert文本分类,基于transformers的BERT分类
2021-07-27 NLP(二十九):BertForSequenceClassification的新闻标题分类,基于pytorch_pretrained_bert
2021-07-27 NLP(二十八):BertForSequenceClassification进行文本分类,基于transformers