条件控制扩散模型

参考:https://www.zhangzhenhu.com/aigc/Guidance.html

无论是 DDPM 还是 DDIM,这些扩散模型在生成图片时,都是输入一个随机高斯噪声数据, 然后逐步的产出一张有意的真实图片。这个过程中每一步都是一个随机过程,所以每次执行产出的图片都不一样, 生成的图像多样性非常好。 但这也是一个缺点:生成的图像不可控,无法控制这个生成过程并令其生成我们想要的图像内容

鉴于此,很多研究中在如何控制图像生成过程方面提出了很多有效的方案。 直觉的讲,我们可以在扩散过程中引入额外的信息来指导或者说控制整个扩散模型, 假设这个额外的信息为 y,它可以是一段文本、一张图片或者图像的类别标签。 引入 y 之后的模型就变成了一个以 y 为条件的条件概率分布。

自然而然地,接下来就需要探讨,引入y 之后对前向扩散过程和逆向采用过程分别有什么影响,需要做出什么调整。 首先看下对前向扩散过程的影响,先说结论:引入 y 之后,对前向扩散过程没有任何影响。 其实,从直觉上讲,前向扩散过程是对原始图片加噪声,直至变成纯噪声,这个过程显然与 y没有任何关系。 但做研究要严谨,还是需要给出数学证明的。 证明过程在论文 1 中已经给出。

条件扩散模型的前向过程与非条件扩散模型的前向过程完全一样

1、classifier guidance

OpenAI 的团队在 2021 年发表一篇论文 1 : A. Diffusion models beat gans on image synthesis ,在这篇论文中,提出一种利用图片类别标签指导图像生成的方案,称为 classifier guidance, 通过这种改进使扩散模型生成图像的质量大幅提升,并在 IS 和 FID 评分上超过了 GAN 模型, 所以你看论文的名字,简单直接。

论文的源码在: https://github.com/openai/guided-diffusion 。

实际上这篇论文做了很多改进,比如对UNET也做了改进。但这里我们只关注 guidance 部分。 原论文的推导过程比较繁杂,这里我们采用另一篇文章 2 的推导方案, 直接从 score function 的角度去理解。

虽然引入 classifier guidance 效果很明显,但缺点也很明显:

  1. 需要额外一个分类器模型,极大增加了成本,包括训练成本和采样成本。
  2. 分类器的类别毕竟是有限集,不能涵盖全部情况,对于没有覆盖的标签类别会很不友好

后来《More Control for Free! Image Synthesis with Semantic Diffusion Guidance》推广了“Classifier”的概念,使得它也可以按图、按文来生成。Classifier-Guidance方案的训练成本比较低(熟悉NLP的读者可能还会想起与之很相似的PPLM模型),但是推断成本会高些,而且控制细节上通常没那么到位。

2、Classifier-free guidance

引导函数的方法存在一些问题:1)额外的计算量比较多;2)引导函数和扩散模型分别进行训练,不利于进一步扩增模型规模,不能够通过联合训练获得更好的效果。

  • 提出了一个等价的结构替换了外部的classifier,从而可以直接使用一个扩散模型来做条件生成任务。

实际做法只是改变了模型输入的内容,有conditional(随机高斯噪声+引导信息的embedding)和unconditional两种采样输入。两种输入都会被送到同一个diffusion model,从而让其能够具有无条件和有条件生成的能力。

3、CLIP Guidance

Radford, A., Kim, J. W., Hallacy, C., Ramesh, A., Goh, G., Agarwal, S., Sastry, G., Askell, A., Mishkin, P., Clark, J., Krueger, G., and Sutskever, I. Learning transferable visual models from natural language supervision. arXiv:2103.00020, 2021

Prafulla Dhariwal and Alex Nichol. Diffusion models beat gans on image synthesis. 2021. arXiv:2105.05233.[2](1,2)

Calvin Luo. Understanding diffusion models: a unified perspective. 2022. arXiv:2208.11970.[3]

Jonathan Ho and Tim Salimans. Classifier-free diffusion guidance. 2022. arXiv:2207.12598.[4]

Alex Nichol, Prafulla Dhariwal, Aditya Ramesh, Pranav Shyam, Pamela Mishkin, Bob McGrew, Ilya Sutskever, and Mark Chen. Glide: towards photorealistic image generation and editing with text-guided diffusion models. 2022. arXiv:2112.10741.[5]

Aditya Ramesh, Prafulla Dhariwal, Alex Nichol, Casey Chu, and Mark Chen. Hierarchical text-conditional image generation with clip latents. 2022. arXiv:2204.06125.[6]

Chitwan Saharia, William Chan, Saurabh Saxena, Lala Li, Jay Whang, Emily Denton, Seyed Kamyar Seyed Ghasemipour, Burcu Karagol Ayan, S. Sara Mahdavi, Rapha Gontijo Lopes, Tim Salimans, Jonathan Ho, David J Fleet, and Mohammad Norouzi. Photorealistic text-to-image diffusion models with deep language understanding. 2022. arXiv:2205.11487.

去噪扩散隐式模型(Denoising Diffusion Implicit Models,DDIM)

Paper: https://arxiv.org/abs/2010.02502

Code: https://github.com/ermongroup/ddim

摘自:扩散模型之DDIM

在 DDPM 中,生成过程被定义为马尔可夫扩散过程的反向过程,在逆向采样过程的每一步,模型预测噪声

DDIM 的作者发现,扩散过程并不是必须遵循马尔科夫链, 在之后的基于分数的扩散模型以及基于随机微分等式的理论都有相同的结论。 基于此,DDIM 的作者重新定义了扩散过程和逆过程,并提出了一种新的采样技巧, 可以大幅减少采样的步骤,极大的提高了图像生成的效率,代价是牺牲了一定的多样性, 图像质量略微下降,但在可接受的范围内。

对于扩散模型来说,一个最大的缺点是需要设置较长的扩散步数才能得到好的效果,这导致了生成样本的速度较慢,比如扩散步数为1000的话,那么生成一个样本就要模型推理1000次。这篇文章我们将介绍另外一种扩散模型DDIMDenoising Diffusion Implicit Models),DDIM和DDPM有相同的训练目标,但是它不再限制扩散过程必须是一个马尔卡夫链,这使得DDIM可以采用更小的采样步数来加速生成过程,DDIM的另外是一个特点是从一个随机噪音生成样本的过程是一个确定的过程(中间没有加入随机噪音)。

前提条件:1.马尔可夫过程。2.微小噪声变化。

步骤一:在DDPM中我们基于初始图像状态以及最终高斯噪声状态,通过贝叶斯公式以及多元高斯分布的散度公式,可以计算出每一步骤的逆向分布。之后继续重复上述对逆向分布的求解步骤,最终实现从纯高斯噪声,恢复到原始图片的步骤。

步骤二:模型优化部分通过最小化分布的交叉熵,预测出模型逆向分布的均值和方差,将其带入步骤一中的推理过程即可。

文章中存在的一个核心问题是:由于1.每个步骤都是马尔可夫链。2.每次加特征的均值和方差都需要控制在很小的范围下。因此我们不得不每一步都进行逆向的推理和运算,导致模型整体耗时很长。本文核心针对耗时问题进行优化,一句话总结:在满足DDPM中逆向推理的条件下,找到一种用 xt  x0 表达 xt−1 且能能大幅减少计算量的推理方式。

代码实现:

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的加速方式非常简单,直接采样一个子序列,其实论文DDPM+也采用了类似的方式来加速。另外DDIM和其它扩散模型的一个较大的区别是其生成过程是确定性的。

stable diffusion:潜在扩散模型

参考:

1、https://zhuanlan.zhihu.com/p/573984443

2、https://zhangzhenhu.github.io/blog/aigc

3、 https://zhuanlan.zhihu.com/p/599160988

扩散概率模型(diffusion probabilistic models)

1、 扩散概率模型(diffusion probabilistic model)

2、降噪扩散概率模型(Denoising diffusion probabilistic model,DDPM)

3、基于分数的解释(Score-based DDPM)

4、扩散模型的三种等价表示

5、改进降噪扩散概率模型(Improved Denoising Diffusion Probabilistic Models,IDDPM)

6. 参考文献

Jascha Sohl-Dickstein, Eric A. Weiss, Niru Maheswaranathan, and Surya Ganguli. Deep unsupervised learning using nonequilibrium thermodynamics. 2015. arXiv:1503.03585.2(1,2,3,4,5,6,7)

Calvin Luo. Understanding diffusion models: a unified perspective. 2022. arXiv:2208.11970.3(1,2,3,4)

Jonathan Ho, Ajay Jain, and Pieter Abbeel. Denoising diffusion probabilistic models. 2020. arXiv:2006.11239.4

Diederik P. Kingma, Tim Salimans, Ben Poole, and Jonathan Ho. Variational diffusion models. 2022. arXiv:2107.00630.5

Yang Song and Stefano Ermon. Generative modeling by estimating gradients of the data distribution. 2019. arXiv:1907.05600.

去噪扩散隐式模型(Denoising Diffusion Implicit Models,DDIM)

Jiaming Song, Chenlin Meng, and Stefano Ermon. Denoising diffusion implicit models. 2022. arXiv:2010.02502.

基于分数的生成模型

Yang Song and Stefano Ermon. Generative modeling by estimating gradients of the data distribution. 2019. arXiv:1907.05600.

Yang Song, Jascha Sohl-Dickstein, Diederik P. Kingma, Abhishek Kumar, Stefano Ermon, and Ben Poole. Score-based generative modeling through stochastic differential equations. 2021. arXiv:2011.13456.

Aapo Hyvärinen and Peter Dayan. Estimation of non-normalized statistical models by score matching. Journal of Machine Learning Research, 2005.

Yang Song and Stefano Ermon. Improved techniques for training score-based generative models. 2020. arXiv:2006.09011.

条件控制扩散模型

Prafulla Dhariwal and Alex Nichol. Diffusion models beat gans on image synthesis. 2021. arXiv:2105.05233.2(1,2)

Calvin Luo. Understanding diffusion models: a unified perspective. 2022. arXiv:2208.11970.3

Jonathan Ho and Tim Salimans. Classifier-free diffusion guidance. 2022. arXiv:2207.12598.4

Alex Nichol, Prafulla Dhariwal, Aditya Ramesh, Pranav Shyam, Pamela Mishkin, Bob McGrew, Ilya Sutskever, and Mark Chen. Glide: towards photorealistic image generation and editing with text-guided diffusion models. 2022. arXiv:2112.10741.5

Aditya Ramesh, Prafulla Dhariwal, Alex Nichol, Casey Chu, and Mark Chen. Hierarchical text-conditional image generation with clip latents. 2022. arXiv:2204.06125.6

Chitwan Saharia, William Chan, Saurabh Saxena, Lala Li, Jay Whang, Emily Denton, Seyed Kamyar Seyed Ghasemipour, Burcu Karagol Ayan, S. Sara Mahdavi, Rapha Gontijo Lopes, Tim Salimans, Jonathan Ho, David J Fleet, and Mohammad Norouzi. Photorealistic text-to-image diffusion models with deep language understanding. 2022. arXiv:2205.11487.

 稳定扩散模型(Stable diffusion model)


Robin Rombach, Andreas Blattmann, Dominik Lorenz, Patrick Esser, and Björn Ommer. High-resolution image synthesis with latent diffusion models. 2021. arXiv:2112.10752.

DDPM 模型在生成图像质量上效果已经非常好,但它也有个缺点, 那就是 尺寸是和图片一致的,元素和图片的像素是一一对应的, 所以称 DDPM 是像素(pixel)空间的生成模型。 我们知道一张图片的尺寸,如果想生成一张高尺寸的图像, 张量大小是非常大的,这就需要极大的显卡(硬件)资源,包括计算资源和显存资源。 同样的,它的训练成本也是高昂的。高昂的成本极大的限制了它在民用领用的发展

