Diffusers
StableDdiffusion
Latent Diffusion
稳定扩散(Stable Diffusion)模型中使用的三个关键组件:自编码器(VAE)、U-Net和文本编码器(Text-encoder)
由于潜在扩散模型的U-Net操作在低维空间上,与像素空间扩散模型相比大大降低了内存和计算要求。例如,稳定扩散中使用的自动编码器具有8的降维因子。这意味着形状为(3,512,512)的图像在潜在空间中变为(3,64,64),这需要的内存减少了64倍。这就是为什么即使在16GB Colab GPU上也能如此迅速地生成512×512大小的图像!
VAE
VAE模型由编码器和解码器两部分组成。编码器用于将图像转换为低维潜在表示,这将作为输入传递给U-Net模型。相反,解码器将潜在表示转换回图像。
在潜在扩散训练过程中,编码器用于获取图像的潜在表示(latent),用于向前扩散过程中逐步增加噪音。在推断过程中,通过反向扩散过程生成的去噪潜在表示将使用VAE解码器转换回图像。在推断过程中,我们只需要VAE解码器。
U-Net
U-Net由编码器部分和解码器部分组成,两者都由ResNet块组成。编码器将图像表示压缩为低分辨率图像表示,解码器将低分辨率图像表示解码回原始更高分辨率图像表示,假定更清晰。具体而言,U-Net输出预测噪声残差,可用于计算预测的去噪图像表示。
为防止U-Net在降采样过程中丢失重要信息,通常在编码器的降采样ResNet和解码器的升采样ResNet之间添加快捷连接(short-cut connections)。此外,稳定扩散U-Net能够通过交叉注意力层将输出条件化为文本嵌入。交叉注意力层通常添加在U-Net的编码器和解码器部分之间,通常在ResNet块之间。
Text-Encoder
文本编码器负责将输入提示(例如"一个骑马的宇航员")转换为U-Net可理解的嵌入空间。它通常是一个简单的基于Transformer的编码器,将一系列输入标记映射到一系列潜在文本嵌入。
受Imagen启发,稳定扩散在训练期间不训练文本编码器,而是简单地使用CLIP已训练好的文本编码器,即CLIPTextModel。
流程
-
step1:输入:稳定扩散模型接受两个输入
- 一个是潜在种子(latent seed),用来生成64×64大小的随机潜在图像表示。
- 另一个是文本提示(text prompt),文本提示通过CLIP的文本编码器转换为77×768大小的文本嵌入,用于指导图像的生成。
-
step2:去噪:U-Net在被条件化于文本嵌入的情况下逐步去噪随机潜在图像表示,U-Net的输出是噪声残差,用于通过调度算法计算去噪随机潜在图像表示。
- 调度算法:去噪后的输出(噪声残差)用于通过调度算法计算去噪后的潜在图像表示。有多种调度算法可供选择,每种算法都有其优缺点。对于Stable Diffusion,推荐使用以下之一:
- PNDM调度器(默认使用)
- K-LMS调度器
- Heun离散调度器
- DPM Solver多步调度器,这种调度器能够在较少的步骤中实现高质量,可以尝试使用25步代替默认的50步。
- 去噪过程:去噪过程大约重复50次,逐步检索更好的潜在图像表示。
- 调度算法:去噪后的输出(噪声残差)用于通过调度算法计算去噪后的潜在图像表示。有多种调度算法可供选择,每种算法都有其优缺点。对于Stable Diffusion,推荐使用以下之一:
-
step3:解码:完成去噪后,潜在图像表示由变分自编码器的解码器部分解码,生成最终的图像。
Code
Stable Diffusion原理详解(附代码实现)
Stable Diffusion原理+代码
HuggingFace&DiffusionPipeline
官网教程
https://huggingface.co/docs/diffusers/main/zh/quicktour
https://huggingface.co/docs/diffusers/main/zh/api/pipelines/overview#diffusers-summary
https://blog.bot-flow.com/diffusers-quicktour/
#先使用from_pretrained()方法加载模型:
from diffusers import DiffusionPipeline
# 这里会下载模型,由于模型一般比较大,默认下载目录为~/.cache/huggingface,可通过export HF_HOME=指定目录,最好写入~/.bashrc持久化
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", use_safetensors=True)
#DiffusionPipeline下载并缓存所有model、tokenization、scheduling组件。
'''pipeline
# StableDiffusionPipeline {
# "_class_name": "StableDiffusionPipeline",
# "_diffusers_version": "0.21.4",
# ...,
# "scheduler": [
# "diffusers",
# "PNDMScheduler"
# ],
# ...,
# "unet": [
# "diffusers",
# "UNet2DConditionModel"
# ],
# "vae": [
# "diffusers",
# "AutoencoderKL"
# ]
# }'''
现在,可以在pipeline中输入文本提示生成图像
image = pipeline("An image of a squirrel in Picasso style").images[0]
image.save("image_of_squirrel_painting.png")#保存图像
自定义shceduler()
from diffusers import EulerDiscreteScheduler
pipeline = DiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", use_safetensors=True)
pipeline.scheduler = EulerDiscreteScheduler.from_config(pipeline.scheduler.config)
MODEL
大多数模型采用噪声样本,并在每个时间步预测噪声残差。您可以混合搭配模型来创建其他扩散系统。模型是使用from_pretrained()方法启动的,该方法还会在本地缓存模型权重,因此下次加载模型时速度会更快。
#加载 UNet2DModel,这是一个基本的无条件图像生成模型:
from diffusers import UNet2DModel
repo_id = "google/ddpm-cat-256"
model = UNet2DModel.from_pretrained(repo_id, use_safetensors=True)
model.config#访问模型参数
模型配置(cinfig)是一个冻结字典,这意味着这些参数在模型创建后无法更改。这是有意为之,并确保一开始用于定义模型架构的参数保持不变,而其他参数仍然可以在推理过程中进行调整
- sample_size:输入样本的高度和宽度尺寸。
- in_channels:输入样本的输入通道数。
- down_block_types和up_block_types:用于创建 UNet 架构的下采样和上采样模块的类型。
- block_out_channels:下采样块的输出通道数;也以相反的顺序用于上采样块的输入通道的数量。
- layers_per_block:每个 UNet 块中存在的 ResNet 块的数量。
如需使用推理(inferrence),首先需要使用随机高斯噪声创建图像(在计算机视觉领域,图像往往通过一个复杂的多维张量表示,不同的维度代表不同的含义),具体来说,这里张量的shape是batch * channel * width * height。
- batch:一个批次想生成的图片张数
- channel:一般为3,RGB色彩空间
- width: 图像宽
- height: 图像高
import torch
torch.manual_seed(0)
noisy_sample = torch.randn(1, model.config.in_channels, model.config.sample_size, model.config.sample_size)
#randn函数生成服从标准正态分布(均值为0,方差为1)的随机数
noisy_sample.shape#输出形状
对于推理,将噪声图像(noisy_sample)和时间步长(timestep)传递给模型。时间步长表示输入图像的噪声程度,开始时噪声多,结束时噪声少。这有助于模型确定其在扩散过程中的位置,是更接近起点还是更接近终点。使用样例方法得到模型输出:
with torch.no_grad():
noisy_residual = model(sample=noisy_sample, timestep=2).sample
#noisy_residual 对应调度,给定模型输出,调度程序管理从噪声样本到噪声较小的样本
调度程序:与model不同,调度程序没有可训练的权重并且是无参数的
#使用其DDPMScheduler的from_config()
from diffusers import DDPMScheduler
scheduler = DDPMScheduler.from_pretrained(repo_id)
scheduler
- num_train_timesteps:去噪过程的长度,或者换句话说,将随机高斯噪声处理为数据样本所需的时间步数。
- beta_schedule:用于推理和训练的噪声计划类型。
- beta_start和beta_end:噪声表的开始和结束噪声值。
要预测噪声稍低的图像,需要传入:模型输出(noisy residual)、步长(timestep) 和 当前样本(noisy sample)。
less_noisy_sample = scheduler.step(model_output=noisy_residual, timestep=2, sample=noisy_sample).prev_sample
less_noisy_sample.shape
import PIL.Image
import numpy as np
#首先,创建一个函数,对去噪图像进行后处理并将其显示为PIL.Image:
def display_sample(sample, i):
image_processed = sample.cpu().permute(0, 2, 3, 1)#匹配PIL的格式
image_processed = (image_processed + 1.0) * 127.5
image_processed = image_processed.numpy().astype(np.uint8)
image_pil = PIL.Image.fromarray(image_processed[0])
display(f"Image at step {i}")
display(image_pil)
#为了加速去噪过程,请将输入和模型移至 GPU:
model.to("cuda")
noisy_sample = noisy_sample.to("cuda")
现在创建一个去噪循环来预测噪声较小的样本的残差,并使用调度程序计算噪声较小的样本:
# 导入必要的库
import torch
import tqdm
# 假设noisy_sample是之前定义的噪声样本张量
sample = noisy_sample
# 使用tqdm库来显示进度条,遍历调度器scheduler中的所有时间步
for i, t in enumerate(tqdm.tqdm(scheduler.timesteps)):
# 1. 预测噪声残差
# 使用torch.no_grad()上下文管理器来禁用梯度计算,这通常在推理或评估阶段使用
with torch.no_grad():
# 调用模型的sample方法来获取噪声残差
# 这里model(sample, t)可能代表模型根据当前样本和时间步预测噪声
residual = model(sample, t).sample
# 2. 计算更少噪声的图像,并更新样本为上一个时间步的值
# scheduler.step方法根据残差、时间步和当前样本来更新样本
# prev_sample属性保存了更新后的样本
sample = scheduler.step(residual, t, sample).prev_sample
# 3. 可选地查看图像
# 每隔50步检查一次图像,以可视化生成过程
# 这里display_sample是一个假设的函数,用于显示或保存图像
if (i + 1) % 50 == 0:
display_sample(sample, i + 1)