当前位置:   article > 正文

diffusers-Understanding models and schedulers

diffusers

https://huggingface.co/docs/diffusers/using-diffusers/write_own_pipelineicon-default.png?t=N7T8https://huggingface.co/docs/diffusers/using-diffusers/write_own_pipelinediffusers有3个模块:diffusion pipelines,noise schedulers,model。这个库很不错,设计思想和mmlab系列的有的一拼,mm系列生成算法在mmagic中,但是不如diffusers丰富,再者几乎所有的新算法的训练和推理都会采用标准的diffusers形式。

给一个标准的diffusers的sd算法的前向加载,配合huggingface hub,遥遥领先了,这是天工巧绘skypaint的文生图算法

  1. from diffusers import StableDiffusionPipeline
  2. device = 'cuda'
  3. pipe = StableDiffusionPipeline.from_pretrained("path_to_our_model").to(device)
  4. prompts = [
  5. '机械狗',
  6. '城堡 大海 夕阳 宫崎骏动画',
  7. '花落知多少',
  8. '鸡你太美',
  9. ]
  10. for prompt in prompts:
  11. prompt = 'sai-v1 art, ' + prompt
  12. image = pipe(prompt).images[0]
  13. image.save("%s.jpg" % prompt)

1.pipelines

将必要组件(多个独立训练的model,scheduler,processor)包装在一个端到端的类中。所有的pipelines都是从DiffusionPipeline中构建而来,该类提供加载,下载和保存所有组件的基本功能。pipelines不提供training,UNet2Model和UNet2DConditionModel都是单独训练的。

下面是目前v0.21.0版本支持的pipelines,后续会一直添加的。

例子: 

  1. from diffusers import DDPMPipeline
  2. ddpm = DDPMPipeline.from_pretrained("google/ddpm-cat-256", use_safetensors=True).to("cuda")
  3. image = ddpm(num_inference_steps=25).images[0]
  4. image

在上面的示例中,pipeline中包含UNet2DModel和DDPMScheduler,pipline通过取随机噪声(与所需输出大小相同)并将其多次输入模型来去噪图像。在每个时间步中,模型预测噪声残差,并且scheduler使用它来预测一个更少噪声的图像。pipeline重复此过程,直到达到指定的推理步数。

分别使用model和scheduler去重新创建pipeline,重新来写去噪过程:

1.加载model和scheduler

  1. from diffusers import DDPMScheduler, UNet2DModel
  2. scheduler = DDPMScheduler.from_pretrained("google/ddpm-cat-256")
  3. model = UNet2DModel.from_pretrained("google/ddpm-cat-256", use_safetensors=True).to("cuda")

2.去噪过程的timesteps

scheduler.set_timesteps(50)

3.设置scheduler timesteps会创建一个张量,在其中均匀地分布元素,本例中为50个元素。每个元素对应于模型去噪图像的一个timestep。当稍后创建去噪循环时,将迭代此张量以去噪图像:

  1. scheduler.timesteps
  2. tensor([980, 960, 940, 920, 900, 880, 860, 840, 820, 800, 780, 760, 740, 720,
  3. 700, 680, 660, 640, 620, 600, 580, 560, 540, 520, 500, 480, 460, 440,
  4. 420, 400, 380, 360, 340, 320, 300, 280, 260, 240, 220, 200, 180, 160,
  5. 140, 120, 100, 80, 60, 40, 20, 0])

4.创建一些和输出形状相同的随机噪声

  1. sample_size = model.config.sample_size
  2. noise = torch.randn((1, 3, sample_size, sample_size)).to("cuda")

5.编写一个循环来迭代timesteps。在每个timestep中,模型执行UNet2DModel.forward()操作并返回带噪声的残差。scheduler的step()方法接受带噪声的残差、timestep和输入,然后预测上一个timestep的图像。该输出成为去噪循环中模型的下一个输入,并一直重复,直到达到时间步骤数组的末尾。这就是整个去噪过程。

  1. input = noise
  2. for t in scheduler.timesteps:
  3. with torch.no_grad():
  4. noisy_residual = model(input, t).sample
  5. previous_noisy_sample = scheduler.step(noisy_residual, t, input).prev_sample
  6. input = previous_noisy_sample

6.最后是将去噪输出转成图像

  1. image = (input / 2 + 0.5).clamp(0, 1).squeeze()
  2. image = (image.permute(1, 2, 0) * 255).round().to(torch.uint8).cpu().numpy()
  3. image = Image.fromarray(image)
  4. image

2.stable diffusion pipeline

stable diffusion是一个文本-图像潜在扩散模型。它被称为潜在扩散模型,是因为它使用图像的较低维度表示而不是实际的像素空间,这使得它更加内存高效。编码器将图像压缩成较小的表示,解码器将压缩表示转换回图像。对于文本到图像的模型,需要一个分词器和一个编码器来生成文本嵌入。从前面的例子中,已经知道需要一个UNet模型和一个调度器。

  1. from PIL import Image
  2. import torch
  3. from transformers import CLIPTextModel, CLIPTokenizer
  4. from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMScheduler
  5. vae = AutoencoderKL.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="vae", use_safetensors=True)
  6. tokenizer = CLIPTokenizer.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="tokenizer")
  7. text_encoder = CLIPTextModel.from_pretrained(
  8. "CompVis/stable-diffusion-v1-4", subfolder="text_encoder", use_safetensors=True
  9. )
  10. unet = UNet2DConditionModel.from_pretrained(
  11. "CompVis/stable-diffusion-v1-4", subfolder="unet", use_safetensors=True
  12. )