潜在扩散模型

2021年德国慕尼黑路德维希-马克西米利安大学计算机视觉和学习研究小组(原海德堡大学计算机视觉小组), 简称 CompVis 小组,发布了论文 High-Resolution Image Synthesis with Latent Diffusion Models 1,针对这个问题做了一些改进, 主要的改进点有:

  • 引入一个自编码器,先对原始对象进行压缩编码,编码后的向量再应用到扩散模型。
  • 通过在 UNET 中加入 Attention 机制,处理条件变量 

Diffusion Models从入门到放弃:必读的10篇经典论文

前言:diffusion models是现在人工智能领域最火的方向之一,并引爆了AIGC领域,一大批创业公司随之诞生。笔者2021年6月开始研究diffusion,见证了扩散模型从无人问津到炙手可热的过程,这些篇经典论文专栏里都详细介绍过原理、复现过代码。这篇博客以时间发展顺序,串讲一下从入门到精(放)通(弃)的10篇必读的经典论文。

摘自:https://zhuanlan.zhihu.com/p/595866176

1、DDPM奠基之作:《Denoising Diffusion Probabilistic Models》

推荐理由:本文是DDPM的奠基之作,是本领域最经典的论文之一。其实扩散模型并不是一个新的概念,这篇论文第一个给出了严谨的数学推导,可以复现的代码,完善了整个推理过程。后面diffusion models相关的论文基本都继承了前向加噪-反向降噪-训练这样的体系。所以强烈推荐初学者精读这篇论文!

博客地址Diffusion Models扩散模型与深度学习(数学原理和代码解读)

代码地址GitHub – lucidrains/denoising-diffusion-pytorch: Implementation of Denoising Diffusion Probabilistic Model in Pytorch

2、从DDPM到DDIM:《Denoising Diffusion Implicit Models》

推荐理由:作者使用简单的重参数化和混合学习目标来学习反向过程方差,该目标将VLB与DDPM的简化目标相结合。在混合目标下,所提出模型获得的对数似然比通过直接优化对数似然获得的对数似然更好,并发现后一个目标在训练期间具有更多的梯度噪声。最关键的是,原先的DDPM需要长达1000steps的推理步骤,而DDIM改善了反向扩散过程中的噪声水平,改变了xt递推公式,在更少的推理步骤(如100步)上取得了更好的结果。这项成果堪称革命性的,后面的大部分diffusion models论文(特别是运算量高的)都采用这一改进技术。

博客地址深入解读:从DDIM到Improved Denoising Diffusion Probabilistic Models

代码地址GitHub – ermongroup/ddim: Denoising Diffusion Implicit Models

3、第一波高潮!首次击败GANs:《Diffusion Models Beat GANs on Image Synthesis》

推荐理由:其实前面diffusion models也只是在生成领域小火了一把,并没有引起太多人的关注。主要原因有两点:一是扩散模型并没有太多数学理论上的创新;二是在生成指标上不如GANs。而这篇论文的出现把diffusion models的推向了第一波高潮!这篇论文有三个需要重点学习的地方:

一是在Unet基础上有了很多改进的小trick(不亏是OpenAI的作品),改进之后的Unet更能适配噪声,因此指标上也进步了很多:

如果看完这部分不妨反问自己这几个问题:模型结构是如何共享信息参数的?self-attention的作用是什么?预测噪声数据和预测真实数据有没有本质区别?为什么要分层设计?为什么一定要使用Unet结构?如果不清楚,欢迎看看我之前的博客:《为什么Diffusion Models钟爱U-net结构?》

二是classifier-guidance的引入,这段推导用了二阶泰勒展开,非常精彩!之后的很多论文将类别引导扩展到一般的条件引导上,包括后来大火的GLIDE。这在latent diffusion models没出来之前,是一项非常成功、被广泛使用的条件引导技术!

三是规范化的代码guided-diffusion。OpenAI的工匠精神,这份代码打磨得非常好,堪称工业级!后面很多顶会论文都是在这份代码的基础上改进的。如果想要学习diffusion models的代码,推荐以这份代码为基础。

博客地址击败GANs的新生成式模型:score-based model(diffusion model)原理、网络结构、应用、代码、实验、展望

代码地址GitHub – openai/guided-diffusion

4、条件分类器技术进一步发展:《Classifier-Free Diffusion Guidance》

推荐理由:我推荐的其他论文基本上都发表机器学习/计算机视觉顶会,而这篇文章虽然只发表于cvpr workshop,但是作者提出了一个新的分数估计函数:有条件分数函数和无条件分数函数的线性组合,平衡了有条件的分数函数和无条件分数函数。当然在论文中作者先论述了《Diffusion Models Beat GANs on Image Synthesis》中提出的分类器技术的问题:额外训练一个分类器,并且往往会造成多样性下降的特点。当然这个问题也是必然出现的,因为分类器就是在生成质量和生成多样性中间做平衡。之所以推荐这篇论文,因为这项技术被后来的论文广泛应用,简单有用,值得学习!

博客地址无分类器指导的Classifier-free Diffusion Models技术

代码地址https://github.com/lucidrains/classifier-free-guidance-pytorch

5、Image-to-Image经典之作《Palette: Image-to-Image Diffusion Models》

推荐理由:我不确定Palette是不是第一个实现diffusion models 图像翻译工作的,但是一定是第一个火起来让很多圈内人关注的!Palette从pix2pix GANs中获取灵感,能够实现图像着色、图像修复、图像剪裁恢复、图像解压缩(超分)等等任务,最大的意义在于让更多人看到了diffusion models在图像翻译领域的潜力。从cvpr 2021开始,海量的相关论文被发表。

博客地址用Diffusion Models实现image-to-image转换

代码地址https://github.com/Janspiry/Palette-Image-to-Image-Diffusion-Models

6、畅游多模态领域:GLIDE

推荐理由:经典的三篇text-to-image的论文:DALLE 2、Imagen、GLIDE。在上半年各领风骚,让text-to-image方向成为diffusion中最受关注的领域。这三篇论文最先推荐的GLIDE的原因是它最先放出完成代码和预训练模型。预训练模型很重要!因为text-to-image领域都是大模型,不放出模型的话,我们这些非大组(指能分到40块显卡以上的)研究者根本无法在这基础上自己做迁移学习。GLIDE的核心跨模态引导公式来自《Diffusion Models Beat GANs on Image Synthesis》中的分类器引导,不同的是,这篇文章并没有给出严谨的证明过程。但是实验结果表明确实取得了很好的效果,后面的研究者从中获得启示,把其他的多模态信息按照这种方法注入,也取得了非常惊艳的结果。

博客地址2021年度最火Diffusion Models:用于图像编辑和text引导图像生成的GLIDE

代码地址https://github.com/openai/glide-text2im/tree/main/glide_text2im

7、stable diffusion的原型:《High-Resolution Image Synthesis with Latent Diffusion Models》

推荐理由:全体起立!终于讲到stable diffusion models了!这篇论文发表在cvpr 2022上,当时就受到了很多研究者们的关注,但是谁也没想到,一年后以latent diffusion models会孵化出stable diffusion这样彻底火出圈的作品。这篇论文有两个关键点值得关注:一是用encoder-decoder放缩到latent域上操作,又回到了生成领域最经典的结构,在latent域(即z)上操作,这种方法在vae上也算常用。二是cross-attention的结构,这种方法早在2020年的论文handwriting diffusion上就用过,但是当时并没有引起广泛的注意。在这之后cross-attention成为多模态的一种常用方法,成为新的常用条件扩散模型。

博客地址详细解读Latent Diffusion Models:原理和代码

代码地址https://github.com/CompVis/latent-diffusion

8、高调进军视频领域:《Video Diffusion Models》

推荐理由:有位“诗人”曾经说过:站在风口上X都能飞。这篇论文出现的时候,diffusion models已经在图像、多模态、3D等领域大杀四方了。video生成很显然是下一个风口,这时候谷歌研究院的作品video diffusion models横空出世。这篇论文需要注意两个点:一是怎样引入时序信息的方法,很值得借鉴。二是梯度引导法是首次被提出,当时我写的博客中说如果好用肯定会很快流行。事实证明,谷歌出品必属精品,果然流行的一番!

Video Diffusion Models:基于扩散模型的视频生成_沉迷单车的追风少年的博客-CSDN博客

博客地址Video Diffusion Models:基于扩散模型的视频生成

代码地址https://github.com/lucidrains/video-diffusion-pytorch

9、了不起的attention:《Prompt-to-Prompt Image Editing with Cross Attention Control》

推荐理由:在今年的ICLR中,diffusion models超过图神经网络,成为投稿最多的主题。这几千篇投稿中,这篇论文取得了审稿人的一致accept好评。这篇文章沿用了latent diffusion models提出了cross-attention的结构,但是做了不少改进,特别需要注意的是可解释性问题,作者将QKV可视化,替换attention map达到控制的目的。这种控制技术相比于LDM更细腻,更有说服力。

博客地址【ICLR 2023】Diffusion Models扩散模型和Prompt Learning提示学习:prompt-to-prompt

代码地址https://github.com/bloc97/CrossAttentionControl

10、Unet已死,transformer当立!《Scalable Diffusion Models with Transformers》

推荐理由:Unet本来是发源于医疗图像分割的backbone,后来pix2pix GANs开始引入到生成领域,diffusion models的研究者们一直想替换掉这个backbone,用更原生的方法。在语音领域、时间序列领域,早在2020年就有论文引入transformer作为backbone。不过笔者尝试将其引入到二维图像生成上,并没有取得好的效果。最近的一项研究成果成功用改进版本的transformer替换掉Unet,并取得了更好的效果。笔者最近复现了代码,大为震撼!我觉得这项研究生过会很快流行,强烈推荐!

博客地址:尚未写完,敬请期待哈哈

代码地址https://github.com/facebookrese

Improved DDPM

作者:Alex Nichol*, Prafulla Dhariwal*

关键词:diffusion model, fast sampling

论文:Improved Denoising Diffusion Probabilistic Models

知乎:https://zhuanlan.zhihu.com/p/557971459

摘要

去噪扩散概率模型(DDPM)是一类生成模型,最近已被证明能产生良好的样本。我们表明,通过一些简单的修改,DDPM也可以在保持高样本质量的同时实现具有竞争力的对数似然。此外,我们发现,反向扩散过程的学习方差允许以数量级更少的正向传递进行采样,样本质量差异可以忽略,这对于这些模型的实际部署非常重要。我们还使用精度和重新调用来比较DDPM和GANs覆盖目标分布的程度。最后,我们表明,这些模型的样本质量和似然性随模型容量和训练计算而平滑扩展,使其易于扩展。

贡献

  • 噪声机制更新,使用cosine
  • 引入了方差项的学习

方差学习

faster sampling

DDPM是一步一步的往上采样,这里有一个strided sampling schedule,也就是每次网上采样100步,参数都没变化。

Paper List

  1. (DDPM) Denoising Diffusion Probabilistic Models. NIPS 20. (Diffusion and deep-learning-based 图像生成开山之作)
  2. More Control for Free! Image Synthesis with Semantic Diffusion Guidance. arXiv 21. (对DDIM进行了推广,引入了一般形式的判别器引导)
  3. Denoising Diffusion Implicit Models. ICLR 21. (提出了一种新的sampling的方法,可以通过改变eta来skip一些step,进而达到加速sampling的目的)
  4. Improved denoising diffusion probabilistic models. ICML 21.
  5. Classifier-Free Diffusion Guidance. NIPSW 21. (引入了等价结构替代了分类器引导)
  6. GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models. ICML 22.
  7. Hierarchical Text-Conditional Image Generation with CLIP Latents. NIPS 22 在投. (DALL-E 2)
  8. Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding. NIPS 22 在投. (Imagen, SOTA)
  9. High-Resolution Image Synthesis with Latent Diffusion Models. CVPR 22. (隐空间LDM)

