DALL·E 1

DALL·E 1可以看成是VQ-VAE和文本经过BPE编码得到的embedding

AE(Auto Encoder)

encoder decoder结构,AE在生成任务时只会模仿不会创造,所有有了后面的VAE

VAE(Variational AutoEncoder)

不再学习固定的bottleneck特征,而开始学习distribution

VQ-VAE(vector quantize)

把VAE的distribution的离散化成一个codebook(K*D,K一般是8192个聚类中心,D是512或者768), Beit也用了VQ-VAE的codebook。

VQ-VAE从编码器输出到解码器输入这一步是不可导的,误差无法从解码器传递到编码器上。要是可以把的梯度直接原封不动地复制到上就好了,这里用了SG技术

以下转载自https://zhuanlan.zhihu.com/p/633744455

VQ-VAE的loss设计

VQ-VAE的优化目标由两部分组成:重建误差和嵌入空间误差。重建误差为输入图片和重建图片的均方误差。为了让梯度从解码器传到编码器,作者使用了一种巧妙的停止梯度算子,让正向传播和反向传播按照不同的方式计算。嵌入空间误差为嵌入和其对应的编码器输出的均方误差。为了让嵌入和编码器以不同的速度优化,作者再次使用了停止梯度算子,把嵌入的更新和编码器的更新分开计算。

VQ-VAE2

层级式

RQ-VAE

截取自论文Recommender Systems with Generative Retrieval 残差量化变分自动编码器(RQ-VAE)是一种多级向量量化器,在残差上进行量化来生成码字元组(语义ID)。通过更新量化码本和DNN编码器-解码器参数来联合训练自动编码器。

为了防止RQ-VAE发生码本崩溃(大多数输入仅映射到少数码本向量),使用k均值聚类来初始化码本,将k-means算法应用于第一个训练批次(first training batch),并使用质心作为初始化。当然除了使用RQ-VAE,也可以使用其他的向量化方法,如LSH等。

碰撞就是发生语义冲突了,多个item映射到同一个码字上了。为了消除冲突,本文在有序语义码字的末尾添加了一个额外的标记,以使它们具有唯一性。例如,如果两个项目共享语义ID(12,24,52),附加额外的令牌来区分它们,将这两个项目表示为(12,24,52,0)和(12,24,52,1)。为了检测冲突,需要维护一个将语义ID映射到相应item的查找表。

DALL·E 2

DALL·E 2本身来自于Hierarchical Text-Conditional Image Generation with CLIP Latents这篇,DALL·E 2一句话说就是prior(CLIP)+decoder(GLIDE)。CLIP不过多说了,GLIDE后面展开了很多细节

这篇里面的技术细节实际上比较少,感觉Yi Zhu老师在视频中最快速地普及了相关背景,后面也是沿着这个思路做了一些笔记:

DDPM(Denoising Diffusion Probabilistic Model)

DDPM来自于Denoising Diffusion Probabilistic Model 20年这篇paper把它在高分辨率图像生成上调试出来了,从而引导出了后面的火热,其实早在15年Deep Unsupervised Learning using Nonequilibrium Thermodynamics这篇里数学推导基本有了,DDPM里有几个要点:

文章中提到的

β

1

,

.

.

.

,

β

T

\beta_1,...,\beta_T

β1​,...,βT​是方差,想预测reverse process(

x

0

x_0

x0​是高清图像)中q的变化需要用到重采样技巧,需要预测均值和方差即可。而根据之前Deep Unsupervised Learning using Nonequilibrium Thermodynamics中的推导,预测均值即可,方差都不用了。

为什么预测均值即可,方差都不用了,再写一点关于这个的解释。从上图中diffusion model的forward process说起,forward process写成公式就是

q

(

x

t

x

t

1

)

=

N

(

x

t

;

1

β

t

x

t

1

,

β

t

I

)

q(x_t|x_{t-1})=N(x_t;\sqrt{1-\beta_t}x_{t-1},\beta_tI)

q(xt​∣xt−1​)=N(xt​;1−βt​

​xt−1​,βt​I),为什么均值是

1

β

t

x

t

1

\sqrt{1-\beta_t}x_{t-1}

1−βt​

​xt−1​呢?其实为了让x_{t-1}和\epi_{t-1}前系数的平方和维持到1,这样每一步前面的系数刚好是递推的形式。可以参考博客https://lilianweng.github.io/posts/2021-07-11-diffusion-models/ 中的解释;博客https://kexue.fm/archives/9119的解释也是一个意思,但符号有些变化: 来看一下hugging face的实现(https://github.com/huggingface/diffusers/blob/v0.19.3/src/diffusers/schedulers/scheduling_ddpm.py#L91),hf里重采样技巧叫做scheduler(有些地方叫做sampler),只截取关键部分,pred_original_sample是上图公式中的

