DDPM代码详解(一)

本文详细介绍了Diffusion概率模型(DDPM)的代码实现,包括计算步骤、线性Beta序列、GaussianDiffusion类的构造,以及正向过程和逆向生成过程的代码。着重展示了如何通过PyTorch实现DDPM的训练和样本生成算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在Diffusion原理(一),Diffusion原理(二)中分别介绍了Diffusion(DDPM)的前向过程和逆向过程原理。这里将进一步介绍一下DDPM的代码实现.

首先我们从整体来思考一下,DDPM的代码实现,会包含哪些部分。可以利用思维导图大致梳理一下:

接下来根据思维导图来逐个模块实现。

        1. 计算\beta, 我们定义一个函数,来获取每一步的\beta. 在DDPM中,\beta采用的是线性生成方式。每一步的\beta成线性增加的。输入参数timesteps是总的步数。由于默认总步数是1000步,但是为了适配不同的定义,可以自己指定总步数,因此这里就会对每一步的步长进行缩放。具体代码如下:

def linear_beta_schedule(timesteps):
    totalstep = 1000
    value_start = 0.0001
    value_end = 0.02
    scale = totalstep / timesteps
    beta_start = scale * value_start
    beta_end = scale * value_end
    return torch.linspace(beta_start, beta_end, timesteps, dtype=torch.float64)

        2. 根据beta计算其他前向过程的参数。这里我们定义一个dGaussianDiffusion的类。先定义属性,利用这些属性(存放在register_buffer中)来设置这些固定的参数,不会参与参数更新。

class GaussianDiffusion(nn.Module):
    def __init__(self,
                 opts,
                 device,
                 network,
                 min_snr_loss_weight=True):
        super().__init__()

        self.opts = opts
        self.device = device

        self.network = network.to(device)

        # define betas: beta1, beta2, ... beta_n
        beta_schedule_fn = linear_beta_schedule
 

        betas = beta_schedule_fn(self.opts['timesteps'])
        self.num_timesteps = int(betas.shape[0])

        # define alphas
        # get a1, a2, ..., an
        alphas = 1.0 - betas

        alphas_cumprod = torch.cumprod(alphas, dim=0)
        alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.)
        sqrt_recip_alphas = 1.0 / torch.sqrt(alphas)

        register_buffer = lambda name, val: self.register_buffer(name, val.to(torch.float32))

        register_buffer('betas', betas)
        register_buffer('alphas_cumprod', alphas_cumprod)
        register_buffer('alphas_cumprod_prev', alphas_cumprod_prev)
        register_buffer('sqrt_recip_alphas', sqrt_recip_alphas)

        # calculations for diffusion q(x_t | x_{t-1}) and others
        # x_t = sqrt(alphas_cumprod)* x_0 + sqrt(1 - alphas_cumprod) * noise
        register_buffer('sqrt_alphas_cumprod', torch.sqrt(alphas_cumprod))
        register_buffer('sqrt_one_minus_alphas_cumprod', torch.sqrt(1.0 - alphas_cumprod))

        # calculations for posterior q(x_{t - 1} | x_t, x_0)

        posterior_variance = betas * (1.0 - alphas_cumprod_prev) / (1.0 - alphas_cumprod)
        register_buffer('posterior_variance', posterior_variance)

        register_buffer('sqrt_recip_alphas_cumprod', torch.sqrt(1.0 / alphas_cumprod))  # A
        register_buffer('sqrt_recipm1_alphas_cumprod', torch.sqrt(1.0 / alphas_cumprod - 1.0))  # B

        # mu_{t - 1} = mean_coef1 * clip(x_{0}) + mean_coef2 * x_{t}

        register_buffer('posterior_log_variance_clipped', torch.log(posterior_variance.clamp(min=1e-20)))
        register_buffer('posterior_mean_coef1', betas * torch.sqrt(alphas_cumprod_prev) / (1.0 - alphas_cumprod))
        register_buffer('posterior_mean_coef2', (1.0 - alphas_cumprod_prev) * torch.sqrt(alphas) /
                        (1.0 - alphas_cumprod))

        snr = alphas_cumprod / (1.0 - alphas_cumprod)
        maybe_clipped_snr =snr.clone()
        if min_snr_loss_weight:
            maybe_clipped_snr.clamp_(max=self.opts['min_snr_gamma'])

        register_buffer('loss_weight', maybe_clipped_snr / snr)

        self.ModelPrediction = namedtuple('ModelPrediction', ['pred_noise', 'pred_x_start'])

        3. 接下来是正向过程:这里完全按照原理部分的公式来获取对应前向过程t时刻的样本。

    def q_sample(self, x_start, t, noise=None):
        sqrt_alphas_cumprod_t = extract(self.sqrt_alphas_cumprod, t, x_start.shape)
        sqrt_one_minus_alphas_cumprod_t = extract(
            self.sqrt_one_minus_alphas_cumprod, t, x_start.shape
        )

        return sqrt_alphas_cumprod_t * x_start + sqrt_one_minus_alphas_cumprod_t * noise

        其中extract的实现如下,这个代表根据t来抽取对应位置的值。