基于扩散模型的语义分割

论文标题:LABEL-EFFICIENT SEMANTIC SEGMENTATION WITH DIFFUSION MODELS

论文地址:https://arxiv.org/pdf/2112.03126.pdf

论文代码:https://github.com/yandex-research/ddpm-segmentation

摘要

  1. 背景介绍: 去噪扩散概率模型DDPM最近受到了很多研究关注,因为它们优于其他方法,如GAN,并且目前提供了最先进的生成性能。差分融合模型的优异性能使其在修复、超分辨率和语义编辑等应用中成为一个很有吸引力的工具。
  2. 研究方法: 作者为了证明扩散模型也可以作为语义分割的工具,特别是在标记数据稀缺的情况下。对于几个预先训练的扩散模型,作者研究了网络中执行逆扩散过程马尔可夫步骤的中间激活。结果表明这些激活有效地从输入图像中捕获语义信息,并且似乎是分割问题的出色像素级表示。基于这些观察结果,作者描述了一种简单的分割方法,即使只提供了少量的训练图像也可以使用。
  3. 实验结果: 提出的算法在多个数据集上显着优于现有的替代方法。

K-L散度(相对熵)

Kullback-Leibler Divergence,即K-L散度,是一种量化两种概率分布P和Q之间差异的方式,又叫相对熵。在概率学和统计学上,我们经常会使用一种更简单的、近似的分布来替代观察数据太复杂的分布。K-L散度能帮助我们度量使用一个分布来近似另一个分布时所损失的信息量。

数据的熵

K-L散度源于信息论。信息论主要研究如何量化数据中的信息。最重要的信息度量单位是Entropy,一般用H表示。分布的熵的公式如下:

上面对数没有确定底数,可以是2e10,等等。如果我们使用以2为底的对数计算H值的话,可以把这个值看作是编码信息所需要的最少二进制位个数bits。上面空间蠕虫的例子中,信息指的是根据观察所得的经验分布给出的蠕虫牙齿数量。计算可以得到原始数据概率分布的熵值为3.12 bits。这个值只是告诉我们编码蠕虫牙齿数量概率的信息需要的二进制位bit的位数。

可是熵值并没有给出压缩数据到最小熵值的方法,即如何编码数据才能达到最优(存储空间最优)。优化信息编码是一个非常有意思的主题,但并不是理解K-L散度所必须的。熵的主要作用是告诉我们最优编码信息方案的理论下界(存储空间),以及度量数据的信息量的一种方式。理解了熵,我们就知道有多少信息蕴含在数据之中,现在我们就可以计算当我们用一个带参数的概率分布来近似替代原始数据分布的时候,到底损失了多少信息。

K-L散度度量信息损失

只需要稍加修改熵H的计算公式就能得到K-L散度的计算公式。设p为观察得到的概率分布,q为另一分布来近似p,则pqK-L散度为:

entropy-p-q

显然,根据上面的公式,K-L散度其实是数据的原始分布p和近似分布q之间的对数差值的期望。如果继续用2为底的对数计算,则K-L散度值表示信息损失的二进制位数。下面公式以期望表达K-L散度:

一般,K-L散度以下面的书写方式更常见:

注:log a - log b = log (a/b)

OK,现在我们知道当用一个分布来近似另一个分布时如何计算信息损失量了

散度并非距离

很自然地,一些同学把K-L散度看作是不同分布之间距离的度量。这是不对的,因为从K-L散度的计算公式就可以看出它不符合对称性(距离度量应该满足对称性)。也就是说,用p近似q和用q近似p,二者所损失的信息并不是一样的。

如果你熟悉神经网络,你肯能已经猜到我们接下来要学习的内容。除去神经网络结构的细节信息不谈,整个神经网络模型其实是在构造一个参数数量巨大的函数(百万级,甚至更多),不妨记为f(x),通过设定目标函数,可以训练神经网络逼近非常复杂的真实函数g(x)。训练的关键是要设定目标函数,反馈给神经网络当前的表现如何。训练过程就是不断减小目标函数值的过程。

图像生成模型 Stable Diffusion|CVPR ’22 Oral

项目地址:https://ommer-lab.com/research/latent-diffusion-models/

试玩: https://huggingface.co/spaces/stabilityai/stable-diffusion

High-Resolution Image Synthesis with Latent Diffusion Models

Stable Diffusion 是一个“文本到图像”的人工智能模型。近日,Stable AI 公司向公众开放了它的预训练模型权重。当输入一个文字描述时,Stable Diffusion 可以生成 512×512 像素的图像,这些图像如相片般真实,反映了文字描述的场景。

这个项目先是经历了早期的代码发布,而后又向研究界有限制地发布了模型权重,现在模型权重已经向公众开放。对于最新版本,任何人都可以在为普通消费者设计的硬件上下载和使用 Stable Diffusion。该模型不仅支持文本到图像的生成,而且还支持图像到图像的风格转换和放大。与之一同发布的还有 DreamStudio 测试版,这是一个用于该模型的 API 和 Web 用户界面。

Stable AI 公司表示:

“Stable Diffusion 是一个文本到图像的模型,它将使数十亿人在几秒钟内创造出令人惊叹的艺术。它在速度和质量上的突破意味着它可以在消费者级的 GPU 上运行。这将允许研究人员和公众在一系列条件下运行它,并使图像生成普及化。我们期待着有围绕这个模型和其他模型的开放生态系统出现,以真正探索潜伏空间的边界。”

Latent Diffusion 模型(LDM)是 Stable Diffusion 模型建立的一种图像生成方法。LDM 通过在潜伏表示空间(latent representation space)中迭代“去噪”输入来创建图像,然后将表示解码为完整的图像,这与其他著名的图像合成技术,如生成对抗网络(GAN)和 DALL-E 采用的自动回归方法不同。最近的 IEEE/CVF 计算机视觉和模式识别会议(CVPR)上有一篇关于 LDM 的论文,它是由慕尼黑路德维希-马克西米利安大学的机器视觉和学习研究小组创建的。今年早些时候,InfoQ 也报道的另一个基于扩散的图片生成 AI 是谷歌的 Imagen 模型。

Stable Diffusion 可以支持众多的操作。与 DALL-E 类似,它可以生成一个高质量的图像,并使其完全符合所需图像的文字描述。我们也可以使用一个直观的草图和所需图像的文字描述,从而创建一个看起来很真实的图像。类似的“图像到图像”的能力可以在 Meta AI 的 Make-A-Scene 模型中找到,该模型刚发布不久。

一些人公开分享了 Stable Diffusion 创建的照片的例子,Stable AI 的首席开发人员 Katherine Crowson 也在 Twitter 上分享了许多照片。毫无疑问,基于人工智能的图片合成技术将对艺术家和艺术界产生影响,这令一些观察家感到担忧。值得注意的是,在 Stable Diffusion 发布的同一周,一幅由人工智能生成的作品在科罗拉多州博览会的艺术竞赛中获得了最高荣誉。

Stable Diffusion 的源代码可以在 GitHub 上查阅。

试玩地址: https://huggingface.co/spaces/stabilityai/stable-diffusion

Contribution

  • Diffusion model是一种likelihood-based的模型,相比GAN可以取得更好的生成效果。然而该模型是一种自回归模型,需要反复迭代计算,因而训练和推理都十分昂贵。本文提出一种diffusion的过程改为在latent space上做的方法,从而大大减少计算复杂度,同时也能达到十分不错的生成效果。( “democratizing” research on DMs),在unconditional image synthesis, inpainting, super-resolution都能表现不错~
  • 相比于其它进行压缩的方法,本文的方法可以生成更细致的图像,并且在高分辨率(风景图之类的,最高达10242px都无压力)的生成也表现得很好。
  • 提出了cross-attention的方法来实现多模态训练,使得class-condition, text-to-image, layout-to-image也可以实现。
方法We condition LDMs either via concatenation or by a
more general cross-attention mechanism.

整体框架如图,先训练好一个AutoEncoder(包括一个encoder和decoder)。因此,我们可以利用encoder压缩后的数据做diffusion操作,再用decoder恢复即可。

  • Autoencoder训练: L1/L2loss来作为重建损失,用GAN来做对抗攻击?,用KL loss来把latent space拉到正态分布,防止搜索空间过大
  • 用了encoder降维后,就可以使用latent space diffusion了~ 具体扩散过程其实没有变,只不过现在扩散和重建的目标为latent space的向量了。Diffusion model具体实现为 time-conditional UNet。

为了引入conditioning的信息,提出了domain specific encoder τθ(y)不同模态的(比如text, class, image…)转成中间表达(intermediate representation),再利用cross-attention来嵌入到UNet中去。

Experiments