代替默认的PNDMScheduler,使用UniPCMultistepScheduler

  1. from diffusers import UniPCMultistepScheduler
  2. scheduler = UniPCMultistepScheduler.from_pretrained("CompVis/stable-diffusion-v1-4", subfolder="scheduler")

加速推理,scheduler没有可训练权重,在不在gpu上推理无影响。

  1. torch_device = "cuda"
  2. vae.to(torch_device)
  3. text_encoder.to(torch_device)
  4. unet.to(torch_device)

2.1 create text embeddings

对文本进行tokenize以生成embedding,该文本用于调节UNet并将扩散模型引导至类似于属于提示的方向。guidance_scale参数决定了生成图像时应赋予提示多少权重。

  1. prompt = ["a photograph of an astronaut riding a horse"]
  2. height = 512 # default height of Stable Diffusion
  3. width = 512 # default width of Stable Diffusion
  4. num_inference_steps = 25 # Number of denoising steps
  5. guidance_scale = 7.5 # Scale for classifier-free guidance
  6. generator = torch.manual_seed(0) # Seed generator to create the inital latent noise
  7. batch_size = len(prompt)

对文本进行tokenize,生成文本embedding

  1. text_input = tokenizer(
  2. prompt, padding="max_length", max_length=tokenizer.model_max_length, truncation=True, return_tensors="pt"
  3. )
  4. with torch.no_grad():
  5. text_embeddings = text_encoder(text_input.input_ids.to(torch_device))[0]

需要生成unconditional text embeddings,即用于填充标记的嵌入。这些嵌入需要与条件文本嵌入具有相同的形状(batch_size和seq_length)

  1. max_length = text_input.input_ids.shape[-1]
  2. uncond_input = tokenizer([""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt")
  3. uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]

把unconditional text embeddings和conditional embeddings放在同一个batch中,避免走两次前向:

text_embeddings = torch.cat([uncond_embeddings, text_embeddings])

2.2 create random noise

接下来,生成一些初始的随机噪声作为扩散过程的起点。这是图像的潜在表示,将逐渐去噪。此时,潜在图像的尺寸比最终的图像尺寸要小,但这没关系,因为模型将在后面将其转换为最终的512x512图像尺寸。

高度和宽度除以8,因为vae有3个下采样层。

2 ** (len(vae.config.block_out_channels) - 1) == 8
  1. latents = torch.randn(
  2. (batch_size, unet.in_channels, height // 8, width // 8),
  3. generator=generator,
  4. )
  5. latents = latents.to(torch_device)

2.3 denoise the image

首先,通过初始噪声分布以及噪声尺度值sigma对输入进行缩放。这对于改进的调度器(如UniPCMultistepScheduler)是必需的。

latents = latents * scheduler.init_noise_sigma

最后一步是创建去噪循环,逐步将潜在的纯噪声转换为由提示描述的图像。请记住,去噪循环需要完成三件事:

1.设置调度器在去噪过程中使用的timesteps。 2.迭代timesteps。 3.在每个timestep中,调用UNet模型来预测噪声残差,并将其传递给scheduler以计算先前的噪声样本。

  1. from tqdm.auto import tqdm
  2. scheduler.set_timesteps(num_inference_steps)
  3. for t in tqdm(scheduler.timesteps):
  4. # expand the latents if we are doing classifier-free guidance to avoid doing two forward passes.
  5. latent_model_input = torch.cat([latents] * 2)
  6. latent_model_input = scheduler.scale_model_input(latent_model_input, timestep=t)
  7. # predict the noise residual
  8. with torch.no_grad():
  9. noise_pred = unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample
  10. # perform guidance
  11. noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
  12. noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
  13. # compute the previous noisy sample x_t -> x_t-1
  14. latents = scheduler.step(noise_pred, t, latents).prev_sample

classifier-free guidance:通过在 UNet 模型中添加分类标签,使得模型在生成图像时可以同时考虑文本嵌入信息和潜在变量。具体地,在每个时间步中,将噪声残差分为无条件部分和有条件部分,其中有条件部分通过加权求和的方式与文本嵌入信息相结合,从而达到有条件的控制效果。这里的加权系数就是指导尺度,用于调节噪声残差对文本嵌入信息的影响。因此,通过这种方式,可以在不使用分类器的情况下,仍然能够结合文本嵌入信息进行有条件的控制。这就是 Classifier-free Guidance 的实现方式之一。latents*2以及后面noise_pred.chunk(2)都是classifier-free guidance的实现。

2.4 decode the image

使用vae将潜在表示解码成图像

  1. # scale and decode the image latents with vae
  2. latents = 1 / 0.18215 * latents
  3. with torch.no_grad():
  4. image = vae.decode(latents).sample
  5. image = (image / 2 + 0.5).clamp(0, 1).squeeze()
  6. image = (image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
  7. images = (image * 255).round().astype("uint8")
  8. image = Image.fromarray(image)
  9. image

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/321850
推荐阅读
  

闽ICP备14008679号