def extract(tensor, t, x_shape):
    batch_size =t.shape[0]
    out = tensor.gather(-1, t.cpu())
    return out.reshape(batch_size,
                       *((1, ) * (len(x_shape) - 1))).to(t.device)

        比如 sqrt_alphas_cumprod_t 就是X_t = \sqrt{\alpha_t\alpha_{t-1}...\alpha_{1}}X_{0} + \sqrt{1 - \alpha_t\alpha_{t-1}...\alpha_{1}}\epsilon'_{0} 中的\sqrt{\alpha_t\alpha_{t-1}...\alpha_{1}}. sqrt_alphas_cumprod其实是所有从0-T的这些alpha在每个时刻都保存的一个tensor。

4. 训练时求loss。前面已经知道了前向过程,loss计算只拟合噪声,这个时候我们就可以得到直接计算loss了。在DDPM原理介绍中,我们最后推到出了整个loss其实可以表达为公式:

这里就会要求,求得每一步的噪声误差的总和。但是呢,为了如果每一步都去求,那么未免太耗时了。比如说总步长是1000, 那么每一个样本求1000次的loss总和未免太耗费训练时间了。这里为了简便,会对每个训练batch中的每张图,对应在[1, 1000]中随机生成一个步长timestep。比如一个batch有四个样本,每个样本的步长在[1, 1000]中随机生成,比如可以是[50, 100, 94, 786]。具体代码如下所示。

    def p_losses(self, x_start, t, noise=None):
        noise = default(noise, lambda: torch.randn_like(x_start))
        x_t = self.q_sample(x_start=x_start, t=t, noise=noise)
        network_out = self.network(x_t, t)
        target = noise

        if self.opts['loss_type'] == 'huber':
            loss = F.smooth_l1_loss(network_out, target, reduction='none')
        elif self.opts['loss_type'] == 'l1':
            loss = F.l1_loss(network_out, target, reduction='none')
        elif self.opts['loss_type'] == 'l2':
            loss = F.mse_loss(network_out, target, reduction='none')
        else:
            raise NotImplementedError()

        loss = reduce(loss, 'b ... -> b (...)', 'mean')
        loss = loss * extract(self.loss_weight, t, loss.shape)
        return loss.mean()
    
    def forward(self, img):
        b, _, _, _ = img.shape
        t = torch.randint(0, self.num_timesteps, (b,), device=self.device).long()
        return self.p_losses(img, t)

        5. 接下来要实现逆向生成过程:逆向生成过程也是按照前面原理部分的公式得到的,只是把数学公式用代码表达而已。这里的stable_sampling只是为了更好实现逆向生成,做了一些数学公式上的变换。

    @torch.inference_mode()
    def p_sample(self, x, t, t_index):
        betas_t = extract(self.betas, t, x.shape)
        sqrt_one_minus_alphas_cumprod_t = extract(self.sqrt_one_minus_alphas_cumprod, t, x.shape)
        sqrt_recip_alphas_t = extract(self.sqrt_recip_alphas, t, x.shape)
        x_mean = sqrt_recip_alphas_t * (
                x - betas_t * self.network(x, t) / sqrt_one_minus_alphas_cumprod_t
            )

        if t_index == 0:
            return x_mean
        else:
            posterior_variance_t = extract(self.posteroir_variance, t, x.shape)
            noise = torch.randn_like(x)
            return x_mean + torch.sqrt(posterior_variance_t) * noise

        上面的p_sample只是一步的逆向生成,要实现从Xt到X0的生成,需要一个循环,如下所示:

    @torch.inference_mode()
    def p_sample_loop(self, shape, return_all_timesteps=False):
        batch_size = self.opts['sample_batch_size']
        image = torch.randn(shape, device=self.device)
        return_images = [image.cpu().numpy()]

        for i in tqdm(reversed(range(0, self.opts['timesteps'])),
                      desc='sampling loop time step',
                      total=self.opts['timesteps']):
            image = self.p_sample(image, torch.full((batch_size, ), i,
                                                    device=self.device, dtype=torch.long), i)
            if return_all_timesteps:
                return_images.append(image.cpu().numpy())
            else:
                if i == 0:
                    return_images.append(image.cpu().numpy())

        return return_images

        至此,原始DDPM的代码已经实现了,还差一个生成噪声的network的定义,之后将在下一次代码详解中介绍。

