DDIM

Dec 11, 2024
2 views
Generative Model

DDPM 有一个非常明显的问题:采样过程很慢。因为 DDPM 的反向过程利用了马尔可夫假设,所以每次都必须在相邻的时间步之间进行去噪,而不能跳过中间步骤。原始论文使用了 1000 个时间步,所以我们在采样时也需要循环 1000 次去噪过程,这个过程是非常慢的。

为了加速 DDPM 的采样过程,DDIM 在不利用马尔可夫假设的情况下推导出了 diffusion 的反向过程,最终可以实现仅采样 20~100 步的情况下达到和 DDPM 采样 1000 步相近的生成效果,也就是提速 10~50 倍。这篇文章将对 DDIM 的理论进行讲解,并实现 DDIM 采样的代码。

DDPM 的反向过程

首先我们回顾一下 DDPM 反向过程的推导,为了推导出 \(q(\mathbf{x}_{t-1}|\mathbf{x}_t)\) 这个条件概率分布,DDPM 利用贝叶斯公式将其变成了先验分布的组合,并且通过向条件中加入 $\mathbf{x}_0 $ 将所有的分布转换为已知分布:

\[ q(\mathbf{x}_{t-1}|\mathbf{x}_t,\mathbf{x}_0)=\frac{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)} \]

在上边这个等式的右侧, \(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)\)。在这里 DDPM 引入马尔可夫假设,认为 \(\mathbf{x}_t\) 只与 \(\mathbf{x}_{t-1}\) 有关,将其转化成了 \(q(\mathbf{x}_t|\mathbf{x}_{t-1})\)。最后经过推导,得出条件概率分布:

\[ q(\mathbf{x}_{t-1}|\mathbf{x}_t)=\mathcal{N}(\mathbf{x}_{t-1};\mu_\theta(\mathbf{x}_t,t),\sigma_t^2\mathbf{I}) \]

推导的路线归纳如下:

\[ 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) \]

我们可以看到之所以 DDPM 很慢,就是因为在推导 \(q(\mathbf{x}_t|\mathbf{x}_{t-1},\mathbf{x}_0)\) 的时候引入了马尔可夫假设,使得去噪只能在相邻时间步之间进行。如果我们可以在不依赖马尔可夫假设的情况下推导出 \(q(\mathbf{x}_{t-1}|\mathbf{x}_t,\mathbf{x}_0)\),就可以将上面式子里的 \(t-1\) 替换为任意的中间时间步 \(\tau\),从而实现采样加速。总结来说,DDIM 主要有两个出发点:

  1. 保持前向过程的分布 \(q(\mathbf{x}_t|\mathbf{x}_{t-1})=\mathcal{N}\left(\mathbf{x}_t;\sqrt{\bar{\alpha}_t}\mathbf{x}_0,(1-\bar{\alpha}_t)\mathbf{I}\right)\) 不变;
  2. 构建一个不依赖于马尔可夫假设的 \(q(\mathbf{x}_\tau|\mathbf{x}_t,\mathbf{x}_0)\) 分布。

\(q(\mathbf{x}_\tau|\mathbf{x}_t,\mathbf{x}_0)\) 的推导

开始推导之前简单说明一下,这个 \(q(\mathbf{x}_\tau|\mathbf{x}_t,\mathbf{x}_0)\) 实际上就是上一章中提到的 \(q(\mathbf{x}_{t-1}|\mathbf{x}_t,\mathbf{x}_0)\),只不过是因为我们的推导不再依赖马尔可夫假设,所以 \(t-1\) 可以替换为任意的 \(\tau\in(0,t)\)。为了避免混淆,我们在这里使用一个通用的符号 \(\tau\in(0,t)\) 表示中间的时间步。

另一点需要说明的是,在 DDIM 的论文中, \(\alpha\) 表示的含义和 DDPM 论文中的 \(\bar{\alpha}\) 相同。为了保证前后一致,我们在这里依然使用 DDPM 的符号约定,令 \(\alpha_t=1-\beta_t\)\(\bar{\alpha}_t=\prod_{i=1}^t\alpha_i\)

我们在 DDPM 里已经推导出了 \(q(\mathbf{x}_{t-1}|\mathbf{x}_t,\mathbf{x}_0)\) 是一个高斯分布,均值和方差为:

\[ \begin{aligned} \mu&=\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\\ \sigma&=\left(\frac{\alpha_t}{\beta_t}+\frac{1}{1-\bar\alpha_{t-1}}\right)^{-1/2} \end{aligned} \]

