资讯动态

【生成模型】DDPM扩散模型:从数学原理到PyTorch实战

发布时间:2026/8/24 4:04:09 来源:尧图企业网站定制
1. 扩散模型的前世今生我第一次接触DDPM是在2021年做图像生成项目时当时被它先破坏再重建的思路惊艳到了。想象一下你有一幅名画每天往上面撒一点盐直到完全看不清原貌——这就是前向扩散过程。神奇的是模型能学会如何一步步把盐粒去掉还原出原始画作传统生成模型如GAN和VAE各有痛点GAN训练不稳定VAE生成质量欠佳。而扩散模型通过渐进式去噪的思路在图像质量上实现了突破。2020年Ho等人的论文《Denoising Diffusion Probabilistic Models》正式提出DDPM框架其核心是把数据生成建模为一个马尔可夫链的逆过程。实际项目中我发现相比GAN那种一步到位的生成方式扩散模型更像是个精益求精的工匠——它通过几十甚至上千步的精细调整逐步雕琢出完美结果。这种特性让它在图像生成质量上远超前辈OpenAI的DALL·E 2和Google的Imagen都采用了这种技术路线。2. 数学原理拆解2.1 前向扩散有序的破坏前向过程就像把一杯清水慢慢滴入墨水。定义q(xₜ|xₜ₋₁)为在时间步t加入噪声的转移概率def forward_process(x0, t, betas): 前向扩散过程 Args: x0: 原始图像 [B,C,H,W] t: 时间步 [B,] betas: 噪声调度表 [T,] Returns: xt: 加噪后的图像 noise: 实际添加的噪声 alphas 1 - betas alpha_bars torch.cumprod(alphas, dim0) sqrt_alpha_bar torch.sqrt(alpha_bars[t])[:,None,None,None] sqrt_one_minus_alpha_bar torch.sqrt(1 - alpha_bars[t])[:,None,None,None] noise torch.randn_like(x0) xt sqrt_alpha_bar * x0 sqrt_one_minus_alpha_bar * noise return xt, noise这里有个关键技巧任意时刻的xₜ可以直接从x₀计算得到不必逐步加噪这大大提高了训练效率。βₜ是噪声调度参数通常从β₁1e-4线性增长到β_T0.02。2.2 逆向过程智能重建逆向过程pθ(xₜ₋₁|xₜ)是模型需要学习的核心。根据论文推导当βₜ足够小时逆过程也服从高斯分布q(xₜ₋₁|xₜ,x₀) N(xₜ₋₁; μ̃ₜ(xₜ,x₀), β̃ₜI)其中均值μ̃ₜ和方差β̃ₜ都可以解析表示。在实际实现中我们让神经网络预测噪声εθ(xₜ,t)然后通过重参数化技巧得到均值def reverse_process(xt, t, model): 逆向去噪过程 Args: xt: 噪声图像 [B,C,H,W] t: 时间步 [B,] model: 噪声预测模型 Returns: x_prev: 去噪后的图像 # 预测噪声 pred_noise model(xt, t) # 计算均值方差 alpha_t 1 - betas[t] alpha_bar_t torch.cumprod(alphas, dim0)[t] beta_t betas[t] sqrt_alpha_t torch.sqrt(alpha_t) sqrt_one_minus_alpha_bar_t torch.sqrt(1 - alpha_bar_t) # 重参数化计算 mean (xt - beta_t/sqrt_one_minus_alpha_bar_t * pred_noise) / sqrt_alpha_t variance beta_t # 采样 if t 0: return mean else: noise torch.randn_like(xt) return mean torch.sqrt(variance) * noise3. PyTorch实战指南3.1 模型架构设计DDPM通常采用U-Net结构我在项目中对其进行了改进class TimeEmbedding(nn.Module): def __init__(self, dim): super().__init__() self.dim dim half_dim dim // 2 emb math.log(10000) / (half_dim - 1) emb torch.exp(torch.arange(half_dim) * -emb) self.register_buffer(emb, emb) def forward(self, t): emb t[:, None] * self.emb[None, :] return torch.cat((emb.sin(), emb.cos()), dim-1) class UNetBlock(nn.Module): def __init__(self, in_c, out_c, time_emb_dim): super().__init__() self.time_mlp nn.Sequential( nn.SiLU(), nn.Linear(time_emb_dim, out_c) ) self.conv nn.Sequential( nn.Conv2d(in_c, out_c, 3, padding1), nn.BatchNorm2d(out_c), nn.SiLU(), nn.Conv2d(out_c, out_c, 3, padding1), nn.BatchNorm2d(out_c), nn.SiLU() ) def forward(self, x, t_emb): h self.conv(x) t_emb self.time_mlp(t_emb) return h t_emb[:,:,None,None]关键点在于时间步通过正弦位置编码嵌入网络每个残差块都接收时间嵌入信息使用SiLU激活函数Swish提升梯度流动3.2 训练流程详解训练时我们发现三个调优技巧特别有效噪声调度优化余弦调度比线性调度效果更好混合损失函数L1L2损失组合EMA模型使用滑动平均模型稳定训练def train_step(model, x0, optimizer, loss_fn): model.train() # 随机采样时间步 t torch.randint(0, T, (x0.size(0),), devicedevice) # 前向加噪 xt, noise forward_process(x0, t) # 预测噪声 pred_noise model(xt, t) # 计算损失 loss loss_fn(pred_noise, noise) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() # 更新EMA模型 update_ema(model, ema_model) return loss.item()4. 采样生成的艺术采样过程是从纯噪声逐步去噪的迭代过程torch.no_grad() def sample(model, num_samples): model.eval() x torch.randn((num_samples, 3, 32, 32), devicedevice) for t in reversed(range(T)): t_batch torch.full((num_samples,), t, devicedevice) x reverse_process(x, t_batch, model) # 最后一步不加噪声 if t 0: noise torch.randn_like(x) x x torch.sqrt(betas[t]) * noise return x.clamp(-1, 1)实际应用中我们可以通过调节采样步数在速度和质量间权衡。50步的快速采样和1000步的精细采样效果差异明显——前者适合实时应用后者适合追求极致质量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价