当前位置:AIGC资讯 > AIGC > 正文

Datawhale X 魔搭 AI夏令营第四期(AIGC学习笔记)

Datawhale X 魔搭 AI夏令营第四期(AIGC学习笔记)

00. Diffusion 扩散模型 去噪扩散模型(Denoising diffusion probabilistic models,DDPM) 正向扩散过程 逆向去噪过程 训练和推理过程 名词解释 条件控制扩散模型(Conditional diffusion models) 潜在扩散模型(Latent diffusion models) Stable Diffusion 01. Task2执行流程示意 Step1. 使用通义千问生成文生图提示词 Step2. 替换提示词,运行baseline 02. Task3执行流程示意 Step1. ComfyUI安装 Step2. 使用ComfyUI工具生图

00. Diffusion 扩散模型

本章是对Diffusion 扩散模型的学习内容总结。

去噪扩散模型(Denoising diffusion probabilistic models,DDPM)

去噪扩散模型的学习笔记主要参考了B站梗直哥丶关于扩散模型的解读视频1。

扩散模型(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论文2中,作了一个强假设:噪声与条件转移分布都假定为高斯分布,因此只需要学习均值和方差。

扩散模型是一种潜在变量模型(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} xt​q(xt​∣x0​)​=αt​ ​xt−1​+1−αt​ ​ϵt−1​;where αt​=1−βt​=αt​αt−1​ ​xt−2​+1−αt​αt−1​ ​ϵˉt−2​=…=αˉt​ ​x0​+1−αˉt​ ​ϵ=N(xt​;αˉt​ ​x0​,(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​),β~​t​I)来逼近逆向过程的转移分布 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∏T​pθ​(xt−1​∣xt​)pθ​(xt−1​∣xt​)=N(xt−1​;μθ​(xt​,t),Σθ​(xt​,t))

训练和推理过程

训练和推理过程截取了MIT 6.5940课程的ppt3进行演示。

训练过程:

推理过程:

名词解释

马尔可夫链(Markov chain):马尔可夫链是满足马尔可夫性质的随机变量序列 X 1 , X 2 , . . . X_1, X_2, ... X1​,X2​,...。下一状态的概率分布只能由当前状态决定,即给出当前状态,将来状态与过去状态时相互独立的。

条件控制扩散模型(Conditional diffusion models)

条件控制扩散模型在模型中引入了额外的信息,来指导图像的生成。引入额外信息 y y y后,对前向扩散过程没有任何影响,而是对逆向去噪(采样)过程产生影响。推导过程可见4。

潜在扩散模型(Latent diffusion models)

在去噪扩散模型中, x t \mathbf{x}_t xt​的元素与图片像素一一对应。要想生成高分辨率的图像,就需要非常大的 x t \mathbf{x}_t xt​。针对这一问题,High-Resolution Image Synthesis with Latent Diffusion Models5引入了一个自编码器(如变分自编码器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

通过参考6与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 is not None or ip_adapter_image_embeds is not None)
      else None
  )

  # 6.2 Optionally get Guidance Scale Embedding
  timestep_cond = None
  if self.unet.config.time_cond_proj_dim is not None:
      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 in enumerate(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 guidance
          if 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 is not None:
              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 provided
          if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
              progress_bar.update()
              if callback is not None and i % callback_steps == 0:
                  step_idx = i // getattr(self.scheduler, "order", 1)
                  callback(step_idx, t, latents)

  # 将潜在数据解码为原尺寸的图像
  if not 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 = None

  if has_nsfw_concept is None:
      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()

  if not return_dict:
      return (image, has_nsfw_concept)

  return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)

01. Task2执行流程示意

Step1. 使用通义千问生成文生图提示词

故事文案采用了了链接7对《小王子》故事的概述。

Step2. 替换提示词,运行baseline

图1:色域绘画,飞行员穿着飞行服,上半身特写,表情忧虑,身旁是严重受损的飞机残骸,撒哈拉沙漠背景,金色头发的小王子站在旁边,认真地看着飞行员手中的画纸,请求飞行员画一只羊。
图2:色域绘画,小王子站在B612小行星上,全身像,身后是一朵孤独绽放的玫瑰花,周围是广袤星空,小王子正低头沉思,似乎在考虑是否离开。
图3:色域绘画,飞行员睡着后的梦境,飞行员的上半身特写,闭着眼睛,面带微笑,背景是星空与沙漠,仿佛正站在一条通往未知的道路旁。
图4:色域绘画,小王子游历各个星球,全身像,背景是不同星球的地平线,包括一个国王、一个虚荣的人、一个酒鬼、一个商人、一个点灯人和一个地理学家的形象,小王子看起来有些困惑和失望。
图5:色域绘画,小王子站在地球的沙漠中,上半身特写,表情惊讶,旁边是一条神秘的蛇,前方是一片盛开的玫瑰花园,小王子显得十分惊讶和沮丧。
图6:色域绘画,小王子与一只狐狸成为朋友,全身像,小王子蹲下与狐狸对话,背景是一片森林,小王子正领悟到“眼睛看不见的事物才是最重要的”这一真理。
图7:色域绘画,小王子和飞行员找到一口井,全身像,两人并肩站立在井边,背景是沙漠与星空,小王子准备离开,而飞行员则显得悲伤。
图8:色域绘画,飞行员坐在沙漠中,上半身特写,手中拿着一张画有羊的纸,望着星空微笑,背景是星空下的沙漠,飞行员在思念小王子,希望他能回来。

02. Task3执行流程示意

Task3主要是对ComfyUI工具的熟悉。
ComfyUI 是一个功能强大、高度模块化的 Stable Diffusion 图形用户界面和后端系统,提供了一个可视化的文生图流程。8

Step1. ComfyUI安装

运行安装脚本,打开链接:

Step2. 使用ComfyUI工具生图

加载工作流配置文件,运行:

https://www.bilibili.com/video/BV1BP411S7Mg/?spm_id_from=333.999.0.0&vd_source=b7b0278844e86ae043f7069b8064a457 ↩︎

https://arxiv.org/abs/2006.11239 ↩︎

https://www.dropbox.com/scl/fi/q8y9ap7mlucmiimyh3zl5/lec16.pdf?rlkey=6wx4z3pnhic8pq0oju8ro3qzr&e=1&dl=0 ↩︎

https://www.zhangzhenhu.com/aigc/Guidance.html ↩︎

https://arxiv.org/abs/2112.10752 ↩︎

https://www.zhangzhenhu.com/aigc/%E7%A8%B3%E5%AE%9A%E6%89%A9%E6%95%A3%E6%A8%A1%E5%9E%8B.html ↩︎

https://www.bilibili.com/read/cv9560704/ ↩︎

https://comfyuidoc.com/zh/ ↩︎

总结

更新时间 2024-09-02