x

0

x_0

x0​,pred_prev_sample是上图公式中的

x

t

1

x_{t-1}

xt−1​,sample刚好是

x

t

x_t

xt​:

# 4. Compute coefficients for pred_original_sample x_0 and current sample x_t

# See formula (7) from https://arxiv.org/pdf/2006.11239.pdf

pred_original_sample_coeff = (alpha_prod_t_prev ** (0.5) * current_beta_t) / beta_prod_t

current_sample_coeff = current_alpha_t ** (0.5) * beta_prod_t_prev / beta_prod_t

# 5. Compute predicted previous sample µ_t

# See formula (7) from https://arxiv.org/pdf/2006.11239.pdf

pred_prev_sample = pred_original_sample_coeff * pred_original_sample + current_sample_coeff * sample

想要预测reverse process中

q

(

x

t

x

t

1

)

q(x_t|x_{t-1})

q(xt​∣xt−1​)预测它们之间的残差就够了,经过推导目标函数长成下图这样: 同时强调的一点,DDPM在预测残差

ϵ

\epsilon

ϵ中会引入time embedding,也就是恢复前期生成大致轮廓,后期生成细节。但是DDPM的部署和训练过程中需要多轮很慢很贵。

q

(

x

t

x

t

1

)

q(x_t|x_{t-1})

q(xt​∣xt−1​)的backbone经常采用类似于Unet的结构

Improved DDPM

Improved DDPM来自于OpenAI的Improved Denoising Diffusion Probabilistic Models,几个要点:

均值和方差都预测一下效果会更好(原文中3.1. Learning Σθ(xt, t))线性的schedule换成余弦的schedule(原文中3.2. Improving the Noise schedule)DDPM在scale后效果比较好

DENOISING DIFFUSION IMPLICIT MODELS,这个工作后面叫DDIM

这篇没有细看,从摘要看简单来说就是把采样过程给加速了,下面截图来自文章:

Diffusion Models Beat GANs on Image Synthesis

这也是发现DDPM在scale后效果后比较好OpenAI继续加大投入,这篇里面管自己的模型叫做ADM(ablated diffusion model):ADM refers to our ablated diffusion model, and ADM-G additionally uses classifier guidance;For upsampling, we use the upsampling stack from Nichol and Dhariwal [43] combined with our architecture improvements, which we refer to as ADM-U,几个要点:

模型变大变复杂,we match BigGAN-deep even with as few as 25 forward passes per sampleAdaptive Group Normalization,We also experiment with a layer [43] that we refer to as adaptive group normalization (AdaGN), which incorporates the timestep and class embedding into each residual block after a group normalization operation [69], similar to adaptive instance norm [27] and FiLM [48].这篇里面用了Classifier Guidance,Classifier Guidance的细节下面说

Classifier Guidance

Classifier Guidance这篇出现前diffusion model的在IS(Inception Score)和FID(Frechet lnception Distance)上的分数比不过GAN,Classifier Guidance的出现改变了这个局面,先来写写IS和FID的定义:

IS(Inception Score):IS 实际上是在做一个 KL 散度计算,具体公式为

I

S

(

G

)

=

e

x

p

(

E

x

p

g

(

x

)

K

L

(

p

(

y

x

)

p

(

y

)

)

)

IS(G)=exp(E_{x-p_g(x)}KL(p(y|x)||p(y)))

IS(G)=exp(Ex−pg​(x)​KL(p(y∣x)∣∣p(y)))其中,

p

(

y

x

)

p(y|x)

p(y∣x)是指对一张给定的生成图像x,将其输入预训练好的Inception-v3 分类网络后输出的类别概率,

p

(

y

)

p(y)

p(y)则是边缘分布,表示对于所有的生成图像来说,这个预训练好的分类网络输出的类别的概率的期望。如果生成图像中包含有意义且清晰可辨认的目标,则分类网络应该以很高的置信度将该图像判定为一个特定的类别,所以

p

(

y

x

)

p(y|x)