<think>嗯,用户想了解DDPM模型的相关信息,包括原理、实现方法和应用场景。首先,我需要回忆DDPM的基本概念。根据提供的引用,DDPM是基于扩散过程的生成模型,灵感来自非平衡热力学。它的核心是通过马尔可夫链逐步添加噪声,然后学习逆向过程去噪生成数据。 接下来,原理部分需要分步骤解释。前向过程是逐步加噪,每次添加高斯噪声,这类似于扩散现象。这里可能需要数学公式,比如使用$q(x_t|x_{t-1})$表示每步的转移。然后逆向过程是学习如何从噪声中恢复数据,这里涉及变分推断,估计条件概率分布。引用中的变分推断和马尔可夫链需要结合起来说明。 实现方法方面,DDPM需要定义时间步的beta调度,比如线性或余弦调度。网络架构通常使用U-Net,结合时间步嵌入。训练过程需要最小化噪声预测的损失函数,这部分可以用均方误差。引用里提到了训练过程的公式分析,可能需要具体说明损失函数的形式。 应用场景方面,用户提供的引用提到高质量图像生成、灵活控制和可解释性。需要列举具体的应用,比如图像合成、风格迁移等,并引用相关引用中的内容。 另外,用户示例中的结构需要遵循,比如分章节,使用LaTeX公式,并在引用后添加标识。要注意所有行内数学表达式用$...$,独立公式用$$...$$,并检查语法正确性。 可能还需要生成相关问题,考虑用户可能进步想知道DDPM与其他模型(如VAE、GAN)的区别,优化方法,或者数学推导细节。需要确保问题相关且基于现有内容。 最后,确保回答结构清晰,逐步引导用户理解,同时保持真实可靠,引用提供的资料。</think>### DDPM模型详解 #### 、基本原理 DDPM(Denoising Diffusion Probabilistic Model)是种基于**扩散过程**的深度生成模型,其核心思想通过**马尔可夫链**逐步向数据添加噪声,再学习逆向过程从噪声中重建数据[^1]。 ##### 1. 前向过程(扩散过程) 定义马尔可夫链逐步添加高斯噪声: $$q(x_{1:T}|x_0) = \prod_{t=1}^T q(x_t|x_{t-1})$$ 其中单步噪声添加公式为: $$q(x_t|x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t}x_{t-1}, \beta_t\mathbf{I})$$ 其中$\beta_t$是预定义的噪声调度参数,控制噪声添加速度[^4]。 ##### 2. 逆向过程(去噪过程) 学习反向分布$p_\theta(x_{t-1}|x_t)$,通过神经网络预测噪声: $$\mu_\theta(x_t,t) = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1-\bar{\alpha}_t}} \epsilon_\theta(x_t,t) \right)$$ 其中$\alpha_t=1-\beta_t$,$\bar{\alpha}_t=\prod_{s=1}^t \alpha_s$,$\epsilon_\theta$是噪声预测网络[^3]。 #### 二、实现方法 ##### 1. 关键组件 - **噪声调度**:采用余弦调度定义$\beta_t$,平衡高频与低频信号处理 - **U-Net架构**:加入时间步嵌入,用于噪声预测 - **损失函数**:简化后的均方误差损失: $$L(\theta) = \mathbb{E}_{t,x_0,\epsilon} \left[ \|\epsilon - \epsilon_\theta(\sqrt{\bar{\alpha}_t}x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon, t)\|^2 \right]$$ ##### 2. 训练流程 ```python # 伪代码示例 for batch in dataset: x0 = sample_real_data() t = uniform_sample(1, T) ε = sample_gaussian_noise() xt = sqrt(α_bar_t)*x0 + sqrt(1-α_bar_t)*ε ε_pred = model(xt, t) loss = mse_loss(ε, ε_pred) update_model(loss) ``` #### 三、应用场景 1. **高质量图像生成**:生成分辨率达1024x1024的逼真图像 2. **医学影像重建**:用于低剂量CT图像去噪 3. **音频合成**:生成自然语音片段 4. **分子设计**:生成新型药物分子结构 #### 四、优势特点 1. **训练稳定性**:相比GAN没有模式崩溃问题 2. **渐进式生成**:可通过调节时间步控制生成过程 3. **理论保障**:基于非平衡热力学的数学框架
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机械系的AI小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值