展示一些可用的任务:

  • layout-to-image 输入bounding box输出图像。
  • text-to-image输入文本,输出图像。
  • 输入bounding box输出图像。
  • 输入文本,输出图像
    • 输入landscape输出高分辨率的风景图。
    • 超分辨率
    • inpainting (图像修复/编辑)

    效率对比。大概时间上缩短为1/3~ 并且,FID的值更小。

    Diffusion Model 综述

    Diffusion Models: A Comprehensive Survey of Methods and Applications来自加州大学&Google Research的Ming-Hsuan Yang、北京大学崔斌实验室以及CMU、UCLA、蒙特利尔Mila研究院等众研究团队,首次对现有的扩散生成模型(diffusion model)进行了全面的总结分析,从diffusion model算法细化分类、和其他五大生成模型的关联以及在七大领域中的应用等方面展开,最后提出了diffusion model的现有limitation和未来的发展方向

    论文:https://arxiv.org/abs/2209.00796

    摘自:AI科技评论

    github链接:https://github.com/YangLing0818/Diffusion-Models-Papers-Survey-Taxonomy(扩散模型论文汇总)

    介绍

    扩散模型(diffusion models)是深度生成模型中新的SOTA。扩散模型在图片生成任务中超越了原SOTA:GAN,并且在诸多应用领域都有出色的表现,如计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化等。此外,扩散模型与其他研究领域有着密切的联系,如稳健学习、表示学习、强化学习。然而,原始的扩散模型也有缺点,它的采样速度慢,通常需要数千个评估步骤才能抽取一个样本;它的最大似然估计无法和基于似然的模型相比;它泛化到各种数据类型的能力较差。如今很多研究已经从实际应用的角度解决上述限制做出了许多努力,或从理论角度对模型能力进行了分析。然而,现在缺乏对扩散模型从算法到应用的最新进展的系统回顾。为了反映这一快速发展领域的进展,我们对扩散模型进行了首个全面综述。我们设想我们的工作将阐明扩散模型的设计考虑和先进方法,展示其在不同领域的应用,并指出未来的研究方向。此综述的概要如下图所示:

    尽管diffusion model在各类任务中都有着优秀的表现,它仍还有自己的缺点,并有诸多研究对diffusion model进行了改善。为了系统地阐明diffusion model的研究进展,我们总结了原始扩散模型的三个主要缺点,采样速度慢,最大化似然差、数据泛化能力弱,并提出将的diffusion models改进研究分为对应的三类:采样速度提升、最大似然增强和数据泛化增强。我们首先说明改善的动机,再根据方法的特性将每个改进方向的研究进一步细化分类,从而清楚的展现方法之间的联系与区别。在此我们仅选取部分重要方法为例, 我们的工作中对每类方法都做了详细的介绍,内容如图所示:

    在分析完三类扩散模型后,我们将介绍其他的五种生成模型GAN,VAE,Autoregressive model, Normalizing flow, Energy-based model。考虑到扩散模型的优良性质,研究者们已经根据其特性将diffusion model与其他生成模型结合,所以为了进一步展现diffusion model 的特点和改进工作,我们详细地介绍了diffusion model和其他生成模型的结合的工作并阐明了在原始生成模型上的改进之处。Diffusion model在诸多领域都有着优异的表现,并且考虑到不同领域的应用中diffusion model产生了不同的变形,我们系统地介绍了diffusion model的应用研究,其中包含如下领域:计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化。对于每个任务,我们定义了该任务并介绍利用扩散模型处理任务的工作,我们将本项工作的主要贡献总结如下:

    • 新的分类方法:我们对扩散模型和其应用提出了一种新的、系统的分类法。具体的我们将模型分为三类:采样速度增强、最大似然估计增强、数据泛化增强。进一步地,我们将扩散模型的应用分为七类:计算机视觉,NLP、波形信号处理、多模态建模、分子图建模、时间序列建模、对抗性净化。
    • 全面的回顾:我们首次全面地概述了现代扩散模型及其应用。我们展示了每种扩散模型的主要改进,和原始模型进行了必要的比较,并总结了相应的论文。对于扩散模型的每种类型的应用,我们展示了扩散模型要解决的主要问题,并说明它们如何解决这些问题。
    • 未来研究方向:我们对未来研究提出了开放型问题,并对扩散模型在算法和应用方面的未来发展提供了一些建议。

    扩散模型基础

    生成式建模的一个核心问题是模型的灵活性和可计算性之间的权衡。扩散模型的基本思想是正向扩散过程来系统地扰动数据中的分布,然后通过学习反向扩散过程恢复数据的分布,这样就了产生一个高度灵活且易于计算的生成模型。

    1.Denoising Diffusion Probabilistic Models(DDPM)

    一个DDPM由两个参数化马尔可夫链组成,并使用变分推断以在有限时间后生成与原始数据分布一致的样本。前向链的作用是扰动数据,它根据预先设计的噪声进度向数据逐渐加入高斯噪声,直到数据的分布趋于先验分布,即标准高斯分布。反向链从给定的先验开始并使用参数化的高斯转换核,学习逐步恢复原数据分布。用表示原始数据及其分布,则前向链的分布是可由下式表达:

    这说明前向链是马尔可夫过程,是加入t步噪音后的样本,是事先给定的控制噪声进度的参数。当 趋于1时,可以近似认为服从标准高斯分布。当很小时,逆向过程的转移核可以近似认为也是高斯的:

    我们可以将变分下界作为损失函数进行学习:

    2.Score-Based Generative Models(SGM)

    上述DDPM可以视作SGM的离散形式。SGM构造一个随机微分方程(SDE)来平滑的扰乱数据分布,将原始数据分布转化到已知的先验分布:

    和一个相应的逆向SDE,来将先验分布变换回原始数据分布:

    因此,要逆转扩散过程并生成数据,我们需要的唯一信息就是在每个时间点的分数函数。利用score-matching的技巧我们可以通过如下损失函数来学习分数函数:

    对两种方法的进一步介绍和两者关系的介绍请参见我们的文章。原始扩散模型的三个主要缺点,采样速度慢,最大化似然差、数据泛化能力弱。最近许多研究都在解决这些缺点,因此我们将改进的扩散模型分为三类:采样速度提升、最大似然增强和数据泛化增强。在接下来的三、四、五节我们将对这三类模型进行详细的介绍。

    采样加速方法

    在应用时,为了让新样本的质量达到最佳,扩散模型往往需要进行成千上万步计算来获取一个新样本。这限制了diffusion model的实际应用价值,因为在实际应用时,我们往往需要产生大量的新样本,来为下一步处理提供材料。研究者们在提高diffusion model采样速度上进行了大量的研究。我们对这些研究进行了详细的阐述。我们将其细化分类为三种方法:Discretization Optimization,Non-Markovian Process,Partial Sampling。

    1.Discretization Optimization

    方法优化求解diffusion SDE的方法。因为现实中求解复杂SDE只能使用离散解来逼近真正的解,所以该类方法试图优化SDE的离散化方法,在保证样本质量的同时减少离散步数。SGM 提出了一个通用的方法来求解逆向过程,即对前向和后向过程采取相同的离散方法。如果给定了前向SDE的离散方式:

    那么我们就可以以相同的方式离散化逆向SDE:

    这种方法比朴素DDPM效果略好一点。进一步,SGM向SDE求解器中加入了一个矫正器,从而让每一步生成的样本都有正确的分布。在求解的每一步,求解器给出一个样本后,矫正器都使用马尔可夫链蒙特卡罗方法来矫正刚生成的样本的分布。实验表明向求解器中加入矫正器比直接增加求解器的步数效率更高。

    2.Non-Markovian Process方法突破了原有Markovian Process的限制,其逆过程的每一步可以依赖更多以往的样本来进行预测新样本,所以在步长较大时也能做出较好的预测,从而加速采样过程。其中主要的工作DDIM,不再假设前向过程是马尔可夫过程,而是服从如下分布:

    DDIM的采样过程可以视为离散化的神经常微分方程,其采样过程更高效,并且支持样本的内插。进一步的研究发现DDIM可以视作流形上扩散模型PNDM的特例。3.Partial Sampling方法通过在generation process中忽略一部分的时间节点,而只使用剩下的时间节点来生成样本,直接减少了采样时间。例如,Progressive Distillation从训练好的扩散模型中蒸馏出效率更高的扩散模型。对于训练好的一个扩散模型,Progressive Distillation会从新训练一个扩散模型,使新的扩散模型的一步对应于训练好的扩散模型的两步,这样新模型就可以省去老模型一半的采样过程。具体算法如下:

    不断循环这个蒸馏过程就能让采样步骤指数级下降。

    最大似然估计加强

    扩散模型在最大似然估计的表现差于基于似然函数的生成模型,但最大化似然估计在诸多应用场景都有重要意义,比如图片压缩, 半监督学习, 对抗性净化。由于对数似然难以直接计算,研究主要集中在优化和分析变分下界(VLB)。我们对提高扩散模型最大似然估计的模型进行了详细的阐述。我们将其细化分类为三类方法:Objectives Designing,Noise Schedule Optimization,Learnable Reverse Variance。

    1.Objectives Designing方法利用扩散 SDE推倒出生成数据的对数似然与分数函数匹配的损失函数的关系。这样通过适当设计损失函数,就可以最大化 VLB 和对数似然。Song et al. 证明了可以设计损失函数的权重函数,使得plug-in reverse SDE 生成样本的似然函数值小于等于损失函数值,即损失函数是似然函数的上界。分数函数拟合的损失函数如下:

    我们只需将权重函数设为扩散系数g(t)即可让损失函数成为似然函数的VLB,即:

    2.Noise Schedule Optimization通过设计或学习前向过程的噪声进度来增大VLB。VDM证明了当离散步数接近无穷时,损失函数完全由信噪比函数SNR(t)的端点决定:

    那么在离散步数接近无穷时,可以通过学习信噪比函数SNR(t)的端点最优化VLB,而通过学习信噪比函数中间部分的函数值来实现模型其他方面的改进。

    3.Learnable Reverse Variance方法学习反向过程的方差,从而较少拟合误差,可以有效地最大化VLB。Analytic-DPM证明,在DDPM和DDIM中存在反向过程中的最优期望和方差:

    使用上述公式和训练好的分数函数,在给定前向过程的条件下,最优的VLB可以近似达到。

    数据泛化增强

    扩散模型假设数据存在于欧几里得空间,即具有平面几何形状的流形,并添加高斯噪声将不可避免地将数据转换为连续状态空间,所以扩散模型最初只能处理图片等连续性数据,直接应用离散数据或其他数据类型的效果较差。这限制了扩散模型的应用场景。数个研究工作将扩散模型推广到适用于其他数据类型的模型,我们对这些方法进行了详细地阐释。我们将其细化分类为两类方法:Feature Space Unification,Data-Dependent Transition Kernels。1.Feature Space Unification方法将数据转化到统一形式的latent space,然后再latent space上进行扩散。LSGM提出将数据通过VAE框架先转换到连续的latent space 上后再在其上进行扩散。这个方法的难点在于如何同时训练VAE和扩散模型。LSGM表明由于潜在先验是intractable的,分数匹配损失不再适用。LSGM直接使用VAE中传统的损失函数ELBO作为损失函数,并导出了ELBO和分数匹配的关系:

    该式在忽略常数的意义下成立。通过参数化扩散过程中样本的分数函数,LSGM可以高效的学习和优化ELBO。

    2.Data-Dependent Transition Kernels方法根据数据类型的特点设计diffusion process 中的transition kernels,使扩散模型可以直接应用于特定的数据类型。D3PM为离散型数据设计了transition kernel,可以设为lazy random-walk,absorbing state等。GEODIFF为3D分子图数据设计了平移-旋转不变的图神经网络,并且证明了具有不变性的初分布和transition kernel可以导出具有不变性的边缘分布。假设是一个平移-旋转变换,如:

    那么生成的样本分布也有平移-旋转不变性:

    和其他生成模型的联系

    在下面的每个小节中,我们首先介绍其他五类重要的生成模型,并分析它们的优势和局限性。然后我们介绍了扩散模型是如何与它们联系起来的,并说明通过结合扩散模型来改进这些生成模型。VAE,GAN,Autoregressive model, Normalizing flow, Energy-based model和扩散模型的联系如下图所示:

    1. DDPM可以视作层次马尔可夫VAE(hierarchical Markovian VAE)。但DDPM和一般的VAE也有区别。DDPM作为VAE,它的encoder和decoder都服从高斯分布、有马尔科夫行;其隐变量的维数和数据维数相同;decoder的所有层都共用一个神经网络。
    2. DDPM可以帮助GAN解决训练不稳定的问题。因为数据是在高维空间中的低维流形中,所以GAN生成数据的分布和真实数据的分布重合度低,导致训练不稳定。扩散模型提供了一个系统地增加噪音的过程,通过扩散模型向生成的数据和真实数据添加噪音,然后将加入噪音的数据送入判别器,这样可以高效地解决GAN无法训练、训练不稳定的问题。
    3. Normalizing flow通过双射函数将数据转换到先验分布,这样的作法限制了Normalizing flow的表达能力,导致应用效果较差。类比扩散模型向encoder中加入噪声,可以增加Normalizing flow的表达能力,而从另一个视角看,这样的做法是将扩散模型推广到前向过程也可学习的模型。
    4. Autoregressive model在需要保证数据有一定的结构,这导致设计和参数化自回归模型非常困难。扩散模型的训练启发了自回归模型的训练,通过特定的训练方式避免了设计的困难。
    5. Energy-based model直接对原始数据的分布建模,但直接建模导致学习和采样都比较困难。通过使用扩散恢复似然,模型可以先对样本加入微小的噪声,再从有略微噪声的样本分布来推断原始样本的分布,使的学习和采样过程更简单和稳定。

    扩散模型的应用

    在本节中,我们分别介绍了扩散模型在计算机视觉、自然语言处理、波形信号处理、多模态学习、分子图生成、时间序列以及对抗学习等七大应用方向中的应用,并对每类应用中的方法进行了细分并解析。例如在计算机视觉中可以用diffusion model进行图像补全修复(RePaint):

    在多模态任务中可以用diffusion model进行文本到图像的生成(GLIDE):

    还可以在分子图生成中用diffusion model进行药物分子和蛋白质分子的生成(GeoDiff):

    应用分类汇总见表:

    未来研究方向

    1. 应用假设再检验。我们需要检查我们在应用中普遍接受的假设。例如,实践中普遍认为扩散模型的前向过程会将数据转换为标准高斯分布,但事实并非如此,更多的前向扩散步骤会使最终的样本分布与标准高斯分布更接近,与采样过程一致;但更多的前向扩散步骤也会使估计分数函数更加困难。理论的条件很难获得,因此在实践中操作中会导致理论和实践的不匹配。我们应该意识到这种情况并设计适当的扩散模型。
    2. 从离散时间到连续时间。由于扩散模型的灵活性,许多经验方法可以通过进一步分析得到加强。通过将离散时间的模型转化到对应的连续时间模型,然后再设计更多、更好的离散方法,这样的研究思路有前景。
    3. 新的生成过程。扩散模型通过两种主要方法生成样本:一是离散化反向扩散 SDE,然后通过离散的反向 SDE 生成样本;另一个是使用逆过程中马尔可夫性质对样本逐步去噪。然而,对于一些任务,在实践中很难应用这些方法来生成样本。因此,需要进一步研究新的生成过程和视角。
    4. 泛化到更复杂的场景和更多的研究领域。虽然目前diffusion model已经应用到多个场景中,但是大多数局限于单输入单输出的场景,将来可以考虑将其应用到更复杂的场景,比如text-to-audiovisual speech synthesis。也可以考虑和更多的研究领域相结合。

    part2:【扩散模型笔记整理】从DDPM到Imagen

    扩散模型(Diffusion Model)

    1. 概述

    • 如图所示,扩散模型分两个过程:扩散(diffusion, 从x0到xT的过程逐步加入噪声)和去噪(denoise, 从xT到x0逐步去噪)。训练的时候,需要利用扩散加噪来生成训练样本;推理的时候,输入一个噪音,逐步去噪输出原始信号(比如图像、语音)。

    参考文献:Denoising Diffusion Probabilistic Models

    2. 扩散和去噪(Diffusion&Denoise)

    • 首先介绍一下高斯分布的表达,记作X∽N(μ,σ2):

    扩散过程每一步都加入一个方差为βt∈(0,1)的高斯噪声可以用马尔科夫链来表示:

    • 这里的βt是一个0到1的等比序列(β0=0),此时表示原始图像;第T步的时候,βt=1,表示标准高斯噪声N∽(0,I)。因而实际上扩散过程是一个从原始图像变为标准高斯分布的过程。加噪和高斯采样等价,无非就是改变了一下高斯采样的均值中心点。
    • 实际训练的时候,我们可以直接用下面的公式一次性算出某一步的加噪图片作为训练素材,无需逐步迭代。
    • 去噪过程和扩散过程反过来:从一张随机采样的高斯噪声图片逐步去噪得到我们想要生成的图像。表达式:
    • 去噪过程,需要用模型预测加入的高斯噪声,得到原始的无噪声的图像。上式表示,利用模型算出原始第n步的未加噪图像,实质上只要算出均值和方差,再做一个采样得到原始图像。而为了算出μ,我们需要预测出噪声ϵ,反推出原始图像的均值中心,方差项可以由网络预测也可以取常数(前者效果好)。下节将介绍模型的训练和推理过程。

    3. 训练和采样(Training&Sampling)

    • 训练其实就是扩散过程,而采样其实就是去噪过程。
    • 算法如上图所示,训练training的过程实际上是随机采第t步的加噪图像,输入带噪图片以及步数t,模型预测噪声ϵ,模型训练目标:预测噪声与实际加入噪声的误差越小越好。
    • 采样sampling的过程(生成过程)为:将有噪声的图像(第一张图像为随机采样的高斯分布噪声)减去模型预测的噪声(噪声前面的其它参数可以由上面加噪的过程反向推导出来)不断把噪声去掉以恢复出原始的图像。
    • 方差项σ也可以由模型来预测。

    参考文献: Improved Denoising Diffusion Probabilistic Models

    引导扩散模型(Guided Diffusion)

    前文已经讲述扩散模型的原理,然而我们随机输入一张高斯噪声显然不能按照人的意愿生成我们想要的内容,因而需要额外的引导guidance以得到我们需要的图像。一种想法是使用外部模型(分类器or广义的判别器)的输出作为引导条件来指导扩散模型的去噪过程,从而得到我们想要的输出;还有一种则比较直观一些:我们直接把我们想要的引导条件condition也作为模型输入的一部分,从而让扩散模型见到这个条件后就可以直接生成我们想要的内容。

    下文将讲解classifier guidance和semantic guidance diffusion model(后者包括前者,前者是比较简单的一个应用),除此之外,由于额外的判别器会拖慢推理速度,因此后来有人提出了 classifier-free guidance diffusion model来替代前面的那种方案,也即把条件作为模型的输入,直接生成我们需要的图像。

    1. Classifier Guidance Diffusion Model

    • 这种方法不用额外训练扩散模型,直接在原有训练好的扩散模型上,通过外部的分类器来引导生成期望的图像。唯一需要改动的地方其实只有sampling过程中的高斯采样的均值,也即采样过程中,期望噪声图像的采样中心越靠近判别器引导的条件越好。
    • 上图总结了采样算法。Algorithm 1和 Algorithm 2其实是等价的(1是直接预测均值和方差,2是预测噪声的误差)。直接看Algorithm 1可知,实质上改变的只有高斯分布的均值中心,将扩散方向“引导”成我们想要的内容。具体而言,用分类模型pϕ对生成的图片进行分类,得到预测分数与目标类别的交叉熵,将其对带噪图像求梯度用梯度引导下一步的生成采样。(实际使用的时候,需要把这个分类器也在带噪数据额外训练一下)
    • 因为我们实际使用的模型预测的是噪音,实际计算为Algorithm 2,可以由1推导而来。(具体推导过程可以参考文献)

    参考文献:Diffusion Models Beat GANs on Image Synthesis

    2. Semantic Guidance Diffusion

    • 介绍完前面的 classifier guidance后,显然我们可以把分类器替换成其它任意的判别器,也即更换引导条件,从而实现利用不同的语义信息来指导扩散模型的去噪过程。比如说,我们可以实现text-guidance和image-guidance等。
    • 实质上就是把classifier guidance的条件推广,表达为:
    • Fϕ表示就是新的引导条件,这里展示的是分类的,其实也可以换成相似度之类的分数指标。具体可以有以下的例子:图像引导、文本引导、图像+文本引导。


    参考文献:More Control for Free! Image Synthesis with Semantic Diffusion Guidance

    3. Classifier-Free Guidance Diffusion

    • 正如前文提到的,额外引入一个网络来指导,推理的时候比较复杂(扩散模型需要反复迭代,每次迭代都需要额外算一个分数)。然而,直接将引导条件作为模型的输入,直到Classifier-Free Diffusion Guidance被提出前似乎效果也一般般。Classifier-Free Diffusion Guidance这篇文章的贡献就是提出了一个等价的结构替换掉了外部的判别器,从而可以直接用一个扩散模型来做条件生成任务。
    • 实际做法只是改变了模型输入的内容,有conditional(除了随机高斯噪声输入外,把引导信息的embedding也加进来)和unconditional 的 sample输入。两种输入都会被送到同一个diffusion model从而让其能够具有无条件和有条件生成的能力。得到这两种输入的输出后,就可以用来引导扩散模型进行训练。
    • 回忆一下前面的 classifier guidance的噪音更新方式:
    • 实质上,这个classifier-free用另一个近似的等价结构替换掉了后面那一项:
    • 其中,ϵθ(xt,y) 表示conditional的输入,而ϵθ(xt)则表示unconditional输入,用这两项之差乘以一个系数来替换掉原来的那项。至于为什么可以这么直接替换,其实可以用贝叶斯公式推导而来:
    • 因而,实际上这个过程就训练了一个 implicit classifier,从而移除外部的分类器。

    参考文献:Classifier-Free Diffusion Guidance

    GLIDE

    • 这篇文章主要就是用到了前面所说的classifier-free扩散模型,只不过把输入的condition换成了文本信息,从而实现文本生成图像,此外还利用diffusion model实现了超分辨率。一些效果展示如下,可以看到,其实已经可以生成一些比较逼真的图片了。
    • 具体可以表达为:
    • 这里无非就是把原来的label y换成了 caption,实际上就是运用了足够量的image-text pair从而可以把caption当作是某种程度上的label。(随机替换为空序列以实现unconditional的训练方式)
    动图封面
    • 由于此时的生成图像质量一般般,文章也提供了图像编辑的方式(具体操作为:将选中区域mask掉,将图像也作为一个condition连同文本输入到模型中去):

    DALL·E 2

    概况

    • 第一版DALL·E用的是GAN+CLIP重排序的结构。
    • DALL·E 2可以把diffusion model和CLIP结合在一起,生成效果十分惊艳,可以直接去官网浏览一下。DALL·E 2 (openai.com)
    • 包括prior网络用于将caption转换为CLIP image embedding,一个decoder把image embedding作为condition来生成图像。prior有两种:一种是autoregressive model、一种是diffusion model(后者效果更好一些);decoder就是diffusion model。总之,这里相比前面的变化主要在于加入了prior,以及把condition换成了CLIP的embedding。

    Decoder

    • 具体而言,把CLIP image embedding作为condition输入到diffusion model中,同时把CLIP image embedding映射成4个额外的tokens接到GLIDE text encoder的输出。
    • 除了用于生成图像的diffusion model,这部分还有2个额外用于超分辨率的diffusion model,生成高清图像。

    Prior

    这部分的内容是为了将caption y转换为 CLIP image embedding,以用于后面decoder的图像生成。

    • 一种是auto-regressive model,将image embedding转换为一串离散的编码,并且基于condition caption y自回归地预测。(这里不一定要condition on caption(GLIDE的方法——额外用一个Transformer处理caption),也可以condition on CLIP text embedding)。此外,这里还用到了PCA来降维,降低运算复杂度。
    • 一种是diffusion model。这是一个decoder-only Transformer,输入是encoded text+CLIP text embedding+noised CLIP image embedding+额外token(类似class embedding)输入,其输出一个unnoised CLIP image embedding(取那个额外的embedding)。

    Variations

    • 这部分是为了给一张图,生成相似的图像。做法很简单:用CLIP把图像编码,把这个CLIP image embedding作为condition引导decoder生成图像。除此之外,还可以对2张图像的CLIP embedding进行插值,以实现风格迁移。( spherical interpolation 几何球面线性插值)。这里证明了CLIP语义空间的可解释性

    量化结果

    • 本文方法又称unCLIP(其实本质上就是把CLIP生成的embedding进行decode),相比GLIDE有小幅的提高。

    Paper List

    1. (DDPM) Denoising Diffusion Probabilistic Models. NIPS 20. (Diffusion and deep-learning-based 图像生成开山之作)
    2. More Control for Free! Image Synthesis with Semantic Diffusion Guidance. arXiv 21. (对DDIM进行了推广,引入了一般形式的判别器引导)
    3. Denoising Diffusion Implicit Models. ICLR 21. (提出了一种新的sampling的方法,可以通过改变eta来skip一些step,进而达到加速sampling的目的)
    4. Improved denoising diffusion probabilistic models. ICML 21.
    5. Classifier-Free Diffusion Guidance. NIPSW 21. (引入了等价结构替代了分类器引导)
    6. GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models. ICML 22.
    7. Hierarchical Text-Conditional Image Generation with CLIP Latents. NIPS 22 在投. (DALL-E 2)
    8. Photorealistic Text-to-Image Diffusion Models with Deep Language Understanding. NIPS 22 在投. (Imagen, SOTA)
    9. High-Resolution Image Synthesis with Latent Diffusion Models. CVPR 22. (隐空间LDM)

    扩散模型DDPM

    摘自:https://zhuanlan.zhihu.com/p/563661713

    “What I cannot create, I do not understand.” — Richard Feynman

    https://github.com/xiaohu2015/nngen/tree/main/models/diffusion_models

    论文:https://arxiv.org/abs/2006.11239

    近段时间最火的方向无疑是基于文本用AI生成图像,继OpenAI在2021提出的文本转图像模型DALLE之后,越来越多的大公司卷入这个方向,如谷歌在今年相继推出了ImagenParti。一些主流的文本转图像模型如DALL·E 2,stable-diffusion和Imagen采用了扩散模型Diffusion Model)作为图像生成模型,这也引发了对扩散模型的研究热潮。相比GAN来说,扩散模型训练更稳定,而且能够生成更多样的样本,OpenAI的论文Diffusion Models Beat GANs on Image Synthesis也证明了扩散模型能够超越GAN。简单来说,扩散模型包含两个过程:前向扩散过程反向生成过程,前向扩散过程是对一张图像逐渐添加高斯噪音直至变成随机噪音,而反向生成过程是去噪音过程,我们将从一个随机噪音开始逐渐去噪音直至生成一张图像,这也是我们要求解或者训练的部分。扩散模型与其它主流生成模型的对比如下所示:

    目前所采用的扩散模型大都是来自于2020年的工作DDPM: Denoising Diffusion Probabilistic Models,DDPM对之前的扩散模型(具体见Deep Unsupervised Learning using Nonequilibrium Thermodynamics)进行了简化,并通过变分推断(variational inference)来进行建模,这主要是因为扩散模型也是一个隐变量模型(latent variable model),相比VAE这样的隐变量模型,扩散模型的隐变量是和原始数据是同维度的,而且推理过程(即扩散过程)往往是固定的。这篇文章将基于DDPM详细介绍扩散模型的原理,并给出具体的代码实现和分析。

    扩散模型原理

    扩散模型包括两个过程:前向过程(forward process)反向过程(reverse process),其中前向过程又称为为扩散过程(diffusion process),如下图所示。无论是前向过程还是反向过程都是一个参数化的马尔可夫链(Markov chain),其中反向过程可以用来生成数据,这里我们将通过变分推断来进行建模和求解。

    扩散过程

    扩散过程是指的对数据逐渐增加高斯噪音直至数据变成随机噪音的过程。对于原始数据

    ,总共包含T步的扩散过程的每一步都是对上一步得到的数据xt-1按如下方式增加高斯噪音:

    这里{βt}t=1~T为每一步所采用的方差,它介于0~1之间。对于扩散模型,我们往往称不同step的方差设定为variance schedule或者noise schedule,通常情况下,越后面的step会采用更大的方差,即满足β1<β2<⋯<βT。在一个设计好的variance schedule下,的如果扩散步数T足够大,那么最终得到的xT就完全丢失了原始数据而变成了一个随机噪音。 扩散过程的每一步都生成一个带噪音的数据xt,整个扩散过程也就是一个马尔卡夫链

    另外要指出的是, 扩散过程往往是固定的, 即采用一个预先定义好的variance schedule, 比 如DDPM就采用一个线性的variance schedule。扩散过程的一个重要特性是我们可以直接基 于原始数据 \(\mathbf{x}0\) 来对任意 \(t\)步的 \(\mathbf{x}_t\) 进行采样: \(\mathbf{x}_t \sim q\left(\mathbf{x}_t \mid \mathbf{x}_0\right)\) 。这里定义 \(\alpha_t=1-\beta_t\) 和 \(\bar{\alpha}_t=\prod{i=1}^t \alpha_i\) , 通过重参数技巧(和VAE类似), 那么有:

    上述推到过程利用了两个方差不同的高斯分布\(\mathcal{N}\left(\mathbf{0}, \sigma_1^2 \mathbf{I}\right)\) 和 \(\mathcal{N}\left(\mathbf{0}, \sigma_2^2 \mathbf{I}\right)\) 相加等于一个新的高斯分 布 \(\mathcal{N}\left(\mathbf{0},\left(\sigma_1^2+\sigma_2^2\right) \mathbf{I}\right)\) 。反重参数化后, 我们得到:
    \[
    q\left(\mathbf{x}_t \mid \mathbf{x}_0\right)=\mathcal{N}\left(\mathbf{x}_t ; \sqrt{\bar{\alpha}_t} \mathbf{x}_0,\left(1-\bar{\alpha}_t\right) \mathbf{I}\right)
    \]
    扩散过程的这个特性很重要。首先, 我们可以看到 \(\mathbf{x}_t\) 其实可以看成是原始数据 \(\mathbf{x}_0\) 和随机噪音 \(\epsilon\) 的线性组合, 其中\(\sqrt{\bar{\alpha}_t}\) 和 \(\sqrt{1-\bar{\alpha}_t}\) 为组合系数, 它们的平方和等于 1 , 我们也可以称两者分别 为 signal_rate 和 noise_rate (见https://keras.io/examples/generative/ddim/#diffusionschedule和Variational Diffusion Models)。更近一步地,我们可以基于 \(\bar{\alpha}_t\) 而不是 \(\beta_t\) 来定义 noise schedule (见Improved Denoising Diffusion Probabilistic Models所设计的cosine schedule), 因为这样处理更直接, 比如我们直接将 \(\bar{\alpha}_T\) 设定为一个接近0的值, 那么就可以保 证最终得到的 \(\mathbf{x}_T\) 近似为一个随机噪音。其次, 后面的建模和分析过程将使用这个特性。

    反向过程

    扩散过程是将数据噪音化,那么反向过程就是一个去噪的过程,如果我们知道反向过程的每一步的真实分布q(xt−1|xt),那么从一个随机噪音xT∼N(0,I)开始,逐渐去噪就能生成一个真实的样本,所以反向过程也就是生成数据的过程

    估计分布 \(q\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)\) 需要用到整个训练样本, 我们可以用神经网络来估计这些分布。这里, 我们将反向过程也定义为一个马尔卡夫链, 只不过它是由一系列用神经网络参数化的高斯分布来组成:

    \[p\theta\left(\mathbf{x}{0: T}\right)=p\left(\mathbf{x}_T\right) \prod{t=1}^T p_\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right) \quad p\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)=\mathcal{N}\left(\mathbf{x}{t-1} ; \boldsymbol{\mu}\theta\left(\mathbf{x}_t, t\right), \mathbf{\Sigma}\theta\left(\mathbf{x}t, t\right)\right)\]

    这里 \(p\left(\mathbf{x}_T\right)=\mathcal{N}\left(\mathbf{x}_T ; \mathbf{0}, \mathbf{I}\right)\), 而 \(p\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)\) 为参数化的高斯分布, 它们的均值和方差由训练的网络 \(\boldsymbol{\mu}\theta\left(\mathbf{x}t, t\right)\) 和 \(\boldsymbol{\Sigma}\theta\left(\mathbf{x}t, t\right)\) 给出。实际上, 扩散模型就是要得到这些训练好的网络, 因为它们构 成了最终的生成模型。虽然分布 \(q\left(\mathbf{x}{t-1} \mid \mathbf{x}t\right)\) 是不可直接处理的, 但是加上条件\(\mathbf{x}_0\) 的后验分布 \(q\left(\mathbf{x}{t-1} \mid \mathbf{x}t, \mathbf{x}_0\right)\) 却是可处理的, 这里有:

    \[q\left(\mathbf{x}{t-1} \mid \mathbf{x}t, \mathbf{x}_0\right)=\mathcal{N}\left(\mathbf{x}{t-1} ; \tilde{\boldsymbol{\mu}}\left(\mathbf{x}_t, \mathbf{x}_0\right), \tilde{\beta}_t \mathbf{I}\right)
    \]

    下面我们来具体推导这个分布,首先根据贝叶斯公式,我们有:

    由于扩散过程的马尔卡夫链特性,我们知道分布

    ,而由前面得到的扩散过程特性可知:

    所以,我们有:

    这里的 \(C\left(\mathbf{x}t, \mathbf{x}_0\right)\) 是一个和 \(\mathbf{x}{t-1}\) 无关的部分,所以省略。根据高斯分布的概率密度函数定义和上 述结果 (配平方),我们可以得到后验分布 \(q\left(\mathbf{x}t \mid \mathbf{x}{t-1}, \mathbf{x}0\right)\) 的均值和方差:


    可以看到方差是一个定量 (扩散过程参数固定),而均值是一个依赖 \(\mathbf{x}_0\) 和 \(\mathbf{x}_t\) 的函数。这个分布将 会被用于推导扩散模型的优化目标。

    优化目标

    上面介绍了扩散模型的扩散过程和反向过程,现在我们来从另外一个角度来看扩散模型:如果我们把中间产生的变量看成隐变量的话,那么扩散模型其实是包含T个隐变量的隐变量模型(latent variable model),它可以看成是一个特殊的Hierarchical VAEs(见Understanding Diffusion Models: A Unified Perspective):

    相比VAE来说,扩散模型的隐变量是和原始数据同维度的,而且encoder(即扩散过程)是固定的。既然扩散模型是隐变量模型,那么我们可以就可以基于变分推断来得到variational lower boundVLB,又称ELBO)作为最大化优化目标,这里有:

    这里最后一步是利用了Jensen’s inequality(不采用这个不等式的推导见博客What are Diffusion Models?),对于网络训练来说,其训练目标为VLB取负

    我们近一步对训练目标进行分解可得:

    可以看到最终的优化目标共包含 \(T+1\) 项,其中 \(L_0\) 可以看成是原始数据重建,优化的是负对数似然, \(L_0\) 可以用估计的 \(\mathcal{N}\left(\mathbf{x}0 ; \boldsymbol{\mu}\theta\left(\mathbf{x}1, 1\right), \mathbf{\Sigma}\theta\left(\mathbf{x}1, 1\right)\right)\) 来构建一个离散化的decoder来计算(见 DDPM论文3.3部分);而 \(L_T\) 计算的是最后得到的噪音的分布和先验分布的KL散度,这个KL散度没有训练参数,近似为 0 ,因为先验 \(p\left(\mathbf{x}_T\right)=\mathcal{N}(\mathbf{0}, \mathbf{I})\) 而扩散过程最后得到的随机噪音 \(q\left(\mathbf{x}_T \mid \mathbf{x}_0\right)\) 也近似为 \(\mathcal{N}(\mathbf{0}, \mathbf{I})\) ;而 \(L{t-1}\) 则是计算的是估计分布 \(p_\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)\) 和真实后验分布 \(q\left(\mathbf{x}{t-1} \mid \mathbf{x}_t, \mathbf{x}_0\right)\) 的KL散度,这里希望䇝们估计的去噪过程和依赖真实数据的去噪过程近似一致:

    之所以前面我们将 \(p_\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)\) 定义为一个用网络参数化的高斯分布 \(\mathcal{N}\left(\mathbf{x}{t-1} ; \boldsymbol{\mu}\theta\left(\mathbf{x}_t, t\right), \mathbf{\Sigma}\theta\left(\mathbf{x}t, t\right)\right)\), 是因为要匹配的后验分布 \(q\left(\mathbf{x}{t-1} \mid \mathbf{x}t, \mathbf{x}_0\right)\)也是一个高斯分布。对 于训练目标 \(L_0\) 和 \(L{t-1}\) 来说, 都是希望得到训练好的网络 \(\boldsymbol{\mu}\theta\left(\mathbf{x}_t, t\right)\) 和 \(\boldsymbol{\Sigma}\theta\left(\mathbf{x}t, t\right)\) (对于 \(L_0, t=1\) )。DDPM对 \(p\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)\) 做了近一步简化, 采用周定的方差: \(\boldsymbol{\Sigma}\theta\left(\mathbf{x}t, t\right)=\sigma_t^2 \mathbf{I}\), 这里的 \(\sigma_t^2\) 可以 设定为 \(\beta_t\) 或者 \(\tilde{\beta}_t\) (这其实是两个极端, 分别是上限和下限, 也可以采用可训练的方差, 见论文 Improved Denoising Diffusion Probabilistic Models 和Analytic-DPM: an Analytic Estimate of the Optimal Reverse Variance in Diffusion Probabilistic Models)。这里假定 \(\sigma_t^2=\tilde{\beta}_t\), 那么:

    \[q\left(\mathbf{x}{t-1} \mid \mathbf{x}t, \mathbf{x}_0\right)=\mathcal{N}\left(\mathbf{x}{t-1} ; \tilde{\boldsymbol{\mu}}\left(\mathbf{x}t, \mathbf{x}_0\right), \sigma_t^2 \mathbf{I}\right) p\theta\left(\mathbf{x}{t-1} \mid \mathbf{x}_t\right)=\mathcal{N}\left(\mathbf{x}{t-1} ; \boldsymbol{\mu}_\theta\left(\mathbf{x}_t, t\right), \sigma_t^2 \mathbf{I}\right)
    \]
    对于两个高斯分布的KL散度, 其计算公式为(具体推导见生成模型之VAE):

    那么就有:

    那么优化目标即\(L{t-1}\)为:

    从上述公式来看, 我们是希望网络学习到的均值 \(\boldsymbol{\mu}\theta\left(\mathbf{x}_t, t\right)\) 和后验分布的均值 \(\tilde{\boldsymbol{\mu}}\left(\mathbf{x}_t, \mathbf{x}_0\right)\) 一致。不 过DDPM发现预测均值并不是最好的选择。根据前面得到的扩散过程的特性, 我们有:

    \(\mathbf{x}{\mathbf{t}}\left(\mathbf{x}_{\mathbf{0}}, \epsilon\right)=\sqrt{\bar{\alpha}_t} \mathbf{x}_0+\sqrt{1-\bar{\alpha}_t \epsilon} \quad \text { where } \epsilon \sim \mathcal{N}(\mathbf{0}, \mathbf{I})
    \)

    将这个公式带入上述优化目标,可以得到:

    近一步地, 我们对 \(\boldsymbol{\mu}\theta\left(\mathbf{x}{\mathbf{t}}\left(\mathbf{x}0, \epsilon\right), t\right)\) 也进行重参数化, 变成:

    \[\boldsymbol{\mu}\theta\left(\mathbf{x}{\mathbf{t}}\left(\mathbf{x}_0, \epsilon\right), t\right)=\frac{1}{\sqrt{\alpha_t}}\left(\mathbf{x}_t\left(\mathbf{x}_0, \epsilon\right)-\frac{\beta_t}{\sqrt{1-\bar{\alpha}_t}} \epsilon\theta\left(\mathbf{x}t\left(\mathbf{x}_0, \epsilon\right), t\right)\right)\]

    这里的 \(\epsilon\theta\) 是一个基于神经网络的拟合函数, 这意味着我们由原来的预测均值而换成预测噪音 \(\epsilon\) 。我们将上述等式带入优化目标, 可以得到:

    DDPM近一步对上述目标进行了简化, 即去掉了权重系数, 变成了: \(L{t-1}^{\text {simple }}=\mathbb{E}{\mathbf{x}_0, \epsilon \sim \mathcal{N}(0, \mathrm{I})}\left[\left|\epsilon-\epsilon\theta\left(\sqrt{\bar{\alpha}_t} \mathbf{x}_0+\sqrt{1-\bar{\alpha}_t} \epsilon, t\right)\right|^2\right]\) 这里的 \(t\) 在 \([1, \mathrm{~T}]\) 范围内取值(如前所述, 其中取 1 时对应 \(L_0\) )。由于去掉了不同 \(t\)的权重系数, 所以这个简化的目标其实是VLB优化 目标进行了 reweight。从DDPM的对比实验结果来看, 预测噪音比预测均值效果要好, 采用简 化版本的优化目标比VLB目标效果要好:

    虽然扩散模型背后的推导比较复杂,但是我们最终得到的优化目标非常简单,就是让网络预测的噪音和真实的噪音一致。DDPM的训练过程也非常简单,如下图所示:随机选择一个训练样本->从1-T随机抽样一个t->随机产生噪音-计算当前所产生的带噪音数据(红色框所示)->输入网络预测噪音->计算产生的噪音和预测的噪音的L2损失->计算梯度并更新网络。

    一旦训练完成,其采样过程也非常简单,如上所示:我们从一个随机噪音开始,并用训练好的网络预测噪音,然后计算条件分布的均值(红色框部分),然后用均值加标准差乘以一个随机噪音,直至t=0完成新样本的生成(最后一步不加噪音)。不过实际的代码实现和上述过程略有区别(见https://github.com/hojonathanho/diffusion/issues/5:先基于预测的噪音生成,并进行了clip处理(范围[-1, 1],原始数据归一化到这个范围),然后再计算均值。我个人的理解这应该算是一种约束,既然模型预测的是噪音,那么我们也希望用预测噪音重构处理的原始数据也应该满足范围要求。

    模型设计

    前面我们介绍了扩散模型的原理以及优化目标,那么扩散模型的核心就在于训练噪音预测模型,由于噪音和原始数据是同维度的,所以我们可以选择采用AutoEncoder架构来作为噪音预测模型。DDPM所采用的模型是一个基于residual block和attention block的U-Net模型。如下所示:

    U-Net属于encoder-decoder架构,其中encoder分成不同的stages,每个stage都包含下采样模块来降低特征的空间大小(H和W),然后decoder和encoder相反,是将encoder压缩的特征逐渐恢复。U-Net在decoder模块中还引入了skip connection,即concat了encoder中间得到的同维度特征,这有利于网络优化。DDPM所采用的U-Net每个stage包含2个residual block,而且部分stage还加入了self-attention模块增加网络的全局建模能力。 另外,扩散模型其实需要的是个噪音预测模型,实际处理时,我们可以增加一个time embedding(类似transformer中的position embedding)来将timestep编码到网络中,从而只需要训练一个共享的U-Net模型。具体地,DDPM在各个residual block都引入了time embedding,如上图所示。

    代码实现

    最后,我们基于PyTorch框架给出DDPM的具体实现,这里主要参考了三套代码实现:

    首先,是time embeding,这里是采用Attention Is All You Need中所设计的sinusoidal position embedding,只不过是用来编码timestep:

    # use sinusoidal position embedding to encode time step (https://arxiv.org/abs/1706.03762)   
    def timestep_embedding(timesteps, dim, max_period=10000):
        """
        Create sinusoidal timestep embeddings.
        :param timesteps: a 1-D Tensor of N indices, one per batch element.
                          These may be fractional.
        :param dim: the dimension of the output.
        :param max_period: controls the minimum frequency of the embeddings.
        :return: an [N x dim] Tensor of positional embeddings.
        """
        half = dim // 2
        freqs = torch.exp(
            -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
        ).to(device=timesteps.device)
        args = timesteps[:, None].float() * freqs[None]
        embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
        if dim % 2:
            embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
        return embedding

    由于只有residual block才引入time embedding,所以可以定义一些辅助模块来自动处理,如下所示:

    # define TimestepEmbedSequential to support `time_emb` as extra input
    class TimestepBlock(nn.Module):
        """
        Any module where forward() takes timestep embeddings as a second argument.
        """
    
        @abstractmethod
        def forward(self, x, emb):
            """
            Apply the module to `x` given `emb` timestep embeddings.
            """
    
    
    class TimestepEmbedSequential(nn.Sequential, TimestepBlock):
        """
        A sequential module that passes timestep embeddings to the children that
        support it as an extra input.
        """
    
        def forward(self, x, emb):
            for layer in self:
                if isinstance(layer, TimestepBlock):
                    x = layer(x, emb)
                else:
                    x = layer(x)
            return x

    这里所采用的U-Net采用GroupNorm进行归一化,所以这里也简单定义了一个norm layer以方便使用:

    # use GN for norm layer
    def norm_layer(channels):
        return nn.GroupNorm(32, channels)

    U-Net的核心模块是residual block,它包含两个卷积层以及shortcut,同时也要引入time embedding,这里额外定义了一个linear层来将time embedding变换为和特征维度一致,第一conv之后通过加上time embedding来编码time:

    # Residual block
    class ResidualBlock(TimestepBlock):
        def __init__(self, in_channels, out_channels, time_channels, dropout):
            super().__init__()
            self.conv1 = nn.Sequential(
                norm_layer(in_channels),
                nn.SiLU(),
                nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
            )
            
            # pojection for time step embedding
            self.time_emb = nn.Sequential(
                nn.SiLU(),
                nn.Linear(time_channels, out_channels)
            )
            
            self.conv2 = nn.Sequential(
                norm_layer(out_channels),
                nn.SiLU(),
                nn.Dropout(p=dropout),
                nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
            )
    
            if in_channels != out_channels:
                self.shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1)
            else:
                self.shortcut = nn.Identity()
    
    
        def forward(self, x, t):
            """
            `x` has shape `[batch_size, in_dim, height, width]`
            `t` has shape `[batch_size, time_dim]`
            """
            h = self.conv1(x)
            # Add time step embeddings
            h += self.time_emb(t)[:, :, None, None]
            h = self.conv2(h)
            return h + self.shortcut(x)

    这里还在部分residual block引入了attention,这里的attention和transformer的self-attention是一致的:

    # Attention block with shortcut
    class AttentionBlock(nn.Module):
        def __init__(self, channels, num_heads=1):
            super().__init__()
            self.num_heads = num_heads
            assert channels % num_heads == 0
            
            self.norm = norm_layer(channels)
            self.qkv = nn.Conv2d(channels, channels * 3, kernel_size=1, bias=False)
            self.proj = nn.Conv2d(channels, channels, kernel_size=1)
    
        def forward(self, x):
            B, C, H, W = x.shape
            qkv = self.qkv(self.norm(x))
            q, k, v = qkv.reshape(B*self.num_heads, -1, H*W).chunk(3, dim=1)
            scale = 1. / math.sqrt(math.sqrt(C // self.num_heads))
            attn = torch.einsum("bct,bcs->bts", q * scale, k * scale)
            attn = attn.softmax(dim=-1)
            h = torch.einsum("bts,bcs->bct", attn, v)
            h = h.reshape(B, -1, H, W)
            h = self.proj(h)
            return h + x

    对于上采样模块和下采样模块,其分别可以采用插值和stride=2的conv或者pooling来实现:

    # upsample
    class Upsample(nn.Module):
        def __init__(self, channels, use_conv):
            super().__init__()
            self.use_conv = use_conv
            if use_conv:
                self.conv = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
    
        def forward(self, x):
            x = F.interpolate(x, scale_factor=2, mode="nearest")
            if self.use_conv:
                x = self.conv(x)
            return x
    
    # downsample
    class Downsample(nn.Module):
        def __init__(self, channels, use_conv):
            super().__init__()
            self.use_conv = use_conv
            if use_conv:
                self.op = nn.Conv2d(channels, channels, kernel_size=3, stride=2, padding=1)
            else:
                self.op = nn.AvgPool2d(stride=2)
    
        def forward(self, x):
            return self.op(x)

    上面我们实现了U-Net的所有组件,就可以进行组合来实现U-Net了:

     The full UNet model with attention and timestep embedding
    class UNetModel(nn.Module):
        def __init__(
            self,
            in_channels=3,
            model_channels=128,
            out_channels=3,
            num_res_blocks=2,
            attention_resolutions=(8, 16),
            dropout=0,
            channel_mult=(1, 2, 2, 2),
            conv_resample=True,
            num_heads=4
        ):
            super().__init__()
    
            self.in_channels = in_channels
            self.model_channels = model_channels
            self.out_channels = out_channels
            self.num_res_blocks = num_res_blocks
            self.attention_resolutions = attention_resolutions
            self.dropout = dropout
            self.channel_mult = channel_mult
            self.conv_resample = conv_resample
            self.num_heads = num_heads
            
            # time embedding
            time_embed_dim = model_channels * 4
            self.time_embed = nn.Sequential(
                nn.Linear(model_channels, time_embed_dim),
                nn.SiLU(),
                nn.Linear(time_embed_dim, time_embed_dim),
            )
            
            # down blocks
            self.down_blocks = nn.ModuleList([
                TimestepEmbedSequential(nn.Conv2d(in_channels, model_channels, kernel_size=3, padding=1))
            ])
            down_block_chans = [model_channels]
            ch = model_channels
            ds = 1
            for level, mult in enumerate(channel_mult):
                for _ in range(num_res_blocks):
                    layers = [
                        ResidualBlock(ch, mult * model_channels, time_embed_dim, dropout)
                    ]
                    ch = mult * model_channels
                    if ds in attention_resolutions:
                        layers.append(AttentionBlock(ch, num_heads=num_heads))
                    self.down_blocks.append(TimestepEmbedSequential(*layers))
                    down_block_chans.append(ch)
                if level != len(channel_mult) - 1: # don't use downsample for the last stage
                    self.down_blocks.append(TimestepEmbedSequential(Downsample(ch, conv_resample)))
                    down_block_chans.append(ch)
                    ds *= 2
            
            # middle block
            self.middle_block = TimestepEmbedSequential(
                ResidualBlock(ch, ch, time_embed_dim, dropout),
                AttentionBlock(ch, num_heads=num_heads),
                ResidualBlock(ch, ch, time_embed_dim, dropout)
            )
            
            # up blocks
            self.up_blocks = nn.ModuleList([])
            for level, mult in list(enumerate(channel_mult))[::-1]:
                for i in range(num_res_blocks + 1):
                    layers = [
                        ResidualBlock(
                            ch + down_block_chans.pop(),
                            model_channels * mult,
                            time_embed_dim,
                            dropout
                        )
                    ]
                    ch = model_channels * mult
                    if ds in attention_resolutions:
                        layers.append(AttentionBlock(ch, num_heads=num_heads))
                    if level and i == num_res_blocks:
                        layers.append(Upsample(ch, conv_resample))
                        ds //= 2
                    self.up_blocks.append(TimestepEmbedSequential(*layers))
    
            self.out = nn.Sequential(
                norm_layer(ch),
                nn.SiLU(),
                nn.Conv2d(model_channels, out_channels, kernel_size=3, padding=1),
            )
    
        def forward(self, x, timesteps):
            """
            Apply the model to an input batch.
            :param x: an [N x C x H x W] Tensor of inputs.
            :param timesteps: a 1-D batch of timesteps.
            :return: an [N x C x ...] Tensor of outputs.
            """
            hs = []
            # time step embedding
            emb = self.time_embed(timestep_embedding(timesteps, self.model_channels))
            
            # down stage
            h = x
            for module in self.down_blocks:
                h = module(h, emb)
                hs.append(h)
            # middle stage
            h = self.middle_block(h, emb)
            # up stage
            for module in self.up_blocks:
                cat_in = torch.cat([h, hs.pop()], dim=1)
                h = module(cat_in, emb)
            return self.out(h)

    对于扩散过程,其主要的参数就是timesteps和noise schedule,DDPM采用范围为[0.0001, 0.02]的线性noise schedule,其默认采用的总扩散步数为1000

    # beta schedule
    def linear_beta_schedule(timesteps):
        scale = 1000 / timesteps
        beta_start = scale * 0.0001
        beta_end = scale * 0.02
        return torch.linspace(beta_start, beta_end, timesteps, dtype=torch.float64)

    我们定义个扩散模型,它主要要提前根据设计的noise schedule来计算一些系数,并实现一些扩散过程和生成过程:

    class GaussianDiffusion:
        def __init__(
            self,
            timesteps=1000,
            beta_schedule='linear'
        ):
            self.timesteps = timesteps
            
            if beta_schedule == 'linear':
                betas = linear_beta_schedule(timesteps)
            elif beta_schedule == 'cosine':
                betas = cosine_beta_schedule(timesteps)
            else:
                raise ValueError(f'unknown beta schedule {beta_schedule}')
            self.betas = betas
                
            self.alphas = 1. - self.betas
            self.alphas_cumprod = torch.cumprod(self.alphas, axis=0)
            self.alphas_cumprod_prev = F.pad(self.alphas_cumprod[:-1], (1, 0), value=1.)
            
            # calculations for diffusion q(x_t | x_{t-1}) and others
            self.sqrt_alphas_cumprod = torch.sqrt(self.alphas_cumprod)
            self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - self.alphas_cumprod)
            self.log_one_minus_alphas_cumprod = torch.log(1.0 - self.alphas_cumprod)
            self.sqrt_recip_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod)
            self.sqrt_recipm1_alphas_cumprod = torch.sqrt(1.0 / self.alphas_cumprod - 1)
            
            # calculations for posterior q(x_{t-1} | x_t, x_0)
            self.posterior_variance = (
                self.betas * (1.0 - self.alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
            )
            # below: log calculation clipped because the posterior variance is 0 at the beginning
            # of the diffusion chain
            self.posterior_log_variance_clipped = torch.log(self.posterior_variance.clamp(min =1e-20))
            
            self.posterior_mean_coef1 = (
                self.betas * torch.sqrt(self.alphas_cumprod_prev) / (1.0 - self.alphas_cumprod)
            )
            self.posterior_mean_coef2 = (
                (1.0 - self.alphas_cumprod_prev)
                * torch.sqrt(self.alphas)
                / (1.0 - self.alphas_cumprod)
            )
        
        # get the param of given timestep t
        def _extract(self, a, t, x_shape):
            batch_size = t.shape[0]
            out = a.to(t.device).gather(0, t).float()
            out = out.reshape(batch_size, *((1,) * (len(x_shape) - 1)))
            return out
        
        # forward diffusion (using the nice property): q(x_t | x_0)
        def q_sample(self, x_start, t, noise=None):
            if noise is None:
                noise = torch.randn_like(x_start)
    
            sqrt_alphas_cumprod_t = self._extract(self.sqrt_alphas_cumprod, t, x_start.shape)
            sqrt_one_minus_alphas_cumprod_t = self._extract(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape)
    
            return sqrt_alphas_cumprod_t * x_start + sqrt_one_minus_alphas_cumprod_t * noise
        
        # Get the mean and variance of q(x_t | x_0).
        def q_mean_variance(self, x_start, t):
            mean = self._extract(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start
            variance = self._extract(1.0 - self.alphas_cumprod, t, x_start.shape)
            log_variance = self._extract(self.log_one_minus_alphas_cumprod, t, x_start.shape)
            return mean, variance, log_variance
        
        # Compute the mean and variance of the diffusion posterior: q(x_{t-1} | x_t, x_0)
        def q_posterior_mean_variance(self, x_start, x_t, t):
            posterior_mean = (
                self._extract(self.posterior_mean_coef1, t, x_t.shape) * x_start
                + self._extract(self.posterior_mean_coef2, t, x_t.shape) * x_t
            )
            posterior_variance = self._extract(self.posterior_variance, t, x_t.shape)
            posterior_log_variance_clipped = self._extract(self.posterior_log_variance_clipped, t, x_t.shape)
            return posterior_mean, posterior_variance, posterior_log_variance_clipped
        
        # compute x_0 from x_t and pred noise: the reverse of `q_sample`
        def predict_start_from_noise(self, x_t, t, noise):
            return (
                self._extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t -
                self._extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise
            )
        
        # compute predicted mean and variance of p(x_{t-1} | x_t)
        def p_mean_variance(self, model, x_t, t, clip_denoised=True):
            # predict noise using model
            pred_noise = model(x_t, t)
            # get the predicted x_0: different from the algorithm2 in the paper
            x_recon = self.predict_start_from_noise(x_t, t, pred_noise)
            if clip_denoised:
                x_recon = torch.clamp(x_recon, min=-1., max=1.)
            model_mean, posterior_variance, posterior_log_variance = \
                        self.q_posterior_mean_variance(x_recon, x_t, t)
            return model_mean, posterior_variance, posterior_log_variance
            
        # denoise_step: sample x_{t-1} from x_t and pred_noise
        @torch.no_grad()
        def p_sample(self, model, x_t, t, clip_denoised=True):
            # predict mean and variance
            model_mean, _, model_log_variance = self.p_mean_variance(model, x_t, t,
                                                        clip_denoised=clip_denoised)
            noise = torch.randn_like(x_t)
            # no noise when t == 0
            nonzero_mask = ((t != 0).float().view(-1, *([1] * (len(x_t.shape) - 1))))
            # compute x_{t-1}
            pred_img = model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise
            return pred_img
        
        # denoise: reverse diffusion
        @torch.no_grad()
        def p_sample_loop(self, model, shape):
            batch_size = shape[0]
            device = next(model.parameters()).device
            # start from pure noise (for each example in the batch)
            img = torch.randn(shape, device=device)
            imgs = []
            for i in tqdm(reversed(range(0, timesteps)), desc='sampling loop time step', total=timesteps):
                img = self.p_sample(model, img, torch.full((batch_size,), i, device=device, dtype=torch.long))
                imgs.append(img.cpu().numpy())
            return imgs
        
        # sample new images
        @torch.no_grad()
        def sample(self, model, image_size, batch_size=8, channels=3):
            return self.p_sample_loop(model, shape=(batch_size, channels, image_size, image_size))
        
        # compute train losses
        def train_losses(self, model, x_start, t):
            # generate random noise
            noise = torch.randn_like(x_start)
            # get x_t
            x_noisy = self.q_sample(x_start, t, noise=noise)
            predicted_noise = model(x_noisy, t)
            loss = F.mse_loss(noise, predicted_noise)
            return loss

    其中几个主要的函数总结如下:

    • q_sample:实现的从x0到xt扩散过程;
    • q_posterior_mean_variance:实现的是后验分布的均值和方差的计算公式;
    • predict_start_from_noiseq_sample的逆过程,根据预测的噪音来生成x0;
    • p_mean_variance:根据预测的噪音来计算pθ(xt−1|xt)的均值和方差;
    • p_sample:单个去噪step;
    • p_sample_loop:整个去噪音过程,即生成过程。

    扩散模型的训练过程非常简单,如下所示:

    # train
    epochs = 10
    
    for epoch in range(epochs):
        for step, (images, labels) in enumerate(train_loader):
            optimizer.zero_grad()
            
            batch_size = images.shape[0]
            images = images.to(device)
            
            # sample t uniformally for every example in the batch
            t = torch.randint(0, timesteps, (batch_size,), device=device).long()
            
            loss = gaussian_diffusion.train_losses(model, images, t)
            
            if step % 200 == 0:
                print("Loss:", loss.item())
                
            loss.backward()
            optimizer.step()
    这里我们以mnist数据简单实现了一个mnist-demo,下面是一些生成的样本:

    对生成过程进行采样,如下所示展示了如何从一个随机噪音生层一个手写字体图像:

    另外这里也提供了CIFAR10数据集的demo:ddpm_cifar10,不过只训练了200epochs,生成的图像只是初见成效。

    小结

    相比VAE和GAN,扩散模型的理论更复杂一些,不过其优化目标和具体实现却并不复杂,这其实也让人感叹:一堆复杂的数据推导,最终却得到了一个简单的结论。要深入理解扩散模型,DDPM只是起点,后面还有比较多的改进工作,比如加速采样的DDIM以及DDPM的改进版本DDPM+和DDPM++。注:本人水平有限,如有谬误,欢迎讨论交流。

    参考

    • Denoising Diffusion Probabilistic Models
    • Understanding Diffusion Models: A Unified Perspective
    • https://spaces.ac.cn/archives/9119
    • https://keras.io/examples/generative/ddim/
    • What are Diffusion Models?
    • https://cvpr2022-tutorial-diffusion-models.github.io/
    • https://github.com/openai/improved-diffusion
    • https://huggingface.co/blog/annotated-diffusion
    • https://github.com/lucidrains/denoising-diffusion-pytorch
    • https://github.com/hojonathanho/diffusion