没有给定\(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)\) 的解空间更大,某种意义上来说是更加容易推导,此时它只需要满足边际分布条件:

\[ \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) \]

我们用待定系数法来求解这个方程。在DDPM中,所解出的\(p(\boldsymbol{x}_{t-1}|\boldsymbol{x}_t, \boldsymbol{x}_0)\)是一个正态分布,而且可以看到均值是 \(\mathbf{x}_0\)\(\mathbf{x}_t\) 的线性组合,方差是时间步的函数。所以这一次我们可以更一般地设DDIM 基于这样的规律,使用待定系数法:

\[ \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}\)的两个采样形式,我们发现要想公式1成立,只需要满足两个方程

\[ \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)\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\)

在上边的结果中,我们得到了 \(q(\mathbf{x}_\tau|\mathbf{x}_t,\mathbf{x}_0)\) 均值中的两个参数,而方差 \(\sigma_t^2\) 并没有唯一定值,因此这个结果对应于一组解,通过规定不同的方差,可以得到不同的采样过程

接下来的事情,就跟上一篇文章一模一样了:我们最终想要\(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)\)替换掉式4中的\(\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}\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}\small{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)}\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\)的两个选择之一,在此选择下式6未能做进一步的化简,但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)\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})\)步了,根据式6有:

\[ \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})\)步。要注意不能直接将式式6\(\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}\),比如式7对应的\(\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步允许我们尝试更多的其他加速手段,但并不会显著增加训练成本。

此外,对于\(\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 区别于 DDPM 的两个特性

  1. 采样一致性:我们知道 DDIM 的采样过程是确定的,生成结果只受 \(\mathbf{x}_T\) 影响。作者经过实验发现对于同一个 \(\mathbf{x}_T\) ,使用不同的采样过程,最终生成的 \(\mathbf{x}_0\) 比较相近,因此 \(\mathbf{x}_T\) 在一定程度上可以看作 \(\mathbf{x}_0\) 的一种嵌入。
  2. 语义插值效应:根据上一条性质, \(\mathbf{x}_T\) 可以看作 \(\mathbf{x}_0\) 的嵌入,那么它可能也具有其他隐概率模型所具有的语义差值效应。作者首先选取两个隐变量 \(\mathbf{x}_T^{(0)}\)\(\mathbf{x}_T^{(1)}\),对其分别采样得到结果,然后使用球面线性插值得到一系列中间隐变量,这个插值定义为:

微分方程

最后,我们来重点分析一下\(\sigma_t = 0\)的情形。此时公式8可以等价地改写成:

\[ \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)\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}\),代入到式13化简得

\[ \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}\),代入到式13化简得

\[ \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} \]

DDIM 的代码实现

从上面的推导过程可以发现,DDIM 假设的前向过程和 DDPM 相同,只有采样过程不同。因此想把 DDPM 改成 DDIM 并不需要重新训练,只要修改采样过程就可以了。我们已经训练好了一个 DDPM 模型,这里我们继续用这个训练好的模型来构造 DDIM 的采样过程。

我们把训练好的 DDPM 模型的权重加载进来用作噪声预测网络:

 from diffusers import UNet2DModel

 model = UNet2DModel.from_pretrained('ddpm-anime-faces-64').cuda()

核心代码

首先我们依然是定义一系列常量, \(\alpha\)\(\beta\) 等都和 DDPM 相同,只有采样的时间步不同。我们在这里直接线性选取 20 个时间步,最大的为 999,最小的为 0:

 import torch

 class DDIM:
     def __init__(
         self,
         num_train_timesteps: int = 1000,
         beta_start: float = 0.0001,
         beta_end: float = 0.02,
         sample_steps: int = 20,
     ):
         self.num_train_timesteps = num_train_timesteps
         self.betas = torch.linspace(beta_start, beta_end, num_train_timesteps, dtype=torch.float32)
         self.alphas = 1.0 - self.betas
         self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
         self.timesteps = torch.linspace(num_train_timesteps - 1, 0, sample_steps).long()

然后是实现采样过程,和 DDPM 一样,我们把需要的公式复制到这里,然后对照着实现:

