Datawhale X 魔搭 AI夏令营第四期(AIGC学习笔记)
00. Diffusion 扩散模型
本章是对Diffusion 扩散模型的学习内容总结。
去噪扩散模型(Denoising diffusion probabilistic models,DDPM)
去噪扩散模型的学习笔记主要参考了B站梗直哥丶关于扩散模型的解读视频。
扩散模型(diffusion probabilistic model)本质上是一种马尔可夫链(Markov chain),使用变分推断(variational inference)进行训练。
扩散模型的目的就是要学习一个转移分布
p
θ
(
x
t
−
1
∣
x
t
)
p_\theta(\mathbf{x}_{t-1}|\mathbf{x}_{t})
pθ(xt−1∣xt),实现对扩散过程的逆转。扩散过程是一个给图片逐渐添加噪声直至完全淹没的过程,在这个过程中,训练一个网络预测噪声。如果噪声预测得非常准确,那么从一个含有噪声的图片中减去预测的噪声,就能恢复原图。在DDPM论文中,作了一个强假设:噪声与条件转移分布都假定为高斯分布,因此只需要学习均值和方差。
扩散模型是一种潜在变量模型(latent variable model),其中
x
1
,
.
.
.
,
x
T
\mathbf{x}_1,...,\mathbf{x}_T
x1,...,xT是与
x
0
\mathbf{x}_0
x0具有相同维度的潜在变量。
正向扩散过程
上图中从右往左的过程即扩散过程。初始数据
x
0
\mathbf{x}_0
x0符合分布
q
(
x
0
)
q(\mathbf{x}_0)
q(x0),即训练集分布。然后往里面不断添加高斯噪声。这个高斯噪声的均值和方差是固定的,只有方差系数
β
t
\beta_t
βt来控制噪声的强度。由以下公式可以从
x
0
\mathbf{x}_0
x0得到
x
t
\mathbf{x}_t
xt:
x
t
=
α
t
x
t
−
1
+
1
−
α
t
ϵ
t
−
1
;
w
h
e
r
e
α
t
=
1
−
β
t
=
α
t
α
t
−
1
x
t
−
2
+
1
−
α
t
α
t
−
1
ϵ
ˉ
t
−
2
=
…
=
α
ˉ
t
x
0
+
1
−
α
ˉ
t
ϵ
q
(
x
t
∣
x
0
)
=
N
(
x
t
;
α
ˉ
t
x
0
,
(
1
−
α
ˉ
t
)
I
)
\begin{aligned} \mathbf{x}_{t}& =\sqrt{\alpha_t}\mathbf{x}_{t-1}+\sqrt{1-\alpha_t}\boldsymbol{\epsilon}_{t-1}\qquad ;where\ \alpha_t=1-\beta_t \\ &=\sqrt{\alpha_t\alpha_{t-1}}\mathbf{x}_{t-2}+\sqrt{1-\alpha_t\alpha_{t-1}}\bar{\boldsymbol{\epsilon}}_{t-2} \\ &=\ldots \\ &=\sqrt{\bar{\alpha}_t}\mathbf{x}_0+\sqrt{1-\bar{\alpha}_t}\boldsymbol{\epsilon} \\ q(\mathbf{x}_t|\mathbf{x}_0)& =\mathcal{N}(\mathbf{x}_t;\sqrt{\bar{\alpha}_t}\mathbf{x}_0,(1-\bar{\alpha}_t)\mathbf{I}) \end{aligned}
xtq(xt∣x0)=αtxt−1+1−αtϵt−1;where αt=1−βt=αtαt−1xt−2+1−αtαt−1ϵˉt−2=…=αˉtx0+1−αˉtϵ=N(xt;αˉtx0,(1−αˉt)I)
逆向去噪过程
逆向去噪过程即上图从左向右的过程。在逆向去噪过程中,用神经网络学习转移分布
p
θ
p_\theta
pθ。其中。网络的输入是
x
t
\mathbf{x}_t
xt和t。在DDPM中,使用正向扩散过程的后验分布
q
(
x
t
−
1
∣
x
t
,
x
0
)
=
N
(
x
t
−
1
;
μ
~
(
x
t
,
x
0
)
,
β
~
t
I
)
q(\mathbf{x}_{t-1}|\mathbf{x}_t,\mathbf{x}_0)=\mathcal{N}(\mathbf{x}_{t-1};\tilde{\boldsymbol{\mu}}(\mathbf{x}_t,\mathbf{x}_0),\tilde{\beta}_t\mathbf{I})
q(xt−1∣xt,x0)=N(xt−1;μ~(xt,x0),β~tI)来逼近逆向过程的转移分布
p
θ
(
x
t
−
1
∣
x
t
)
p_\theta(\mathbf{x}_{t-1}|\mathbf{x}_t)
pθ(xt−1∣xt)
p
θ
(
x
0
:
T
)
=
p
(
x
T
)
∏
t
=
1
T
p
θ
(
x
t
−
1
∣
x
t
)
p
θ
(
x
t
−
1
∣
x
t
)
=
N
(
x
t
−
1
;
μ
θ
(
x
t
,
t
)
,
Σ
θ
(
x
t
,
t
)
)
p_\theta(\mathbf{x}_{0:T})=p(\mathbf{x}_T)\prod_{t=1}^Tp_\theta(\mathbf{x}_{t-1}|\mathbf{x}_t)\quad p_\theta(\mathbf{x}_{t-1}|\mathbf{x}_t)=\mathcal{N}(\mathbf{x}_{t-1};\boldsymbol{\mu}_\theta(\mathbf{x}_t,t),\boldsymbol{\Sigma}_\theta(\mathbf{x}_t,t))
pθ(x0:T)=p(xT)t=1∏Tpθ(xt−1∣xt)pθ(xt−1∣xt)=N(xt−1;μθ(xt,t),Σθ(xt,t))
训练和推理过程
训练和推理过程截取了MIT 6.5940课程的ppt进行演示。
训练过程:
推理过程:
名词解释
马尔可夫链(Markov chain):马尔可夫链是满足马尔可夫性质的随机变量序列
X
1
,
X
2
,
.
.
.
X_1, X_2, ...
X1,X2,...。下一状态的概率分布只能由当前状态决定,即给出当前状态,将来状态与过去状态时相互独立的。
条件控制扩散模型(Conditional diffusion models)
条件控制扩散模型在模型中引入了额外的信息,来指导图像的生成。引入额外信息
y
y
y后,对前向扩散过程没有任何影响,而是对逆向去噪(采样)过程产生影响。推导过程可见。
潜在扩散模型(Latent diffusion models)
在去噪扩散模型中,
x
t
\mathbf{x}_t
xt的元素与图片像素一一对应。要想生成高分辨率的图像,就需要非常大的
x
t
\mathbf{x}_t
xt。针对这一问题,***High-Resolution Image Synthesis with Latent Diffusion Models***引入了一个自编码器(如变分自编码器VAE),先对原始对象进行压缩编码,编码后的向量再应用到扩散模型。
Stable Diffusion
Stable Diffusion是一种潜在的文生图扩散模型。
Stable Diffusion v1 是一种特定配置,该配置使用下采样因子为 8 的自动编码器和 860M UNet 以及 CLIP ViT-L/14 文本编码器作为扩散模型。
huggingface的diffusers库集成了stable diffusion:
from diffusers import StableDiffusionPipeline
import torch
pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5").to("cuda")# can be replaced with local path
prompt ="portrait photo of a old warrior chief"
generator = torch.Generator("cuda").manual_seed(0)
image = pipe(prompt, generator=generator).images[0]
image
通过参考与Huggingface源码
diffusers.StableDiffusionPipeline
熟悉Stable Diffusion(SD)的推理实现:
在初始化代码中,可以了解到SD的几个关键组件:
vae:变分自编码器,负责将数据压缩到潜在空间(latent space)与潜在数据的解压缩。
text_encoder:文本编码器, 默认使用CLIP模型中的text-encoder,将控制图像生成的文本进行编码。
unet:噪声预测模型,在潜在空间执行。
scheduler: 负责去噪过程的计算。
def__init__(
self,
vae: AutoencoderKL,
text_encoder: CLIPTextModel,
tokenizer: CLIPTokenizer,
unet: UNet2DConditionModel,
scheduler: KarrasDiffusionSchedulers,
safety_checker: StableDiffusionSafetyChecker,
feature_extractor: CLIPImageProcessor,
image_encoder: CLIPVisionModelWithProjection =None,
requires_safety_checker:bool=True,):...
核心逻辑实现在
__call__
方法中
def__call__(...)...# 省略第0~2步的潜在空间尺寸赋值,输入检查,batch_size, device等参数定义。# 3. 输入文本编码,这里包括了对负提示词的处理
prompt_embeds, negative_prompt_embeds = self.encode_prompt(
prompt,
device,
num_images_per_prompt,
self.do_classifier_free_guidance,
negative_prompt,
prompt_embeds=prompt_embeds,
negative_prompt_embeds=negative_prompt_embeds,
lora_scale=lora_scale,
clip_skip=self.clip_skip,)# 4. Prepare timesteps
timesteps, num_inference_steps = retrieve_timesteps(
self.scheduler, num_inference_steps, device, timesteps, sigmas
)# 5. Prepare latent variables
num_channels_latents = self.unet.config.in_channels
latents = self.prepare_latents(
batch_size * num_images_per_prompt,
num_channels_latents,
height,
width,
prompt_embeds.dtype,
device,
generator,
latents,)# 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)# 6.1 Add image embeds for IP-Adapter
added_cond_kwargs =({"image_embeds": image_embeds}if(ip_adapter_image isnotNoneor ip_adapter_image_embeds isnotNone)elseNone)# 6.2 Optionally get Guidance Scale Embedding
timestep_cond =Noneif self.unet.config.time_cond_proj_dim isnotNone:
guidance_scale_tensor = torch.tensor(self.guidance_scale -1).repeat(batch_size * num_images_per_prompt)
timestep_cond = self.get_guidance_scale_embedding(
guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim
).to(device=device, dtype=latents.dtype)# 7. Denoising loop
num_warmup_steps =len(timesteps)- num_inference_steps * self.scheduler.order
self._num_timesteps =len(timesteps)with self.progress_bar(total=num_inference_steps)as progress_bar:for i, t inenumerate(timesteps):if self.interrupt:continue# expand the latents if we are doing classifier free guidance
latent_model_input = torch.cat([latents]*2)if self.do_classifier_free_guidance else latents
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)# predict the noise residual 在潜在空间预测噪声
noise_pred = self.unet(
latent_model_input,
t,
encoder_hidden_states=prompt_embeds,
timestep_cond=timestep_cond,
cross_attention_kwargs=self.cross_attention_kwargs,
added_cond_kwargs=added_cond_kwargs,
return_dict=False,)[0]# perform guidanceif self.do_classifier_free_guidance:
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + self.guidance_scale *(noise_pred_text - noise_pred_uncond)if self.do_classifier_free_guidance and self.guidance_rescale >0.0:# Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf
noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=self.guidance_rescale)# compute the previous noisy sample x_t -> x_t-1
latents = self.scheduler.step(noise_pred, t, latents,**extra_step_kwargs, return_dict=False)[0]if callback_on_step_end isnotNone:
callback_kwargs ={}for k in callback_on_step_end_tensor_inputs:
callback_kwargs[k]=locals()[k]
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
latents = callback_outputs.pop("latents", latents)
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds)# call the callback, if providedif i ==len(timesteps)-1or((i +1)> num_warmup_steps and(i +1)% self.scheduler.order ==0):
progress_bar.update()if callback isnotNoneand i % callback_steps ==0:
step_idx = i //getattr(self.scheduler,"order",1)
callback(step_idx, t, latents)# 将潜在数据解码为原尺寸的图像ifnot output_type =="latent":
image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False, generator=generator)[0]
image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype)else:
image = latents
has_nsfw_concept =Noneif has_nsfw_concept isNone:
do_denormalize =[True]* image.shape[0]else:
do_denormalize =[not has_nsfw for has_nsfw in has_nsfw_concept]
image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize)# Offload all models
self.maybe_free_model_hooks()ifnot return_dict:return(image, has_nsfw_concept)return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
01. Task2执行流程示意
Step1. 使用通义千问生成文生图提示词
故事文案采用了了链接对《小王子》故事的概述。
Step2. 替换提示词,运行baseline
图1:色域绘画,飞行员穿着飞行服,上半身特写,表情忧虑,身旁是严重受损的飞机残骸,撒哈拉沙漠背景,金色头发的小王子站在旁边,认真地看着飞行员手中的画纸,请求飞行员画一只羊。
图2:色域绘画,小王子站在B612小行星上,全身像,身后是一朵孤独绽放的玫瑰花,周围是广袤星空,小王子正低头沉思,似乎在考虑是否离开。
图3:色域绘画,飞行员睡着后的梦境,飞行员的上半身特写,闭着眼睛,面带微笑,背景是星空与沙漠,仿佛正站在一条通往未知的道路旁。
图4:色域绘画,小王子游历各个星球,全身像,背景是不同星球的地平线,包括一个国王、一个虚荣的人、一个酒鬼、一个商人、一个点灯人和一个地理学家的形象,小王子看起来有些困惑和失望。
图5:色域绘画,小王子站在地球的沙漠中,上半身特写,表情惊讶,旁边是一条神秘的蛇,前方是一片盛开的玫瑰花园,小王子显得十分惊讶和沮丧。
图6:色域绘画,小王子与一只狐狸成为朋友,全身像,小王子蹲下与狐狸对话,背景是一片森林,小王子正领悟到“眼睛看不见的事物才是最重要的”这一真理。
图7:色域绘画,小王子和飞行员找到一口井,全身像,两人并肩站立在井边,背景是沙漠与星空,小王子准备离开,而飞行员则显得悲伤。
图8:色域绘画,飞行员坐在沙漠中,上半身特写,手中拿着一张画有羊的纸,望着星空微笑,背景是星空下的沙漠,飞行员在思念小王子,希望他能回来。
02. Task3执行流程示意
Task3主要是对ComfyUI工具的熟悉。
ComfyUI 是一个功能强大、高度模块化的 Stable Diffusion 图形用户界面和后端系统,提供了一个可视化的文生图流程。
Step1. ComfyUI安装
运行安装脚本,打开链接:
Step2. 使用ComfyUI工具生图
加载工作流配置文件,运行:
版权归原作者 kongtiaochengtailang 所有, 如有侵权,请联系我们删除。