imtoken2.9.4|ddim医学上是什么
imtoken2.9.4|ddim医学上是什么
心梗五项中ddim是什么
中ddim是什么百度健康百度首页提示:本内容仅作参考,不能代替面诊,如有不适请尽快线下就医心梗五项中ddim是什么石波副主任医师周围血管科中国中医科学院西苑医院三甲百度健康内容审核团队优选心梗五项中的ddim即D-Dimer,交联型纤维蛋白降解产物D-D二聚体,是血栓性疾病检查的重要指标。心梗五项是指肌钙蛋白、肌红蛋白、B型钠尿肽、肌酸激酶同工酶、交联型纤维蛋白降解产物D-D二聚体。而ddim通常通过检测血液中D二聚体的含量,与标准值进行对比,是来检测病人血栓形成的重要分子标志物。D-二聚体测定可用于诊断弥漫性血管内凝血(DIC)、筛查深静脉血栓形成、判断脑血管疾病、诊断肝脏疾病及恶性肿瘤预后判断,以及检测溶栓治疗效果。心梗五项检查,主要是抽血检查,急性心肌梗死是冠状动脉急性、持续性的缺血缺氧,导致部分心肌缺血坏死,是冠心病最严重的类型之一,临床上常抽血做心梗五项检查协助诊断,如果抽血检查超过正常值,结合临床症状和心电图检查,心脏彩超检查,就可以确诊是不是心肌梗塞。一般情况下,心梗病人需要定期进行检查,以便准确检测机体变化,预防并发症。并且应遵循医嘱避免过度劳累、适度锻炼、保持平和扩散模型之DDIM - 知乎
扩散模型之DDIM - 知乎首发于机器学习算法工程师切换模式写文章登录/注册扩散模型之DDIM小小将人工智能话题下的优秀答主 “What I cannot create, I do not understand.” -- Richard Feynman 上一篇文章扩散模型之DDPM介绍了经典扩散模型DDPM的原理和实现,对于扩散模型来说,一个最大的缺点是需要设置较长的扩散步数才能得到好的效果,这导致了生成样本的速度较慢,比如扩散步数为1000的话,那么生成一个样本就要模型推理1000次。这篇文章我们将介绍另外一种扩散模型DDIM(Denoising Diffusion Implicit Models),DDIM和DDPM有相同的训练目标,但是它不再限制扩散过程必须是一个马尔卡夫链,这使得DDIM可以采用更小的采样步数来加速生成过程,DDIM的另外是一个特点是从一个随机噪音生成样本的过程是一个确定的过程(中间没有加入随机噪音)。DDIM原理在介绍DDIM之前,先来回顾一下DDPM。在DDPM中,扩散过程(前向过程)定义为一个马尔卡夫链:q(\mathbf{x}_{1:T} \vert \mathbf{x}_0) = \prod^T_{t=1} q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) \quad q(\mathbf{x}_t \vert \mathbf{x}_{t-1}) = \mathcal{N}(\mathbf{x}_t; \sqrt{\frac{\alpha_t}{\alpha_{t-1}}} \mathbf{x}_{t-1}, \Big(1-\frac{\alpha_t}{\alpha_{t-1}}\Big)\mathbf{I}) \\注意,在DDIM的论文中,\alpha_t其实是DDPM论文中的\bar{\alpha}_t,那么DDPM论文中的前向过程\beta_t就为:\beta_t = \Big(1-\frac{\alpha_t}{\alpha_{t-1}}\Big) \\扩散过程的一个重要特性是可以直接用\mathbf{x}_0来对任意的\mathbf{x}_t进行采样:q(\mathbf{x}_t \vert \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t; \sqrt{{\alpha}_t} \mathbf{x}_0, (1 - {\alpha}_t)\mathbf{I}) \\而DDPM的反向过程也定义为一个马尔卡夫链:p_\theta(\mathbf{x}_{0:T}) = p(\mathbf{x}_T) \prod^T_{t=1} p_\theta(\mathbf{x}_{t-1} \vert \mathbf{x}_t) \quad p_\theta(\mathbf{x}_{t-1} \vert \mathbf{x}_t) = \mathcal{N}(\mathbf{x}_{t-1}; \boldsymbol{\mu}_\theta(\mathbf{x}_t, t), \boldsymbol{\Sigma}_\theta(\mathbf{x}_t, t))\\这里用神经网络p_\theta(\mathbf{x}_{t-1} \vert \mathbf{x}_t)来拟合真实的分布q(\mathbf{x}_{t-1} \vert \mathbf{x}_t)。DDPM的前向过程和反向过程如下所示: 我们近一步发现后验分布q(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)是一个可获取的高斯分布:q(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_{t-1}; \color{blue}{\tilde{\boldsymbol{\mu}}}(\mathbf{x}_t, \mathbf{x}_0), \color{red}{\tilde{\beta}_t} \mathbf{I}) \\其中这个高斯分布的方差是定值,而均值是一个依赖\mathbf{x}_0和\mathbf{x}_t的组合函数:\tilde{\boldsymbol{\mu}}_t (\mathbf{x}_t, \mathbf{x}_0) = \frac{\sqrt{\alpha_t}(1 - {\alpha}_{t-1})}{\sqrt{\alpha_{t-1}}(1 - {\alpha}_t)} \mathbf{x}_t + \frac{\sqrt{{\alpha}_{t-1}}\beta_t}{1 - {\alpha}_t} \mathbf{x}_0\\然后我们基于变分法得到如下的优化目标:\begin{aligned} L &= \mathbb{E}_{q(\mathbf{x}_{1:T}\vert \mathbf{x}_{0})} \Big[ \log\frac{q(\mathbf{x}_{1:T}\vert\mathbf{x}_0)}{p_\theta(\mathbf{x}_{0:T})} \Big] \\ &= \underbrace{D_\text{KL}(q(\mathbf{x}_T \vert \mathbf{x}_0) \parallel p_\theta(\mathbf{x}_T))}_{L_T} + \sum_{t=2}^T \underbrace{\mathbb{E}_{q(\mathbf{x}_{t}\vert \mathbf{x}_{0})}\Big[D_\text{KL}(q(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0) \parallel p_\theta(\mathbf{x}_{t-1} \vert\mathbf{x}_t))\Big]}_{L_{t-1}} -\underbrace{\mathbb{E}_{q(\mathbf{x}_{1}\vert \mathbf{x}_{0})}\log p_\theta(\mathbf{x}_0 \vert \mathbf{x}_1)}_{L_0} \end{aligned}\\根据两个高斯分布的KL公式,我们近一步得到:L_{t-1}=\mathbb{E}_{q(\mathbf{x}_{t}\vert \mathbf{x}_{0})}\Big[ \frac{1}{2{\sigma_t^2}}\|\tilde{\boldsymbol{\mu}}_t(\mathbf{x}_t, \mathbf{x}_0) - {\boldsymbol{\mu}_\theta(\mathbf{x}_t, t)} \|^2\Big] \\根据扩散过程的特性,我们通过重参数化可以近一步简化上述目标:L_{t-1}=\mathbb{E}_{\mathbf{x}_{0},\mathbf{\epsilon}\sim \mathcal{N}(\mathbf{0}, \mathbf{I})}\Big[ \frac{\beta_t^2}{2{\sigma_t^2}\alpha_t(1-\bar{\alpha}_t)}\| \mathbf{\epsilon}- \mathbf{\epsilon}_\theta\big(\sqrt{\bar{\alpha}_t}\mathbf{x}_0 + \sqrt{1 - \bar{\alpha}_t}\mathbf{\epsilon}, t\big)\|^2\Big] \\如果去掉系数,那么就能得到更简化的优化目标:L_{t-1}^{\text{simple}}=\mathbb{E}_{\mathbf{x}_{0},\mathbf{\epsilon}\sim \mathcal{N}(\mathbf{0}, \mathbf{I})}\Big[ \| \mathbf{\epsilon}- \mathbf{\epsilon}_\theta\big(\sqrt{\bar{\alpha}_t}\mathbf{x}_0 + \sqrt{1 - \bar{\alpha}_t}\mathbf{\epsilon}, t\big)\|^2\Big] \\仔细分析DDPM的优化目标会发现,DDPM其实仅仅依赖边缘分布q(\mathbf{x}_t \vert \mathbf{x}_0),而并不是直接作用在联合分布q(\mathbf{x}_{1:T} \vert \mathbf{x}_0)。这带来的一个启示是:DDPM这个隐变量模型可以有很多推理分布来选择,只要推理分布满足边缘分布条件(扩散过程的特性)即可,而且这些推理过程并不一定要是马尔卡夫链。但值得注意的一个点是,我们要得到DDPM的优化目标,还需要知道分布q(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0),之前我们在根据贝叶斯公式推导这个分布时是知道分布q(\mathbf{x}_t \vert \mathbf{x}_{t-1})的,而且依赖了前向过程的马尔卡夫链特性。如果要解除对前向过程的依赖,那么我们就需要直接定义这个分布q(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)。 基于上述分析,DDIM论文中将推理分布定义为:q_{\sigma}(\mathbf{x}_{1:T} \vert \mathbf{x}_0) = q_{\sigma}(\mathbf{x}_{T} \vert \mathbf{x}_0)\prod^T_{t=2} q_{\sigma}(\mathbf{x}_{t-1} \vert \mathbf{x}_{t},\mathbf{x}_{0}) \\这里要同时满足q_{\sigma}(\mathbf{x}_{T} \vert \mathbf{x}_0)=\mathcal{N}(\sqrt{\alpha_T}\mathbf{x}_{0},(1-{\alpha_T})\mathbf{I})以及对于所有的t\ge2有:q_\sigma(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_{t-1}; \sqrt{\alpha_{t-1}}\mathbf{x}_0 + \sqrt{1 - \alpha_{t-1} - \sigma_t^2} \frac{\mathbf{x}_t - \sqrt{\alpha_t}\mathbf{x}_0}{\sqrt{1 - \alpha_t}}, \sigma_t^2 \mathbf{I})\\这里的方差\sigma_t^2是一个实数,不同的设置就是不一样的分布,所以q_{\sigma}(\mathbf{x}_{1:T} \vert \mathbf{x}_0)其实是一系列的推理分布。可以看到这里分布q_\sigma(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)的均值也定义为一个依赖\mathbf{x}_0和\mathbf{x}_t的组合函数,之所以定义为这样的形式,是因为根据q_{\sigma}(\mathbf{x}_{T} \vert \mathbf{x}_0),我们可以通过数学归纳法证明,对于所有的t均满足:q_{\sigma}(\mathbf{x}_t \vert \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t; \sqrt{{\alpha}_t} \mathbf{x}_0, (1 - {\alpha}_t)\mathbf{I}) \\这部分的证明见DDIM论文的附录部分,另外博客生成扩散模型漫谈(四):DDIM = 高观点DDPM也从待定系数法来证明了分布q_\sigma(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)要构造的形式。 可以看到这里定义的推理分布q_{\sigma}(\mathbf{x}_{1:T} \vert \mathbf{x}_0)并没有直接定义前向过程,但这里满足了我们前面要讨论的两个条件:边缘分布q_{\sigma}(\mathbf{x}_t \vert \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t; \sqrt{{\alpha}_t} \mathbf{x}_0, (1 - {\alpha}_t)\mathbf{I}),同时已知后验分布q_\sigma(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)。同样地,我们可以按照和DDPM的一样的方式去推导优化目标,最终也会得到同样的L^{\text{simple}}(虽然VLB的系数不同,论文3.2部分也证明了这个结论)。 论文也给出了一个前向过程是非马尔可夫链的示例,如下图所示,这里前向过程是q_\sigma(\mathbf{x}_{t} \vert \mathbf{x}_{t-1}, \mathbf{x}_0),由于生成\mathbf{x}_t不仅依赖\mathbf{x}_{t-1},而且依赖\mathbf{x}_0,所以是一个非马尔可夫链: 注意,这里只是一个前向过程的示例,而实际上我们上述定义的推理分布并不需要前向过程就可以得到和DDPM一样的优化目标。与DDPM一样,这里也是用神经网络\mathbf{\epsilon}_\theta来预测噪音,那么根据q_\sigma(\mathbf{x}_{t-1} \vert \mathbf{x}_t, \mathbf{x}_0)的形式,在生成阶段,我们可以用如下公式来从\mathbf{x}_{t}生成\mathbf{x}_{t-1}:\mathbf{x}_{t-1} = \sqrt{\alpha_{t-1}}\Big(\underbrace{\frac{\mathbf{x}_t-\sqrt{1-\alpha_{t}}\mathbf{\epsilon}_\theta(\mathbf{x}_t, t)}{\sqrt{\alpha_{t}}}}_{\text{predicted}\ \mathbf{x}_0}\Big) + \underbrace{\sqrt{1 - \alpha_{t-1} - \sigma_t^2} \cdot \mathbf{\epsilon}_\theta(\mathbf{x}_t, t)}_{\text{direction pointing to }\ \mathbf{x}_t} + \underbrace{\sigma_t\epsilon_t}_{\text {random noise}}\\这里将生成过程分成三个部分:一是由预测的\mathbf{x}_0来产生的,二是由指向\mathbf{x}_t的部分,三是随机噪音(这里\epsilon_t是与\mathbf{x}_t无关的噪音)。论文将\sigma_t^2近一步定义为:\sigma_t^2 = \eta \cdot \tilde{\beta}_t=\eta \cdot\sqrt{(1-\alpha_{t-1})/(1-\alpha_{t})}\sqrt{(1-\alpha_{t}/\alpha_{t-1})} \\这里考虑两种情况,一是\eta=1,此时\sigma_t^2 = \tilde{\beta}_t,此时生成过程就和DDPM一样了。另外一种情况是\eta=0,这个时候生成过程就没有随机噪音了,是一个确定性的过程,论文将这种情况下的模型称为DDIM(denoising diffusion implicit model),一旦最初的随机噪音\mathbf{x}_T确定了,那么DDIM的样本生成就变成了确定的过程。上面我们终于得到了DDIM模型,那么我们现在来看如何来加速生成过程。虽然DDIM和DDPM的训练过程一样,但是我们前面已经说了,DDIM并没有明确前向过程,这意味着我们可以定义一个更短的步数的前向过程。具体地,这里我们从原始的序列[1, ..., T]采样一个长度为S的子序列[\tau_1,...,\tau_S],我们将\mathbf{x}_{\tau _{1}},...,\mathbf{x}_{\tau_{S}}的前向过程定义为一个马尔卡夫链,并且它们满足:q(\mathbf{x}_{\tau_{i}} \vert \mathbf{x}_0) = \mathcal{N}(\mathbf{x}_t; \sqrt{{\alpha}_{\tau_{i}}} \mathbf{x}_0, (1 - {\alpha}_{\tau_{i}})\mathbf{I})。下图展示了一个具体的示例: 那么生成过程也可以用这个子序列的反向马尔卡夫链来替代,由于S可以设置比原来的步数L要小,那么就可以加速生成过程。这里的生成过程变成:\mathbf{x}_{\tau_{i-1}} = \sqrt{\alpha_{\tau_{i-1}}}\Big(\frac{\mathbf{x}_{\tau_{i}}-\sqrt{1-\alpha_{\tau_{i}}}\mathbf{\epsilon}_\theta(\mathbf{x}_{\tau_{i}}, \tau_{i})}{\sqrt{\alpha_{\tau_{i}}}}\Big) + \sqrt{1 - \alpha_{\tau_{i-1}} - \sigma_{\tau_{i}}^2} \cdot \mathbf{\epsilon}_\theta(\mathbf{x}_{\tau_{i}}, \tau_{i})+\sigma_{\tau_{i}}\epsilon\\其实上述的加速,我们是将前向过程按如下方式进行了分解:q_{\sigma,\tau}(\mathbf{x}_{1:T} \vert \mathbf{x}_0) = q_{\sigma,\tau}(\mathbf{x}_{T} \vert \mathbf{x}_0)\prod^S_{i=1} q_{\sigma}(\mathbf{x}_{\tau_{i-1}} \vert \mathbf{x}_{\tau_{i}},\mathbf{x}_{0})\prod_{t\in \bar\tau}q_{\sigma,\tau}(\mathbf{x}_{t}\vert \mathbf{x}_{0}) \\其中\bar\tau=\{1,...,T\}\backslash \tau。这包含了两个图:其中一个就是由\{\mathbf{x}_{\tau_i}\}_{i=1}^S组成的马尔可夫链,另外一个是剩余的变量\{\mathbf{x}_{t}\}_{t\in \bar\tau}组成的星状图。同时生成过程,我们也只用马尔可夫链的那部分来生成:p_\theta(\mathbf{x}_{0:T}) = p(\mathbf{x}_T) \underbrace {\prod^S_{i=1} p_\theta(\mathbf{x}_{\tau_{i-1}} \vert \mathbf{x}_{\tau_{i}})}_{\text{use to produce sample}} \times \underbrace {\prod_{t\in \bar\tau}p_\theta(\mathbf{x}_{0} \vert \mathbf{x}_{t})}_{\text {only for VLB}} \\论文共设计了两种方法来采样子序列,分别是:Linear:采用线性的序列\tau_i=\lfloor ci\rfloor;Quadratic:采样二次方的序列\tau_i=\lfloor ci^2\rfloor;这里的c是一个定值,它的设定使得\tau_{-1}最接近T。论文中只对CIFAR10数据集采用Quadratic序列,其它数据集均采用Linear序列。实验结果下表为不同的\eta下以及不同采样步数下的对比结果,可以看到DDIM(\eta=0)在较短的步数下就能得到比较好的效果,媲美DDPM(\eta=1)的生成效果。如果S设置为50,那么相比原来的生成过程就可以加速20倍。 代码实现DDIM和DDPM的训练过程一样,所以可以直接在DDPM的基础上加一个新的生成方法(这里主要参考了DDIM官方代码以及diffusers库),具体代码如下所示:class GaussianDiffusion:
def __init__(self, timesteps=1000, beta_schedule='linear'):
pass
# ...
# use ddim to sample
@torch.no_grad()
def ddim_sample(
self,
model,
image_size,
batch_size=8,
channels=3,
ddim_timesteps=50,
ddim_discr_method="uniform",
ddim_eta=0.0,
clip_denoised=True):
# make ddim timestep sequence
if ddim_discr_method == 'uniform':
c = self.timesteps // ddim_timesteps
ddim_timestep_seq = np.asarray(list(range(0, self.timesteps, c)))
elif ddim_discr_method == 'quad':
ddim_timestep_seq = (
(np.linspace(0, np.sqrt(self.timesteps * .8), ddim_timesteps)) ** 2
).astype(int)
else:
raise NotImplementedError(f'There is no ddim discretization method called "{ddim_discr_method}"')
# add one to get the final alpha values right (the ones from first scale to data during sampling)
ddim_timestep_seq = ddim_timestep_seq + 1
# previous sequence
ddim_timestep_prev_seq = np.append(np.array([0]), ddim_timestep_seq[:-1])
device = next(model.parameters()).device
# start from pure noise (for each example in the batch)
sample_img = torch.randn((batch_size, channels, image_size, image_size), device=device)
for i in tqdm(reversed(range(0, ddim_timesteps)), desc='sampling loop time step', total=ddim_timesteps):
t = torch.full((batch_size,), ddim_timestep_seq[i], device=device, dtype=torch.long)
prev_t = torch.full((batch_size,), ddim_timestep_prev_seq[i], device=device, dtype=torch.long)
# 1. get current and previous alpha_cumprod
alpha_cumprod_t = self._extract(self.alphas_cumprod, t, sample_img.shape)
alpha_cumprod_t_prev = self._extract(self.alphas_cumprod, prev_t, sample_img.shape)
# 2. predict noise using model
pred_noise = model(sample_img, t)
# 3. get the predicted x_0
pred_x0 = (sample_img - torch.sqrt((1. - alpha_cumprod_t)) * pred_noise) / torch.sqrt(alpha_cumprod_t)
if clip_denoised:
pred_x0 = torch.clamp(pred_x0, min=-1., max=1.)
# 4. compute variance: "sigma_t(η)" -> see formula (16)
# σ_t = sqrt((1 − α_t−1)/(1 − α_t)) * sqrt(1 − α_t/α_t−1)
sigmas_t = ddim_eta * torch.sqrt(
(1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t) * (1 - alpha_cumprod_t / alpha_cumprod_t_prev))
# 5. compute "direction pointing to x_t" of formula (12)
pred_dir_xt = torch.sqrt(1 - alpha_cumprod_t_prev - sigmas_t**2) * pred_noise
# 6. compute x_{t-1} of formula (12)
x_prev = torch.sqrt(alpha_cumprod_t_prev) * pred_x0 + pred_dir_xt + sigmas_t * torch.randn_like(sample_img)
sample_img = x_prev
return sample_img.cpu().numpy()
这里以MNIST数据集为例,训练的扩散步数为500,直接采用DDPM(即推理500次)生成的样本如下所示: 同样的模型,我们采用DDIM来加速生成过程,这里DDIM的采样步数为50,其生成的样本质量和500步的DDPM相当: 完整的代码示例见https://github.com/xiaohu2015/nngen。其它:重建和插值在DDIM论文中,还额外讨论了两个小点的内容:重建和插值。所谓重建是指的首先用原始图像求逆得到对应的噪音然后再进行生成的过程;而插值是指的对两个随机噪音进行插值从而得到融合两种噪音的图像。 首先是重建,对于DDIM,其\eta=0,这个时候从\mathbf{x}_{t}生成\mathbf{x}_{t-1}的更新公式就变为:\mathbf{x}_{t-1} = \sqrt{\alpha_{t-1}}\Big(\frac{\mathbf{x}_t-\sqrt{1-\alpha_{t}}\mathbf{\epsilon}_\theta(\mathbf{x}_t, t)}{\sqrt{\alpha_{t}}}\Big)+ \sqrt{1 - \alpha_{t-1}} \cdot \mathbf{\epsilon}_\theta(\mathbf{x}_t, t)\\我们进一步对上述公式进行变换可得到:\frac{\mathbf{x}_{t-1}}{\sqrt{\alpha_{t-1}}} = \frac{\mathbf{x}_t}{\sqrt{\alpha_{t}}} + \Big(\sqrt{\frac{1-\alpha_{t-1}}{\alpha_{t-1}}} - \sqrt{\frac{1-\alpha_{t}}{\alpha_{t}}} \Big)\mathbf{\epsilon}_\theta(\mathbf{x}_t, t) \\当T足够大时,以上公式其实可以看成用欧拉法来求解一个常微分方程(ODE,ordinary differential equation):\frac{\mathbf{x}_{t-\Delta t}}{\sqrt{\alpha_{t-\Delta t}}} = \frac{\mathbf{x}_t}{\sqrt{\alpha_{t}}} + \Big(\sqrt{\frac{1-\alpha_{t-\Delta t}}{\alpha_{t-\Delta t}}} - \sqrt{\frac{1-\alpha_{t}}{\alpha_{t}}} \Big)\mathbf{\epsilon}_\theta(\mathbf{x}_t, t) \\这里令\sigma=\sqrt{1-\alpha}/\sqrt{\alpha},\bar{\mathbf{x}}=\mathbf{x}/\sqrt{\alpha},它们都是关于t的函数,这样对应的ODE就是:\text{d}\bar{\mathbf{x}}(t)=\mathbf{\epsilon}_\theta(\frac{\bar{\mathbf{x}}(t)}{\sqrt{\sigma^2+1}}, t)\text{d}\sigma(t) \\看成ODE后,我们可以利用如下公式对生成过程进行逆操作:\frac{\mathbf{x}_{t+1}}{\sqrt{\alpha_{t+1}}} = \frac{\mathbf{x}_t}{\sqrt{\alpha_{t}}} + \Big(\sqrt{\frac{1-\alpha_{t+1}}{\alpha_{t+1}}} - \sqrt{\frac{1-\alpha_{t}}{\alpha_{t}}} \Big)\mathbf{\epsilon}_\theta(\mathbf{x}_t, t) \\这意味着,我们可以由一个原始图像\mathbf{x}_0得到对应的随机噪音\mathbf{x}_T,然后我们再用\mathbf{x}_T进行生成就可以重建原始图像\mathbf{x}_0(具体的代码实现见https://github.com/openai/improved-diffusion/blob/main/improved_diffusion/gaussian_diffusion.py#L524-L560)。论文也通过在CIFAR10测试集上的实验来证明当步数足够时,这种方式可以得到较低的重建误差: 第二个插值,对于DDIM,两个不同的随机噪音会产生不同的图像,但是如果我们对这两个随机噪音进行插值生成新的\mathbf{x}_T,那么将生成融合的图像。这里采用的插值方法是球面线性插值( spherical linear interpolation):\mathbf{x}_T^{(\alpha)}=\frac{\sin ((1-\alpha)\theta)}{\sin (\theta)}\mathbf{x}_T^{(0)}+\frac{\sin (\alpha\theta)}{\sin (\theta)}\mathbf{x}_T^{(1)} \quad \theta = \arccos\Big(\frac{(\mathbf{x}_T^{(0)})^\text{T}\mathbf{x}_T^{(1)}}{\| \mathbf{x}_T^{(0)}\|\|\mathbf{x}_T^{(1)}\|}\Big) \\这里的参数\alpha\in[0,1]控制插值系数,具体的代码实现见https://github.com/ermongroup/ddim/blob/main/runners/diffusion.py#L296-L334。下图展示了一些具体的插值效果: DDIM的重建和插值也在文本转图像模型DALLE-2中使用,不过这里插值的是扩散模型的条件CLIP image embedding,详情见论文Hierarchical Text-Conditional Image Generation with CLIP Latents。 小结如果从直观上看,DDIM的加速方式非常简单,直接采样一个子序列,其实论文DDPM+也采用了类似的方式来加速。另外DDIM和其它扩散模型的一个较大的区别是其生成过程是确定性的。参考Denoising Diffusion Implicit Modelshttps://github.com/ermongroup/ddimhttps://github.com/openai/improved-diffusionhttps://github.com/huggingface/diffusers/blob/main/src/diffusers/schedulers/scheduling_ddim.pyhttps://github.com/CompVis/latent-diffusion/blob/main/ldm/models/diffusion/ddim.pyhttps://kexue.fm/archives/9181编辑于 2023-05-25 11:31・IP 属地广东计算机视觉深度学习(Deep Learning)生成模型赞同 66240 条评论分享喜欢收藏申请转载文章被以下专栏收录机器学习算法工程师欢迎关注同名微信
一文带你看懂DDPM和DDIM(含原理简易推导,pytorch代码) - 知乎
一文带你看懂DDPM和DDIM(含原理简易推导,pytorch代码) - 知乎切换模式写文章登录/注册一文带你看懂DDPM和DDIM(含原理简易推导,pytorch代码)Deja vu华五硕在读写在最前面,此文所要介绍的两个模型是AIGC领域大火的Diffusion模型原版及其变种,其中DDPM是开山之作,DDIM是基于DDPM的。DDPM原始论文的原理推导非常复杂,对于刚入门的新手非常不友好。本文将对原理进行一个简单梳理,过程可能不严谨,适用于有一定概率论和深度学习基础的新手小白,并结合pytorch代码实现,若有错误希望能够得到指正。本文仅作学习使用,不作商业用途,若有版权问题请联系笔者,代码是在他人基础上进行改的,感谢开源社区提供的pytorch代码实现。笔者本人学习中参考的主要资料为:大白话DDPM:https://www.bilibili.com/video/BV1tz4y1h7q1DDPM和DDIM公式推导:https://www.bilibili.com/video/BV1Zh411A72y迪哥讲Diffusion(含原理和代码):https://www.bilibili.com/video/BV1pD4y1179T代码来源:https://github.com/w86763777/pytorch-ddpm一、背景何为AIGC?AIGC即为生成式人工智能(Artificial Intelligence Generated Content),利用人工智能算法生成具有一定创意和质量的内容。DDPM全称为Denoising diffusion probabilistic models,目标是利用diffusion算法产生新的图片,diffusion分为加噪和去噪两个过程,后续小节将会对diffusion进行详细介绍。DDPM最核心的工作,就是训练了一个神经网络model,使其能够学习到训练数据集Dataset已有图片的数据分布,并有能力产生新的图片。即给猫的图片进行训练,模型自己能产生新的猫的图片。二、前置知识2.1 数学知识2.1.1 概率首先引出最基本的概念,何为随机试验?随机试验(random experiment):在相同条件下,对某随机现象进行的大量重复观测。例如抛骰子,观察其点数;抛硬币,看其哪一面向上。相信大家对于日常生活中的概率这个词已经耳熟能详了,我们接下来定义三种概率。现在有长度为n且按照时间分布的序列,x_1,x_2,...,x_{t-1},x_t,...,x_n 先验概率(根据以往经验和分析得到的概率): q(x_t|x_{t-1}),给定前一时刻的x_{t-1}预测当前时刻x_t的概率后验概率(指在得到结果的信息后重新修正的概率): p(x_{t-1}|x_t),给定当前时刻的x_{t}预测当前时刻x_{t-1}的概率接下来,我们介绍一下条件概率和大名鼎鼎的贝叶斯公式。条件概率:设A,B是随机试验E的两个随机试验,且P(B)>0,称P(A|B) = \frac{P(AB)}{P(B)}为事件B发生的条件下,事件A发生的条件概率。如何进行推导,可以观察韦恩图:条件概率韦恩图P(AB)即为A事件和B事件的交集(既发生A也发生B),那么在已有B的情况下,发生A事件的概率,显然是二者的交集的面积除以B的面积,因为B的其他部分,A此时并没有作用也就是没发生。同理可得,P(B|A) = \frac{P(AB)}{P(A)},那么P(A|B) = \frac{P(B|A)P(A)}{P(B)},这就是贝叶斯公式。在求P(A|B)的情况下,我们可以引入其他事件C,相当于在已知C事件的情况下,去计算条件概率,公式变形为P(A|B,C) = \frac{P(B|A,C)P(A|C)}{P(B|C)} \\ 2.1.2 统计下面将要介绍期望,方差,以及一个非常重要的分布-正态分布期望:一个随机变量x,它在取不同值时的概率函数为f(x),对于不同的取值,需要根据概率进行加权E(x) = \mu = \sum xf(x)\\ 方差:用于表示数据的分散程度,数据波动越大,方差越大,其中\sigma为标准差,标准差是方差的算数平方根Var(x) = \sigma^2 = \sum (x-\mu)^2f(x)\\ 若一个随机变量X服从一个位置参数\mu和尺度参数\sigma的概率分布,且其概率密度函数为f(x) = \frac{1}{\sqrt{2\pi\sigma}}exp(-\frac{(x-\mu)^2}{2\sigma^2})\\ 则称这个随机变量为正态随机变量,服从的分布称为正态(高斯)分布,记作X \sim N(\mu,\sigma^2),\mu,\sigma^2分别是分布的期望和方差。其中又有一种特殊的分布叫做标准正态分布,此时\mu = 0, \sigma = 1,记作X \sim N(0,1) 正态分布满足一系列比较好的性质,例如X \sim N(\mu,\sigma^2),则aX+b \sim N(a\mu+b,a^2\sigma^2) 对于X_1 \sim N(\mu_1,\sigma_1),X_2 \sim N(\mu_2,\sigma_2),则二者进行叠加仍然是高斯分布,且X_1 + X_2 \sim N(\mu_1+\mu_2,\sigma_1^2+\sigma_2^2)\\ 2.1.3 马尔科夫链Diffusion模型中利用到了马尔科夫链,这里我们简要介绍一下。随机过程:一串随机变量的序列,在这个序列当中,每一个数据都可以被看作是一个随机变量。例如某十字路口每分钟通过的车辆数量构成的序列。马尔科夫链(Markov Chain):状态空间中经过一个状态到另一个状态的转换的随机过程,该过程具备无记忆性(马尔科夫性质),即下一状态的概率分布只能由当前状态决定,在时间序列中它前面的事件均与之无关。(例如循环神经网络RNN)。如下图所示,其中的带箭头的横线代表预测或者是说转换过程,每个圆圈代表一个状态,马尔科夫链就是基于前一时刻预测下一时刻的信息。马尔科夫链一句话总结马尔科夫链,就是The future is independent of the past given the present。也就是说过去所有的信息都已经被保存到了现在的状态,基于现在就可以预测未来。2.1 深度学习知识本文论文代码是基于pytorch框架,需要阅读者具备一定的深度学习基础,掌握unet模型,列出以下资源吴恩达深度学习:https://www.bilibili.com/video/BV1FT4y1E74V小土堆pytorch学习:https://www.bilibili.com/video/BV1hE411t7RNunet实战:https://www.bilibili.com/video/BV11341127iK在本小节中列出DDPM中经常使用的用法torch.randn_like(x_0) # 产生与x_0相同维度的,且满足正态分布的随机tensor
# 指数平滑保存模型
def ema(source, target, decay):
source_dict = source.state_dict()
target_dict = target.state_dict()
for key in source_dict.keys():
target_dict[key].data.copy_(
target_dict[key].data * decay +
source_dict[key].data * (1 - decay))
# 模块初始化的方法
def initialize(self):
for module in self.modules():
if isinstance(module, nn.Linear):
nn.init.xavier_uniform_(module.weight)
nn.init.zeros_(module.bias)三、DDPM算法总算到了最为核心的算法部分,相信前置知识小节中的内容并没有难倒大家,或者是求知心切的大家可能一步就跳到了这一部分。算法部分将涉及到原理推导,请概率论基础薄弱的读者一定要再去看一下前置知识中的数学小节的内容,只记住公式也可以。DDPM最为核心的部分就是Diffusion算法,其如下图所示DDPM中的马尔科夫链相信大家第一次看到也肯定是一脸懵逼,这是什么东西,请听我娓娓道来。从图片中可以看出有两种方向不同的箭头,有一张非常模糊的图片,有一张非常清晰人脸的图片,还有一张处于二者之间的图片。这里就暗示着我们,DDPM包括两个步骤。这两个步骤在原文中定义为前向加噪(forward)和后向去噪(reverse)。3.1 前向加噪forward从x_0到x_T的过程就是前向加噪过程,我们可以看到加噪过程顾名思义就是对原始图片x_0进行了一系列操作,使其变得"模糊"起来,而与之相对应的去噪过程就特别像还原过程,使得图片变得清晰。这里的操作实际上就是指在图片加入噪声noise,噪声noise本身的分布可以是很多样的,而论文中采用的是标准正态分布,其理由是考虑到其优良的性质,在接下来的公式推理中见到。我们通过往图片中加入噪声,使得图片变得模糊起来,当加的步骤足够多的时候(也就是T的取值越大的时候),图片已经非常接近一张纯噪声。纯噪声也就意味着多样性,我们的模型在去噪(还原)的过程中能够产生更加多样的图片。好的,我们下面正式进入原理推导部分马尔科夫链x_0是原始图片,其满足初始分布q(x_0),即x_0 \sim q(x_0) 对于t \in [1,T]时刻,x_t和x_{t-1}满足如下关系式 x_t = \sqrt{1-\beta_t}x_{t-1} + \sqrt{\beta_t}\epsilon\\ \epsilon \sim N(0,1) \\ 令\alpha_t = 1 - \beta_t,则公式变形为x_t = \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon \\ 其中的\beta_t是固定常数,其随着t的增加而增加,代码形式self.register_buffer('betas', torch.linspace(beta_1, beta_T, T).double()) # \beta_t
alphas = 1. - self.betas # \alpha继续进行推导x_t = \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon = \sqrt{\alpha_t}(\sqrt{\alpha_{t-1}}x_{t-2}+\sqrt{1-\alpha_{t-1}}\epsilon) + \sqrt{1-\alpha_t}\epsilon = \sqrt{\alpha_t\alpha_{t-1}}x_{t-2}+\sqrt{\alpha_t}\sqrt{1-\alpha_{t-1}}\epsilon + \sqrt{1-\alpha_t}\epsilon 由于正态分布的叠加性,\sqrt{\alpha_t}\sqrt{1-\alpha_{t-1}}\epsilon + \sqrt{\alpha_t}\sqrt{1-\alpha_t}\epsilon可以看作X_1 \sim \sqrt{\alpha_t}\sqrt{1-\alpha_{t-1}}\epsilon = N(0,{\alpha_t(1-\alpha_{t-1})}) \\ X_2 \sim \sqrt{1-\alpha_t}\epsilon = N(0,{1-\alpha_{t}}) \\ X_1 + X_2 = N(0,1-{\alpha_t\alpha_{t-1}}) \\ 则公式可以进一步简化为x_t = \sqrt{\alpha_t\alpha_{t-1}}x_{t-2} + (\sqrt{1-\alpha_t\alpha_{t-1}})\epsilon \\ 由数学归纳法,可以进一步推导得x_t = \sqrt{\alpha_t\alpha_{t-1}...\alpha_1}x_{0} + (\sqrt{1-\alpha_t\alpha_{t-1}\alpha_{1}})\epsilon \\ 令\bar{\alpha_t} = {\alpha_t\alpha_{t-1}...\alpha_1},则公式可以进一步化简为x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon \\ \bar{\alpha_t},\frac{1}{\sqrt{\bar{\alpha_t}}},\frac{\sqrt{1-\bar{\alpha_t}}}{\sqrt{\bar{\alpha_t}}}的代码形式如下alphas_bar = torch.cumprod(alphas, dim=0)
self.register_buffer('sqrt_recip_alphas_bar', torch.sqrt(1. / alphas_bar))
self.register_buffer('sqrt_recipm1_alphas_bar', torch.sqrt(1. / alphas_bar - 1))x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon可求出x_0的表达式x_0 = \frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon}{\sqrt{\bar{\alpha_t}}} 代码形式如下:# 根据时间t来取对应的系数
def extract(v, t, x_shape):
# v[T]
# t[B] x_shape = [B,C,H,W]
out = torch.gather(v, index=t, dim=0).float()
# [B,1,1,1],分别代表batch_size,通道数,长,宽
return out.view([t.shape[0]] + [1] * (len(x_shape) - 1))
# eps代表正态分布噪声,函数目标是计算x_0
def predict_xstart_from_eps(self, x_t, t, eps):
return (
extract(self.sqrt_recip_alphas_bar, t, x_t.shape) * x_t -
extract(self.sqrt_recipm1_alphas_bar, t, x_t.shape) * eps
)通过x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon和x_t = \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon比较,我们可以发现最终推导公式是如此简洁,可以由x_0一步得到,无需多次迭代的过程,这一点非常令人欣喜。由于\beta_t一直在变大,则\alpha_t一直在变小,则当t \to T,\bar{\alpha_T} \to 0,则x_T \to \epsilon 所以我们认为在前向加噪的过程,进行非常多的步骤的时候(例如T=1000),最终产生的图片x_T 接近于高斯分布这里我们引入了一个简单的例子,用来演示逐步加噪的过程import torch
from torchvision.utils import make_grid, save_image
from torchvision import transforms
from PIL import Image
betas = torch.linspace(0.02, 1e-4, 1000).double()
alphas = 1. - betas
alphas_bar = torch.cumprod(alphas, dim=0)
sqrt_alphas_bar = torch.sqrt(alphas_bar)
sqrt_m1_alphas_bar = torch.sqrt(1 - alphas_bar)
img = Image.open('car.png') # 读取图片
trans = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor() # 转换为tensor
])
x_0 = trans(img)
img_list = [x_0]
noise = torch.randn_like(x_0)
for i in range(15):
x_t = sqrt_alphas_bar[i] * x_0 + sqrt_m1_alphas_bar[i] * noise
img_list.append(x_t)
all_img = torch.stack(img_list, dim=0)
all_img = make_grid(all_img)
save_image(all_img, 'car_noise.png')逐步加噪过程3.2 反向去噪reverse去噪顾名思义就是在已经有噪声的图片,进行去除噪声的步骤,使得图片恢复成原来图片的样子。在加噪过程中,我们产生一系列带有噪声的图片,并利用前一时刻的图片去预测下一时刻的图片。而在去噪的过程中,我们需要根据当前时刻的图片去预测前一时刻的图片,也就意味着去除一部分噪声,还原到上一时刻的图片。从上一小节得知,我们去噪过程最开始的图片x_T本身来自高斯分布,写作代码x_T = torch.randn(sample_size, 3, img_size, img_size)
# sample_size代表测试图片个数
# 3代表通道数,意味着这是一张RGB图片
# img_size代表图片大小在去噪过程中,我们并不知道上一时刻x_{t-1}的值,是需要用x_t进行预测,所以我们只能用概率的形式,采用贝叶斯公式去计算后验概率P(x_{t-1}|x_t) P(x_{t-1}|x_t) = \frac{P(x_{t-1}x_t)}{P(x_t)} = \frac{P(x_t|x_{t-1})P(x_{t-1})}{P(x_t)} \\ 进一步在已知原图x_0的情况下,进行公式改写P(x_{t-1}|x_t,x_0) = \frac{P(x_t|x_{t-1},x_0)P(x_{t-1}|x_0)}{P(x_t|x_0)} \\ 等式右边部分都变成先验概率,我们由前向加噪过程即可对公式进行改写,依据为x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon和x_t = \sqrt{1-\beta_t}x_{t-1} + \sqrt{\beta_t}\epsilon两个公式P(x_{t-1}|x_t,x_0) = \frac{N(\sqrt{\alpha_t}x_{t-1},1-\alpha_t) N(\sqrt{\bar{\alpha_{t-1}}}x_0,1-\bar{\alpha_{t-1}})}{N(\sqrt{\bar{\alpha_{t}}}x_0,1-\bar{\alpha_{t}})}\\ 在前面的小节中,我们已经给出了正态分布的概率密度函数,我们可以对上述公式进行展开。展开的依据为展开的时候注意x_t和x_{t-1}的区别,在|前的变量才是概率密度函数f(x)里的x,\propto代表成正比,即我们不关心前面的系数P(x_{t-1}|x_t,x_0) \propto exp -\frac{1}{2}[\frac{(x_t-\sqrt{\alpha_t}x_{t-1})^2}{1-\alpha_t} + \frac{(x_{t-1}-\sqrt{\bar{\alpha_{t-1}}}x_0)^2}{1-\bar{\alpha_{t-1}}} - \frac{(x_t-\sqrt{\bar{\alpha_t}}x_0)^2}{1-\bar{\alpha_t}}] 此时由于x_{t-1}是我们关注的变量,所以整理成关于x_{t-1}的形式P(x_{t-1}|x_t,x_0) \propto exp -\frac{1}{2}[(\frac{\alpha_t}{1-\alpha_t}+\frac{1}{1-\bar{\alpha_{t-1}}})x_{t-1}^2 - (\frac{2\sqrt{\alpha_t}}{1-\alpha_t}x_t+\frac{2\sqrt{\bar{\alpha_{t-1}}}}{1-\bar{\alpha_{t-1}}}x_0)x_{t-1} + C(x_t,x_0)],其中C(x_t,x_0)与x_{t-1}无关,只影响最前面的系数由于标准正态分布满足 \propto exp -\frac{x^2+\mu^2-2x\mu}{2\sigma^2},则\frac{1}{\sigma^2} = \frac{\alpha_t}{1-\alpha_t} + \frac{1}{1-\bar{\alpha_{t-1}}} = \frac{1-\bar{\alpha_t}}{(1-\alpha_t)(1-\bar{\alpha_{t-1}})} \\ \sigma^2 = \frac{\beta_t(1-\bar{\alpha_{t-1}})}{1-\bar{\alpha_t}} \\ \mu = \frac{1}{2}\sigma^2(\frac{2\sqrt{\alpha_t}}{1-\alpha_t}x_t+\frac{2\sqrt{\bar{\alpha_{t-1}}}}{1-\bar{\alpha_{t-1}}}x_0) = \frac{\sqrt{\bar{\alpha_{t-1}}}(1-\alpha_t)}{1-\bar{\alpha_t}}x_0 + \frac{(1-\bar{\alpha_{t-1}})\sqrt{\alpha_t}}{1-\bar{\alpha_{t}}}x_t \\ \alpha_{t-1},\frac{\sqrt{\bar{\alpha_{t-1}}}(1-\alpha_t)}{1-\bar{\alpha_t}},\frac{(1-\bar{\alpha_{t-1}})\sqrt{\alpha_t}}{1-\bar{\alpha_{t}}},\mu,\ln \sigma^2的代码形式如下alphas_bar_prev = F.pad(alphas_bar, [1, 0], value=1)[:T] # 在左侧补充1 alpha_0 = 1
self.register_buffer('posterior_mean_coef1', torch.sqrt(alphas_bar_prev) * self.betas / (1. - alphas_bar))
self.register_buffer('posterior_mean_coef2', torch.sqrt(alphas) * (1. - alphas_bar_prev) / (1. - alphas_bar))
# \mu
posterior_mean = (
extract(self.posterior_mean_coef1, t, x_t.shape) * x_0 +
extract(self.posterior_mean_coef2, t, x_t.shape) * x_t
)
# \ln \sigma^2
self.register_buffer('posterior_var', self.betas * (1. - alphas_bar_prev) / (1. - alphas_bar))
self.register_buffer('posterior_log_var_clipped',torch.log(torch.cat([self.posterior_var[1:2], self.posterior_var[1:]])))
posterior_log_var_clipped = extract(self.posterior_log_var_clipped, t, x_t.shape)又因为x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon,则可以将上式的x_0全部换掉\mu = \frac{1}{\sqrt{\alpha_t}}(\frac{\alpha_t-\bar{\alpha_t}}{1-\bar{\alpha_t}}x_t + \frac{\sqrt{\bar{\alpha_t}}(1-\alpha_t)}{1-\bar{\alpha_t}}*\frac{x_t-\sqrt{1-\bar{\alpha_t}}\epsilon}{\sqrt{\bar{\alpha_t}}}) = \frac{1}{\sqrt{\alpha_t}}[\frac{\alpha_t-\bar{\alpha_t}}{1-\bar{\alpha_t}}x_t + \frac{1-\alpha_t}{1-\bar{\alpha_t}}*(x_t-\sqrt{1-\bar{\alpha_t}}\epsilon)] = \frac{1}{\sqrt{{\alpha_t}}}(x_t - \frac{1-\alpha_t}{1-\bar{\alpha_t}}\sqrt{1-\bar{\alpha_t}}\epsilon) = \frac{1}{\sqrt{\alpha_t}}(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar{\alpha_t}}}\epsilon) 下面就展示了一下反向去噪的过程,其中的x_T是随机高斯噪声,也就是左上角第一张图片去噪过程总结一下,P(x_{t-1}|x_t) = N(\frac{1}{\sqrt{\alpha_t}}(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar{\alpha_t}}}\epsilon),\frac{(1-\alpha_t)(1-\bar{\alpha_{t-1}})}{1-\bar{\alpha_t}}) 对于上述公式来说,似乎一切都很完美,但是\epsilon具体的值我们并不知道,我们只知道其服从正态分布,如何去解决这个问题?采用暴力美学,没法计算出来的,就靠神经网络去解决!3.3 训练网络回顾一下前两小节的重点公式,分别对应前向加噪forward和反向去噪过程reverse:x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon \\ P(x_{t-1}|x_t) = N(\frac{1}{\sqrt{\alpha_t}}(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar{\alpha_t}}}\epsilon),\frac{(1-\alpha_t)(1-\bar{\alpha_{t-1}})}{1-\bar{\alpha_t}}) \\ 从两个重要公式中,我们可以看出这里面与\alpha相关的都是常数,唯一不确定的就是\epsilon。如何去解决呢?上一小节中,我们提出用神经网络去解决。重新考虑前向加噪过程,我们能否使用神经网络从x_t中提取出\epsilon,这个想法具备可行性。因为x_t本身就是一张图片,可以用卷积神经网络进行特征提取,原本采用的是unet+self-attention,这里特征提取的网络可以很多样,也可以换成经典的resnet,vgg,也可以是最近爆火的vision-transformer。下面用代码实现训练器,我们针对同一个batch内的样本,可以采取不同的T,这样使得模型学到更多样的东西。这也就是torch.randint(self.T)的含义,我们人为加了一个噪声noise,其值通过noise = torch.randn_like(x_0)产生,向model传递两个参数一个是t代表时间信息,另一个就是前向加噪的结果x_t,采用MSE计算loss结果class GaussianDiffusionTrainer(nn.Module):
def __init__(self, model, beta_1, beta_T, T):
super(GaussianDiffusionTrainer, self).__init__()
self.model = model
self.T = T
self.register_buffer('betas', torch.linspace(beta_1, beta_T, T).double())
alphas = 1. - self.betas
alphas_bar = torch.cumprod(alphas, dim=0)
self.register_buffer('sqrt_alphas_bar', torch.sqrt(alphas_bar))
self.register_buffer('sqrt_one_minus_alphas_bar', torch.sqrt(1. - alphas_bar))
def forward(self, x_0):
# x [B,C,H,W]
# t [B], [T1,T2,T3,...,TB]
t = torch.randint(self.T, size=(x_0.shape[0],), device=x_0.device)
noise = torch.randn_like(x_0) #
# 加噪
x_t = (
extract(self.sqrt_alphas_bar, t, x_0.shape) * x_0 +
extract(self.sqrt_one_minus_alphas_bar, t, x_0.shape) * noise
)
loss = F.mse_loss(self.model(x_t, t), noise, reduction='none')
return loss3.3.1 Self-Attention自注意力机制在nlp领域非常常见,由于篇幅原因不再细讲如何实现,可以参考以下博客来了解原理pytorch-sa: https://blog.csdn.net/weixin_53598445/article/details/125009686这里是ddpm中有关注意力机制的pytorch实现class AttnBlock(nn.Module):
def __init__(self, in_ch):
super(AttnBlock, self).__init__()
self.group_norm = nn.GroupNorm(32, in_ch)
# 尺寸不变
self.proj_q = nn.Conv2d(in_ch, in_ch, kernel_size=1, stride=1, padding=0)
self.proj_k = nn.Conv2d(in_ch, in_ch, kernel_size=1, stride=1, padding=0)
self.proj_v = nn.Conv2d(in_ch, in_ch, kernel_size=1, stride=1, padding=0)
self.proj = nn.Conv2d(in_ch, in_ch, kernel_size=1, stride=1, padding=0)
self.initialize()
def initialize(self):
for module in [self.proj_q, self.proj_k, self.proj_v, self.proj]:
nn.init.xavier_uniform_(module.weight)
nn.init.zeros_(module.bias)
nn.init.xavier_uniform_(self.proj.weight, gain=1e-5)
# 要满足x的维度C与in_ch一致
def forward(self, x):
B, C, H, W = x.shape
h = self.group_norm(x)
q = self.proj_q(h)
k = self.proj_k(h)
v = self.proj_v(h) # [B,C,H,W]
# [B,H,W,C]
q = q.permute(0, 2, 3, 1).view(B, H * W, C) # [B,HW,C]
k = k.view(B, C, H * W) # [B,C,HW]
w = torch.bmm(q, k) * (int(C) ** (-0.5))
w = F.softmax(w, dim=-1) # [B,HW,HW]
v = v.permute(0, 2, 3, 1).view(B, H * W, C) # [B,HW,C]
h = torch.bmm(w, v) # [B,HW,C]
h = h.view(B, H, W, C).permute(0, 3, 1, 2) # [B,C,H,W]
h = self.proj(h) # [B,C,H,W]
return x + h # [B,C,H,W]3.3.2 Unetunet的网络结构如下图,主要包括下采样和上采样。这里列出较为简单的实现的代码链接Unet: https://github.com/bigmb/Unet-Segmentation-Pytorch-Nest-of-Unets/blob/master/Models.pyUnet结构3.3.3 训练过程这里采用的是cifar10数据集进行训练,共有60000张彩色图像,每个图像为32*32,总共10个类别,常用于深度学习分类问题,下图展示了一下具体的图片Cifar10数据集图片读取数据集,并做一些初始的处理,代码如下from torchvision.transforms import transforms
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader
dataset = CIFAR10(
root='./data', train=True, download=True,
transform=transforms.Compose([
transforms.RandomHorizontalFlip(), # 扩充数据集
# 将像素值从 [0, 255] 范围归一化到 [0, 1] 范围内
transforms.ToTensor(),
# 使得像素值在 [-1, 1] 范围内
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
)
dataloader = DataLoader(
dataset, batch_size_own, shuffle=True, drop_last=True, num_workers=num_workers
)定义模型,优化器,训练步骤net_model = Unet(xx=xx)
trainer = GaussianDiffusionTrainer(net_model, beta_1, beta_T, T)
optim = torch.optim.Adam(net_model.parameters(), lr)
net_model.train()
for epoch in tqdm(range(total_steps)):
for step, (images, labels) in enumerate(dataloader):
optim.zero_grad() # 清除梯度
x_0 = images.to(device) # 加载数据
loss = trainer(x_0).mean() # 计算loss
loss.backward() # 反向传播
clip_grad_norm_(net_model.parameters(), grad_clip) # 剪裁梯度
optim.step() # 梯度器优化3.4 推理(反向去噪)推理过程就是反向去噪的过程,这里主要展示forward代码,p_mean_variance用3.2小节中的公式进行计算均值和方差,用eps = model(x_t, t)解决\epsilon的问题def forward(self, x_T):
x_t = x_T
for time_step in tqdm(reversed(range(self.T)), desc="Inference"):
t = x_t.new_ones([x_T.shape[0], ], dtype=torch.long) * time_step
mean, log_var = self.p_mean_variance(x_t, t)
if time_step > 0:
noise = torch.randn_like(x_t)
else:
noise = 0
x_t = mean + torch.exp(0.5 * log_var) * noise
x_0 = x_t
return torch.clip(x_0, -1, 1)推理过程的代码如下model = UNet(T=T, ch=ch, ch_mult=ch_mult, attn=attn, num_res_blocks=num_res_blocks, dropout=dropout)
sampler = GaussianDiffusionSampler(ema_model, beta_1, beta_T, T, img_size, mean_type, var_type).to(device)
model.eval()
with torch.no_grad():
x_T = torch.randn(sample_size, 3, img_size, img_size) # 随机产生高斯噪声
x_0 = sampler(x_T)
# 可视化操作3.5 DDPM训练与推理结果下图为笔者在模型训练的过程中,对同一个高斯噪声进行预测,可以很明显地看出来,随着轮数不断增加,画质越来越清晰,细节也越来越丰富,模型学习的能力(以假乱真)也不断越来越强。DDIM训练过程四、DDIM算法4.1 原理推导DDPM看起来似乎很完美,但其有一个致命的缺点便是推理速度过慢,无法避免的迭代过程。为什么无法避免迭代过程是因为,其本身是一个马尔科夫链的过程,即前后时刻数据有非常紧密的绑定关系,无法进行跳跃预测,比如说用x_t直接去预测x_{t-2}。但是想要高质量的生成图片,就意味着T要取一个比较大的值,用一张4090显卡进行推理,T=1000的情况下,推理64张32*32图片需要耗时20s,这个时间显然是无法忍受的。如何加速推理步骤,这个时候救星-DDIM出现了,DDIM通过数学推理,打破了马尔科夫链的过程,最为巧妙的是其无需重新训练DDPM(无需改变前向加噪),只对采样器进行修改即可,修改后的采样器能够大幅增加采样速度。下面我们就来分析DDIM的原理,从一个假设出发,假设P(x_{t-1}|x_t,x_0)满足如下正态分布则P(x_{t-1}|x_t,x_0) \sim N(kx_0+mx_t,\sigma^2) \\ x_{t-1} = kx_0 + mx_t + \sigma\epsilon \epsilon \sim N(0,1) \\ 又因为加噪过程满足公式x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon \epsilon \sim N(0,1) \\ 进行代入和合并同类项x_{t-1} = kx_0 + m[\sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon] + \sigma\epsilon \\ x_{t-1} = (k+m\sqrt{\bar{\alpha_t}})x_0 + \epsilon' \epsilon' \sim N(0,m^2(1-\bar{\alpha_t})+\sigma^2) \\ 同样地,x_{t-1} = \sqrt{\bar{\alpha_{t-1}}}x_0 + \sqrt{1-\bar{\alpha_{t-1}}}\epsilon \\ 则满足对应系数相同k + m\sqrt{\bar{\alpha_t}} = \sqrt{\bar{\alpha_{t-1}}} \\ m^2(1-\bar{\alpha_t})+\sigma^2 = 1 - \bar{\alpha_{t-1}} \\ 求得m = \frac{\sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}}{\sqrt{1-\bar{\alpha_t}}} \\ k = \sqrt{\bar{\alpha_{t-1}}} - \frac{\sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}}{\sqrt{1-\bar{\alpha_t}}}\sqrt{\bar{\alpha_t}} \\ 则表达式为P(x_{t-1}|x_t,x_0) \sim N((\sqrt{\bar{\alpha_{t-1}}} - \frac{\sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}}{\sqrt{1-\bar{\alpha_t}}}\sqrt{\bar{\alpha_t}})x_0 + (\frac{\sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}}{\sqrt{1-\bar{\alpha_t}}})x_t,\sigma^2) \\ \sim N(\sqrt{\bar{\alpha_{t-1}}}x_0 + \sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}\frac{x_t - \sqrt{\bar{\alpha_t}}x_0}{\sqrt{1-\bar{\alpha_t}}},\sigma^2) 采用与反向去噪同样的原理,将上述公式的x_0进行替换,这里采用\epsilon_t是因为前文已经说明过采用的是模型model预测的正态分布x_0 = \frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon_t}{\sqrt{\bar{\alpha_t}}} \\ \epsilon_t = \frac{x_t - \sqrt{\bar{\alpha_t}}x_0}{\sqrt{1-\bar{\alpha_t}}} \\ 最终得到结果,后面的\epsilon采用的是随机采样torch.randn_like(x_t)x_{t-1} = \sqrt{\bar{\alpha_{t-1}}}(\frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon_t}{\sqrt{\bar{\alpha_t}}}) + \sqrt{1-\bar{\alpha_{t-1}}-\sigma^2}\epsilon_t + \sigma^2\epsilon \\ 由于全程推导并未使用\sigma^2则其取值可以为0。且全程推导没有用到马尔可夫的过程x_t = \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon可以认为不需要严格的由x_t算到x_{t-1},令x_{prev}替代x_{t-1},则公式变形为x_{prev} = \sqrt{\bar{\alpha_{prev}}}(\frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon_t}{\sqrt{\bar{\alpha_t}}}) + \sqrt{1-\bar{\alpha_{prev}}-\sigma^2}\epsilon_t + \sigma^2\epsilon \\ 其中的x_t和x_{prev}可以相隔多个迭代步数4.2 代码实现DDPM和DDIM最大的区别就是,DDIM的采样器可以隔多步进行采样,我们可以设置sample_steps用来标记每隔多少步进行采样。通过观察我们DDPM发现x_t和x_{t-1}有如下规律T = 100x_t10099...21x_{t-1}9998...10如果我们加大采样间距可以得到DDIM,这里以sample_steps = 20为例T = 100x_t10080604020x_{t-1}806040200可以发现,x_t的范围为[sample_steps,T]中间间隔sample_steps。x_{t-1}的范围为[0,T-sample_steps],中间的间隔也是为sample_steps。x_t,x_{t-1}的长度是等长的等于T // sample_steps转换为代码如下:t_seq = torch.arange(sample_steps, self.T + 1, sample_steps) # x_t
t_prev_seq = t_seq - sample_steps # x_{prev}根据公式x_0 = \frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon}{\sqrt{\bar{\alpha_t}}},定义计算x_0的函数self.register_buffer('sqrt_recip_alphas_bar', torch.sqrt(1. / alphas_bar))
self.register_buffer('sqrt_recipm1_alphas_bar', torch.sqrt(1. / alphas_bar - 1))
def predict_xstart_from_eps(self, x_t, t, eps):
return (
extract(self.sqrt_recip_alphas_bar, t, x_t.shape) * x_t -
extract(self.sqrt_recipm1_alphas_bar, t, x_t.shape) * eps
)定义\sigma,其值可以为0,也可以为论文里的值alphas = 1. - self.betas
alphas_bar = torch.cumprod(alphas, dim=0)
self.register_buffer('alphas_bar_prev_whole', F.pad(alphas_bar, [1, 0], value=1))
alpha_cumprod_t = extract(self.alphas_bar_prev_whole, t, x_t.shape)
alpha_cumprod_t_prev = extract(self.alphas_bar_prev_whole, prev_t, x_t.shape)
self.ddim_eta = 0 # 默认为0
sigma_t = self.ddim_eta * torch.sqrt(
(1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t) * (1 - alpha_cumprod_t / alpha_cumprod_t_prev))x_{prev} = \sqrt{\bar{\alpha_{prev}}}x_0 + \sqrt{1-\bar{\alpha_{prev}}-\sigma^2}\epsilon_t + \sigma^2\epsilon \\ 完整的计算代码如下:for i, j in tqdm(zip(reversed(list(t_seq)), reversed(list(t_prev_seq))), desc='Inference'):
t = x_t.new_ones([x_T.shape[0], ], dtype=torch.long) * i
prev_t = x_t.new_ones([x_T.shape[0], ], dtype=torch.long) * j
alpha_cumprod_t = extract(self.alphas_bar_prev_whole, t, x_t.shape)
alpha_cumprod_t_prev = extract(self.alphas_bar_prev_whole, prev_t, x_t.shape)
# 根据训练好的ddpm算eps
eps = self.model(x_t, t - 1) # 采用t-1是因为原本的ddpm的0位置元素代表t=1时刻,差了一个1
# 计算x_0,用于第一项
x_0 = self.predict_xstart_from_eps(x_t, t - 1, eps)
if self.clip_denoised:
x_0 = torch.clamp(x_0, min=-1., max=1.) # 裁剪梯度
# 计算sigma,用于第三项
sigma_t = self.ddim_eta * torch.sqrt(
(1 - alpha_cumprod_t_prev) / (1 - alpha_cumprod_t) * (1 - alpha_cumprod_t / alpha_cumprod_t_prev))
# 用于第二项
pred_dir_xt = torch.sqrt(1 - alpha_cumprod_t_prev - sigma_t ** 2) * eps
x_prev = torch.sqrt(alpha_cumprod_t_prev) * x_0 + pred_dir_xt + sigma_t ** 2 * torch.randn_like(x_t)
x_t = x_prev4.3 DDIM推理结果笔者采用sample_steps = 5,来加速采样,在同样的条件下,一张4090的推理速度提升到将近原来的5倍,只需要大概4s的时间即可推理出64张32*32的图片,推理的步骤如下,其生成的图片质量也比较高。笔者也发现当sample_steps取过大的时候,图片的质量往往也不太高,这也是比较好理解的。如果取极限的话,只用一次推理,那最终的结果也一定是不理想的,因为最开始的x_T只是随机的高斯噪声。DDIM采样结果五、总结对DDPM和DDIM中的重要公式进行梳理5.1 DDPM公式梳理首先是定义x_t = \sqrt{\alpha_t}x_{t-1} + \sqrt{1-\alpha_t}\epsilon \\ \epsilon \sim N(0,1) \\ 经过推导可得x_0和x_t的关系x_t = \sqrt{\bar{\alpha_t}}x_0 + \sqrt{1-\bar{\alpha_t}}\epsilon \\ x_0 = \frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon}{\sqrt{\bar{\alpha_t}}} \\ 进一步求得后验概率P(x_{t-1}|x_t) P(x_{t-1}|x_t) = N(\frac{1}{\sqrt{\alpha_t}}(x_t - \frac{1-\alpha_t}{\sqrt{1-\bar{\alpha_t}}}\epsilon),\frac{(1-\alpha_t)(1-\bar{\alpha_{t-1}})}{1-\bar{\alpha_t}}) \\ 其中的\epsilon由模型model提供5.2 DDIM公式推理DDIM只修改了采样器,所以只需要重新定义后验概率即可x_{prev} = \sqrt{\bar{\alpha_{prev}}}(\frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon_t}{\sqrt{\bar{\alpha_t}}}) + \sqrt{1-\bar{\alpha_{prev}}-\sigma^2}\epsilon_t + \sigma^2\epsilon \\ \\ \epsilon_t = \frac{x_t - \sqrt{\bar{\alpha_t}}x_0}{\sqrt{1-\bar{\alpha_t}}} \\ x_0 = \frac{x_t - \sqrt{1-\bar{\alpha_t}}\epsilon_t}{\sqrt{\bar{\alpha_t}}} \\ 其中的\epsilon_t由模型提供,\sigma的值可以为0,x_{prev},x_t中间可以差多个间隔5.3 模型算法Unet、Self-Attention、ResNet、TimeEmbedding编辑于 2023-11-13 13:47・IP 属地中国香港深度学习(Deep Learning)DiffusionAIGC赞同 21824 条评论分享喜欢收藏申请
D-二聚体_百度百科
体_百度百科 网页新闻贴吧知道网盘图片视频地图文库资讯采购百科百度首页登录注册进入词条全站搜索帮助首页秒懂百科特色百科知识专题加入百科百科团队权威合作下载百科APP个人中心收藏查看我的收藏0有用+10D-二聚体播报上传视频本词条由《中国科技信息》杂志社 参与编辑并审核,经科普中国·科学百科认证 。血液中有纤维蛋白,纤维蛋白经过活化和水解,产生特异的降解产物称为“纤维蛋白降解产物。D-二聚体是最简单的纤维蛋白降解产物,D-二聚体水平升高说明体内存在高凝状态和继发性的纤维蛋白溶解亢进。因此,D-二聚体质量浓度对血栓性疾病的诊断、疗效评估和预后判断具有重要的意义。中文名D-二聚体外文名D-Dimer定性阴性定量小于200μg/L图集科普中国致力于权威的科学传播本词条认证专家为杨铁生丨主任医师北京大学人民医院 检验科审核目录1临床意义2正常值参考范围3检查介绍4生理学背景基本信息中文名D-二聚体外文名D-Dimer定 性阴性定 量小于200μg/L临床意义播报编辑D-二聚体来源于纤溶酶溶解的交联纤维蛋白凝块,主要反映纤维蛋白溶解功能。D-二聚体的临床检测主要应用在静脉血栓栓塞(VTE)、深静脉血栓形成(DVT)和肺栓塞(PE)的诊断。增高:见于继发性纤维蛋白溶解功能亢进,如高凝状态、弥散性血管内凝血、肾脏疾病、器官移植排斥反应、溶栓治疗等。心肌梗死、脑梗死、肺栓塞、静脉血栓形成、手术、肿瘤、弥漫性血管内凝血、感染及组织坏死等也可导致D-二聚体升高。正常值参考范围播报编辑不同的试剂正常值范围不同,一般为<0.3mg/L或<0.5mg/L。检查介绍播报编辑血浆D二聚体测定是了解继发性纤维蛋白溶解功能的一个试验。检查原理:抗D-D单克隆抗体包被于胶乳颗粒上,受体血浆中如果存在D-二聚体,将产生抗原-抗体反应,乳胶颗粒发生聚集现象。但是,凡有血块形成的出血,本试验均可呈阳性,故其特异性低,敏感度高。生理学背景播报编辑纤维蛋白溶解系统(fibrinolysis system)是人体最重要的抗凝系统,由4种主要部分组成:纤溶酶原(plasmingen)、纤溶酶原激活剂(plasmingen activator,如t-PA,u-PA)、纤溶酶(plasmin)、纤溶酶抑制物(plasmin activator inhibitor,PAI-1,antiplasmin)。当纤维蛋白凝结块(fibrin clot)形成时,在tPA的存在下,纤溶酶原激活转化为纤溶酶,纤维蛋白溶解过程开始,纤溶酶降解纤维蛋白凝结块形成各种可溶片段,形成纤维蛋白产物(FDP),FDP由下列物质:X-寡聚体(X-oligomer)、D-二聚体(D-Dimer)、中间片段(Intermediate fragments)、片段E(Fragment E)组成。其中,X-寡聚体和D-聚体均含D-二聚体单位。人体纤溶系统,它对保持血管壁的正常通透性,维持血液的流动状态和组织修复起着重要作用。D-二聚体血浆中水平增高说明存在继发性纤溶过程,而先生成凝血酶,后又有纤溶系统活化;并且也反映在血栓形成的局部纤溶酶活性或浓度超过血浆2‰─抗纤溶酶活性或浓度。溶栓治疗是指用药物来活化纤维蛋白溶解系统。一般为投入一种纤溶酶原活化物如尿液酶、链激酶或组织型纤溶酶原活化物(tpA),使大量纤溶酶生成,从而加速已形成血栓的溶解。FDP或D-二聚体生成,则表明达到溶栓效果。纤溶蛋白降解产物中,唯D-二聚体交联碎片可反映血栓形成后的溶栓活性。因此,理论上,D-二聚体的定量检测可定量反映药物的溶栓效果、及可用于诊断、筛选新形成的血栓。但是,到目前为止,商品的D-二聚体检测手段都尚存在一定局限性。其中D-二聚体的胶体金免疫过滤检测法,由于其快速测定、灵敏度高、阴性预报值高,重复性良好,临床医师较多采用。新手上路成长任务编辑入门编辑规则本人编辑我有疑问内容质疑在线客服官方贴吧意见反馈投诉建议举报不良信息未通过词条申诉投诉侵权信息封禁查询与解封©2024 Baidu 使用百度前必读 | 百科协议 | 隐私政策 | 百度百科合作平台 | 京ICP证030173号 京公网安备110000020000关于 DDIM 采样算法的推导 | Ze's Blog
关于 DDIM 采样算法的推导 | Ze's Blog
Ze's Blog
Chinese
English
Notes
Concept
Paper
AIGC
Matrix
Posts
Algorithms
Profile
Github
写在前面
DDIM 出发点
DDIM 推导
DDIM 论文的一些 typo
DDIM 采样
快速采样
讨论
关于 DDIM 采样算法的推导
November 5, 2023
扩散模型
作者: 赖泽强
写在前面
#
DDIM 全称 【Denoising Diffusion Implict Model】,是一篇发表于 ICLR 2021 的论文,不过实际时间也就比 DDPM 晚几个月(2020 年 10 月)就挂在 arXiv 上了。作者呢,则是 Yang Song 博士(也就是 Score-based Diffusion Model 的奠基人)的同门,Jiaming Song 博士, Chenlin Meng 博士(她在Guided Diffusion 蒸馏领域的早期工作获得了 CVPR2023 的 Award Candidate),以及 Stefano Ermon 教授。
老实说,这是一篇比较难懂的论文,一方面是里面涉及大量的推导以及概率论等相关背景知识,另一方面也是推导过程不太直观,以及刚接触的人难以获取到的 intutition,其他也包括一些 notation 的差异和 typo。
以下是个人对 DDIM 的一些理解(数学不好,记性也不行,也当是方便以后查阅的笔记),如果有理解上的错误,还请大佬们批评指正。
撰写过程不仅参考了原论文,还包括了
苏剑林老师的
《生成扩散模型漫谈(四):DDIM = 高观点DDPM》
张振虎的博客
《去噪扩散隐式模型(Denoising Diffusion Implicit Models,DDIM)》
在此向互联网上热心分享的同僚表示敬意。
DDIM 出发点
#
DDIM 虽然是一个比较难的算法,但这并不意味着每一个从事扩散模型研究的人员都需要知道其具体的原理。对于大部分人来说,其实只需要知道
DDIM 是 DDPM 的一种加速采样算法,它可以进行确定性采样(也即给定一个初始的随机噪声,通过DDIM 进行采样,不管采样多少次,最终的结果是一样的,而原始的 DDPM 采样是随机采样,即便初始噪声一致,最终结果也可能不一致),除此之外,DDIM 采样可以在 50 步左右达到 DDPM 1000 步的性能/图片质量。
不过如果想要基于 DDIM 做一些进一步的研究,我们则必须更进一步:
因为不是作者本人,所以我们也无从得知,DDIM 一开始怎么被想出来的。但是从论文以及个人理解来看,DDIM 的推导基石应该是发现了 DDPM 的训练目标实际上只跟 $q(x_t|x_0)$ 这个边缘分布有关,这意味着我们有一簇不同的生成模型可以与之对应,换言之,通过这个训练目标训练出来的模型可以用于不同生成模型的采样过程,如果我们能找到一个收敛更快的生成模型,那么我们实际上变相的加速了 DDPM 的采样(为什么说是变相呢,因为严格来说,这时候的生成模型应该不算是 DDPM 了)。
那么为什么说 DDPM 的训练目标实际上只跟 $q(x_t|x_0)$ 这个边缘分布有关 呢 ?
如果我们从 Latent variable model 的角度考虑 Diffusion Model,我们的隐变量是一个集合 $x_{1:T}$,我们在优化的时候,实际上是在优化下面的 变分下界(ELBO):
$$
\begin{aligned}
log P(x) & = log\int P(x_0|x_{1:T})P(x_{1:T})dx_{1:T} \\
& \geq E_{x\sim Q}[log\frac{ P(x_0|x_{1:T})P(x_{1:T})}{Q(x_{1:T}|x_0)}] \\
& = E_{x\sim Q}[log\frac{ P(x_{0:T})}{Q(x_{1:T}|x_0)}]
\end{aligned}
$$
乍一看,这个优化目标和联合分布 $P(x_{0:T})$ 和 $Q(x_{1:T}|x_0)$ 有关。
但是如果我们把这个优化目标展开,参考 DDPM 原始论文,我们可以得到这么一个优化目标
$$
\mathbb{E}_q[\underbrace{D_{\mathrm{KL}}\left(q\left(\mathbf{x}_T \mid \mathbf{x}_0\right) \| p\left(\mathbf{x}_T\right)\right)}_{L_T}+\sum_{t>1} \underbrace{D_{\mathrm{KL}}\left(q\left(\mathbf{x}_{t-1} \mid \mathbf{x}_t, \mathbf{x}_0\right) \| p_\theta\left(\mathbf{x}_{t-1} \mid \mathbf{x}_t\right)\right)}_{L_{t-1}} \underbrace{-\log p_\theta\left(\mathbf{x}_0 \mid \mathbf{x}_1\right)}_{L_0}]
$$
其中 $L_0$ 和 $L_T$ 都是不训练的,所以实际 loss 里没有他们,对于 $L_{t-1}$,DDPM 的 formulation 是
$$
\mathbb{E}_{\mathbf{x}_0, \boldsymbol{\epsilon}}\left[\frac{\beta_t^2}{2 \sigma_t^2 \alpha_t\left(1-\bar{\alpha}_t\right)}\left\|\boldsymbol{\epsilon}-\boldsymbol{\epsilon}_\theta\left(\sqrt{\bar{\alpha}_t} \mathbf{x}_0+\sqrt{1-\bar{\alpha}_t} \boldsymbol{\epsilon}, t\right)\right\|^2\right]
$$
而 $\sqrt{\bar{\alpha}_t} \mathbf{x}_0+\sqrt{1-\bar{\alpha}_t} \boldsymbol{\epsilon}$ 对应的是 $q(x_t|x_0)$ 的采样过程。
所以,实际上 DDPM 的优化目标实际上只与 $q(x_t|x_0)$ 有关,因此,通过这个优化目标训练出来的模型,可以用到任何具有相同 $q(x_t|x_0)$ 的生成模型。
DDIM 推导
#
在理解出发点之后,我们的目标就很明确了,怎么找一个具有相同 $q(x_t|x_0)$ 的生成模型(论文里称之为 generative process)。这部分就是 DDIM 原论文第三章《Variational Inference for Non-Markovian Forward Processes》的内容。
这一章作者提到他们使用了一个 non-Markovian 的 inference process 去替代 DDPM 的 Markovian inference process,至于二者的区别可以在后面再细究。
马尔科夫过程
每个状态转移到下一个状态的概率只与当前状态有关。
原论文里 3.1 节是比较魔幻,容易让人莫名其妙的,因为作者这里直接给出来他们推导出来的结果,然后在附录里附上了证明过程,证明该 generative process 和 DDPM 具有相同的 $q(x_t|x_0)$,但这个结果肯定不是拍脑袋出来的,在苏剑林老师的博客里,他通过待定系数法,从约束出发呈现了该结果的推导过程,虽然这不一定是原作者的真实推导过程,但一定程度上也可以供参考:
首先我们回顾一下 DDPM 的推导过程
$$
p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})\xrightarrow{\text{推导}}p(\boldsymbol{x}_t|\boldsymbol{x}_0)\xrightarrow{\text{推导}}q(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)\xrightarrow{\text{近似}}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)
$$
其中采样过程理论上需要用到 $p(x_{t-1}|x_t)$, 但实际上采用 $q(x_{t-1}|x_t, x_0)$ 做近似,因此我们需要做的就是推导一个不一样的 $q(x_{t-1}|x_t, x_0)$,在原论文里,作者直接写出了这个公式(原论文的公式 7):
$$
q_\sigma\left(\boldsymbol{x}_{t-1} \mid \boldsymbol{x}_t, \boldsymbol{x}_0\right)=\mathcal{N}\left(\sqrt{\alpha_{t-1}} \boldsymbol{x}_0+\sqrt{1-\alpha_{t-1}-\sigma_t^2} \cdot \frac{\boldsymbol{x}_t-\sqrt{\alpha_t} \boldsymbol{x}_0}{\sqrt{1-\alpha_t}}, \sigma_t^2 \boldsymbol{I}\right) .
$$
那么怎么推导呢?在 DDPM 的论文里,我们有
$$
\begin{aligned}
q\left(\mathbf{x}_{t-1} \mid \mathbf{x}_t, \mathbf{x}_0\right) & =\mathcal{N}\left(\mathbf{x}_{t-1} ; \tilde{\boldsymbol{\mu}}_t\left(\mathbf{x}_t, \mathbf{x}_0\right), \tilde{\beta}_t \mathbf{I}\right) \\
\text { where } \quad \tilde{\boldsymbol{\mu}}_t\left(\mathbf{x}_t, \mathbf{x}_0\right) & :=\frac{\sqrt{\bar{\alpha}_{t-1}} \beta_t}{1-\bar{\alpha}_t} \mathbf{x}_0+\frac{\sqrt{\alpha_t}\left(1-\bar{\alpha}_{t-1}\right)}{1-\bar{\alpha}_t} \mathbf{x}_t \quad \text { and } \quad \tilde{\beta}_t:=\frac{1-\bar{\alpha}_{t-1}}{1-\bar{\alpha}_t} \beta_t
\end{aligned}
$$
可以看到 $q(x_{t-1}|x_t, x_0)$ 这里是一个正态分布,基于此,我们可以假设一个更general的形式,我们假设:
$$
q(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I})
$$
回忆
以下推导基于苏剑林的博客和原论文附录 B
我们的约束是和 DDPM 具有相同的 $q(x_t|x_0)$,那么根据归纳法,这等价于约束和 DDPM 具有相同的 $q(x_{t-1}|x_0)$。
为什么要做这个转换呢?
因为如果我们想要约束 $q(x_t|x_0)$,那么直觉上第一步是建立 $q(x_t|x_0)$ 和 $q(x_{t-1}|x_t, x_0)$ 的联系,这可以通过贝叶斯公式实现:
$$
q(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) \sim p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \frac{p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)}{p(\boldsymbol{x}_t|\boldsymbol{x}_0)}
$$
因为 q 分布是对 p 分布的等价近似,下面使用 p 分布进行推导。
首先进行一个等价变换
$$
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t|\boldsymbol{x}_0) = p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1}) p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)
$$
左右两边同时积分消掉 $p(x_t|x_{t-1})$ 得到
$$
\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t|\boldsymbol{x}_0) d\boldsymbol{x}_t = p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)\label{eq:margin}
$$
关于这个转换,概率论小白一开始表示非常难以理解,不过仔细想想,右边之所以能消掉是因为 $p(x_{t-1}|x_0)$ 跟 $x_t$ 无关,所以对右边积分,等价于 $p(x_{t-1}|x_0) \int p(x_t|x_{t-1}) dx_t = p(x_{t-1}|x_0) \times 1$。
另外,其实这玩意也怪怪的 $\int p(x_t|x_{t-1}) dx_t = 1$,因为我们一般见到的是边缘概率的形式 $\int p(x_t) dx_t = 1$,而非前面那种条件概率。不过等式左边积分实际上 $x_t$ 每个取值的和,但$x_t$ 本身是关于 $x_{t-1}$ 的函数,不过因为是概率密度函数,所以和自然也还是 1 。
回到刚刚的积分,等式左边都包含有 $x_t$,自然是没法像右边一样消掉。
可以看到,我们至此得到了 $p(x_{t-1}|x_0)$ 的表达式,我们只要把前面假设的更 general 的 $q(x_{t-1}|x_t, x_0)$(如下) 的代入等式,用待定系数法求解即可得到 $q(x_{t-1}|x_t, x_0)$ 的表达式。
$$
q(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I})
$$
待定系数法求解
那么问题来了,我们知道 $p(x_{t-1}|x_0) = N(\sqrt{\alpha_{t-1}}x_0, (1-\alpha_{t-1}) I)$,但等式里的积分怎么办?
注意
这里的 $\alpha_{t-1}$ 等价于 DDPM 里的 $\bar{\alpha_{t-1}}$,作者在附录里有专门讲为什么他要用这套 notation。
DDIM 里的原论文是先给出了 $p(x_{t-1}|x_t, x_0)$,然后基于条件 $p(x_{t}|x_0) = N(\sqrt{\alpha_{t}}, (1-\alpha_{t}) I)$,通过 Bishop (2006) (2.115), 证明等式成立。
Bishop(2006): Christopher M Bishop. Pattern recognition and machine learning. springer, 2006.
那 Bishop(2006) 2.115 究竟讲了个什么呢,没有书的看
这里。
Bishop(2006) 2.115
这里直接截图了,其实讲的就是当给定一个高斯的边缘分布 $p(x)$ 和条件高斯分布$p(y|x)$时,另一个边缘分布是 $p(y)$ 是怎么样的。利用这个性质(2.115),我们可以把它套到之前的等式里。
$$
\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t|\boldsymbol{x}_0) d\boldsymbol{x}_t = p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)
$$
不过直接套,还是有一些心理障碍,因为这个 $x_0$ 摆在上面看着实在憋屈,没法直接对应过来。直觉上,把 $x_0$ 直接去掉等式应该也是成立的,不过我不知道这种做法的出处是什么,如果有佬知道,还请麻烦留个言,不胜感激。
当我们去掉$x_0$之后,那就简单了(第一个等号利用了全概率公式)
$$
\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t) p(\boldsymbol{x}_t) d\boldsymbol{x}_t = p(\boldsymbol{x}_{t-1}) = p(\boldsymbol{x}_{t-1})
$$
还原回来
$$
\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t| \boldsymbol{x}_0) d\boldsymbol{x}_t = p(\boldsymbol{x}_{t-1}| \boldsymbol{x}_0) = p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)
$$
结合 Bishop(2006) 2.115,以及
$$
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I})
$$
$$
p(\boldsymbol{x}_{t}|\boldsymbol{x}_0) = \mathcal{N}(\sqrt{\alpha_{t}}x_0, (1-\alpha_{t}) \boldsymbol{I})
$$
其中 $A = \kappa_t$, $b=\lambda_t x_0$, $\mu=\sqrt{\alpha_{t}}x_0$
我们可以推导出等式左边,也即 $p(x_{t-1}|x_0)$ 的均值为:
$$
A\mu + b = \kappa_t \sqrt{\alpha_{t}}x_0 + \lambda_t x_0
$$
方差为:
$$
\mathbf{L}^{-1}+\mathbf{A} \mathbf{\Lambda}^{-1} \mathbf{A}^{\mathrm{T}} = \sigma^2_t \boldsymbol{I} + \kappa_t (1-\alpha_{t}) \boldsymbol{I} \kappa_t^T
$$
讲了半天终于把等式两边都求出来了,这下可以列方程求解了,回顾等式右边是 $p(x_{t-1}|x_0) = N(\sqrt{\alpha_{t-1}}x_0, (1-\alpha_{t-1}) I)$,那么我们有两个方程:
$$
\sqrt{\alpha_{t-1}}x_0 = \kappa_t \sqrt{\alpha_{t}}x_0 + \lambda_t x_0
$$
$$
(1-\alpha_{t-1}) \boldsymbol{I} = \sigma^2_t \boldsymbol{I} + \kappa_t (1-\alpha_{t}) \boldsymbol{I} \kappa_t^T
$$
由第二个方程,我们可以解出:
$$
\kappa_t = \sqrt{\frac{1-\alpha_{t-1} - \sigma^2_t}{1-\alpha_{t}}}
$$
结合上述结果和第一个方程,我们可以解出:
$$
\lambda_t = \sqrt{\alpha_{t-1}} - \sqrt{\frac{1-\alpha_{t-1} - \sigma^2_t}{1-\alpha_{t}}} \sqrt{\alpha_{t}}
$$
代回
$$
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I})
$$
我们就可以得到
\begin{aligned}
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) &= \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + (\sqrt{\alpha_{t-1}}- \kappa_t \sqrt{\alpha_{t}}) \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I}) \\
&=\mathcal{N}(\boldsymbol{x}_{t-1}; \sqrt{\alpha_{t-1}} \boldsymbol{x}_0 + \kappa_t (\boldsymbol{x}_t - \sqrt{\alpha_{t}} \boldsymbol{x}_0), \sigma_t^2 \boldsymbol{I}) \\
&=\mathcal{N}(\boldsymbol{x}_{t-1}; \sqrt{\alpha_{t-1}} \boldsymbol{x}_0 + \sqrt{\frac{1-\alpha_{t-1} - \sigma^2_t}{1-\alpha_{t}}} (\boldsymbol{x}_t - \sqrt{\alpha_{t}} \boldsymbol{x}_0), \sigma_t^2 \boldsymbol{I})
\end{aligned}
至此我们就得到了 DDIM 论文里的直接给出的公式 7 了。
DDIM 论文的一些 typo
#
附录 B 里的公式 22 和 23 应该是打错了, 方差不应该带根号,原论文多了个根号,应该是下面这个公式
$$
p(x_{t}|x_0) = N(\sqrt{\alpha_{t}}x_0, (1-\alpha_{t}) I)
$$
DDIM 采样
#
基于推导出来的 $p(x_{t-1}|x_t, x_0)$,我们可以很容易写出下面的采样公式(利用重参数化技巧):
$$
x_{t-1} = \sqrt{\alpha_{t-1}} \boldsymbol{x}_0 + \sqrt{\frac{1-\alpha_{t-1} - \sigma^2_t}{1-\alpha_{t}}} (\boldsymbol{x}_t - \sqrt{\alpha_{t}} \boldsymbol{x}_0) + \sigma_t^2 \epsilon_t
$$
然后,我们需要用网络预测的 $x_0$ 替代上面的 $x_0$。对于 epsilon 参数化的网络 $ \epsilon_{\theta}^{(t)}$,即网络预测的是
$$
x_t = \sqrt{\alpha_{t}}x_0 + \sqrt{(1-\alpha_{t})} \epsilon_{\theta}^{(t)}(x_t)
$$
我们有
$$
x_0 = \frac{x_t - \sqrt{(1-\alpha_{t})} \epsilon_{\theta}^{(t)}(x_t)}{\sqrt{\alpha_{t}}}
$$
把这个式子往回代,我们就得到了最终的采样公式(对应原论文的公式 12)
$$
\boldsymbol{x}_{t-1}=\sqrt{\alpha_{t-1}} \underbrace{\left(\frac{\boldsymbol{x}_{t}-\sqrt{1-\alpha_{t}} \epsilon_{\theta}^{(t)}\left(\boldsymbol{x}_{t}\right)}{\sqrt{\alpha_{t}}}\right)}_{\text {"predicted } \boldsymbol{x}_{0} "}+\underbrace{\sqrt{1-\alpha_{t-1}-\sigma_{t}^{2}} \cdot \epsilon_{\theta}^{(t)}\left(\boldsymbol{x}_{t}\right)}_{\text {"direction pointing to } \boldsymbol{x}_{t} \text { " }}+\underbrace{\sigma_{t} \epsilon_{t}}_{\text {random noise }}
$$
快速采样
#
事实上,DDIM 本身并不能带来加速效果,DDIM 快速采样的原理本质上就是 Stride Sampling,即间隔采样。而间隔采样的基石则是
DDPM的训练结果实质上包含了它的任意子序列参数的训练结果。
具体地,DDPM 的训练过程可以用一个 $\alpha_t$ 的序列定义,每个 step 都是随机采样一个 $\alpha_t$ 对样本进行加噪,然后训练一个去噪模型预测噪声。在训练阶段,t 最大取值为 T,一般为 1000。
朴素的采样方式是 1000,999,998,997 一步一步采样。
间隔采样则是取1000,980,960,940 一步一步采样(采样总步数为 50)。
讨论
#
事实上,通过 DDIM 的推导,我们得到了一个更加 general 的采样过程 $q(x_{t-1}|x_t, x_0)$,这个采样过程包含一个可以调节的参数 $\sigma_t$,通过选取的不同的值,我们可以得到一簇不同的采样过程。
例如,如果选取 $\sigma_t = 0$,那么我们就得了一个确定性的采样过程。
除此之外,我们还可以选取一个特殊的 $\sigma_t$ 得到 DDPM 的 $q(x_{t-1}|x_t, x_0)$,也就是说 DDPM 是 DDIM 的一个特例。事实上,这个也很好理解,因为我们在推导的时候是根据 DDPM 的形式,撰写了一个更 general 的表达式,那么推导出来必然有这个结果。
那么怎么选取$\sigma_t$才能获得最佳的加速效果呢?
为此,作者做了一些实验,作者选取的$\sigma_t$为,通过控制$\eta$可以控制其大小。
$$
\sigma_{\tau_i}(\eta)=\eta \sqrt{\left(1-\alpha_{\tau_{i-1}}\right) /\left(1-\alpha_{\tau_i}\right)} \sqrt{1-\alpha_{\tau_i} / \alpha_{\tau_{i-1}}}
$$
事实上,当$\eta=1$ 的时候我们就变成 DDPM 了,这也对应上我们刚刚提到的特殊的 $\sigma_t$。
可以看到当 $\eta = 0$ 的时候,快速采样效果是最好的。
一个有趣的性质
对于加噪过程,我们有
$$
x_t = \sqrt{\alpha_{t}}x_0 + \sqrt{(1-\alpha_{t})} \epsilon_{\theta}^{(t)}(x_t)
$$
$$
x_{t-1} = \sqrt{\alpha_{t-1}}x_0 + \sqrt{(1-\alpha_{t-1})} \epsilon_{\theta}^{(t-1)}(x_{t-1})
$$
而当 $\sigma_t = 0$ 时,我们有
$$
x_{t-1} = \sqrt{\alpha_{t-1}}x_0 + \sqrt{(1-\alpha_{t-1})} \epsilon_{\theta}^{(t)}(x_t)
$$
所以可以看出 DDIM $\eta = 0$ 实际上用 $\epsilon_{\theta}^{(t)}(x_t)$ 去近似 $\epsilon_{\theta}^{(t-1)}(x_{t-1})$ 。不过因为 $x_0$ 也是预测出来的,所以这里面还会有一个误差。
进一步思考,DDIM 的性能应该取决于相邻两个时间步预测结果的一致性,这也意味着,如果我们用相邻步数的结果去 finetune 一下 SD,即用噪声小的,也就是 t 更小的步数去监督 t+1 步,这样应该可以让性能更好。很好,然后我们就得到了 Consistency Model?
November 6, 2023
写在前面
DDIM 出发点
DDIM 推导
DDIM 论文的一些 typo
DDIM 采样
快速采样
讨论
请问心肺功能五项DDIM是什么检查? - 知乎
请问心肺功能五项DDIM是什么检查? - 知乎首页知乎知学堂发现等你来答切换模式登录/注册临床医学心脑血管疾病请问心肺功能五项DDIM是什么检查?父亲因身体不适送医院就医,医生只是告诉我这一项偏高,让我签了病危通知书,但没过多解释,请问这一项是关于什么的?关注者4被浏览2,086关注问题写回答邀请回答好问题添加评论分享暂时还没有回答,开始写第一
DDIM 简明讲解与 PyTorch 实现:加速扩散模型采样的通用方法 - 知乎
DDIM 简明讲解与 PyTorch 实现:加速扩散模型采样的通用方法 - 知乎首发于周弈帆的博客切换模式写文章登录/注册DDIM 简明讲解与 PyTorch 实现:加速扩散模型采样的通用方法周弈帆相比于多数图像生成模型,去噪扩散概率模型(Denoising Diffusion Probabilistic Model, DDPM)的采样速度非常慢。这是因为DDPM在采样时通常要做1000次去噪操作。但如果你玩过基于扩散模型的图像生成应用的话,你会发现,大多数应用只需要20次去噪即可生成图像。这是为什么呢?原来,这些应用都使用了一种更快速的采样方法——去噪扩散隐式模型(Denoising Diffusion Implicit Model, DDIM)。基于DDPM,DDIM论文主要提出了两项改进。第一,对于一个已经训练好的DDPM,只需要对采样公式做简单的修改,模型就能在去噪时「跳步骤」,在一步去噪迭代中直接预测若干次去噪后的结果。比如说,假设模型从时刻T=100开始去噪,新的模型可以在每步去噪迭代中预测10次去噪操作后的结果,也就是逐步预测时刻t=90, 80, ..., 0的结果。这样,DDPM的采样速度就被加速了10倍。第二,DDIM论文推广了DDPM的数学模型,从更高的视角定义了DDPM的前向过程(加噪过程)和反向过程(去噪过程)。在这个新数学模型下,我们可以自定义模型的噪声强度,让同一个训练好的DDPM有不同的采样效果。在这篇文章中,我将言简意赅地介绍DDIM的建模方法,并给出我的DDIM PyTorch实现与实验结果。本文不会深究DDIM的数学推导,对这部分感兴趣的读者可以去阅读我在文末给出的参考资料。回顾 DDPMDDIM是建立在DDPM之上的一篇工作。在正式认识DDIM之前,我们先回顾一下DDPM中的一些关键内容,再从中引出DDIM的改进思想。DDPM是一个特殊的VAE。它的编码器是T步固定的加噪操作,解码器是T步可学习的去噪操作。模型的学习目标是让每一步去噪操作尽可能抵消掉对应的加噪操作。DDPM的加噪和去噪操作其实都是在某个正态分布中采样。因此,我们可以用概率q, p分别表示加噪和去噪的分布。比如q(\mathbf{x}_t | \mathbf{x}_{t-1})就是由第t-1时刻的图像到第t时刻的图像的加噪声分布,p(\mathbf{x}_{t-1} | \mathbf{x}_{t})就是由第t时刻的图像到第t-1时刻的图像的去噪声分布。这样,我们可以说网络的学习目标是让p(\mathbf{x}_{t-1} | \mathbf{x}_{t})尽可能与q(\mathbf{x}_t | \mathbf{x}_{t-1})和互逆。但是,「互逆」并不是一个严格的数学表述。更具体地,我们应该让分布p(\mathbf{x}_{t-1} | \mathbf{x}_{t})和分布q(\mathbf{x}_{t-1} | \mathbf{x}_{t})尽可能相似。q(\mathbf{x}_{t-1} | \mathbf{x}_{t})和p(\mathbf{x}_{t-1} | \mathbf{x}_{t})的关系就和VAE中原图像与重建图像的关系一样。q(\mathbf{x}_{t-1} | \mathbf{x}_{t})是不好求得的,但在给定了输入数据\mathbf{x}_{0}时,q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_{0})是可以用贝叶斯公式求出来的:q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0) = q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)\frac{q(\mathbf{x}_{t-1} | \mathbf{x}_0)}{q(\mathbf{x}_{t} | \mathbf{x}_0)} \\我们不必关心具体的求解方法,只需要知道从等式右边的三项q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)、q(\mathbf{x}_{t-1} | \mathbf{x}_0)、q(\mathbf{x}_{t} | \mathbf{x}_0)可以推导出等式左边的那一项。在DDPM中,q(\mathbf{x}_{t} | \mathbf{x}_{t - 1})是一个定义好的式子,且q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}) = q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)。根据q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}),可以推出q(\mathbf{x}_{t} | \mathbf{x}_0)。知道了q(\mathbf{x}_{t} | \mathbf{x}_0),q(\mathbf{x}_{t-1} | \mathbf{x}_0)也就知道了(把公式里的t换成t-1就行了)。这样,在DDPM中,等式右边的式子全部已知,等式左边的q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_{0})可以直接求出来。上述推理过程可以简单地表示为:知道q(\mathbf{x}_{t} | \mathbf{x}_0)和q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0),就知道了神经网络的学习目标q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)。这几个公式在DDPM中的具体形式如下:\begin{aligned} q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0) &=\mathcal{N}(\mathbf{x}_{t};\sqrt{1 - \beta_t}\mathbf{x}_{t - 1},\beta_t\mathbf{I}) \\ q(\mathbf{x}_{t} | \mathbf{x}_0)&=\mathcal{N}(\mathbf{x}_{t}; \sqrt{\bar{\alpha}_t}\mathbf{x}_{0}, (1-\bar{\alpha}_t)\mathbf{I}) \\ q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0) &= \mathcal{N}(\mathbf{x}_{t-1}; \frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1 - \bar{\alpha}_{t}}\mathbf{x}_{t}+\frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1 - \bar{\alpha}_{t}}\mathbf{x}_{0}, \frac{1-\bar{\alpha}_{t-1}}{1 - \bar{\alpha}_{t}} \cdot \beta_t\mathbf{I}) \end{aligned} \\其中,只有参数\beta_t是可调的。\bar{\alpha}_t是根据\beta_t算出的变量,其计算方法为:\alpha_t=1-\beta_t, \bar{\alpha}_t=\prod_{i=1}^t\alpha_i。由于学习目标q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)里只有一个未知变量\mathbf{x}_0,DDPM把学习目标简化成了只让神经网络根据\mathbf{x}_{t}拟合公式里的\mathbf{x}_{0}(更具体一点,是拟合从\mathbf{x}_{0}到\mathbf{x}_{t}的噪声)。也就是说,在训练时,q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)的公式不会被用到,只有\mathbf{x}_{t}和\mathbf{x}_{0}两个量之间的公式q(\mathbf{x}_{t} | \mathbf{x}_0)会被用到。只有在采样时,q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)的公式才会被用到。训练目标的推理过程可以总结为: 理解「DDPM的训练目标只有\mathbf{x}_{0}」对于理解DDIM非常关键。如果你在回顾DDPM时出现了问题,请再次阅读DDPM的相关介绍文章。 加速 DDPM我们再次审视一下DDPM的推理过程:首先有q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}) = q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)。根据q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}),可以推出q(\mathbf{x}_{t} | \mathbf{x}_0)。知道q(\mathbf{x}_{t} | \mathbf{x}_0)和q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0),由贝叶斯公式,就知道了学习目标q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)。根据这一推理过程,DDIM论文的作者想到,假如我们把贝叶斯公式中的t替换成t_2, t-1替换成t_1,其中t_2是比t_1大的任意某一时刻,那么我们不就可以从t_2到t_1跳步骤去噪了吗?比如令t_2 = t_1 + 10,我们就可以求出去除10次噪声的公式,去噪的过程就快了10倍。q(\mathbf{x}_{t_1} | \mathbf{x}_{t_2}, \mathbf{x}_0) = q(\mathbf{x}_{t_2} | \mathbf{x}_{t_1}, \mathbf{x}_0)\frac{q(\mathbf{x}_{t_1} | \mathbf{x}_0)}{q(\mathbf{x}_{t_2} | \mathbf{x}_0)} \\修改之后,q(\mathbf{x}_{t_1} | \mathbf{x}_0)和q(\mathbf{x}_{t_2} | \mathbf{x}_0)依然很好求,只要把t_1, t_2代入普通的q(\mathbf{x}_{t} | \mathbf{x}_0)公式里就行。q(\mathbf{x}_{t} | \mathbf{x}_0)=\mathcal{N}(\mathbf{x}_{t}; \sqrt{\bar{\alpha}_t}\mathbf{x}_{0}, (1-\bar{\alpha}_t)\mathbf{I}) \\但是,q(\mathbf{x}_{t_2} | \mathbf{x}_{t_1}, \mathbf{x}_0)怎么求呢?原来的q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)=\mathcal{N}(\mathbf{x}_{t};\sqrt{1 - \beta_t}\mathbf{x}_{t - 1},\beta_t\mathbf{I})来自于DDPM的定义,我们能直接把公式拿来用。能不能把q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)的公式稍微修改一下,让它兼容q(\mathbf{x}_{t_2} | \mathbf{x}_{t_1}, \mathbf{x}_0)呢?修改q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)的思路如下:假如我们能把公式中的\beta_t换成一个由t和t-1决定的变量,我们就能把t换成t_2,t-1换成t_1,也就得到了q(\mathbf{x}_{t_2} | \mathbf{x}_{t_1}, \mathbf{x}_0)。那怎么修改\beta_t的形式呢?很简单。我们知道\beta_t决定了\bar{\alpha}_t:\alpha_t=1-\beta_t, \bar{\alpha}_t=\prod_{i=1}^t\alpha_i。那么我们用\bar{\alpha}_t除以\bar{\alpha}_{t-1},不就得到了1-\beta_t了吗?也就是说:\beta_t = 1-\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}. \\我们把这个用\bar{\alpha}_t和\bar{\alpha}_{t-1}表示的\beta_t套入q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)的公式里,再把t换成t_2,t-1换成t_1,就得到了q(\mathbf{x}_{t_2} | \mathbf{x}_{t_1}, \mathbf{x}_0)。有了这一项,贝叶斯公式等式右边那三项我们就全部已知,可以求出q(\mathbf{x}_{t_1} | \mathbf{x}_{t_2}, \mathbf{x}_0),也就是可以一次性得到多个时刻后的去噪结果。在这个过程中,我们只是把DDPM公式里的\bar{\alpha}_t换成\bar{\alpha}_{t2},\bar{\alpha}_{t-1}换成\bar{\alpha}_{t1},公式推导过程完全不变。网络的训练目标\mathbf{x}_{0}也没有发生改变,只是采样时的公式需要修改。这意味着我们可以先照着原DDPM的方法训练,再用这种更快速的方式采样。我们之前只讨论了t_1到t_2为固定值的情况。实际上,我们不一定要间隔固定的时刻去噪一次,完全可以用原时刻序列的任意一个子序列来去噪。比如去噪100次的DDPM的去噪时刻序列为[99, 98, ..., 0],我们可以随便取一个长度为10的子序列:[99, 98, 77, 66, 55, 44, 33, 22, 1, 0],按这些时刻来去噪也能让采样速度加速10倍。但实践中没人会这样做,一般都是等间距地取时刻。这样看来,在采样时,只有部分时刻才会被用到。那我们能不能顺着这个思路,干脆训练一个有效时刻更短(总时刻T不变)的DDPM,以加速训练呢?又或者保持有效训练时刻数不变,增大总时刻T呢?DDIM论文的作者提出了这些想法,认为这可以作为后续工作的研究方向。从 DDPM 到 DDIM除了加速DDPM外,DDIM论文还提出了一种更普遍的DDPM。在这种新的数学模型下,我们可以任意调节采样时的方差大小。让我们来看一下这个数学模型的推导过程。DDPM的学习目标q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)由q(\mathbf{x}_{t} | \mathbf{x}_0)和q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)决定。具体来说,在求解正态分布q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0)时,我们会将它的均值\tilde{\mu}_t和方差\tilde{\beta}_t设为未知量,并将条件q(\mathbf{x}_{t} | \mathbf{x}_0)、q(\mathbf{x}_{t-1} | \mathbf{x}_0)、q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)代入,求解出确定的\tilde{\mu}_t和\tilde{\beta}_t。在上文我们分析过,DDPM训练时只需要拟合\mathbf{x}_0,只需要用到\mathbf{x}_0和\mathbf{x}_t的关系q(\mathbf{x}_{t} | \mathbf{x}_0)。在不修改训练过程的前提下,我们能不能把限制q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)去掉(即q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)可以是任意一个正态分布,而不是我们提前定义好的一个正态分布),得到一个更普遍的DDPM呢?这当然是可以的。根据基础的解方程知识,我们知道,去掉一个方程后,会多出一个自由变量。取消了q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)的限制后,均值\tilde{\mu}_t和方差\tilde{\beta}_t就不能同时确定下来了。我们可以令方差\tilde{\beta}_t为自由变量,并让\tilde{\mu}_t用含\tilde{\beta}_t的式子表示出来。这样,我们就得到了一个方差可变的更一般的DDPM。让我们来看一下这个新模型的具体公式。原来的DDPM的加噪声逆操作的分布为:\begin{aligned} q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0) = & \mathcal{N}(\mathbf{x}_{t-1}; \\ &\frac{\sqrt{\alpha_t}(1-\bar{\alpha}_{t-1})}{1 - \bar{\alpha}_{t}}\mathbf{x}_{t}+\frac{\sqrt{\bar{\alpha}_{t-1}}\beta_t}{1 - \bar{\alpha}_{t}}\mathbf{x}_{0}, \\ &\frac{1-\bar{\alpha}_{t-1}}{1 - \bar{\alpha}_{t}} \cdot \beta_t\mathbf{I}) \end{aligned} \\新的分布公式为:\begin{aligned} q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0) &= \mathcal{N}(\mathbf{x}_{t-1}; \\ &\sqrt{\bar{\alpha}_{t-1}} \mathbf{x}_{0}+\sqrt{1-\bar{\alpha}_{t-1}-\tilde{\beta}_t} \cdot \frac{\mathbf{x}_{t}-\sqrt{\bar{\alpha}_t}\mathbf{x}_{0}}{\sqrt{1 - \bar{\alpha}_{t}}}, \\ &\tilde{\beta}_t\mathbf{I}) \end{aligned} \\新公式是旧公式的一个推广版本。如果我们把DDPM的方差(1-\bar{\alpha}_{t-1})/(1 - \bar{\alpha}_{t}) \cdot \beta_t代入新公式里的\tilde{\beta}_t,就能把新公式还原成DDPM的公式。和DDPM的公式一样,我们也可以把\mathbf{x}_{0}拆成\mathbf{x}_{t}和噪声\epsilon表示的式子。现在采样时方差可以随意取了,我们来讨论一种特殊的方差取值——\tilde{\beta}_t=0。也就是说,扩散模型的反向过程变成了一个没有噪声的确定性过程。给定随机噪声\mathbf{x}_{T},我们只能得到唯一的采样结果\mathbf{x}_{0}。这种结果确定的概率模型被称为隐式概率模型(implicit probabilistic model)。所以,论文作者把方差为0的这种扩散模型称为DDIM(Denoising Diffusion Implicit Model)。为了方便地选取方差值,作者将方差改写为\tilde{\beta}_t(\eta)=\eta\frac{(1-\bar{\alpha}_{t-1})}{(1 - \bar{\alpha}_{t})} \cdot \beta_t \\其中,\eta\in[0, 1]。通过选择不同的\eta,我们实际上是在DDPM和DDIM之间插值。\eta控制了插值的比例。\eta=0,模型是DDIM;\eta=1,模型是DDPM。除此之外,DDPM论文曾在采样时使用了另一种方差取值:\tilde{\beta}_t=\beta_t,即去噪方差等于加噪方差。实验显示这个方差的采样结果还不错。我们可以把这个取值也用到DDIM论文提出的方法里,只不过这个方差值不能直接套进上面的公式。在代码实现部分我会介绍该怎么在DDIM方法中使用这个方差。注意,在这一节的推导过程中,我们依然没有修改DDPM的训练目标。我们可以把这种的新的采样方法用在预训练的DDPM上。当然,我们可以在使用新的采样方法的同时也使用上一节的加速采样方法。实验到这里为止,我们已经学完了DDIM论文的两大内容:加速采样、更换采样方差。加速采样的意义很好理解,它能大幅减少采样时间。可更换采样方差有什么意义呢?我们看完论文中的实验结果就知道了。论文展示了新采样方法在不同方差、不同采样步数下的FID指标(越小越好)。其中,\hat{\sigma}表示使用DDPM中的\tilde{\beta}_t=\beta_t方差取值。实验结果非常有趣。在使用采样加速(步数比总时刻1000要小)时,\eta=0的DDIM的表现最好,而\hat{\sigma}的情况则非常差。而当\eta增大,模型越来越靠近DDPM时,用\hat{\sigma}的结果会越来越好。而在DDPM中,用\hat{\sigma}的结果是最好的。从这个实验结果中,我们可以得到一条很简单的实践指南:如果使用了采样加速,一定要用效果最好的DDIM;而使用原DDPM的话,可以维持原论文提出的\tilde{\beta}_t=\beta_t方差取值。总结DDIM论文提出了DDPM的两个拓展方向:加速采样、变更采样方差。通过同时使用这两个方法,我们能够在不重新训练DDPM、尽可能不降低生成质量的前提下,让扩散模型的采样速度大幅提升(一般可以快50倍)。让我们再从头理一理提出DDIM方法的思考过程。为了能直接使用预训练的DDPM,我们希望在改进DDPM时不更改DDPM的训练过程。而经过简化后,DDPM的训练目标只有拟合\mathbf{x}_{0},训练时只会用到前向过程公式q(\mathbf{x}_{t} | \mathbf{x}_0)=\mathcal{N}(\mathbf{x}_{t}; \sqrt{\bar{\alpha}_t}\mathbf{x}_{0}, (1-\bar{\alpha}_t)\mathbf{I})。所以,我们的改进应该建立在公式q(\mathbf{x}_{t} | \mathbf{x}_0)完全不变的前提下。通过对DDPM反向过程公式的简单修改,也就是把t改成t_2,t-1改成t_1,我们可以把去噪一步的公式改成去噪多步的公式,以大幅加速DDPM。可是,这样改完之后,采样的质量会有明显的下降。我们可以猜测,减少了采样迭代次数后,采样质量之所以下降,是因为每次估计的去噪均值更加不准确。而每次去噪迭代中的噪声(由方差项决定的那一项)放大了均值的不准确性。我们能不能干脆让去噪时的方差为0呢?为了让去噪时的方差可以自由变动,我们可以去掉DDPM的约束条件。由于贝叶斯公式里的q(\mathbf{x}_{t} | \mathbf{x}_0)不能修改,我们只能去掉q(\mathbf{x}_{t} | \mathbf{x}_{t - 1}, \mathbf{x}_0)的限制。去掉限制后,方差就成了自由变量。我们让去噪方差为0,让采样过程没有噪声。这样,就得到了本文提出的DDIM模型。实验证明,在采样迭代次数减少后,使用DDIM的生成结果是最优的。在本文中,我较为严格地区分了DDPM和DDIM的叫法:DDPM指DDPM论文中提出的有1000个扩散时刻的模型,它的采样方差只有两种取值(\tilde{\beta}_t=(1-\bar{\alpha}_{t-1})/(1 - \bar{\alpha}_{t}) \cdot \beta_t, \tilde{\beta}_t=\beta_t)。DDIM指DDIM论文中提出的\eta=0的推广版DDPM模型。DDPM和DDIM都可以使用采样加速。但是,从习惯上我们会把没有优化加速的DDPM称为"DDPM",把\eta可以任取,采样迭代次数可以任取的采样方法统称为"DDIM"。一些开源库中会有叫DDIMSampler的类,调节\eta的参数大概会命名为eta,调节迭代次数的参数大概会命名为ddim_num_steps。一般我们令eta=0,ddim_num_steps=20即可。DDIM的代码实现没有太多的学习价值,只要在DDPM代码的基础上把新数学公式翻译成代码即可。其中唯一值得注意的就是如何在DDIM中使用DDPM的方差\tilde{\beta}_t=\beta_t。对此感兴趣的话可以阅读我接下来的代码实现介绍。在这篇解读中,我略过了DDIM论文中的大部分数学推导细节。对DDIM数学模型的推导过程感兴趣的话,可以阅读我在参考文献中推荐的文章,或者看一看原论文。DDIM PyTorch 实现在这个项目中,我们将对一个在CelebAHQ上预训练的DDPM执行DDIM采样,尝试复现论文中的那个FID表格,以观察不同eta和ddim_steps对于采样结果的影响。代码仓库:https://github.com/SingleZombie/DL-Demos/tree/master/dldemos/ddimDDPM 基础项目DDIM只是DDPM的一种采样改进策略。为了复现DDIM的结果,我们需要一个DDPM基础项目。由于DDPM并不是本文的重点,在这一小节里我将简要介绍我的DDPM实现代码的框架。我们的实验需要使用CelebAHQ数据集,请在 https://www.kaggle.com/datasets/badasstechie/celebahq-resized-256x256 下载该数据集并解压到项目的data/celebA/celeba_hq_256目录下。另外,我在Hugging Face上分享了一个在64x64 CelebAHQ上训练的DDPM模型:https://huggingface.co/SingleZombie/dldemos/tree/main/ckpt/ddim ,请把它放到项目的dldemos/ddim目录下。先运行dldemos/ddim/dataset.py下载MNIST,再直接运行dldemos/ddim/main.py,代码会自动完成MNIST上的训练,并执行步数1000的两种采样和步数20的三种采样,同时将结果保存在目录work_dirs中。以下是我得到的MNIST DDPM采样结果(存储在work_dirs/diffusion_ddpm_sigma_hat.jpg中)。为了查看64x64 CelebAHQ上的采样结果,可以在dldemos/ddim/main.py的main函数里把config_id改成2,再注释掉训练函数。# 0 for MNIST. See configs.py
config_id = 2
cfg = configs[config_id]
n_steps = 1000
device = 'cuda'
model_path = cfg['model_path']
img_shape = cfg['img_shape']
to_bgr = False if cfg['dataset_type'] == 'MNIST' else True
net = UNet(n_steps, img_shape, cfg['channels'], cfg['pe_dim'],
cfg.get('with_attn', False), cfg.get('norm_type', 'ln'))
ddpm = DDPM(device, n_steps)
# train(ddpm,
# net,
# cfg['dataset_type'],
# resolution=(img_shape[1], img_shape[2]),
# batch_size=cfg['batch_size'],
# n_epochs=cfg['n_epochs'],
# device=device,
# ckpt_path=model_path)
以下是我得到的CelebAHQ DDPM采样结果(存储在work_dirs/diffusion_ddpm_sigma_hat.jpg中)。项目目录下的configs.py存储了训练配置,dataset.py定义了DataLoader,network.py定义了U-Net的结构,ddpm.py和ddim.py分别定义了普通的DDPM前向过程和采样以及DDIM采样,dist_train.py提供了并行训练脚本,dist_sample.py提供了并行采样脚本,main.py提供了单卡运行的所有任务脚本。在这个项目中,我们的主要的目标是基于其他文件,编写ddim.py。我们先来看一下原来的DDPM类是怎么实现的,再仿照它的接口写一个DDIM类。实现 DDIM 采样在我的设计中,DDPM类不是一个神经网络(torch.nn.Module),它仅仅维护了扩散模型的alpha等变量,并描述了前向过程和反向过程。在DDPM类中,我们可以在初始化函数里定义好要用到的self.betas, self.alphas, self.alpha_bars变量。如果在工程项目中,我们可以预定义好更多的常量以节约采样时间。但在学习时,我们可以少写一点代码,让项目更清晰一点。class DDPM():
def __init__(self,
device,
n_steps: int,
min_beta: float = 0.0001,
max_beta: float = 0.02):
betas = torch.linspace(min_beta, max_beta, n_steps).to(device)
alphas = 1 - betas
alpha_bars = torch.empty_like(alphas)
product = 1
for i, alpha in enumerate(alphas):
product *= alpha
alpha_bars[i] = product
self.betas = betas
self.n_steps = n_steps
self.alphas = alphas
self.alpha_bars = alpha_bars
前向过程就是把正态分布的公式q(\mathbf{x}_{t} | \mathbf{x}_0)=\mathcal{N}(\mathbf{x}_{t}; \sqrt{\bar{\alpha}_t}\mathbf{x}_{0}, (1-\bar{\alpha}_t)\mathbf{I})翻译一下。def sample_forward(self, x, t, eps=None):
alpha_bar = self.alpha_bars[t].reshape(-1, 1, 1, 1)
if eps is None:
eps = torch.randn_like(x)
res = eps * torch.sqrt(1 - alpha_bar) + torch.sqrt(alpha_bar) * x
return res
在反向过程中,我们从self.n_steps到1枚举时刻t(代码中时刻和数组下标有1的偏差),按照公式算出每一步的去噪均值和方差,执行去噪。算法流程如下:参数simple_var=True表示令方差\sigma_t^2=\beta_t,而不是(1-\bar{\alpha}_{t-1})/(1 - \bar{\alpha}_{t}) \cdot \beta_t。def sample_backward(self, img_or_shape, net, device, simple_var=True):
if isinstance(img_or_shape, torch.Tensor):
x = img_or_shape
else:
x = torch.randn(img_or_shape).to(device)
net = net.to(device)
for t in tqdm(range(self.n_steps - 1, -1, -1), "DDPM sampling"):
x = self.sample_backward_step(x, t, net, simple_var)
return x
def sample_backward_step(self, x_t, t, net, simple_var=True):
n = x_t.shape[0]
t_tensor = torch.tensor([t] * n,
dtype=torch.long).to(x_t.device).unsqueeze(1)
eps = net(x_t, t_tensor)
if t == 0:
noise = 0
else:
if simple_var:
var = self.betas[t]
else:
var = (1 - self.alpha_bars[t - 1]) / (
1 - self.alpha_bars[t]) * self.betas[t]
noise = torch.randn_like(x_t)
noise *= torch.sqrt(var)
mean = (x_t -
(1 - self.alphas[t]) / torch.sqrt(1 - self.alpha_bars[t]) *
eps) / torch.sqrt(self.alphas[t])
x_t = mean + noise
return x_t
接下来,我们来实现DDIM类。DDIM是DDPM的推广,我们可以直接用DDIM类继承DDPM类。它们共享初始化函数与前向过程函数。class DDIM(DDPM):
def __init__(self,
device,
n_steps: int,
min_beta: float = 0.0001,
max_beta: float = 0.02):
super().__init__(device, n_steps, min_beta, max_beta)
我们要修改的只有反向过程的实现函数。整个函数的代码如下:def sample_backward(self,
img_or_shape,
net,
device,
simple_var=True,
ddim_step=20,
eta=1):
if simple_var:
eta = 1
ts = torch.linspace(self.n_steps, 0,
(ddim_step + 1)).to(device).to(torch.long)
if isinstance(img_or_shape, torch.Tensor):
x = img_or_shape
else:
x = torch.randn(img_or_shape).to(device)
batch_size = x.shape[0]
net = net.to(device)
for i in tqdm(range(1, ddim_step + 1),
f'DDIM sampling with eta {eta} simple_var {simple_var}'):
cur_t = ts[i - 1] - 1
prev_t = ts[i] - 1
ab_cur = self.alpha_bars[cur_t]
ab_prev = self.alpha_bars[prev_t] if prev_t >= 0 else 1
t_tensor = torch.tensor([cur_t] * batch_size,
dtype=torch.long).to(device).unsqueeze(1)
eps = net(x, t_tensor)
var = eta * (1 - ab_prev) / (1 - ab_cur) * (1 - ab_cur / ab_prev)
noise = torch.randn_like(x)
first_term = (ab_prev / ab_cur)**0.5 * x
second_term = ((1 - ab_prev - var)**0.5 -
(ab_prev * (1 - ab_cur) / ab_cur)**0.5) * eps
if simple_var:
third_term = (1 - ab_cur / ab_prev)**0.5 * noise
else:
third_term = var**0.5 * noise
x = first_term + second_term + third_term
return x
我们来把整个函数过一遍。先看一下函数的参数。相比DDPM,DDIM的采样会多出两个参数:ddim_step, eta。如正文所述,ddim_step表示执行几轮去噪迭代,eta表示DDPM和DDIM的插值系数。def sample_backward(self,
img_or_shape,
net,
device,
simple_var=True,
ddim_step=20,
eta=1):
在开始迭代前,要做一些预处理。根据论文的描述,如果使用了DDPM的那种简单方差,一定要令eta=1。所以,一开始我们根据simple_var对eta做一个处理。之后,我们要准备好迭代时用到的时刻。整个迭代过程中,我们会用到从self.n_steps到0等间距的ddim_step+1个时刻(self.n_steps是初始时刻,不在去噪迭代中)。比如总时刻self.n_steps=100,ddim_step=10,ts数组里的内容就是[100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]。if simple_var:
eta = 1
ts = torch.linspace(self.n_steps, 0,
(ddim_step + 1)).to(device).to(torch.long)
if isinstance(img_or_shape, torch.Tensor):
x = img_or_shape
else:
x = torch.randn(img_or_shape).to(device)
batch_size = x.shape[0]
net = net.to(device)
做好预处理后,进入去噪循环。在for循环中,我们从1到ddim_step遍历ts的下标,从时刻数组ts里取出较大的时刻cur_t(正文中的t_2)和较小的时刻prev_t(正文中的t_1)。由于self.alpha_bars存储的是t=1, t=2, ..., t=n_steps时的变量,时刻和数组下标之间有一个1的偏移,我们要把ts里的时刻减去1得到时刻在self.alpha_bars里的下标,再取出对应的变量ab_cur, ab_prev。注意,在当前时刻为0时,self.alpha_bars是没有定义的。但由于self.alpha_bars表示连乘,我们可以特别地令当前时刻为0(prev_t=-1)时的alpha_bar=1。for i in tqdm(range(1, ddim_step + 1),
f'DDIM sampling with eta {eta} simple_var {simple_var}'):
cur_t = ts[i - 1] - 1
prev_t = ts[i] - 1
ab_cur = self.alpha_bars[cur_t]
ab_prev = self.alpha_bars[prev_t] if prev_t >= 0 else 1
准备好时刻后,我们使用和DDPM一样的方法,用U-Net估计生成x_t时的噪声eps,并准备好DDPM采样算法里的噪声noise(公式里的\mathbf{z})。 与DDPM不同,在计算方差var时(公式里的\sigma_t^2),我们要给方差乘一个权重eta。 t_tensor = torch.tensor([cur_t] * batch_size,
dtype=torch.long).to(device).unsqueeze(1)
eps = net(x, t_tensor)
var = eta * (1 - ab_prev) / (1 - ab_cur) * (1 - ab_cur / ab_prev)
noise = torch.randn_like(x)
接下来,我们要把之前算好的所有变量用起来,套入DDIM的去噪均值计算公式中。\begin{aligned} q(\mathbf{x}_{t-1} | \mathbf{x}_{t}, \mathbf{x}_0) &= \mathcal{N}(\mathbf{x}_{t-1}; \\ &\sqrt{\bar{\alpha}_{t-1}} \mathbf{x}_{0}+\sqrt{1-\bar{\alpha}_{t-1}-\tilde{\beta}_t} \cdot \frac{\mathbf{x}_{t}-\sqrt{\bar{\alpha}_t}\mathbf{x}_{0}}{\sqrt{1 - \bar{\alpha}_{t}}}, \\ &\tilde{\beta}_t\mathbf{I}) \end{aligned} \\也就是(设\sigma_t^2 = \tilde{\beta}_t, \mathbf{z}为来自标准正态分布的噪声):\begin{aligned} \mathbf{x}_{t-1} =& \sqrt{\bar{\alpha}_{t-1}} \mathbf{x}_{0}+\sqrt{1-\bar{\alpha}_{t-1}-\sigma_t^2} \cdot \frac{\mathbf{x}_{t}-\sqrt{\bar{\alpha}_t}\mathbf{x}_{0}}{\sqrt{1 - \bar{\alpha}_{t}}} + \\ &\sigma_t\mathbf{z} \end{aligned} \\由于我们只有噪声\epsilon,要把\mathbf{x}_0=(\mathbf{x}_t-\sqrt{1-\bar{\alpha}_t}\epsilon)/\sqrt{\bar{\alpha}_t}代入,得到不含\mathbf{x}_0的公式:\begin{aligned} \mathbf{x}_{t-1} =& \sqrt{\frac{\bar{\alpha}_{t-1}}{\bar{\alpha}_{t}}} \mathbf{x}_{t}+ \\ &(\sqrt{1-{\bar{\alpha}}_{t-1}-\sigma_t^2} - \sqrt{\frac{\bar{\alpha}_{t-1}(1-\bar{\alpha}_t)}{\bar{\alpha}_t}})\epsilon+\\ &\sigma_t\mathbf{z} \end{aligned} \\我在代码里把公式的三项分别命名为first_term, second_term, third_term,以便查看。特别地,当使用DDPM的\hat{\sigma_t}方差取值(令\sigma_t^2=\beta_t=\hat{\sigma_t}^2)时,不能把这个方差套入公式中,不然\sqrt{1-{\bar{\alpha}}_{t}-\sigma_t^2}的根号里的数会小于0。DDIM论文提出的做法是,只修改后面和噪声\mathbf{z}有关的方差项,前面这个根号里的方差项保持\sigma_t^2=(1-\bar{\alpha}_{t-1})/(1 - \bar{\alpha}_{t}) \cdot \beta_t (\eta=1)的取值。\begin{aligned} \mathbf{x}_{t-1} =& \sqrt{\frac{\bar{\alpha}_{t-1}}{\bar{\alpha}_{t}}} \mathbf{x}_{t}+ \\ &(\sqrt{1-{\bar{\alpha}}_{t-1}-\frac{1-\bar{\alpha}_{t-1}}{1 - \bar{\alpha}_{t}}\beta_t} - \sqrt{\frac{\bar{\alpha}_{t-1}(1-\bar{\alpha}_t)}{\bar{\alpha}_t}})\epsilon+\\ &\hat{\sigma_t}\mathbf{z} \end{aligned} \\当然,上面这些公式全都是在描述t到t-1。当描述t_2到t_1时,只需要把\beta_t换成1-\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}},再把所有t换成t_2,t-1换成t_1即可。把上面的公式和处理逻辑翻译成代码,就是这样:first_term = (ab_prev / ab_cur)**0.5 * x
second_term = ((1 - ab_prev - var)**0.5 -
(ab_prev * (1 - ab_cur) / ab_cur)**0.5) * eps
if simple_var:
third_term = (1 - ab_cur / ab_prev)**0.5 * noise
else:
third_term = var**0.5 * noise
x = first_term + second_term + third_term
这样,下一刻的x就算完了。反复执行循环即可得到最终的结果。实验写完了DDIM采样后,我们可以编写一个随机生成图片的函数。由于DDPM和DDIM的接口非常相似,我们可以用同一套代码实现DDPM或DDIM的采样。def sample_imgs(ddpm,
net,
output_path,
img_shape,
n_sample=64,
device='cuda',
simple_var=True,
to_bgr=False,
**kwargs):
if img_shape[1] >= 256:
max_batch_size = 16
elif img_shape[1] >= 128:
max_batch_size = 64
else:
max_batch_size = 256
net = net.to(device)
net = net.eval()
index = 0
with torch.no_grad():
while n_sample > 0:
if n_sample >= max_batch_size:
batch_size = max_batch_size
else:
batch_size = n_sample
n_sample -= batch_size
shape = (batch_size, *img_shape)
imgs = ddpm.sample_backward(shape,
net,
device=device,
simple_var=simple_var,
**kwargs).detach().cpu()
imgs = (imgs + 1) / 2 * 255
imgs = imgs.clamp(0, 255).to(torch.uint8)
img_list = einops.rearrange(imgs, 'n c h w -> n h w c').numpy()
output_dir = os.path.splitext(output_path)[0]
os.makedirs(output_dir, exist_ok=True)
for i, img in enumerate(img_list):
if to_bgr:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
cv2.imwrite(f'{output_dir}/{i+index}.jpg', img)
# First iteration
if index == 0:
imgs = einops.rearrange(imgs,
'(b1 b2) c h w -> (b1 h) (b2 w) c',
b1=int(batch_size**0.5))
imgs = imgs.numpy()
if to_bgr:
imgs = cv2.cvtColor(imgs, cv2.COLOR_RGB2BGR)
cv2.imwrite(output_path, imgs)
index += batch_size
为了生成大量图片以计算FID,在这个函数中我加入了很多和batch有关的处理。剔除这些处理代码以及图像存储后处理代码,和采样有关的核心代码为:def sample_imgs(ddpm,
net,
output_path,
img_shape,
n_sample=64,
device='cuda',
simple_var=True,
to_bgr=False,
**kwargs):
net = net.to(device)
net = net.eval()
with torch.no_grad():
shape = (n_sample, *img_shape)
imgs = ddpm.sample_backward(shape,
net,
device=device,
simple_var=simple_var,
**kwargs).detach().cpu()
如果是用DDPM采样,把参数表里的那些参数填完就行了;如果是DDIM采样,则需要在kwargs里指定ddim_step和eta。使用这个函数,我们可以进行不同ddim_step和不同eta下的64x64 CelebAHQ采样实验,以尝试复现DDIM论文的实验结果。我们先准备好变量。net = UNet(n_steps, img_shape, cfg['channels'], cfg['pe_dim'],
cfg.get('with_attn', False), cfg.get('norm_type', 'ln'))
ddpm = DDPM(device, n_steps)
ddim = DDIM(device, n_steps)
net.load_state_dict(torch.load(model_path))
第一组实验是总时刻保持1000,使用\hat{\sigma}_t(标准DDPM)和\eta=0(标准DDIM)的实验。sample_imgs(ddpm,
net,
'work_dirs/diffusion_ddpm_sigma_hat.jpg',
img_shape,
device=device,
to_bgr=to_bgr)
sample_imgs(ddim,
net,
'work_dirs/diffusion_ddpm_eta_0.jpg',
img_shape,
device=device,
to_bgr=to_bgr,
ddim_step=1000,
simple_var=False,
eta=0)
把参数n_samples改成30000,就可以生成30000张图像,以和30000张图像的CelebAHQ之间算FID指标。由于总时刻1000的采样速度非常非常慢,建议使用dist_sample.py并行采样。算FID指标时,可以使用torch fidelity库。使用pip即可安装此库。pip install torch-fidelity
之后就可以使用命令fidelity来算指标了。假设我们把降采样过的CelebAHQ存储在data/celebA/celeba_hq_64,把我们生成的30000张图片存在work_dirs/diffusion_ddpm_sigma_hat,就可以用下面的命令算FID指标。fidelity --gpu 0 --fid --input1 work_dirs/diffusion_ddpm_sigma_hat --input2 data/celebA/celeba_hq_64
整体来看,我的模型比论文差一点,总的FID会高一点。各个配置下的对比结果也稍有出入。在第一组实验中,使用\hat{\sigma}_t时,我的FID是13.68;使用\eta=0时,我的FID是13.09。而论文中用\hat{\sigma}_t时的FID比\eta=0时更低。我们还可以做第二组实验,测试ddim_step=20(我设置的默认步数)时使用\eta=0, \eta=1, \hat{\sigma}_t的生成效果。sample_imgs(ddim,
net,
'work_dirs/diffusion_ddim_sigma_hat.jpg',
img_shape,
device=device,
simple_var=True,
to_bgr=to_bgr)
sample_imgs(ddim,
net,
'work_dirs/diffusion_ddim_eta_1.jpg',
img_shape,
device=device,
simple_var=False,
eta=1,
to_bgr=to_bgr)
sample_imgs(ddim,
net,
'work_dirs/diffusion_ddim_eta_0.jpg',
img_shape,
device=device,
simple_var=False,
eta=0,
to_bgr=to_bgr)
我的FID结果是:eta=0: 17.80
eta=1: 24.00
sigma hat: 213.16
这里得到的实验结果和论文一致。减少采样迭代次数后,生成质量略有降低。同采样步数下,eta=0最优。使用sigma hat的结果会有非常多的噪声,差得完全不能看。综合上面两个实验来看,不管什么情况下,使用eta=0,得到的结果都不会太差。从生成速度上来看,在64x64 CelebAHQ上生成256张图片,ddim_step=20时只要3秒不到,而ddim_step=1000时要200秒。基本上是步数减少到几分之一就提速几倍。可见,DDIM加速采样对于扩散模型来说是必要的。参考文献及学习提示如果对DDIM公式推导及其他数学知识感兴趣,欢迎阅读苏剑林的文章: https://spaces.ac.cn/archives/9181。DDIM的论文为Denoising diffusion implicit models(https://arxiv.org/abs/2010.02502)。我在本文使用的公式符号都基于DDPM论文,与上面两篇文章使用的符号不一样。比如DDIM论文里的\alpha在本文中是用\bar{\alpha}表示。DDIM论文在介绍新均值公式时很不友好地在3.1节直接不加解释地给出了公式的形式,并在附录B中以先给结论再证明这种和逻辑思维完全反过来的方法介绍了公式的由来。建议去阅读苏剑林的文章,看看是怎么按正常的思考方式正向推导出DDIM公式。除了在3.1节直接甩给你一个公式外,DDIM论文后面的地方都很好读懂。DDIM后面还介绍了一些比较有趣的内容,比如4.3节介绍了扩散模型和常微分方程的关系,它可以帮助我们理解为什么DDPM会设置T=1000这么长的加噪步数。5.3节中作者介绍了如何用DDIM在两幅图像间插值。要回顾DDPM的知识,欢迎阅读我之前的文章:编辑于 2023-07-07 19:15・IP 属地新加坡深度学习(Deep Learning)PyTorch扩散模型赞同 673 条评论分享喜欢收藏申请转载文章被以下专栏收录周弈帆的博客对天才游戏设计师、编程高手周弈帆个人博客
为什么stable diffusion训练用ddpm, 采样用ddim呢? - 知乎
为什么stable diffusion训练用ddpm, 采样用ddim呢? - 知乎首页知乎知学堂发现等你来答切换模式登录/注册深度学习(Deep Learning)扩散模型diffusion modelStable DiffusionDeep Generative Model为什么stable diffusion训练用ddpm, 采样用ddim呢?二者有何区别和联系显示全部 关注者53被浏览61,270关注问题写回答邀请回答好问题 3添加评论分享7 个回答默认排序鲁提辖老算法工程师一枚 关注DDIM就是专门设计用来给DDPM做采样加速的。原来DDPM的采样步长和训练时的步长一样,导致采样步数过多。DDIM说,训练时该细致细致,步子可以小一些。训练好了采样时,咱们步子可以迈得大一点。之前写过一篇文章,我转帖这里:DDIM(Denoising Diffusion Implicit Models)[1]是对DDPM[2]的改进。贡献主要有三点。一是,采样时可以跳步,速度提升10+倍。在DDPM中,损失函数 L_{\mathrm{simple}} ,对于正向和逆向过程其实都没有步骤上的要求。对于正向过程,到底是T=1000, T=2000,还是连续的马氏过程,噪声水平schedule到底是线性的,还是非线性,都无所谓,只要能训练出一个良好的噪声预测函数 \epsilon_\theta(x, \alpha) 就可以。良好的意思是 \epsilon_\theta(x, \alpha) 在其定义域上普遍有值且值近似正确。对逆向的恢复过程,则完全可以跳步,不需要按照DDPM那么规矩走一个1000步的马氏链条。走20步就有样子,50步比较清晰,100步已经比较逼真了。其关键观察是,我们可以从一个噪声大的图像,直接跳步恢复到一个噪声小的图像。这与正向过程中这两个噪声水平中间多有少步无关。“从一个噪声大的图像,直接跳步恢复到一个噪声小的图像”,也要有个限度,步子不能太大。上一步还是 N(0,1) ,下一步想直接出逼真图像,那指定会扯着蛋的。50步可能是个极限。想再快,估计得换思路。比如,像LDM[3]那样只在低维空间搞采样,搞出图像的高层语义。图像像素细节,用类似于自编码器的方案去解决。二是,采样过程确定,采样结果一致性良好,较少漂移。DDPM的逆向恢复过程是一个stochastic process。所以相同的latent values两次采样生成的最终图像不一定一样,一次采样不同的步数输出的结果高层语义可能也不一样。DDIM改成确定性的,自然没这个问题。三是,论文Fomula参数化得更好。其中的 \alpha ,直接对应DDPM中的 \bar{\alpha}。对照DDPM,会发现读起来省力很多 :)参考^Denoising Diffusion Implicit Models https://arxiv.org/abs/2010.02502^Denoising Diffusion Probabilistic Models https://arxiv.org/abs/2006.11239^High-Resolution Image Synthesis with Latent Diffusion Models https://arxiv.org/abs/2112.10752参考^Denoising Diffusion Implicit Models https://arxiv.org/abs/2010.02502^Denoising Diffusion Probabilistic Models https://arxiv.org/abs/2006.11239^High-Resolution Image Synthesis with Latent Diffusion Models https://arxiv.org/abs/2112.10752编辑于 2023-06-17 00:56赞同 359 条评论分享收藏喜欢收起平凡的世界CS&AI | 社会百态 | 只做免费咨询 | 最爱说大实话 关注先抄题:一言以蔽之:ddpm采样太慢了,iddpm(improved)和ddim的初衷都是为了加速采样而设计的。只不过iddpm是采取了子序列采样的方式,而ddim解决的比较彻底,直接推广到了非马尔科夫过程,实质也是子序列采样的方式。网上有许多讲解,但很多博主都没说清楚一个关键问题,那就是为什么“马尔科夫过程”可以被推广到“非马尔科夫过程”。搞清楚这个问题就得明白ddpm到底什么地方依赖于马尔科夫假设,而这个地方对于ddim为什么不需要依赖马尔科夫假设。盲猜很多博主自己都不是很清楚,所以直接说了一句:ddpm虽然推导使用了马氏过程假设,但推导结果不需要。真正的原因在这里。首先请打开:里面有段公式是这样的,这个推导过程用了大量的马氏过程假设,不然有些地方根本拆不开:注意前面这个期望Eq,下标是q也就是前向过程的分布。这里写的比较简洁,而论文《Diffusion Models in Vision: A Survey》附录A写的比较清楚。不愧是TPAMI的综述。这篇论文第24页把Eq写的比较规范,参见公式(19):一直推导到 {L_{VLB}} 的最后一步:你会发现这里面很多项都是同时具有 t 和 t-1 的,所以前面的下标里的联合分布我们是一定不能拆开的。但是接下来的简化目标就不是这样了,根据文章:你看到 L_t 只跟 x_0 有关,这个时候我就可以把上面的联合分布拆开,所以作者才会说, L_t 只跟边缘有关,而不直接取决于联合分布:综上所述:DDPM在 L_{VLB} 推导过程中是的确使用了马氏过程假设,只不过 L_{simple} 不需要了。而DDIM的作用,就是找一个分布,看看能否不使用马氏过程假设来推导,也能形成L_{simple}。如果这么做可行,那非马氏过程就不用一步步这么繁琐的推导,而可以加快采样速度了,这就是ddim干的事情。那么这个分布要怎么构造出呢?请参考:第三部分(文中的“三”)说的已经非常明白了。编辑于 2024-01-02 20:59赞同 394 条评论分享收藏喜欢
生成扩散模型漫谈(四):DDIM = 高观点DDPM - 科学空间|Scientific Spaces
生成扩散模型漫谈(四):DDIM = 高观点DDPM - 科学空间|Scientific Spaces
SEARCH
MENU
打赏公式天象链接时光博览归档
CATEGORIES
千奇百怪天文探索数学研究物理化学信息时代生物自然图片摄影问题百科生活/情感资源共享
NEWPOSTS
用傅立叶级数拟合一维概率密度函数
配置不同的学习率,LoRA还能再涨一点?
“闭门造车”之多模态模型方案浅谈
更便捷的Cool Papers打开...
幂等生成网络IGN:试图将判别和生...
Transformer升级之路:1...
旁门左道之如何让Python的重试...
局部余弦相似度大,全局余弦相似度一...
新年快乐!记录一下 Cool Pa...
写了个刷论文的辅助网站:Cool ...
COMMENTS
余评秋: 这个式子里两项的噪声都是预测的,原论文有说,第一项是预测的X0...
无剑: CPU: 11th Gen Intel(R) Core(TM)...
游嘉诚: 才发现有这功能,没看见= =
游嘉诚: 现在In-page search可以Filter,能不能加一个...
游嘉诚: 我刚准备写个脚本加一个跳转原始链接的元素,审查元素一看发现左上...
NickyMouse: 其实自己之前也尝试过收集各个conference的论文和对应的...
luobotaxinghu: 数值上看,增加了这个“温度”,是让n较大的序列更加去注意那些a...
洋博: prompt """请问 当 x -> 0 的时候, ((a^...
洋博: GPT 的答案略作修改为了计算这个极限 $\lim_{x \t...
puz3d: 公式(2)稍微有一点点类似。 每个样本是高斯球中心。 公式(3...
USERLOGIN
登录
科学空间|Scientific Spaces
登录
打赏公式天象链接时光博览归档
渴望成为一个小飞侠
欢迎订阅
个性邮箱
天象信息
观测ISS
LaTeX
关于博主
欢迎访问“科学空间”,这里将与您共同探讨自然科学,回味人生百态;也期待大家的分享~
千奇百怪Everything天文探索Astronomy数学研究Mathematics物理化学Phy-chem信息时代Big-Data生物自然Biology图片摄影Photograph问题百科Questions生活/情感Life-Feeling资源共享Resources
千奇百怪天文探索数学研究物理化学信息时代生物自然图片摄影问题百科生活/情感资源共享
首页
信息时代 生成扩散模型漫谈(四):DDIM = 高观点DDPM
27
Jul
生成扩散模型漫谈(四):DDIM = 高观点DDPM
By
苏剑林 |
2022-07-27 |
105247位读者
|
:
相信很多读者都听说过甚至读过克莱因的《高观点下的初等数学》这套书,顾名思义,这是在学到了更深入、更完备的数学知识后,从更高的视角重新审视过往学过的初等数学,以得到更全面的认知,甚至达到温故而知新的效果。类似的书籍还有很多,比如《重温微积分》、《复分析:可视化方法》等。回到扩散模型,目前我们已经通过三篇文章从不同视角去解读了DDPM,那么它是否也存在一个更高的理解视角,让我们能从中得到新的收获呢?当然有,《Denoising Diffusion Implicit Models》介绍的DDIM模型就是经典的案例,本文一起来欣赏它。思路分析 #在《生成扩散模型漫谈(三):DDPM = 贝叶斯 + 去噪》中,我们提到过该文章所介绍的推导跟DDIM紧密相关。具体来说,文章的推导路线可以简单归纳如下:
\begin{equation}p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})\xrightarrow{\text{推导}}p(\boldsymbol{x}_t|\boldsymbol{x}_0)\xrightarrow{\text{推导}}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)\xrightarrow{\text{近似}}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)\end{equation}
这个过程是一步步递进的。然而,我们发现最终结果有着两个特点:1、损失函数只依赖于$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$;2、采样过程只依赖于$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)$。也就是说,尽管整个过程是以$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$为出发点一步步往前推的,但是从结果上来看,压根儿就没$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$的事。那么,我们大胆地“异想天开”一下:高观点1: 既然结果跟$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$无关,可不可以干脆“过河拆桥”,将$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$从整个推导过程中去掉?DDIM正是这个“异想天开”的产物!待定系数 #可能有读者会想,根据上一篇文章所用的贝叶斯定理
\begin{equation}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \frac{p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)}{p(\boldsymbol{x}_t|\boldsymbol{x}_0)}\end{equation}
没有给定$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$怎么能得到$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)$?这其实是思维过于定式了,理论上在没有给定$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$的情况下,$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)$的解空间更大,某种意义上来说是更加容易推导,此时它只需要满足边际分布条件:
\begin{equation}\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t|\boldsymbol{x}_0) d\boldsymbol{x}_t = p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)\label{eq:margin}\end{equation}
我们用待定系数法来求解这个方程。在上一篇文章中,所解出的$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)$是一个正态分布,所以这一次我们可以更一般地设
\begin{equation}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I})\end{equation}
其中$\kappa_t,\lambda_t,\sigma_t$都是待定系数,而为了不重新训练模型,我们不改变$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)$和$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$,于是我们可以列出
\begin{array}{c|c|c}
\hline
\text{记号} & \text{含义} & \text{采样}\\
\hline
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0) & \mathcal{N}(\boldsymbol{x}_{t-1};\bar{\alpha}_{t-1} \boldsymbol{x}_0,\bar{\beta}_{t-1}^2 \boldsymbol{I}) & \boldsymbol{x}_{t-1} = \bar{\alpha}_{t-1} \boldsymbol{x}_0 + \bar{\beta}_{t-1} \boldsymbol{\varepsilon} \\
\hline
p(\boldsymbol{x}_t|\boldsymbol{x}_0) & \mathcal{N}(\boldsymbol{x}_t;\bar{\alpha}_t \boldsymbol{x}_0,\bar{\beta}_t^2 \boldsymbol{I}) & \boldsymbol{x}_t = \bar{\alpha}_t \boldsymbol{x}_0 + \bar{\beta}_t \boldsymbol{\varepsilon}_1 \\
\hline
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) & \mathcal{N}(\boldsymbol{x}_{t-1}; \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I}) & \boldsymbol{x}_{t-1} = \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0 + \sigma_t \boldsymbol{\varepsilon}_2 \\
\hline
{\begin{array}{c}\int p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) \\
p(\boldsymbol{x}_t|\boldsymbol{x}_0) d\boldsymbol{x}_t\end{array}} & & {\begin{aligned}\boldsymbol{x}_{t-1} =&\, \kappa_t \boldsymbol{x}_t + \lambda_t \boldsymbol{x}_0 + \sigma_t \boldsymbol{\varepsilon}_2 \\
=&\, \kappa_t (\bar{\alpha}_t \boldsymbol{x}_0 + \bar{\beta}_t \boldsymbol{\varepsilon}_1) + \lambda_t \boldsymbol{x}_0 + \sigma_t \boldsymbol{\varepsilon}_2 \\
=&\, (\kappa_t \bar{\alpha}_t + \lambda_t) \boldsymbol{x}_0 + (\kappa_t\bar{\beta}_t \boldsymbol{\varepsilon}_1 + \sigma_t \boldsymbol{\varepsilon}_2) \\
\end{aligned}} \\
\hline
\end{array}
其中$\boldsymbol{\varepsilon},\boldsymbol{\varepsilon}_1,\boldsymbol{\varepsilon}_2\sim \mathcal{N}(\boldsymbol{0},\boldsymbol{I})$,并且由正态分布的叠加性我们知道$\kappa_t\bar{\beta}_t \boldsymbol{\varepsilon}_1 + \sigma_t \boldsymbol{\varepsilon}_2\sim \sqrt{\kappa_t^2\bar{\beta}_t^2 + \sigma_t^2} \boldsymbol{\varepsilon}$。对比$\boldsymbol{x}_{t-1}$的两个采样形式,我们发现要想$\eqref{eq:margin}$成立,只需要满足两个方程
\begin{equation}\bar{\alpha}_{t-1} = \kappa_t \bar{\alpha}_t + \lambda_t, \qquad\bar{\beta}_{t-1} = \sqrt{\kappa_t^2\bar{\beta}_t^2 + \sigma_t^2}\end{equation}
可以看到有三个未知数,但只有两个方程,这就是为什么说没有给定$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$时解空间反而更大了。将$\sigma_t$视为可变参数,可以解出
\begin{equation}\kappa_t = \frac{\sqrt{\bar{\beta}_{t-1}^2 - \sigma_t^2}}{\bar{\beta}_t},\qquad \lambda_t = \bar{\alpha}_{t-1} - \frac{\bar{\alpha}_t\sqrt{\bar{\beta}_{t-1}^2 - \sigma_t^2}}{\bar{\beta}_t}\end{equation}
或者写成
\begin{equation}p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) = \mathcal{N}\left(\boldsymbol{x}_{t-1}; \frac{\sqrt{\bar{\beta}_{t-1}^2 - \sigma_t^2}}{\bar{\beta}_t} \boldsymbol{x}_t + \left(\bar{\alpha}_{t-1} - \frac{\bar{\alpha}_t\sqrt{\bar{\beta}_{t-1}^2 - \sigma_t^2}}{\bar{\beta}_t}\right) \boldsymbol{x}_0, \sigma_t^2 \boldsymbol{I}\right)\label{eq:p-xt-x0}\end{equation}
方便起见,我们约定$\bar{\alpha}_0=1, \bar{\beta}_0=0$。特别地,这个结果并不需要限定$\bar{\alpha}_t^2 + \bar{\beta}_t^2 = 1$,不过为了简化参数设置,同时也为了跟以往的结果对齐,这里还是约定$\bar{\alpha}_t^2 + \bar{\beta}_t^2 = 1$。一如既往 #现在我们在只给定$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$、$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)$的情况下,通过待定系数法求解了$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)$的一簇解,它带有一个自由参数$\sigma_t$。用《生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼》中的“拆楼-建楼”类比来说,就是我们知道楼会被拆成什么样【$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$、$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)$】,但是不知道每一步怎么拆【$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$】,然后希望能够从中学会每一步怎么建【$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)$】。当然,如果我们想看看每一步怎么拆的话,也可以反过来用贝叶斯公式
\begin{equation} p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1}, \boldsymbol{x}_0) = \frac{p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0) p(\boldsymbol{x}_t|\boldsymbol{x}_0)}{p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_0)}\end{equation}接下来的事情,就跟上一篇文章一模一样了:我们最终想要$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)$而不是$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)$,所以我们希望用
\begin{equation}\bar{\boldsymbol{\mu}}(\boldsymbol{x}_t) = \frac{1}{\bar{\alpha}_t}\left(\boldsymbol{x}_t - \bar{\beta}_t \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_t, t)\right)\end{equation}
来估计$\boldsymbol{x}_0$,由于没有改动$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$,所以训练所用的目标函数依然是$\left\Vert\boldsymbol{\varepsilon} - \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\bar{\alpha}_t \boldsymbol{x}_0 + \bar{\beta}_t \boldsymbol{\varepsilon}, t)\right\Vert^2$(除去权重系数),也就是说训练过程没有改变,我们可以用回DDPM训练好的模型。而用$\bar{\boldsymbol{\mu}}(\boldsymbol{x}_t)$替换掉式$\eqref{eq:p-xt-x0}$中的$\boldsymbol{x}_0$后,得到
\begin{equation}\begin{aligned}
p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t) \approx&\, p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0=\bar{\boldsymbol{\mu}}(\boldsymbol{x}_t)) \\
=&\, \mathcal{N}\left(\boldsymbol{x}_{t-1}; \frac{1}{\alpha_t}\left(\boldsymbol{x}_t - \left(\bar{\beta}_t - \alpha_t\sqrt{\bar{\beta}_{t-1}^2 - \sigma_t^2}\right) \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_t, t)\right), \sigma_t^2 \boldsymbol{I}\right)
\end{aligned}\label{eq:p-xt-x0-2}\end{equation}
这就求出了生成过程所需要的$p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t)$,其中$\alpha_t=\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}$。它的特点是训练过程没有变化(也就是说最终保存下来的模型没有变化),但生成过程却有一个可变动的参数$\sigma_t$,就是这个参数给DDPM带来了新鲜的结果。几个例子 #原则上来说,我们对$\sigma_t$没有过多的约束,但是不同$\sigma_t$的采样过程会呈现出不同的特点,我们举几个例子进行分析。第一个简单例子就是取$\sigma_t = \frac{\bar{\beta}_{t-1}\beta_t}{\bar{\beta}_t}$,其中$\beta_t = \sqrt{1 - \alpha_t^2}$,相应地有
\begin{equation} p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t) \approx p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0=\bar{\boldsymbol{\mu}}(\boldsymbol{x}_t)) = \mathcal{N}\left(\boldsymbol{x}_{t-1}; \frac{1}{\alpha_t}\left(\boldsymbol{x}_t - \frac{\beta_t^2}{\bar{\beta}_t}\boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_t, t)\right),\frac{\bar{\beta}_{t-1}^2\beta_t^2}{\bar{\beta}_t^2} \boldsymbol{I}\right)\label{eq:choice-1}\end{equation}
这就是上一篇文章所推导的DDPM。特别是,DDIM论文中还对$\sigma_t = \eta\frac{\bar{\beta}_{t-1}\beta_t}{\bar{\beta}_t}$做了对比实验,其中$\eta\in[0, 1]$。第二个例子就是取$\sigma_t = \beta_t$,这也是前两篇文章所指出的$\sigma_t$的两个选择之一,在此选择下式$\eqref{eq:p-xt-x0-2}$未能做进一步的化简,但DDIM的实验结果显示此选择在DDPM的标准参数设置下表现还是很好的。最特殊的一个例子是取$\sigma_t = 0$,此时从$\boldsymbol{x}_t$到$\boldsymbol{x}_{t-1}$是一个确定性变换
\begin{equation}\boldsymbol{x}_{t-1} = \frac{1}{\alpha_t}\left(\boldsymbol{x}_t - \left(\bar{\beta}_t - \alpha_t \bar{\beta}_{t-1}\right) \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_t, t)\right)\label{eq:sigma=0}\end{equation}
这也是DDIM论文中特别关心的一个例子,准确来说,原论文的DDIM就是特指$\sigma_t=0$的情形,其中“I”的含义就是“Implicit”,意思这是一个隐式的概率模型,因为跟其他选择所不同的是,此时从给定的$\boldsymbol{x}_T = \boldsymbol{z}$出发,得到的生成结果$\boldsymbol{x}_0$是不带随机性的。后面我们将会看到,这在理论上和实用上都带来了一些好处。加速生成 #值得指出的是,在这篇文章中我们没有以$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$为出发点,所以前面的所有结果实际上全都是以$\bar{\alpha}_t,\bar{\beta}_t$相关记号给出的,而$\alpha_t,\beta_t$则是通过$\alpha_t=\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}$和$\beta_t = \sqrt{1 - \alpha_t^2}$派生出来的记号。从损失函数$\left\Vert\boldsymbol{\varepsilon} - \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\bar{\alpha}_t \boldsymbol{x}_0 + \bar{\beta}_t \boldsymbol{\varepsilon}, t)\right\Vert^2$可以看出,给定了各个$\bar{\alpha}_t$,训练过程也就确定了。从这个过程中,DDIM进一步留意到了如下事实:高观点2: DDPM的训练结果实质上包含了它的任意子序列参数的训练结果。具体来说,设$\boldsymbol{\tau} = [\tau_1,\tau_2,\dots,\tau_{\dim(\boldsymbol{\tau})}]$是$[1,2,\cdots,T]$的任意子序列,那么我们以$\bar{\alpha}_{\tau_1},\bar{\alpha}_{\tau_2},\cdots,\bar{\alpha}_{\dim(\boldsymbol{\tau})}$为参数训练一个扩散步数为$\dim(\boldsymbol{\tau})$步的DDPM,其目标函数实际上是原来以$\bar{\alpha}_1,\bar{\alpha}_2,\cdots,\bar{\alpha}_T$的$T$步DDPM的目标函数的一个子集!所以在模型拟合能力足够好的情况下,它其实包含了任意子序列参数的训练结果。那么反过来想,如果有一个训练好的$T$步DDPM模型,我们也可以将它当成是以$\bar{\alpha}_{\tau_1},\bar{\alpha}_{\tau_2},\cdots,\bar{\alpha}_{\dim(\boldsymbol{\tau})}$为参数训练出来的$\dim(\boldsymbol{\tau})$步模型,而既然是$\dim(\boldsymbol{\tau})$步的模型,生成过程也就只需要$\dim(\boldsymbol{\tau})$步了,根据式$\eqref{eq:p-xt-x0-2}$有:
\begin{equation}p(\boldsymbol{x}_{\tau_{i-1}}|\boldsymbol{x}_{\tau_i}) \approx \mathcal{N}\left(\boldsymbol{x}_{\tau_{i-1}}; \frac{\bar{\alpha}_{\tau_{i-1}}}{\bar{\alpha}_{\tau_i}}\left(\boldsymbol{x}_{\tau_i} - \left(\bar{\beta}_{\tau_i} - \frac{\bar{\alpha}_{\tau_i}}{\bar{\alpha}_{\tau_{i-1}}}\sqrt{\bar{\beta}_{\tau_{i-1}}^2 - \tilde{\sigma}_{\tau_i}^2}\right) \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_{\tau_i}, \tau_i)\right), \tilde{\sigma}_{\tau_i}^2 \boldsymbol{I}\right)\end{equation}
这就是加速采样的生成过程了,从原来的$T$步扩散生成变成了$\dim(\boldsymbol{\tau})$步。要注意不能直接将式$\eqref{eq:p-xt-x0-2}$的$\alpha_t$换成$\alpha_{\tau_i}$,因为我们说过$\alpha_t$是派生记号而已,它实际上等于$\frac{\bar{\alpha}_t}{\bar{\alpha}_{t-1}}$,因此$\alpha_t$要换成$\frac{\bar{\alpha}_{\tau_i}}{\bar{\alpha}_{\tau_{i-1}}}$才对。同理,$\tilde{\sigma}_{\tau_i}$也不是直接取$\sigma_{\tau_i}$,而是在将其定义全部转化为$\bar{\alpha},\bar{\beta}$符号后,将$t$替换为$\tau_i$、$t-1$替换为$\tau_{i-1}$,比如式$\eqref{eq:choice-1}$对应的$\tilde{\sigma}_{\tau_i}$为
\begin{equation}\sigma_t = \frac{\bar{\beta}_{t-1}\beta_t}{\bar{\beta}_t}=\frac{\bar{\beta}_{t-1}}{\bar{\beta}_t}\sqrt{1 - \frac{\bar{\alpha}_t^2}{\bar{\alpha}_{t-1}^2}}\quad\to\quad\frac{\bar{\beta}_{\tau_{i-1}}}{\bar{\beta}_{\tau_i}}\sqrt{1 - \frac{\bar{\alpha}_{\tau_i}^2}{\bar{\alpha}_{\tau_{i-1}}^2}}=\tilde{\sigma}_{\tau_i}\end{equation}可能读者又想问,我们为什么干脆不直接训练一个$\dim(\boldsymbol{\tau})$步的扩散模型,而是要先训练$T > \dim(\boldsymbol{\tau})$步然后去做子序列采样?笔者认为可能有两方面的考虑:一方面从$\dim(\boldsymbol{\tau})$步生成来说,训练更多步数的模型也许能增强泛化能力;另一方面,通过子序列$\boldsymbol{\tau}$进行加速只是其中一种加速手段,训练更充分的$T$步允许我们尝试更多的其他加速手段,但并不会显著增加训练成本。实验结果 #原论文对不同的噪声强度和扩散步数$\dim(\boldsymbol{\tau})$做了组合对比,大致上的结果是“噪声越小,加速后的生成效果越好”,如下图
DDIM的实验结果,显示噪声越小,加速后的生成效果越好笔者的参考实现如下:Github:https://github.com/bojone/Keras-DDPM/blob/main/ddim.py个人的实验结论是:1、可能跟直觉相反,生成过程中的$\sigma_t$越小,最终生成图像的噪声和多样性反而相对来说越大;2、扩散步数$\dim(\boldsymbol{\tau})$越少,生成的图片更加平滑,多样性也会有所降低;3、结合1、2两点得知,在扩散步数$\dim(\boldsymbol{\tau})$减少时,可以适当缩小$\sigma_t$,以保持生成图片质量大致不变,这跟DDIM原论文的实验结论是一致的;4、在$\sigma_t$较小时,相比可训练的Embedding层,用固定的Sinusoidal编码来表示$t$所生成图片的噪声要更小;5、在$\sigma_t$较小时,原论文的U-Net架构(Github中的ddpm2.py)要比笔者自行构思的U-Net架构(Github中的ddpm.py)所生成图片的噪声要更小;6、但个人感觉,总体来说不带噪声的生成过程的生成效果不如带噪声的生成过程,不带噪声时生成效果受模型架构影响较大。此外,对于$\sigma_t=0$时的DDIM,它就是将任意正态噪声向量变换为图片的一个确定性变换,这已经跟GAN几乎一致了,所以跟GAN类似,我们可以对噪声向量进行插值,然后观察对应的生成效果。但要注意的是,DDPM或DDIM对噪声分布都比较敏感,所以我们不能用线性插值而要用球面插值,因为由正态分布的叠加性,如果$\boldsymbol{z}_1,\boldsymbol{z}_2\sim\mathcal{N}(\boldsymbol{0}, \boldsymbol{I})$,$\lambda\boldsymbol{z}_1 + (1-\lambda)\boldsymbol{z}_2$一般就不服从$\mathcal{N}(\boldsymbol{0}, \boldsymbol{I})$,要改为
\begin{equation}\boldsymbol{z} = \boldsymbol{z}_1 \cos\frac{\lambda\pi}{2} + \boldsymbol{z}_2 \sin\frac{\lambda\pi}{2},\quad \lambda\in[0, 1]\end{equation}插值效果演示(笔者自己训练的模型):
DDIM随机向量的插值生成效果微分方程 #最后,我们来重点分析一下$\sigma_t = 0$的情形。此时$\eqref{eq:sigma=0}$可以等价地改写成:
\begin{equation}\frac{\boldsymbol{x}_t}{\bar{\alpha}_t} - \frac{\boldsymbol{x}_{t-1}}{\bar{\alpha}_{t-1}} = \left(\frac{\bar{\beta}_t}{\bar{\alpha}_t} - \frac{\bar{\beta}_{t-1}}{\bar{\alpha}_{t-1}}\right) \boldsymbol{\epsilon}_{\boldsymbol{\theta}}(\boldsymbol{x}_t, t)\end{equation}
当$T$足够大,或者说$\alpha_t$与$\alpha_{t-1}$足够小时,我们可以将上式视为某个常微分方程的差分形式。特别地,引入虚拟的时间参数$s$,我们得到
\begin{equation}\frac{d}{ds}\left(\frac{\boldsymbol{x}(s)}{\bar{\alpha}(s)}\right) = \boldsymbol{\epsilon}_{\boldsymbol{\theta}}\left(\boldsymbol{x}(s), t(s)\right)\frac{d}{ds}\left(\frac{\bar{\beta}(s)}{\bar{\alpha}(s)}\right)\label{eq:ode}\end{equation}
不失一般性,假设$s\in[0,1]$,其中$s=0$对应$t=0$、$s=1$对应$t=T$。注意DDIM原论文直接用$\frac{\bar{\beta}(s)}{\bar{\alpha}(s)}$作为虚拟时间参数,这原则上是不大适合的,因为它的范围是$[0,\infty)$,无界的区间不利于数值求解。那么现在我们要做的事情就是在给定$\boldsymbol{x}(1)\sim \mathcal{N}(\boldsymbol{0},\boldsymbol{I})$的情况下,去求解出$\boldsymbol{x}(0)$。而DDPM或者DDIM的迭代过程,对应于该常微分方程的欧拉方法。众所周知欧拉法的效率相对来说是最慢的,如果要想加速求解,可以用Heun方法、R-K方法等。也就是说,将生成过程等同于求解常微分方程后,可以借助常微分方程的数值解法,为生成过程的加速提供更丰富多样的手段。以DDPM的默认参数$T=1000$、$\alpha_t = \sqrt{1 - \frac{0.02t}{T}}$为例,我们重复《生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼》所做的估计
\begin{equation}\log \bar{\alpha}_t = \sum_{i=k}^t \log\alpha_k = \frac{1}{2} \sum_{k=1}^t \log\left(1 - \frac{0.02k}{T}\right) < \frac{1}{2} \sum_{k=1}^t \left(- \frac{0.02k}{T}\right) = -\frac{0.005t(t+1)}{T}\end{equation}
事实上,由于每个$\alpha_k$都很接近于1,所以上述估计其实也是一个很好的近似。而我们说了本文的出发点是$p(\boldsymbol{x}_t|\boldsymbol{x}_0)$,所以应该以$\bar{\alpha}_t$为起点,根据上述近似,我们可以直接简单地取
\begin{equation}\bar{\alpha}_t = \exp\left(-\frac{0.005t^2}{T}\right) = \exp\left(-\frac{5t^2}{T^2}\right)\end{equation}
如果取$s=t/T$为参数,那么正好$s\in[0,1]$,此时$\bar{\alpha}(s)=e^{-5s^2}$,代入到式$\eqref{eq:ode}$化简得
\begin{equation}\frac{d\boldsymbol{x}(s)}{ds} = 10s\left(\frac{\boldsymbol{\epsilon}_{\boldsymbol{\theta}}\left(\boldsymbol{x}(s), sT\right)}{\sqrt{1-e^{-10s^2}}} - \boldsymbol{x}(s)\right)\end{equation}
也可以取$s=t^2/T^2$为参数,此时也有$s\in[0,1]$,以及$\bar{\alpha}(s)=e^{-5s}$,代入到式$\eqref{eq:ode}$化简得
\begin{equation}\frac{d\boldsymbol{x}(s)}{ds} = 5\left(\frac{\boldsymbol{\epsilon}_{\boldsymbol{\theta}}\left(\boldsymbol{x}(s), \sqrt{s}T\right)}{\sqrt{1-e^{-10s}}} - \boldsymbol{x}(s)\right)\end{equation}文章小结 #本文接着上一篇DDPM的推导思路来介绍了DDIM,它重新审视了DDPM的出发点,去掉了推导过程中的$p(\boldsymbol{x}_t|\boldsymbol{x}_{t-1})$,从而获得了一簇更广泛的解和加速生成过程的思路,最后这簇新解还允许我们将生成过程跟常微分方程的求解联系起来,从而借助常微分方程的方法进一步对生成过程进行研究。
转载到请包括本文地址:https://spaces.ac.cn/archives/9181
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
打赏
微信打赏
支付宝打赏
因为网站后台对打赏并无记录,因此欢迎在打赏时候备注留言。你还可以点击这里或在下方评论区留言来告知你的建议或需求。
如果您需要引用本文,请参考:
苏剑林. (Jul. 27, 2022). 《生成扩散模型漫谈(四):DDIM = 高观点DDPM 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/9181
@online{kexuefm-9181,
title={生成扩散模型漫谈(四):DDIM = 高观点DDPM},
author={苏剑林},
year={2022},
month={Jul},
url={\url{https://spaces.ac.cn/archives/9181}},
}
分类:信息时代 标签:微分方程, 生成模型, DDPM, 扩散
106 评论
< 生成扩散模型漫谈(三):DDPM = 贝叶斯 + 去噪 | 生成扩散模型漫谈(五):一般框架之SDE篇 >
你也许还对下面的内容感兴趣
“闭门造车”之多模态模型方案浅谈
幂等生成网络IGN:试图将判别和生成合二为一的GAN
生成扩散模型漫谈(二十一):中值定理加速ODE采样
简单得令人尴尬的FSQ:“四舍五入”超越了VQ-VAE
当生成模型肆虐:互联网将有“疯牛病”之忧?
生成扩散模型漫谈(二十):从ReFlow到WGAN-GP
生成扩散模型漫谈(十九):作为扩散ODE的GAN
Google新作试图“复活”RNN:RNN能否再次辉煌?
生成扩散模型漫谈(十八):得分匹配 = 条件得分匹配
生成扩散模型漫谈(十七):构建ODE的一般步骤(下)
发表你的看法
«1234
鹿星荷
February 23rd, 2024
苏神,微分方程这里没有看明白,需要先学习什么样的前置知识呢
回复评论
苏剑林 发表于
February 25th, 2024
看完数学分析,基本上就可以看常微分方程了吧
回复评论
«1234
取消回复
你的大名
电子邮箱
个人网站(选填)
1. 可以在评论中使用LaTeX代码,点击“预览效果”可即时查看效果,点击这里可以查看更多内容;2. 可以通过点击评论楼层编号来引用该楼层;3. 提交评论之前,建议复制一下评论内容,避免提交失败导致辛苦打的字没了。
内容速览
思路分析
待定系数
一如既往
几个例子
加速生成
实验结果
微分方程
文章小结
智能搜索
支持整句搜索!网站自动使用结巴分词进行分词,并结合ngrams排序算法给出合理的搜索结果。
热门标签
模型
attention
生成模型
语言模型
优化
概率
网站
转载
天象
深度学习
微分方程
积分
python
分析
力学
无监督
梯度
节日
几何
文本生成
数论
矩阵
GAN
生活
情感
随机文章
python简单实现gillespie模拟从动力学角度看优化算法(四):GAN的第三个阶段【翻译】巨型望远镜:要继续,就得有牺牲!关于光的传播定律【分享】千万级百度知道语料2012北约自主招生数学祝大家端午节快乐!当概率遇上复变:随机游走与路径积分泰迪杯赛前培训之数据挖掘与建模“慢谈”你跳绳的时候,想过绳子的形状曲线是怎样的吗?
最近评论
余评秋: 这个式子里两项的噪声都是预测的,原论文有说,第一项是预测的X0,第二项是指向Xt的方向。如果你...
无剑: CPU: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.4...
游嘉诚: 才发现有这功能,没看见= =
游嘉诚: 现在In-page search可以Filter,能不能加一个反向Filter,也就是Excl...
游嘉诚: 我刚准备写个脚本加一个跳转原始链接的元素,审查元素一看发现左上方的数字就是跳转到abs,这太难...
NickyMouse: 其实自己之前也尝试过收集各个conference的论文和对应的meta信息,整理成一个可搜索关...
luobotaxinghu: 数值上看,增加了这个“温度”,是让n较大的序列更加去注意那些a_{i,j}数值高的token,...
洋博: prompt """请问 当 x -> 0 的时候, ((a^x+b^x) / 2 )^(3/...
洋博: GPT 的答案略作修改为了计算这个极限 $\lim_{x \to 0} \left(\frac...
puz3d: 公式(2)稍微有一点点类似。 每个样本是高斯球中心。 公式(3)就和3d高斯无关了。是为了采样...
友情链接
Cool Papers
数学研发
Seatop
Xiaoxia
积分表-网络版
丝路博傲
ph4ntasy 饭特稀
数学之家
有趣天文奇观
bsky
TwistedW
godweiyang
AI柠檬
王登科-DK博客
ESON
枫之羽
Mathor's blog
孙云增的博客
coding-zuo
博科园
孔皮皮的博客
运鹏的博客
jiming.site
OmegaXYZ
Blog by Eacls
申请链接
本站采用创作共用版权协议,要求署名、非商业用途和保持一致。转载本站内容必须也遵循“署名-非商业用途-保持一致”的创作共用协议。
© 2009-2024 Scientific Spaces. All rights reserved. Theme by laogui. Powered by Typecho. 备案号: 粤ICP备09093259号-1/2。
扩散模型从原理到实践-第四周_ddim inversion-CSDN博客
>扩散模型从原理到实践-第四周_ddim inversion-CSDN博客
扩散模型从原理到实践-第四周
stevensmj
已于 2023-11-08 15:36:00 修改
阅读量818
收藏
2
点赞数
2
文章标签:
算法
人工智能
于 2023-11-08 15:33:43 首次发布
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/stevensmh/article/details/134286520
版权
任务四:
视频讲解:DDIM反转的应用 DDIM反转 音频扩散模型 打卡内容:
学习笔记 作业:使用DDIM反转技术把demo图片中的小猫替换为小狗
扩散模型的精细控制和拓展应用
DDIM Inversion
DDIM Inversion技术,是基于ODE过程可以在小步长的限制下进行反转的假设。它的采样过程跟DDIM正常采样过程相反,是从 , 数学表示为:
其中 是给定的真实图像的编码,Inversion最后得到包含原图像信息的噪声编码 ,后面DDIM采样过程以 为初始值,能够近似重建原图像编码 ,因此DDIM Inversion常用于真实图像编辑。
在实践中,DDIM Inversion每一步都会产生误差,对于无条件扩散模型,累积误差可以忽略。但是对基于classifier-free guidance( w >1 )的扩散模型,累积误差会不断增加,DDIM Inversion最终获得的噪声向量可能会偏离高斯分布,再经过DDIM采样,最终生成的图像会严重偏离原图像,并可能产生视觉伪影。因此,如果希望 DDIM Inversion之后的采样结果在layout上与原始图像相似,通常使用 当引导系数 w = 1, 即无 negative prompt时,DDIM Inversion产生的轨迹提供了原始图像的粗略近似。
DDIM Sampling
下面, 通过加载一个名为"runwayml/stable-diffusion-v1-5"的stable diffusion管线,实现这一想法:
import torch
import requests
import torch.nn as nn
import torch.nn.functional as F
from PIL import Image
from io import BytesIO
from tqdm.auto import tqdm
from matplotlib import pyplot as plt
from torchvision import transforms as tfms
from diffusers import StableDiffusionPipeline, DDIMScheduler
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available()
# Useful function for later
def load_image(url, size=None):
response = requests.get(url,timeout=0.2)
img = Image.open(BytesIO(response.content)).convert('RGB')
if size is not None:
img = img.resize(size)
return img
# Load a pipeline
pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5").to(device)
# Set up a DDIM scheduler
pipe.scheduler = DDIMScheduler.from_config(pipe.scheduler.config)
简而言之,DDIM采样是指在特定时间点,嘈杂的图像是原始图像与一些噪声的混合,其中噪声是具有单位方差的高斯噪声。在DDPM论文中,这个高斯噪声的参数被称为'alpha'(α),并且它定义了噪声调度器。在Diffusers中,通过计算alpha调度器的值并将其存储在scheduler.alphas_cumprod中来处理这些值。
# Plot 'alpha' (alpha_bar in DDPM language, alphas_cumprod in Diffusers for clarity)
timesteps = pipe.scheduler.timesteps.cpu()
alphas = pipe.scheduler.alphas_cumprod[timesteps]
plt.plot(timesteps, alphas, label='alpha_t');
plt.legend();
最初(时间步0,图表的左侧),我们从一个干净的图像开始,没有噪音。 随着时间步的增加,我们最终几乎全部都是噪音,并且噪音逐渐减少至接近0。
在采样过程中,我们从时间步1000开始,纯粹是噪音,然后慢慢移动到时间步0。为了计算采样轨迹中的下一个时间步(因为我们是从高时间步向低时间步移动),我们预测噪音(这是我们模型的输出),然后使用它来计算预测的去噪图像。然后我们使用这个预测来沿着指向某个方向的小距离移动。最后,我们可以添加一些额外的噪音,按照某个因子缩放。
因此,基于DDIM 的采样过程可以描述为以下代码:
# Sample function (regular DDIM)
@torch.no_grad()
def sample(prompt, start_step=0, start_latents=None,
guidance_scale=3.5, num_inference_steps=30,
num_images_per_prompt=1, do_classifier_free_guidance=True,
negative_prompt='', device=device):
# Encode prompt
text_embeddings = pipe._encode_prompt(
prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt
)
# Set num inference steps
pipe.scheduler.set_timesteps(num_inference_steps, device=device)
# Create a random starting point if we don't have one already
if start_latents is None:
start_latents = torch.randn(1, 4, 64, 64, device=device)
start_latents *= pipe.scheduler.init_noise_sigma
latents = start_latents.clone()
for i in tqdm(range(start_step, num_inference_steps)):
t = pipe.scheduler.timesteps[i]
# Expand the latents if we are doing classifier free guidance
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
# Predict the noise residual
noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
# Perform guidance
if do_classifier_free_guidance:
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
# Normally we'd rely on the scheduler to handle the update step:
# latents = pipe.scheduler.step(noise_pred, t, latents).prev_sample
# Instead, let's do it ourselves:
prev_t = max(1, t.item() - (1000//num_inference_steps)) # t-1
alpha_t = pipe.scheduler.alphas_cumprod[t.item()]
alpha_t_prev = pipe.scheduler.alphas_cumprod[prev_t]
predicted_x0 = (latents - (1-alpha_t).sqrt()*noise_pred) / alpha_t.sqrt()
direction_pointing_to_xt = (1-alpha_t_prev).sqrt()*noise_pred
latents = alpha_t_prev.sqrt()*predicted_x0 + direction_pointing_to_xt
# Post-processing
images = pipe.decode_latents(latents)
images = pipe.numpy_to_pil(images)
return images
我们测试一个例子如下:
# Test our sampling function by generating an image
sample('Watercolor painting of a beach sunset', negative_prompt=negative_prompt, num_inference_st
Inversion Concept
下面我们将用具体的代码来显示 DDIM Inversion 这个概念。
# https://www.pexels.com/photo/a-beagle-on-green-grass-field-8306128/
input_image = load_image('https://images.pexels.com/photos/8306128/pexels-photo-8306128.jpeg', size=(512, 512))
input_image
input_image_prompt = "Photograph of a puppy on the grass"
# Encode with VAE
with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.to_tensor(input_image).unsqueeze(0).to(device)*2-1)
l = 0.18215 * latent.latent_dist.sample()
## Inversion
@torch.no_grad()
def invert(start_latents, prompt, guidance_scale=3.5, num_inference_steps=80,
num_images_per_prompt=1, do_classifier_free_guidance=True,
negative_prompt='', device=device):
# Encode prompt
text_embeddings = pipe._encode_prompt(
prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt
)
# Latents are now the specified start latents
latents = start_latents.clone()
# We'll keep a list of the inverted latents as the process goes on
intermediate_latents = []
# Set num inference steps
pipe.scheduler.set_timesteps(num_inference_steps, device=device)
# Reversed timesteps <<<<<<<<<<<<<<<<<<<<
timesteps = reversed(pipe.scheduler.timesteps)
for i in tqdm(range(1, num_inference_steps), total=num_inference_steps-1):
# We'll skip the final iteration
if i >= num_inference_steps - 1: continue
t = timesteps[i]
# Expand the latents if we are doing classifier free guidance
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
latent_model_input = pipe.scheduler.scale_model_input(latent_model_input, t)
# Predict the noise residual
noise_pred = pipe.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
# Perform guidance
if do_classifier_free_guidance:
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
current_t = max(0, t.item() - (1000//num_inference_steps)) #t
next_t = t # min(999, t.item() + (1000//num_inference_steps)) # t+1
alpha_t = pipe.scheduler.alphas_cumprod[current_t]
alpha_t_next = pipe.scheduler.alphas_cumprod[next_t]
# Inverted update step (re-arranging the update step to get x(t) (new latents) as a function of x(t-1) (current latents)
latents = (latents - (1-alpha_t).sqrt()*noise_pred)*(alpha_t_next.sqrt()/alpha_t.sqrt()) + (1-alpha_t_next).sqrt()*noise_pred
# Store
intermediate_latents.append(latents)
return torch.cat(intermediate_latents)
这个函数与上面的采样函数相似,但我们在时间步上沿着相反的方向移动,从t=0开始,向更高噪音的方向移动。不同之处在于,我们不是更新我们的潜在变量以减少噪音,而是估计预测的噪音并使用它来撤销一个更新步骤,将它们从t移动到t+1。
下面使用这个 invert 函数来创建一系列中间表示:
inverted_latents = invert(l, input_image_prompt, num_inference_steps=50)
inverted_latents.shape
# Decode the final inverted latents
with torch.no_grad():
im = pipe.decode_latents(inverted_latents[-1].unsqueeze(0))
pipe.numpy_to_pil(im)[0]
上面是其中最后一层中间表示的可视化图像,也是加噪声最多的一张图,此时已经很难分辨出原始的小狗图片,但是接下来,我们将基于这个 噪声图片,作为新的采样起始点,生成一系列不同的新图片(基于一些新的prompt)。在新的图片里面,与原始图像相匹配,但在与新提示相关的地方有所不同。
# Sampling with a new prompt
start_step = 10
new_prompt = input_image_prompt.replace('puppy', 'cat')
sample(new_prompt, start_latents=inverted_latents[-(start_step+1)][None],
start_step=start_step, num_inference_steps=50)[0]
通过 new prompt 能够将 原始图片中的 puppy 替换为 cat,并且 周围的环境是相似的,可以看作好像我们直接对 图像进行了 edit 导致原图中的 puppy这一概念 被替换为了 cat 概念,并且其他的地方都没有发生大的变动。
现在我们将创建一个 整合的 edit 函数,从而将上面所有的分步操作结合在一起:
from PIL import Image
import numpy as np
def edit_image_with_inversion(input_image, input_image_prompt, edit_prompt, num_steps=100, start_step=30, guidance_scale=3.5):
with torch.no_grad(): latent = pipe.vae.encode(tfms.functional.to_tensor(input_image).unsqueeze(0).to(device)*2-1)
l = 0.18215 * latent.latent_dist.sample()
inverted_latents = invert(l, input_image_prompt, num_inference_steps=num_steps)
final_im = sample(edit_prompt, start_latents=inverted_latents[-(start_step+1)][None],
start_step=start_step, num_inference_steps=num_steps, guidance_scale=g
return final_im
尝试更多的 new prompt:
edit(input_image, 'A puppy on the grass', 'an old grey dog on the grass', num_steps=50, start_step=10)
edit(input_image, 'A puppy on the grass', 'A blue dog on the lawn', num_steps=50, start_step=12,
更多的步骤 = 更好的性能,如果您在使用较不精确的反演时遇到问题,您可以尝试使用更多的步骤(代价是更长的运行时间)。为了测试反演,您可以使用我们的编辑函数,并使用相同的提示:
# Inversion test with far more steps
edit(input_image, 'A puppy on the grass', 'A puppy on the grass', num_steps=350, start_step=1)
edit(input_image, 'A photograph of a puppy', 'A photograph of a grey cat', num_steps=150, start_
Null-text Inversion
问题:DDIM inversion 在有 guidance 的情形下并不能较好地重建输入,该如何找到更好的inversion方式?
如上图上半部分所示,设 DDIM inversion 过程为 ,往回采样的过程为 . 二者每一步都会有一定的误差,在没有 guidance(即 w =1 )的情形下,这些误差比较小,重建结果还是能几乎还原图像的。但是当 w >1 时,这些误差会被放大,导致重建结果与原图有明显的差异。而实践中我们常常依靠大 guidance scale(如 w =7.5 )来生成高质量图像,因此这个问题必须得到解决。
我们现在仔细考虑采样过程的每一步。如上图下半部分所示,classifier-free guidance 会让网络推理两次,分别对应有条件(上分支)和无条件(下分支),二者结果依据 w 做线性组合得到这一步的最终预测值。由于 prompt-to-prompt 方法要利用文本条件和 attention map 之间的关系,所以我们并不想改变条件分支和网络权重,于是我们能改变的只有无条件分支的输入,也即 null-text embedding . 我们希望通过优化 ,使得采样过程能够重建出原图,这就是该方法的名字 null-text inversion 的意义。考虑到 w =1 下的 DDIM inversion 能较好的重建原图,我们自然想到以 作为目标,让 去接近它,因此损失函数就是二者的 MSE.
按照扩散过程 t=T→1 的顺序对每个时间步 t 的采样执行单独的优化,尽可能使inversion proess接近samping process,那么新轨迹将在 z0 附近结束,从而保留原图像的语义和视觉信息。
实现上,由于采样过程是随时间步进行的,因此我们要按 t=T→1 的顺序逐步训练——当前一步训练好之后,在前一步的采样基础之上训练下一步。另外,虽然原本的 null-text 对应的 embedding 只有一个全局的 ∅ ,但是作者发现为每一步都定义自己的 能大幅提高性能。综上所述,训练算法如下图所示:
总结一下,Null-text inversion 的亮点在于它揭示了 classifier-free guidance 下,结果受无条件分支的影响非常大,并成功地利用这种影响达到了重建输入图像的目的.
Diffusion for Audio
From Audio to Image and Back Again
使用 diffusion 模型来制作 audio 的本质上,其实是通过diffusion模型生成对应的 spectrogram 图像,再在此频谱图上做进一步的后处理得到对应的 audio。
下面是一个简单的 spectrogram 与 audio 直接的互相转换。这里使用 mel spectrogram,以帮助人耳能够识别的有效信息得以凸显出来。
import torch, random
import numpy as np
import torch.nn.functional as F
from tqdm.auto import tqdm
from IPython.display import Audio
from matplotlib import pyplot as plt
from diffusers import DiffusionPipeline
from torchaudio import transforms as AT
from torchvision import transforms as IT
# Load a pre-trained audio diffusion pipeline
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
pipe = DiffusionPipeline.from_pretrained("teticio/audio-diffusion-instrumental-hiphop-256").to(device)
# Sample from the pipeline and display the outputs
output = pipe()
display(output.images[0])
display(Audio(output.audios[0], rate=pipe.mel.get_sample_rate()))
pipe.mel
Mel {
"_class_name": "Mel",
"_diffusers_version": "0.12.0.dev0",
"hop_length": 512,
"n_fft": 2048,
"n_iter": 32,
"sample_rate": 22050,
"top_db": 80,
"x_res": 256,
"y_res": 256
}
通过 使用 函数image_to_audio() 来实现 从频谱图到音频的转换,使用 函数 audio_slice_to_image() 可以实现从 音乐片段到对应频谱图的转换。
a = pipe.mel.image_to_audio(output.images[0])
a.shape
pipe.mel.load_audio(raw_audio=a)
im = pipe.mel.audio_slice_to_image(0)
im
sample_rate_pipeline = pipe.mel.get_sample_rate()
sample_rate_pipeline
Fine-Tuning the pipeline
首先 加载来自 'lewtun/music_genres' 的数据集:
from datasets import load_dataset
dataset = load_dataset('lewtun/music_genres', split='train')
dataset
audio_array = dataset[0]['audio']['array']
sample_rate_dataset = dataset[0]['audio']['sampling_rate']
print('Audio array shape:', audio_array.shape)
print('Sample rate:', sample_rate_dataset)
display(Audio(audio_array, rate=sample_rate_dataset))
a = dataset[0]['audio']['array'] # Get the audio array
pipe.mel.load_audio(raw_audio=a) # Load it with pipe.mel
pipe.mel.audio_slice_to_image(0) # View the first 'slice' as a spectrogram
sample_rate_dataset = dataset[0]['audio']['sampling_rate']
sample_rate_dataset
Audio array shape: (1323119,)
Sample rate: 44100
44100
请注意,这个音频的采样率较高 - 如果我们想要使用现有的流程,我们需要对其进行“重新采样”以匹配。音频片段的长度也比流程设置的要长。幸运的是,当我们使用pipe.mel加载音频时,它会自动将音频剪切成较小的部分。在这里,我们使用torchaudio的转换(作为AT导入)来进行重新采样,使用pipe的mel将音频转化为图像,然后使用torchvision的转换(作为IT导入)将图像转化为张量。这样,我们就得到了一个将音频片段转化为可用于训练的频谱图张量的函数:
resampler = AT.Resample(sample_rate_dataset, sample_rate_pipeline, dtype=torch.float32)
to_t = IT.ToTensor()
def to_image(audio_array):
audio_tensor = torch.tensor(audio_array).to(torch.float32)
audio_tensor = resampler(audio_tensor)
pipe.mel.load_audio(raw_audio=np.array(audio_tensor))
num_slices = pipe.mel.get_number_of_slices()
slice_idx = random.randint(0, num_slices-1) # Pic a random slice each time (excluding the last short slice)
im = pipe.mel.audio_slice_to_image(slice_idx)
return im
我们将使用我们的to_image()函数作为自定义collate函数的一部分,将我们的数据集转化为一个数据加载器(dataloader),以便用于训练。collate函数定义了如何将数据集中的一批示例转化为最终用于训练的批处理数据。在这种情况下,我们将每个音频样本转化为频谱图像,然后将生成的张量堆叠在一起:
def collate_fn(examples):
# to image -> to tensor -> rescale to (-1, 1) -> stack into batch
audio_ims = [to_t(to_image(x['audio']['array']))*2-1 for x in examples]
return torch.stack(audio_ims)
# Create a dataset with only the 'Chiptune / Glitch' genre of songs
batch_size = 4 # 4 on colab, 12 on A100
chosen_genre = 'Electronic' # <<< Try training on different genres <<<
indexes = [i for i, g in enumerate(dataset['genre']) if g == chosen_genre]
filtered_dataset = dataset.select(indexes)
dl = torch.utils.data.DataLoader(filtered_dataset.shuffle(), batch_size=batch_size, collate_fn=collate_fn, shuffle=True)
batch = next(iter(dl))
print(batch.shape)
最后, 我们定义一个训练过程如下:
epochs = 3
lr = 1e-4
pipe.unet.train()
pipe.scheduler.set_timesteps(1000)
optimizer = torch.optim.AdamW(pipe.unet.parameters(), lr=lr)
for epoch in range(epochs):
for step, batch in tqdm(enumerate(dl), total=len(dl)):
# Prepare the input images
clean_images = batch.to(device)
bs = clean_images.shape[0]
# Sample a random timestep for each image
timesteps = torch.randint(
0, pipe.scheduler.num_train_timesteps, (bs,), device=clean_images.device
).long()
# Add noise to the clean images according to the noise magnitude at each timestep
noise = torch.randn(clean_images.shape).to(clean_images.device)
noisy_images = pipe.scheduler.add_noise(clean_images, noise, timesteps)
# Get the model prediction
noise_pred = pipe.unet(noisy_images, timesteps, return_dict=False)[0]
# Calculate the loss
loss = F.mse_loss(noise_pred, noise)
loss.backward(loss)
# Update the model parameters with the optimizer
optimizer.step()
optimizer.zero_grad()
output = pipe()
display(output.images[0])
display(Audio(output.audios[0], rate=22050))
最终我们可以得到基于 “Electronic” 电子音乐 style 的歌曲和对应的spectrogram:
此外, 我们还可以通过输入更大的起始 noise image 来得到 更长的 audio 片段。
# Make a longer sample by passing in a starting noise tensor with a different shape
noise = torch.randn(1, 1, pipe.unet.sample_size[0], pipe.unet.sample_size[1]*4).to(device)
output = pipe(noise=noise)
display(output.images[0])
display(Audio(output.audios[0], rate=22050))
优惠劵
stevensmj
关注
关注
2
点赞
踩
2
收藏
觉得还不错?
一键收藏
知道了
1
评论
扩散模型从原理到实践-第四周
DDIM Inversion每一步都会产生误差,对于无条件扩散模型,累积误差可以忽略。但是对基于classifier-free guidance(w >1)的扩散模型,累积误差会不断增加,DDIM Inversion最终获得的噪声向量可能会偏离高斯分布,再经过DDIM采样,最终生成的图像会严重偏离原图像,并可能产生视觉伪影。
复制链接
扫一扫
高斯扩散模型matlab代码-uFab-formalElement-diffusion:uFab-形式元素扩散
05-26
高斯扩散模型matlab代码uFab-形式元素扩散
背景技术请阅读James
D.
Plummer等人的《硅VLSI技术:基础知识,实践和建模》第7章。
请特别注意第7.5.1节。
Nicholas
J.
Giordano和Hisao
Nakanishi在《计算物理学》第二版中讨论了扩散方程的数值解。
这两本书在凯文街图书馆都有。
您还应该阅读我有关扩散的注释和有关Plummer中扩散的章节。
热扩散是掺杂物扩散的一个很好的类比,方程很相似。
以下是一些参考,以及一些可能有用的代码片段。
任务您的工作是使用MATLAB或您选择的另一种语言编写程序,以构建扩散的数学模型并探索其实用性。
编写MATLAB程序,使用有限差分对扩散方程进行数值求解。
首先,通过使DΔt/(Δx^
2)=
1/2简化模型。
这使我们可以将硼驱入扩散的情况下的扩散方程从等式(7.38)简化为等式(7.40)。
将初始轮廓建模为增量函数,即高浓度预沉积。
使用2
x
1019
cm-3的表面浓度。
如下所示……还有一些简单的MATLAB可以生成此初始向量。
您将需要仔细考虑如何处理第一个点,因为它的左边没有点。
您
035_SS_Prompt Tuning Inversion for Text-Driven Image Editing Using Diffusion Models
D_Trump的博客
06-11
246
本文要利用Diffusion实现高保真高质量的文本-图像编辑,也就是既保证editability,又要保证fidelity。前者要求编辑后的图像应该包含与目标提示中提供的相应文本内容良好对齐的视觉内容,而后者期望编辑部分以外的区域应尽可能接近输入图像的区域。然而,大多数方法缺乏以下之一:用户友好性(例如,需要额外的掩码或输入图像的精确描述)、对更大域的泛化或对输入图像的高保真度。
1 条评论
您还未登录,请先
登录
后发表或查看评论
高斯扩散模型matlab代码-phardi:帕尔迪
05-26
高斯扩散模型matlab代码自述文件
并行高角度分辨率扩散成像(pHARDI)是用于从扩散磁共振成像(dMRI)数据进行GPU
/
CPU加速的体素内重建方法重建的工具包。
它旨在支持多种设备中的多个线性代数加速器,例如多核GPU设备(CUDA和OpenCL),甚至是协处理器(例如Intel
Xeon
Phi)。
对于不支持任何基于GPU的加速器的平台,我们的解决方案也可以使用高度可调的线性代数库在多核处理器(CPU)上运行。
我们在线性代数加速器之上使用Armadillo来提供通用接口,并在ArrayFire上使用以支持GPU设备。
重建方法清单:
具有对称正定约束(DTI-SPD)的扩散张量成像(2阶或更高)(Barmpoutis,2010年)
Q球成像(QBI)(Tuch,2004;
Descoteaux,2007)
恒定立体角(QBI-CSA)的Q球成像(Aganj,2010)
扩散方向转换(DOT-R2)的修订版(Canales-Rodríguez,2010a)
广义Q采样成像(GQI)(Fang-Cheng,2010年)
扩散光谱成像(DSI)(Wedeen,2005;Ca
双向增强扩散滤波的图像去噪模型-论文
05-22
双向增强扩散滤波的图像去噪模型
高斯扩散模型-高斯烟羽大气污染扩散模型 GetQx.m、GetQy.m、Qmain.m
09-17
MATLAB 源代码、高斯扩散模型-高斯烟羽大气污染扩散模型 GetQx.m、GetQy.m、Qmain.m
论文学习——Tune-A-Video
胖虎的博客
03-28
4287
通过文本生成视频的深度学习方法
[论文解析] Null-text Inversion for Editing Real Images using Guided Diffusion Models
Joselynzhao
12-09
2967
在本文中,我们引入了一种精确的反演技术,从而方便了直观的基于文本的图像修改。(i)扩散模型的关键反演。我们为每个时间戳使用单个关键噪声向量,并围绕它进行优化。我们证明了直接反演本身是不够的,但确实为我们的优化提供了一个很好的锚定。(ii)空文本优化,我们只修改用于无分类器引导的无条件文本嵌入,而不是输入文本嵌入。这允许保持模型权重和条件嵌入不变,因此可以应用基于提示的编辑,同时避免对模型权重进行繁琐的调优。在各种图像和提示编辑上进行了广泛的评估,显示了对真实图像的高保真编辑。
01_ddim_inversion_CN
Mr_yuekitty的博客
11-10
783
这是因为DDIM的反转依赖一个重要的假设,在t时刻预测的噪声与t+1时刻会是相同的 - 这在我们只反转50或100步时是不陈立的。这个函数看起来和上面的取样函数很像,但我们在timesteps上是在向相反的方向移动,从t=0开始,向越来越多的噪声前进。嗯,这是因为如果我们现在若要用一个新的prompt来生成图像,我们会得到一个匹配于源图像,除了,与新prompt相关的内容。我们可以这么做,但这会带来一个到处都被改变得夸张得多的照片(如果我们加入了很多噪声),或哪也没怎么变的图像(如果加了太少的噪声)。
【深度学习】InST,Inversion-Based Style Transfer with Diffusion Models,论文,风格迁移,实战
q742971636的博客
07-28
3771
一幅绘画中的艺术风格是表达的手段,其中包括绘画材料、颜色和笔触,还有高层次的属性,如语义元素、物体形状等。先前的任意样例引导的艺术图像生成方法通常无法控制形状变化或传达元素。预训练的文本到图像合成扩散概率模型已经取得了显著的质量,但通常需要大量的文本描述来准确描绘特定绘画的属性。我们相信艺术作品的独特之处正是因为它无法用普通语言充分解释。我们的关键想法是直接从一幅绘画中学习艺术风格,然后在不提供复杂的文本描述的情况下指导合成。**具体而言,我们将风格视为一幅绘画的可学习文本描述。
027_SSS_Direct Inversion Optimization-Free Text-Driven Real Image Editing with Diffusion Models
D_Trump的博客
02-08
361
Direct Inversion: Optimization-Free Text-Driven Real Image Editing with Diffusion Models
高斯扩散模型matlab代码-Kalman-TD-Model:该程序使用Python允许Kalman-TD模型拟合模拟的行为数据
05-26
高斯扩散模型matlab代码
该程序使用Python,可以使Kalman-TD模型适合模拟的行为数据。
Matlab中的初始代码基于哈佛大学心理学系和脑科学中心的Samuel
Gershman的先前工作。
在他的论文中介绍了该模型,该模型描述了一个包含贝叶斯和强化学习理论的联想学习框架。
有关详细信息,请通过与我联系。
目录
什么是Kalman-TD模型
联想学习理论
学习能力对于动物的生存至关重要。
最近的两个概念使我们对这种学习的发生方式有了更好的理解。
两种理论都可以看作是对开创性Rescorla-Wagner的概括,但是它们是基于关于学习任务的目标和不确定性表示的不同假设而得出的。
代理使用贝叶斯原理估计关联的强度并跟踪其不确定性,并由卡尔曼滤波器(KF)体现。
KF了解预期立即奖励的后验分布
代理人使用强化学习(RL)原理来学习长期累积的未来奖励,并以时间差异(TD)表示。
TD了解预期的未来累积奖励的单一价值
统一模型
这两个理论模型可以以Kalman-TD模型的形式组合在一起。
该实时模型表示权重的分布,而不是点估计。
这与一种更规范的方法是一致的,因为我们可以相信理想的
从DDPM到DDIM:深入解读《Denoising Diffusion Implicit Models》
沉迷单车的追风少年
07-10
8729
DDIM发表在ICRL2021上,是DDPM重要的改进之一,能显著提高DDPM的样本质量、减少采样时间,并且能显式控制插值,已经被广泛应用到现在的Diffusion Models上。这篇博客和大家一起详细解读一下DDIM,认识这一伟大的模型。.........
【扩散模型】4、DDIM | 加速 DDPM 的采样速度
呆呆的猫的博客
09-06
2298
本文主要介绍 DDIM
谷歌发布MediaPipe Diffusion插件
xifenglie123321的博客
07-27
77
在这项工作中,研究人员提出了MediaPipe,一个可在移动端使用的、有条件的文本到图像生成插件,将从条件图像中提取的特征注入扩散模型,从而控制图像的生成过程。便携式插件可以连接到在服务器或设备上运行的预训练的扩散模型,通过在设备上完全运行文本到图像生成和插件,可以更灵活地应用生成式AI。
文本生成图像应用指南【Stable Diffusion】
新缸中之脑
02-20
3610
Stable Diffusion 是一种文本到图像的潜在扩散模型,由来自 CompVis、Stability AI 和 LAION 的研究人员和工程师创建。它使用来自 LAION-5B 数据库子集的 512x512 图像进行训练。稳定扩散,生成人脸,也可以在自己的机器上运行,如下图所示:推荐:将加入你的3D开发工具链。如果你足够聪明和有创意,你可以创建一系列图像,然后形成视频。
classifier-free-guidance 扩散模型引导生成
最新发布
liguandong
11-24
535
新的生成过程不再依赖显式的classifier,训练时,classifier-free guidance需要训练两个模型,一个是无条件生成模型(DDPM),一个是条件生成模型,这两个模型可以用一个模型表示,训练时只需要在无条件生成时将条件向量置为零即可。classifier-free guidance一方面大大减轻了条件生成的训练代价,无需训练额外的分类器,只需要在训练时进行随机drop out condition来同时训练两个目标,另一方面,这样的条件生成并不是以一个类似于对抗攻击的方式进行。
DDIM代码详细解读(3):核心采样代码、超分辨率重建
沉迷单车的追风少年
11-02
2246
之前写过三篇详细解读DDPM代码的博客,随着时间已经来到2022年10月,单纯使用DDPM已经逐渐被淘汰,最新的论文更多使用DDPM的改进版本。DDIM作为DDPM最重要的改进版本之一,从本篇博客开始详细解读一下DDIM代码。这篇博客详细讲解一下如何设计核心采样代码、 如何用diffusion models做超分辨重建。
1. DDIM反转2. 音频扩散模型
addminister的博客
11-10
107
最后,我们定义了一个 ddim_reverse 函数,用于使用训练好的模型对输入图片进行 DDIM 反转,并将结果保存到输出文件。完成任务后,可以对比分析原始图片、模糊小猫图片、替换小猫后的图片,从而了解 DDIM 反转技术在图像处理中的应用和效果。此外,可以尝试使用不同的图像和扩散程度,进一步探索 DDIM 反转技术的灵活性和适用范围。- 最后,将生成的小猫图像与原始的 demo 图片进行融合,得到一张中小猫被替换为小狗的图片。- 然后,对生成的模糊图像进行 DDIM 反转,使其恢复到清晰的小猫图像。
【diffusers】(一) diffusers库介绍 & 框架代码解析
Lizhi_Tech的博客
08-18
5121
说到现在最常用的stable diffusion代码,那肯定莫过于stable-diffusion-webui了,它的快捷安装、可视化界面、extension模块等等功能都拓展了使用人群。虽然在大多数情况下webui都有很好的适用性,但是在某些特殊需求或者应用场景下,我们需要对模型部分结构进行修改(比如把condition模块从文字换成图像,甚至是点云、图表、图结构等数据形式),这时修改模型的同时也需要修改前端可视化代码,时间成本上会较高(主要是我也不会Gradio)。那可不可以在源码上进行修改呢?
请描述扩散模型的数学原理
04-11
扩散模型的数学原理是基于扩散方程式的描述。扩散方程式是一个偏微分方程,描述了扩散现象的时间和空间演化。具体而言,它描述了物质在一定浓度梯度下从高浓度向低浓度的运动。扩散方程式中的未知数是浓度,它是一个随着时间和空间位置而变化的函数。扩散方程式可以用于描述各种扩散现象,包括热传导、化学反应、质量传递等。
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
stevensmj
CSDN认证博客专家
CSDN认证企业博客
码龄4年
暂无认证
4
原创
151万+
周排名
19万+
总排名
1370
访问
等级
42
积分
5
粉丝
2
获赞
1
评论
8
收藏
私信
关注
热门文章
扩散模型从原理到实践-第四周
799
扩散模型从原理到实践-第一周
218
扩散模型从原理到实践-第三周
204
扩散模型从原理到实践-第二周
128
分类专栏
笔记
最新评论
扩散模型从原理到实践-第四周
CSDN-Ada助手:
恭喜您撰写了第四篇博客!标题“扩散模型从原理到实践-第四周”让我对您的内容充满了好奇。通过您的努力和持续创作,我相信您已经深入了解了扩散模型的原理,并且能够将其应用到实际情境中。
在下一步的创作中,我希望您可以更深入地探讨扩散模型的实践应用,或者分享一些实际案例,让读者更容易理解和应用这个模型。此外,您也可以考虑加入一些图表或数据来支持您的观点,这将进一步提升您博客的可信度和吸引力。
总之,您的创作已经引起了我的兴趣,我期待着阅读更多有关扩散模型的内容。谦虚的建议,希望对您有所帮助!继续加油!
如何快速涨粉,请看该博主的分享:https://hope-wisdom.blog.csdn.net/article/details/130544967?utm_source=csdn_ai_ada_blog_reply5
扩散模型从原理到实践-第三周
CSDN-Ada助手:
非常感谢您的持续创作,标题为“扩散模型从原理到实践-第三周”的博客。您以如此系统和深入的方式探讨扩散模型的原理和实践,令人印象深刻。我真诚地祝贺您在这个主题上取得的进展。
在下一步的创作中,我谦虚地建议您可以考虑深入研究扩散模型在实际应用中的潜力和局限性。或许可以探讨一些具体的案例,如在社交媒体上的信息传播,或者市场营销中的产品推广等。通过这样的案例研究,可以更好地理解扩散模型的实际应用,并为读者提供更多有用的见解。
再次恭喜您的连续创作,并期待您在未来的博客中继续分享您的知识和见解。谢谢!
CSDN 正在通过评论红包奖励优秀博客,请看红包流:https://bbs.csdn.net/?type=4&header=0&utm_source=csdn_ai_ada_blog_reply3
扩散模型从原理到实践-第二周
CSDN-Ada助手:
非常感谢您在第二周写的这篇博客!您对扩散模型的原理和实践进行了深入的探讨,这对于学习和应用扩散模型的读者来说将会非常有帮助。
我鼓励您继续创作,分享更多关于扩散模型的知识和实践经验。除了调整和引导现有的扩散模型,您还可以考虑探讨如何评估扩散模型的效果以及如何解决模型中可能出现的问题。此外,您还可以介绍一些常用的扩散模型算法和工具,以及如何选择合适的模型应用于不同的实际情境中。
再次感谢您的分享,期待您更多关于扩散模型的精彩内容!
如何写出更高质量的博客,请看该博主的分享:https://blog.csdn.net/lmy_520/article/details/128686434?utm_source=csdn_ai_ada_blog_reply2
扩散模型从原理到实践-第一周
CSDN-Ada助手:
恭喜您开始博客创作!标题中的“扩散模型从原理到实践”听起来非常有趣。我期待着阅读您的博客,并深入了解这个话题。在下一步的创作中,也许您可以考虑提供一些实际案例或者应用场景,让读者更好地理解扩散模型的实际运用。希望您能够继续努力,写出更多有价值的内容!
推荐【每天值得看】:https://bbs.csdn.net/forums/csdnnews?typeId=21804&utm_source=csdn_ai_ada_blog_reply1
您愿意向朋友推荐“博客详情页”吗?
强烈不推荐
不推荐
一般般
推荐
强烈推荐
提交
最新文章
扩散模型从原理到实践-第三周
扩散模型从原理到实践-第二周
扩散模型从原理到实践-第一周
2023年4篇
目录
目录
分类专栏
笔记
目录
评论 1
被折叠的 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
个
红包个数最小为10个
红包总金额
元
红包金额最低5元
余额支付
当前余额3.43元
前往充值 >
需支付:10.00元
取消
确定
下一步
知道了
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom 发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额
0
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。
余额充值