p(y∣x)应该具有较小的。如果p(y)的熵较大,p(y|x)熵较小,即所生成的图像包含了非常多的类别,而每一张图像的类别又明确且置信度离,此时p(y|x)与p(y)的 KL 散度很大。可以看出,The original authors suggest using a sample of 50,000 generated images for this,IS并没有将真实样本与生成样本进行比较,它仅在量化生成样本的质量和多样性,IS分数越大越好。FID(Frechet lnception Distance):加入了真实样本与生成样本的比较它同样是将生成样本输入到分类网络中,不同的是,FID 不是对网络最后一层的输出概率P(y|x)进行操作,而是对网络倒数第二层的响应即特征图进行操作。具体来说,FID 是通过比较真实样本和生成样本的特征图的均值和方差来计算的,FID越小越好sFID: 在FID度量中增加spatial信息,除了标准FID引入的最后一层pooling之外,还额外引入了前边的7层conv的feature map来计算mean和covariance。

这里再补充一点Inception Score的核心code,code转载自https://github.com/sbarratt/inception-score-pytorch/tree/master

import torch

from torch import nn

from torch.autograd import Variable

from torch.nn import functional as F

import torch.utils.data

from torchvision.models.inception import inception_v3

import numpy as np

from scipy.stats import entropy

def inception_score(imgs, cuda=True, batch_size=32, resize=False, splits=1):

"""Computes the inception score of the generated images imgs

imgs -- Torch dataset of (3xHxW) numpy images normalized in the range [-1, 1]

cuda -- whether or not to run on GPU

batch_size -- batch size for feeding into Inception v3

splits -- number of splits

"""

N = len(imgs)

assert batch_size > 0

assert N > batch_size

# Set up dtype

if cuda:

dtype = torch.cuda.FloatTensor

else:

if torch.cuda.is_available():

print("WARNING: You have a CUDA device, so you should probably set cuda=True")

dtype = torch.FloatTensor

# Set up dataloader

dataloader = torch.utils.data.DataLoader(imgs, batch_size=batch_size)

# Load inception model

inception_model = inception_v3(pretrained=True, transform_input=False).type(dtype)

inception_model.eval();

up = nn.Upsample(size=(299, 299), mode='bilinear').type(dtype)

def get_pred(x):

if resize:

x = up(x)

x = inception_model(x)

return F.softmax(x).data.cpu().numpy()

# Get predictions

preds = np.zeros((N, 1000))

for i, batch in enumerate(dataloader, 0):

batch = batch.type(dtype)

batchv = Variable(batch)

batch_size_i = batch.size()[0]

preds[i*batch_size:i*batch_size + batch_size_i] = get_pred(batchv)

# Now compute the mean kl-div

split_scores = []

for k in range(splits):

part = preds[k * (N // splits): (k+1) * (N // splits), :]

py = np.mean(part, axis=0)

# py相当于求Inception Score的均值,也就是边缘概率P(y)

scores = []

for i in range(part.shape[0]):

pyx = part[i, :] # pyx来自于Inception Score的向量,最后两个向量算KL

scores.append(entropy(pyx, py))

split_scores.append(np.exp(np.mean(scores)))

return np.mean(split_scores), np.std(split_scores)

if __name__ == '__main__':

class IgnoreLabelDataset(torch.utils.data.Dataset):

def __init__(self, orig):

self.orig = orig

def __getitem__(self, index):

return self.orig[index][0]

def __len__(self):

return len(self.orig)

import torchvision.datasets as dset

import torchvision.transforms as transforms

cifar = dset.CIFAR10(root='data/', download=True,

transform=transforms.Compose([

transforms.ToTensor(),

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))

])

)

IgnoreLabelDataset(cifar)

print ("Calculating Inception Score...")

print (inception_score(IgnoreLabelDataset(cifar), cuda=True, batch_size=32, resize=True, splits=10))

继续回到Classifier Guidance这个方法,这里其实是通过牺牲一部分图片的多样性来换取真实性。这里的guidance一般是一个在imagenet上预训练好的分类器,这个分类器的梯度刚好暗含了是否包含某类物体,具体来说,对于DDPM是把梯度加到了均值上;对于DDIM是把梯度加在了残差

ϵ

\epsilon

ϵ上,下图来自Diffusion Models Beat GANs on Image Synthesis:

CLASSIFIER-FREE DIFFUSION GUIDANCE

这篇的改进顾名思义,把Classifier Guidance里的Classifier通过有没有y:这里的y就表示guidance的信号,原来是个classifier,现在换成有这个文本就是y,没有这个文本就是空,来学这种距离。好处当然是摆脱了分类器限制,但缺点是有没有这个条件增加了forward的成本

GLIDE (Guided Language to Image Diffusion for Generation and Editing)

Glide来自于GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models这篇,有了上面那堆铺垫才能理解这篇,无非是把ADM改进成了Classifier Free Guidance,也就是摘要里提的 CLIP guidance and classifier-free guidance,重点关注下Guidance是怎么加进去的,直接看文中2.4部分即可: 文本经过transformer输出一组embedding后,进入ADM模型需要通过两条路径,一条是经过AdaGN进入ADM,另一条是进入concat到ADM的attention context中。这里的

f

(

x

t

)

f(x_t)

f(xt​)是CLIP image encoder,

g

(

c

)

g(c)

g(c) 是CLIP text encoder。其中的 s 称作guidance scale(非常重要)。为什么对均值更新做改造就可以引入CLIP来guide,需要参考Diffusion Models Beat GANs on Image Synthesis的4.1章,需要一些公式推导,它的物理意义是在逐步采样过程中,多往caption和图像匹配值大的方向走,少往不一致的方向走。就这个物理意义而言,其实有很多别的实现方式。

Stable Diffusion原理

来自于High-Resolution Image Synthesis with Latent Diffusion Models(https://ommer-lab.com/research/latent-diffusion-models/),Stable diffusion更像是商品名称,Latent diffusion更接近算法方案的描述,这俩说的是一个东西。 对于diffusion模型,在图像上的forward diffusion过程和reverse学习过程的计算量很大,而大部分的计算是花在了对于语义和感知影响不大的像素上。用在图像冗余信息的计算是可以被节省的。Latent diffusion模型的就是按照这个思路,先通过encoder来将图片变换到latent space,只保留语义信息,感知信息通过引入encoder/decoder来学习和压缩。即diffusion模型在stable diffusion中只处理低维的、强语义信息、低冗余的数据。对latent使用denoising diffusion模型来建模生成过程,并在denoising过程中通过attention机制引入用于指导生成内容的文本、图像等信息。整体目标函数如下图: 模型的实现包含了三个子模型,分别对应

图像像素空间到latent空间转化的AutoencoderKLlatent空间中的Denoising UNetModel引入文本prompt特征的FrozenOpenCLIPEmbedder

下面对代码进行一些分析

AutoencoderKL

使用https://huggingface.co/stabilityai/stable-diffusion-2的参数,对几个样例图像做encode和decode的结果,会发现decode后和原始图像非常接近。这部分代码在https://github.com/Stability-AI/stablediffusion/blob/main/ldm/models/autoencoder.py#L13

Denoising Unet

UNet中的prompt 特征的引入使用了cross attention,代码在https://github.com/Stability-AI/stablediffusion/blob/main/ldm/modules/diffusionmodules/openaimodel.py#L277。参考原文中的描述,y就是文本,KV就是文本学到的intermediate layers,用Q对应的UNet embedding来去找KV

Prompt的encoding

文中采用了ViT-H/14 on LAION-2B的embedding,文本的embedding tensor是高维的(77*1024),并没有清晰的图像语义信息,描述的语义相近的文本的embedding l2距离并不一定相近

Negative Prompt

回想一下,在文本到图像条件化中,提示被转换为嵌入向量,然后被输入到U-Net噪声预测器中。实际上有两组嵌入向量,一组用于正向提示,另一组用于负向提示。正向提示和负向提示地位平等。它们都有75个标记。你可以随时使用其中一个,或不使用另一个。负向提示是在采样器中实现的,采样器负责实现反向差分。要理解负向提示的工作原理,我们首先需要理解在不使用负向提示的情况下采样是如何工作的。   更多可以参考https://zhuanlan.zhihu.com/p/644879268

Stable Diffusion XL

以下转载自 https://huggingface.co/docs/diffusers/en/using-diffusers/sdxl

the UNet is 3x larger and SDXL combines a second text encoder (OpenCLIP ViT-bigG/14) with the original text encoder to significantly increase the number of parametersintroduces size and crop-conditioning to preserve training data from being discarded and gain more control over how a generated image should be croppedintroduces a two-stage model process; the base model (can also be run as a standalone model) generates an image as an input to the refiner model which adds additional high-quality details

笔记参考了以下信息

【DALL·E 2(内含扩散模型介绍)【论文精读】】https://www.bilibili.com/video/BV17r4y1u77B?vd_source=e260233b721e72ff23328d5f4188b304https://kexue.fm/archives/9119Denoising Diffusion Probabilistic ModelsImproved Denoising Diffusion Probabilistic ModelsHulu书GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion ModelsStable Diffusion:High-Resolution Image Synthesis with Latent Diffusion Models

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: