我们该如何才能用好stable diffusion这个工具呢?AI究竟在stable diffusion中承担了什么样的角色?如何能尽可能快、成本低地得到我们期望的结果?
源于这一系列的疑问,我开始了漫长的论文解读。High-Resolution Image Synthesis with Latent Diffusion Models(地址:https://arxiv.org/abs/2112.10752?spm=ata.21736010.0.0.7d0b28addsl7xQ&file=2112.10752)
当然这论文看的云里雾里的,加篇读了How does Stable Diffusion work?(地址:https://stable-diffusion-art.com/how-stable-diffusion-work/?spm=ata.21736010.0.0.7d0b28addsl7xQ)
先简要概括下,stable diffusion的努力基本是为了2个目的:
低成本、高效验证。设计了Latent Space
Conditioning Mechanisms。条件控制,如果不能输出我们想要的图片,那这就像Monkey Coding。耗费无限的时间与资源。
这是整个内容里最重要最核心的两个部分。
随着深度神经网络的发展,生成模型已经有了巨大的发展,主流的有以下几种:
自回归模型(AutoRegressive model):按照像素点生成图像,导致计算成本高。实验效果还不错
变分自编码器(Variational Autoencoder):Image to Latent, Latent to Image,VAE存在生成图像模糊或者细节问题
基于流的方法(Glow)
生成对抗网络(Generative adversarial network):利用生成器(G)与判别器(D)进行博弈,不断让生成的图像与真实的图像在分布上越来越接近。
其中AR与GAN的生成都是在pixel space进行模型训练与推理。
▐ 模型是如何生成图片的?
以一只猫作为案例。当我们想画一只猫的时候,也都是从一个白板开始,框架、细节不断完善。
从图中的流程,我们可以看到推理的过程如下:
生成一个随机的noise image图片。这个noise取决于Random这个参数。相同的Random生成的noise image是相同的。
使用noise predictor预测这个图里加了多少noise,生成一个predicted noise。
使用原始的noise减去predicted noise。
不断循环2、3,直到我们的执行steps。
最终我们会得到一只猫。
在这个过程中,我们会以下疑问:
如何得到一个noise predictor?
怎么控制我们最终能得到一只猫?而不是一只狗或者其他的东西?
在回答这些疑问之前,我先贴一部分公式:
我们定义一个noise predictor:,
是第 t 个step过程中的noise image,t 表示第t个stop。
这是一个训练的过程。过程如下图所示:
选择一张训练用的图片,比如说一张猫
生成一个随机的noise图片
将noise图叠加到训练用的图片上,得到一张有一些noise的图片。(这里可以叠加1~T步noise
训练noise predictor,告诉我们加了多少noise。通过正确的noise答案来调整模型权重。
最终我们能得到一个相对准确的noise-predictor。这是一个U-Net model。在stable-diffusion-model中。
通过这一步,我们最终能得到一个noise encoder与noise decoder。
PS: noise encoder在image2image中会应用到。
以上noise与noise-predictor的过程均在pixel space,那么就会存在巨大的性能问题。比如说一张1024x1024x3的RBG图片对应3,145,728个数字,需要极大的计算资源。在这里stable diffusion定义了一个Latent Space,来解决这个问题。
▐ Latent Space
Latent Space的提出基于一个理论:Manifold_hypothesis
它假设现实世界中,许多高维数据集实际上位于该高维空间内的低维Latent manifolds。像Pixel Space,就存在很多的难以感知的高频细节,而这些都是在Latent Space中需要压缩掉的信息。
那么基于这个假设,我们先定义一个在RGB域的图片
然后存在一个方法z=varepsilon(x),,z是x在latent space的一种表达。
你可能在想,为什么VAE可以把一张图片压缩到更小的latent space,并且可以不丢失信息。
图像的高维性是人为的,而自然的图像可以很容易地压缩为更小的空间中而不丢失任何信息。
▐ 结合Latent Space与noise predictor的图像生成过程
生成一个随机的latent space matrix,也可以叫做latent representation。一种中间表达
noise-predictor预测这个latent representation的noise.并生成一个latent space noise
latent representation减去latent space noise
重复2~3,直到step结束
通过VAE的decoder将latent representation生成最终的图片
直到目前为止,都还没有条件控制的部分。按这个过程,我们最终只会得到一个随机的图片。
条件控制
非常关键,没有条件控制,我们最终只能不断地进行Monkey Coding,得到源源不断的随机图片。
相信你在上面的图片生成的过程中,已经感知到一个问题了,如果只是从一堆noise中去掉noise,那最后得到的为什么是有信息的图片,而不是一堆noise呢?
noise-predictor在训练的时候,其实就是基于已经成像的图片去预测noise,那么它预测的noise基本都来自于有图像信息的训练数据。
在这个denoise的过程中,noise会被附加上各种各样的图像信息。
怎么控制noise-predictor去选择哪些训练数据去预测noise,就是条件控制的核心要素。
下面的流程图,展示了一个prompt如何处理,并提供给noise predictor。
tokenized将自然语言转成计算机可理解的数字(NLP),它只能将words转成token。比如说dreambeach
会被CLIP模型拆分成dream
和beach
。一个word,并不意味着一个token。同时dream
与beach
也不等同于dream
和<space>beach
,stable diffusion model目前被限制只能使用75个tokens来进行prompt,并不等同于75个word。
4x768
的矩阵。man
,但这是不是同时可以意味着gentleman
、guy
、sportsman
、boy
。他们可能说在向量空间中,与man
的距离由近而远。而你不一定非要一个完全准确无误的man
。通过embedding的向量,我们可以决定究竟取多近的信息来生成图片。对应stable diffusion的参数就是(Classifier-Free Guidance scale)CFG。相当于用一个scale去放大距离,因此scale越大,对应的能获取的信息越少,就会越遵循prompt。而scale越小,则越容易获取到关联小,甚至无关的信息。我们经常会遇到stable diffusion无法准确绘制出我们想要的内容。那么这里我们发现了第一种条件控制的方式:textual inversion
将我们想要的token用一个全新的别名定义,这个别名对应一个准确的token。那么就能准确无误地使用对应的embedding生成图片。
这里的embedding可以是新的对象,也可以是其他已存在的对象。
toy cat
就能产生如下的效果。LoRA models modify the cross-attention module to change styles。后面在研究Lora,这里把原话摘到这。
blue
、eyes
,然后有一个集合同时满足blue
和eye
。去取这个交叉的集合。问题:对应的embedding是不是不一样的?该如何区分blue planet in eye
和blue eye in planet
的区别?感觉这应该是NLP的领域了。stable diffusion生成一个随机的latent space matrix。这个由Random决定,如果Random不变,则这个latent space matrix不变。
通过noise-predictor,将noisy image与text prompt作为入参,预测predicted noise in latent space
latent noise减去predicted noise。将其作为新的latent noise
不断重复2~3执行step次。比如说step=20
最终,通过VAE的decoder将latent representation生成最终的图片
这个时候就可以贴Stable diffusion论文中的一张图了
手撕一下公式:
左上角的定义为一张RGB像素空间的图。经过
的变化,生成
这个latent space representation。再经过一系列的noise encoder,得到
,T表示step。
而这个过程则是img2img的input。如果是img2img,那么初始的noise latent representation就是这个不断加noise之后的。
如果是tex2img,初始的noise latent representation则是直接random出来的。
具体的细节说实话没看懂,而这一部分在controlnet中也有解释,打算从controlnet的部分进行理解。
SD Encoder Block_1(64x64) -> SD Encoder Block_2(32x32) -> SD Encoder Block_3(16x16) -> SD Encoder(Block_4 8x8) -> SD Middle(Block 8x8) -> SD Decoder(Block_4 8x8) -> SD Decoder Block_3(16x16) -> SD Decoder Block_2(32x32) -> SD Decoder Blocker_1(64x64)
64x64
-> 8x8
-> 64x64
的过程,具体为啥,得等我撕完controlnet的论文。回到过程图中,我们可以看到denoising step则是在Latent Space的左下角进行了一个循环,这里与上面的流程一直。最终通过VAE的decoder D,输出图片
结合上面的图看,基本还是比较清晰的,不过这个和代表了啥就不是很清楚了。结合python代码看流程更清晰~删掉了部分代码,只留下了关键的调用。
pipe = StableDiffusionPipeline.from_pretrained(
"CompVis/stable-diffusion-v1-4", torch_dtype=torch.float16
)
vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae")
tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")
unet = UNet2DConditionModel.from_pretrained(
"CompVis/stable-diffusion-v1-4", subfolder="unet"
)
scheduler = LMSDiscreteScheduler.from_pretrained(
"CompVis/stable-diffusion-v1-4", subfolder="scheduler"
)
prompt = ["a photograph of an astronaut riding a horse"]
generator = torch.manual_seed(32)
text_input = tokenizer(
prompt,
padding="max_length",
max_length=tokenizer.model_max_length,
truncation=True,
return_tensors="pt",
)
with torch.no_grad():
text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]
max_length = text_input.input_ids.shape[-1]
uncond_input = tokenizer(
[""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
)
with torch.no_grad():
uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
latents = torch.randn(
(batch_size, unet.in_channels, height // 8, width // 8), generator=generator
)
scheduler.set_timesteps(num_inference_steps)
latents = latents * scheduler.init_noise_sigma
for t in tqdm(scheduler.timesteps):
latent_model_input = torch.cat([latents] * 2)
latent_model_input = scheduler.scale_model_input(latent_model_input, t)
with torch.no_grad():
noise_pred = unet(
latent_model_input, t, encoder_hidden_states=text_embeddings
).sample
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + guidance_scale * (
noise_pred_text - noise_pred_uncond
)
latents = scheduler.step(noise_pred, t, latents).prev_sample
latents = 1 / 0.18215 * latents
with torch.no_grad():
image = vae.decode(latents).sample
这个其实在上面的流程图中已经解释了。这里把步骤列一下:
输入的image,通过VAE的encoder变成latent space representation
往里面加noise,总共加T个noise,noise的强度由Denoising strength控制。noise其实没有循环加的过程,就是不断叠同一个noise T次,所以可以一次计算完成。
noisy image和text prompt作为输入,由noise predictor U-Net预测一个新的noise
noisy image减去预测的noise
重复3~4 step次
通过VAE的decoder将latent representation转变成image
基于上面的原理,Inpainting就很简单了,noise只加到inpaint的部分。其他和Img2Img一样。相当于只生成inpaint的部分。所以我们也经常发现inpaint的边缘经常无法非常平滑~如果能接受图片的细微变化,可以调低Denoising strength,将inpaint的结果,再进行一次img2img
Stable Diffusion的一些常见问题
beautiful hands
和detailed fingers
,期望其中有部分图片满足要求。或者用inpaint。反复重新生成手部。(这个时候可以用相同的prompt。)我们是淘天集团-场景智能技术团队,作为一支专注于通过AI和3D技术驱动商业创新的技术团队, 依托大淘宝丰富的业务形态和海量的用户、数据, 致力于为消费者提供创新的场景化导购体验, 为商家提供高效的场景化内容创作工具, 为淘宝打造围绕家的场景的第一消费入口。我们不断探索并实践新的技术, 通过持续的技术创新和突破,创新用户导购体验, 提升商家内容生产力, 让用户享受更好的消费体验, 让商家更高效、低成本地经营。