\[ \begin{aligned} \mathbf{x}_\tau&=\sqrt{\bar{\alpha}_\tau}\frac{\mathbf{x}_t-\sqrt{1-\bar{\alpha}_t}\epsilon_\theta(\mathbf{x}_t,t)}{\sqrt{\bar{\alpha}_t}}+\sqrt{1-\bar{\alpha}_\tau-\sigma_t^2}\epsilon_\theta(\mathbf{x}_t,t)+\sigma_t\epsilon\\ \sigma_t&=\eta\sqrt{\frac{1-\bar{\alpha}_\tau}{1-\bar{\alpha}_t}}\sqrt{1-\alpha_t} \end{aligned} \]
 import math
 from tqdm import tqdm

 class DDIM:
     ...

     @torch.no_grad()
     def sample(
         self,
         unet: UNet2DModel,
         batch_size: int,
         in_channels: int,
         sample_size: int,
         eta: float = 0.0,
     ):
         alphas = self.alphas.to(unet.device)
         alphas_cumprod = self.alphas_cumprod.to(unet.device)
         timesteps = self.timesteps.to(unet.device)
         images = torch.randn((batch_size, in_channels, sample_size, sample_size), device=unet.device)
         for t, tau in tqdm(list(zip(timesteps[:-1], timesteps[1:])), desc='Sampling'):
             pred_noise: torch.Tensor = unet(images, t).sample

             # sigma_t
             if not math.isclose(eta, 0.0):
                 one_minus_alpha_prod_tau = 1.0 - alphas_cumprod[tau]
                 one_minus_alpha_prod_t = 1.0 - alphas_cumprod[t]
                 one_minus_alpha_t = 1.0 - alphas[t]
                 sigma_t = eta * (one_minus_alpha_prod_tau * one_minus_alpha_t / one_minus_alpha_prod_t) ** 0.5
             else:
                 sigma_t = torch.zeros_like(alphas[0])

             # first term of x_tau
             alphas_cumprod_tau = alphas_cumprod[tau]
             sqrt_alphas_cumprod_tau = alphas_cumprod_tau ** 0.5
             alphas_cumprod_t = alphas_cumprod[t]
             sqrt_alphas_cumprod_t = alphas_cumprod_t ** 0.5
             sqrt_one_minus_alphas_cumprod_t = (1.0 - alphas_cumprod_t) ** 0.5
             first_term = sqrt_alphas_cumprod_tau * (images - sqrt_one_minus_alphas_cumprod_t * pred_noise) / sqrt_alphas_cumprod_t

             # second term of x_tau
             coeff = (1.0 - alphas_cumprod_tau - sigma_t ** 2) ** 0.5
             second_term = coeff * pred_noise

             epsilon = torch.randn_like(images)
             images = first_term + second_term + sigma_t * epsilon
         images = (images / 2.0 + 0.5).clamp(0, 1).cpu().permute(0, 2, 3, 1).numpy()
         return images

上面的内容和 DDPM 大同小异,只有计算公式变了,应该没有太多坑,只要看清楚变量就可以了。最后我们执行采样过程:

 ddim = DDIM()
 images = ddim.sample(model, 32, 3, 64)

 from diffusers.utils import make_image_grid, numpy_to_pil
 image_grid = make_image_grid(numpy_to_pil(images), rows=4, cols=8)
 image_grid.save('ddim-sample-results.png')

结果展示

采样速度的确是变快了很多,得到的结果如下图所示:

image

DDIM 采样结果

感觉总体上采样效果比 DDPM 稍微有所下降,不过也还在可以接受的范围内,算是一种速度-质量的 tradeoff。

语义插值效应复现

语义插值效应也比较简单,只需要修改初始化的$ \mathbf{x}_T$ 即可。根据上文的叙述,我们首先实现球面线性插值:

\[ \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)},~~\mathrm{where}~\theta=\arccos\left(\frac{(\mathbf{x}_T^{(0)})^T\mathbf{x}_T^{(1)}}{||\mathbf{x}_T^{(0)}||~||\mathbf{x}_T^{(1)}||}\right) \]
 import torch

 def slerp(
     x0: torch.Tensor,
     x1: torch.Tensor,
     alpha: float,
 ):
     theta = torch.acos(torch.sum(x0 * x1) / (torch.norm(x0) * torch.norm(x1)))
     w0 = torch.sin((1.0 - alpha) * theta) / torch.sin(theta)
     w1 = torch.sin(alpha * theta) / torch.sin(theta)
     return w0 * x0 + w1 * x1

我们这次要实现的和原论文不同,原论文的插值只在一行内部,我们希望实现一个二维的插值,也就是在一个图片网格中,从左上角到右下角存在一个渐变效果。为此,我们需要先构建一个二维的图片网格,然后按以下的步骤完成二维插值: