上一篇三月份发的使用本地模型生成图像的文章只能实现最基础的文生图功能,对图像生成过程完全不可控,更不可能引导图像生成过程。
本文将学习更多图像生成的可选操作,学习资料来自官方文档,有关底层原理可以参照扩散生成基本原理(没变蓝就说明文章还没写好)。
快速复习
diffuser库的三个核心组件:
DiffusionPipeline
是一个高级端到端类,旨在从预训练的扩散模型中快速生成样本以进行推理。
流行的预训练模型架构和模块
,可用作创建扩散系统的构建块。
许多不同的调度程序
- 控制如何为训练添加噪声以及如何在推理期间生成去噪图像的算法。
安装方法pip install --upgrade diffusers accelerate transformers
,其中accelerate
非必选,用于并行计算提升效率。
扩散管道
DiffusionPipeline
是使用预训练的扩散系统进行推理的最简单方法。使用from_pretrained(模型名称或路径)
方法加载模型(一切自动化操作,模型没下载自动下载,但我们特殊的网络环境不允许,所以只能自行下载)。
该管道由 UNet2DConditionModel 和 PNDMScheduler 等组成,如下:
pipeline
StableDiffusionPipeline {"_class_name": "StableDiffusionPipeline","_diffusers_version": "0.21.4",...,"scheduler": ["diffusers","PNDMScheduler"],...,"unet": ["diffusers","UNet2DConditionModel"],"vae": ["diffusers","AutoencoderKL"]
}
使用的示例代码为:
from diffusers import DiffusionPipelinepipeline = DiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5"(可更换为本地路径), use_safetensors=True)
# GPU运行管道
pipeline.to("cuda")
# 传递文本生成图像
image = pipeline("An image of a squirrel in Picasso style").images[0]
# 保存图像
image.save("image_of_squirrel_painting.png")
管道本质上是一个封装好调度器和其他模块的结构,用于整合所有资源生成图像,是用户操作的最高层次。
模型
模型负责预测噪声,对图像去噪,同时将文本映射为词嵌入,将图像转换为潜空间,这些内容可见扩散生成基础原理。
模型通过 from_pretrained()
方法启动,模型的参数可使用model.config
方法查看。
模型的配置文件是一个冻结字典,其中的参数创建后无法更改,确保开始时用于定义模型架构的参数保持不变,而其他参数在推理过程中仍可进行调整。
以v1.4模型为例,其中参数为:
FrozenDict([('vae', ('diffusers', 'AutoencoderKL')), ('text_encoder', ('transformers', 'CLIPTextModel')),
('tokenizer', ('transformers', 'CLIPTokenizer')), ('unet', ('diffusers', 'UNet2DConditionModel')),
('scheduler', ('diffusers', 'PNDMScheduler')),('safety_checker', ('stable_diffusion', 'StableDiffusionSafetyChecker')),
('feature_extractor', ('transformers', 'CLIPImageProcessor')),
('image_encoder', (None, None)), ('requires_safety_checker', True),
('_name_or_path', '../../model/stable-diffusion-v1-4')])
一些最重要的参数是:
sample_size
:输入样本的高度和宽度维度。
in_channels
:输入采样的输入通道数。
down_block_types
:用于创建 UNet 架构的下采样和上采样块的类型。up_block_types
block_out_channels
: 下采样模块的输出通道数;也以相反的顺序用于 Upsampling 模块的 input channels 数量。
layers_per_block
:每个 UNet 块中存在的 ResNet 块的数量。
调度器
模型是降噪过程的具体执行者,调度器就是降噪过程的管理者,负责控制任务执行顺序、资源分配、动态策略调整。比如模型通过U-Net从噪声中恢复图像,调度器使用DDPM控制扩散步骤的顺序和混合策略。
使用DDPMScheduler
调度器的方法为
from diffusers import DDPMScheduler
scheduler = DDPMScheduler.from_pretrained(repo_id)
调度器没有可训练的权重,并且使用是无参的,也就是说我们使用的调度器全是现成的,基本都是在各大会议论文发表过的,效果优良的调度器。
管道参数
管道加载模型后的生成指令是pipe(prompt等参数).image[0]
,其中可以设置更多参数指导生成过程:
控制生成图像大小通过在参数中传递height
和width
实现,如image = pipe(prompt,height=256,width=256).images[0]
。但因为模型的训练都是基于其默认尺寸的,硬调整大小可能造成生成质量降低。
设置生成器随机种子,控制确定性生成,否则每次由随机噪声生成
generator = torch.Generator("cuda").manual_seed(0)
image = pipeline(prompt, generator=generator).images[0]
切换低精度为float16半精度,diffuser文档中说暂时没见到切换精度后质量下降的
pipe = StableDiffusionPipeline.from_pretrained(model_path,torch_dtype=torch.float16)
提升速度的另一种方法是减少推理步数,为保证生成质量,同时还需切换调度器。
可通过pipe.scheduler.compatibles
查看哪些调度器与当前模型兼容。
模型都默认使用PNDM调度器,通常需要50个步骤加载,可使用如下代码切换调度器
from diffusers import DPMSolverMultistepScheduler
pipeline.scheduler=DPMSolverMultistepScheduler.from_config(pipeline.scheduler.config)
# 将推理步数设置为20
image = pipeline(prompt, generator=generator, num_inference_steps=20).images[0]
批量生成下使用enable_attention_slicing()
节约内存。
批量生成的示例代码为:
# 批量生成随机种子和prompt提示
def get_inputs(batch_size=1):generator = [torch.Generator("cuda").manual_seed(i) for i in range(batch_size)]prompts = batch_size * [prompt]num_inference_steps = 20
# 将生成的参数以字典返回return {"prompt": prompts, "generator": generator, "num_inference_steps": num_inference_steps}from diffusers.utils import make_image_grid
# 解构字典,一次生成四张图像
images = pipeline(**get_inputs(batch_size=4)).images
make_image_grid(images, 2, 2)
如果此时报了OOMOutOfMemoryError
错误,添加pipeline.enable_attention_slicing()
函数将batch_size
增加到8也能运行,即分片注意力计算,将传统的全局注意力(Global Attention)拆分为多个 局部注意力块(Slices),每次仅计算一个块内的注意力,显著降低单次显存需求。
若要提升质量,入手点主要是更新的模型、管道,以及提示词,这部分还需要自行研究。
解构管道
管道通过获取所需输出大小的随机噪声并将其多次传递到模型来对图像进行降噪。在每个时间步长,模型预测噪声残差,调度程序使用它来预测噪声较小的图像。管道会重复此过程,直到到达指定数量的推理步骤的末尾。
管道结构
在工程中管道的from_pretrained()
负责下载推理所需的模型,如果模型已在本地缓存中,则无需下载复用模型,随后将缓存的权重加载到正确的管道类中并返回实例。
如Stable StableDiffusionPipeline
的一个实例由七个组件构成:
"feature_extractor":来自 Transformers 的 🤗 CLIPImageProcessor。
"safety_checker":用于筛选有害内容的组件。
"scheduler":PNDMScheduler 的实例。
"text_encoder":来自 Transformer 的 🤗 CLIPTextModel。
"tokenizer":来自 Transformers 的 🤗 CLIPTokenizer。
"unet":UNet2DConditionModel 的实例。
"vae":AutoencoderKL 的一个实例。
我们下载的模型文件如stable-diffusion-v1-5
中每个组件都有对应文件夹:
每个管道都有一个model_index.json
文件,该文件告诉DiffusionPipeline
:
要从哪个管道类加载class_name
使用哪个版本的 🧨 Diffusers 创建模型diffusers_version
子文件夹中存储了哪个库中的哪些组件(对应于组件和子文件夹名称,对应于要从中加载类的库的名称,对应于类名称)namelibraryclass
要不说还得是官网文档呢,还提供了模型所需内存的查询接口,就是有些模型得登陆,应该也得魔法。
管道加载可以使用通用的DiffusionPipeline
或使用专门的管道如StableDiffusionPipeline
,相同的模型也可用于其他任务,如文生图、图生图或修补,不同的任务需要使用不同的管道,如图生图任务需使用StableDiffusionImg2ImgPipeline
加载模型
Stable Diffusion 是一个文本到图像的潜在扩散模型。它被称为潜在扩散模型,因为它使用图像的低维表示,而不是实际的像素空间,这使得它更加内存效率高。编码器将图像压缩为较小的表示形式,解码器将压缩的表示形式转换回图像。对于文本到图像模型,您需要一个分词器和一个编码器来生成文本嵌入,此外还需要一个Unet模型和调度器。
使用如下代码分别加载调度器、分词器、编码器和Unet:
from PIL import Image
import torch
from transformers import CLIPTextModel, CLIPTokenizer
from diffusers import AutoencoderKL, UNet2DConditionModel, PNDMSchedulermodel_path="../../model/stable-diffusion-v1-5"
# 潜空间编码解码器
vae = AutoencoderKL.from_pretrained(model_path, subfolder="vae", use_safetensors=True)
# 加载分词器
tokenizer = CLIPTokenizer.from_pretrained(model_path, subfolder="tokenizer")
# 加载词嵌入编码器
text_encoder = CLIPTextModel.from_pretrained(model_path, subfolder="text_encoder", use_safetensors=True
)
unet = UNet2DConditionModel.from_pretrained(model_path, subfolder="unet", use_safetensors=True
)
切换调度器为 UniPCMultistepScheduler
:
from diffusers import UniPCMultistepScheduler
scheduler = UniPCMultistepScheduler.from_pretrained(model_path, subfolder="scheduler")
将工程移动到GPU以加速推理速度:
torch_device = "cuda"
vae.to(torch_device)
text_encoder.to(torch_device)
unet.to(torch_device)
创建文本嵌入
该过程对文本进行标记化以生成嵌入,该文本用于调节 UNet 模型,并将扩散过程引导到类似于输入提示的内容。
其中guidance_scale
确定在生成图像时应为提示赋予多少权重,即生成图像与文本的相关性。
扩散生成过程同时需要有条件文本和无条件引导,这是平衡生成质量与多样性综合考虑的设计。
有条件引导通过文本编码器如CLIP
将用户输入转化为语义向量,用于约束生成内容的语义方向,扩散过程中通过交叉注意力机制,将文本特征与图像特征对齐;
无条件引导潜在空间中引入高斯噪声,保持生成结果的多样性。
二者结合使用对最终输出的影响为: 最终输出 = α × 有条件文本 × ( 1 − α ) 无条件文本 最终输出=\alpha\times 有条件文本\times(1-\alpha)无条件文本 最终输出=α×有条件文本×(1−α)无条件文本
其中 α \alpha α就是权重guidance_scale
。
使用如下设置作为示例:
prompt = ["a photograph of an astronaut riding a horse"]
height = 512
width = 512
num_inference_steps = 25 # 去噪步数
guidance_scale = 7.5 # 文本权重
generator = torch.Generator(device=torch_device).manual_seed(0) # 潜空间种子
batch_size = len(prompt)
根据文本创建有条件文本嵌入:
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")
uncond_embeddings = text_encoder(uncond_input.input_ids.to(torch_device))[0]
将两个嵌入组合连接:
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
生成初始噪声
随机噪声的生成使用torch
库中的随机生成函数即可,如rand(行,列)
生成 行 × 列 行\times列 行×列格式的 [ 0 , 1 ] [0,1] [0,1]区间内的随机数,randn(尺寸)
生成从标准分布中抽取的随机数的矩阵,详细可见pytorch的randn和五种随机矩阵构造方法。
以如下代码生成高斯噪声为例:
# 噪声大小
sample_size = 64
# 生成1批,3通道,尺寸为64x64的噪声图像
noise = torch.randn((1, 3, sample_size, sample_size), device="cuda")# 输出为
torch.Size([1, 3, 64, 64])
tensor([[[[-0.4310, 1.1391, -0.0969, ..., -0.7259, -0.5462, 1.0064],[-0.7265, 0.2478, -1.1461, ..., -0.5822, 1.9437, 0.6647],[ 2.4440, -1.3060, -0.0568, ..., 0.6300, -1.3967, -1.2497],...,[ 0.4298, -0.5998, 0.9940, ..., -1.3417, 0.3811, 0.1598],[ 0.4932, 0.7464, 0.5163, ..., 1.7027, 0.0055, -0.3161],[-1.1510, 0.1065, 0.9482, ..., 0.5141, 1.0136, 0.2452]],[[-0.3130, 0.3198, 0.8023, ..., 1.2813, -1.2361, 1.9114],[ 1.2118, -2.0110, 1.9303, ..., -1.4969, -0.0209, 3.0643],[-0.8348, 0.1059, -1.0100, ..., 0.4698, 0.3961, -0.6087],...,[ 1.2917, 1.3164, -1.2170, ..., -0.4630, -1.6052, 1.1139],[ 1.7848, -0.1516, 1.0837, ..., 0.4286, -0.2923, 0.4433],[-1.1558, -2.4568, 0.1679, ..., 1.3434, 1.0621, 1.0178]],[[ 1.6242, -0.0391, 1.6987, ..., -0.5999, 0.2564, 0.9113],[ 1.2004, 0.4729, -1.3854, ..., -1.9621, -0.4954, 1.0868],[ 1.4938, -2.1733, 0.4676, ..., 0.7477, 0.8756, -0.4507],...,[-1.3528, -0.3727, -1.1883, ..., 0.2449, -0.6095, 1.5977],[-3.1204, -0.9469, -0.3748, ..., 0.6987, 0.9751, 0.3290],[ 0.9805, -0.3028, 0.3809, ..., -0.6049, 0.7995, 0.2541]]]],device='cuda:0')
潜空间负责将高分辨率图像映射到低维连续空间,缩放因子由2 ** (len(vae.config.block_out_channels) - 1)
计算得出,本示例中模型下采样层数为3,故缩放因子计算为8,生成随机潜空间代码为:
latents = torch.randn((batch_size, unet.config.in_channels, height // 8, width // 8),generator=generator,device=torch_device,
)
循环去噪
首先使用初始噪声分布 sigma(噪声标度值)缩放,这是改进调度器(本示例 UniPCMultistepScheduler
)所必须的,缩放过程为latents = latents * scheduler.init_noise_sigma
。
最后是循环去噪过程,还是绕不过扩散过程基本原理,先通过代码理解吧:
# 进度条库
from tqdm.auto import tqdm
# 调度器设置步长
scheduler.set_timesteps(num_inference_steps)
# 用tqdm包装timesteps,显示进度条
for t in tqdm(scheduler.timesteps):# 使用无分类器指导,复制潜在变量避免两次前向传递latent_model_input = torch.cat([latents] * 2)# 缩放输入latent_model_input = scheduler.scale_model_input(latent_model_input, timestep=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
解码图像
经过前面的循环去噪我们已经获得了去噪后的潜空间latent
,最后一步是使用解码器将latent
解码为图像:
# 特征空间缩放,暂不理解
latents = 1 / 0.18215 * latents
with torch.no_grad():# 解码并采样image = vae.decode(latents).sample
# image转换为图像
# 归一化,将其从[-1, 1]范围归一化到[0, 1]范围,裁剪到[0,1],并去除图像张量中的单维度条目
# 即将形状如(1, H, W, C)或(H, 1, W, C)等转换为(H, W, C)
image = (image / 2 + 0.5).clamp(0, 1).squeeze()
# 将图像数据的通道维度从最后一位移动到第一位,即将形状从(H, W, C)转换为(C, H, W)。为了符合PIL的输入要求
# 将归一化到[0, 1]范围的图像数据线性变换到[0, 255]范围的整数,这是为了得到标准的8位图像数据。
# 将图像数据类型转换为torch.uint8,即无符号8位整数类型
# 将PyTorch张量转换为NumPy数组
image = (image.permute(1, 2, 0) * 255).to(torch.uint8).cpu().numpy()
# 将NumPy数组转换为PIL图像对象。
image = Image.fromarray(image)
image.save("test.png")
自动管道
管道太多让人眼花缭乱,diffusers设计了自动管道类AutoPipeline
,该类根据任务使用(如AutoPipelineForText2Image
、AutoPipelineForImage2Image
、AutoPipelineForInpainting
),该管道会自动检测要使用的正确管道类。
给该类传入模型后,AutoPipeline
首先会从 model_index.json
文件中检测类型,根据任务类型选择加载StableDiffusionPipeline
、StableDiffusionImg2ImgPipeline
或StableDiffusionInpaintPipeline
。
使用自动管道进行图生图的示例如下:
from diffusers import AutoPipelineForImage2Image
from diffusers.utils import load_image
import torch
pipe_img2img = AutoPipelineForImage2Image.from_pretrained(model_path, torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
init_image=load_image("./test.png")
prompt = "cinematic photo of Godzilla eating sushi with a cat in a izakaya, 35mm photograph, film, professional, 4k, highly detailed"
generator = torch.Generator(device="cuda").manual_seed(37)
image = pipe_img2img(prompt,image=init_image, generator=generator).images[0]
image.save("ge.png")
遇到不支持的模型自动管道会报错"ValueError: AutoPipeline can't find a pipeline linked to ShapEImg2ImgPipeline for None"
。
管道复用
假如使用StableDiffusionPipeline
生成图像后,又希望使用StableDiffusionSAGPipeline
提高其质量,但二者使用相同的模型,无需再次使用from_pretrained
方法将其加载到内存中,仅需使用from_pipe(已有管道)
即可实现,可以在多个管道之间切换以利用它们的不同功能,而不会增加内存使用量。类似于在管道中打开和关闭功能。
从StableDiffusionPipeline
开始,然后重用加载的模型组件来创建 StableDiffusionSAGPipeline
以提高生成质量。使用带有IP适配器的StableDiffusionPipeline
来生成一只吃披萨的熊,使用实例如下:
from diffusers import DiffusionPipeline, StableDiffusionSAGPipeline
import torch
import gc
from diffusers.utils import load_image
from accelerate.utils import compute_module_sizes
# 加载已有图像
image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/load_neg_embed.png")
# 加载模型
pipe_sd = DiffusionPipeline.from_pre
trained("SG161222/Realistic_Vision_V6.0_B1_noVAE", torch_dtype=torch.float16)
# 加载ip适配器
pipe_sd.load_ip_adapter("h94/IP-Adapter", subfolder="models", weight_name="ip-adapter_sd15.bin")
pipe_sd.set_ip_adapter_scale(0.6)
pipe_sd.to("cuda")generator = torch.Generator(device="cpu").manual_seed(33)
out_sd = pipe_sd(# 正向提示词prompt="bear eats pizza",# 反向提示词,需排除的特征negative_prompt="wrong white balance, dark, sketches,worst quality,low quality",ip_adapter_image=image,num_inference_steps=50,generator=generator,
).images[0]
# 重用管道,加载pipe_sd到新的pipe_sag以提高质量,相当于图生图的两轮训练
pipe_sag = StableDiffusionSAGPipeline.from_pipe(pipe_sd
)
generator = torch.Generator(device="cpu").manual_seed(33)
out_sag = pipe_sag(prompt="bear eats pizza",negative_prompt="wrong white balance, dark, sketches,worst quality,low quality",ip_adapter_image=image,num_inference_steps=50,generator=generator,guidance_scale=1.0,sag_scale=0.75
).images[0]
需要注意的是对管道状态的修改会作用于全体复用者,比如在该过程中去掉了IP-Adapter
,不论哪个管道调用都不会出现该组件。
加载多个管道时的内存由需要最多的内存管道决定。
杂项
安全检查器可使用pipeline = DiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", safety_checker=None, use_safetensors=True)
设置关闭。
调度器
Diffusion 管道是可互换的调度程序和模型的集合,可以混合和匹配,以针对特定使用案例定制管道。调度器封装了整个降噪过程,例如去噪步骤的数量和查找去噪样本的算法。调度器没有参数化或训练,因此它们不会占用太多内存。该模型通常只关注从噪声输入到噪声较小的样本的正向传递。
加载模型查看调度器信息示例为:
import torch
from diffusers import DiffusionPipelinepipeline = DiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
pipeline.scheduler
PNDMScheduler {"_class_name": "PNDMScheduler","_diffusers_version": "0.21.4","beta_end": 0.012,"beta_schedule": "scaled_linear","beta_start": 0.00085,"clip_sample": false,"num_train_timesteps": 1000,"set_alpha_to_one": false,"skip_prk_steps": true,"steps_offset": 1,"timestep_spacing": "leading","trained_betas": null
}
加载调度程序
调度程序可以使用调度器类的from_pretrained
方法创建,随后在管道中通过参数更换模型的调度器,示例如下:
from diffusers import DDIMScheduler, DiffusionPipeline
ddim = DDIMScheduler.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", subfolder="scheduler")
pipeline = DiffusionPipeline.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", scheduler=ddim, torch_dtype=torch.float16, use_safetensors=True
).to("cuda")
调度器比较
通常来说调度器都是各有优劣,在速度和生成质量间平衡,要根据使用项目的具体需求来定,也很难说哪种调度器更适合管道,所以通常要进行多次尝试比较。
首先可以通过pipeline.scheduler.compatibles
查看哪些调度器与模型兼容,以v1.5
为例,支持的调度器有:
[<class 'diffusers.schedulers.scheduling_heun_discrete.HeunDiscreteScheduler'>,<class 'diffusers.schedulers.scheduling_ddpm.DDPMScheduler'>, <class 'diffusers.schedulers.scheduling_k_dpm_2_discrete.KDPM2DiscreteScheduler'>,<class 'diffusers.schedulers.scheduling_k_dpm_2_ancestral_discrete.KDPM2AncestralDiscreteScheduler'>, <class 'diffusers.schedulers.scheduling_deis_multistep.DEISMultistepScheduler'>, <class 'diffusers.schedulers.scheduling_pndm.PNDMScheduler'>, <class 'diffusers.schedulers.scheduling_edm_euler.EDMEulerScheduler'>, <class 'diffusers.schedulers.scheduling_euler_discrete.EulerDiscreteScheduler'>, <class 'diffusers.utils.dummy_torch_and_scipy_objects.LMSDiscreteScheduler'>, <class 'diffusers.schedulers.scheduling_dpmsolver_singlestep.DPMSolverSinglestepScheduler'>, <class 'diffusers.utils.dummy_torch_and_torchsde_objects.DPMSolverSDEScheduler'>, <class 'diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler'>, <class 'diffusers.schedulers.scheduling_euler_ancestral_discrete.EulerAncestralDiscreteScheduler'>, <class 'diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler'>, <class 'diffusers.schedulers.scheduling_ddim.DDIMScheduler'>]
加载不同调度器的方法为:
from diffusers import 调度器类
pipeline.scheduler = 调度器类.from_config(pipeline.scheduler.config)
有关调度器比较和说明可见官方文档的调度程序,其实文档也只是提供了调度器的大概说法,真正要比较可能还得看看论文,不过截至2025年6月,迭代步数最少生成质量还有保证的调度器应该是就是DPM-Solvers,官方文档对DPM-Solvers的介绍,大概需要十到二十个步骤就能生成质量较好的图像。
LoRA
Low-Rank Adaptation低秩适应。是一种为新任务快速训练模型的方法。它的工作原理是冻结原始模型权重并添加少量新的可训练参数。这意味着使现有模型适应新任务(例如以新样式生成图像)的速度要快得多,成本也更低。
其实就是机器学习中的迁移学习方法,小样本场景将已有的大模型拿过来重新训练,LoRA 只是更近一步,把前面训练好的参数都冻结,只保留少数可调整的参数,LoRA 通常还是要结合模型使用,只作为额外的补充。
使用 load_lora_weights()
将这些较小的权重集加载到现有基础模型中,使用实例如下:
import torch
from diffusers import AutoPipelineForText2Imagepipeline = AutoPipelineForText2Image.from_pretrained("stabilityai/stable-diffusion-xl-base-1.0",torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights("ostris/super-cereal-sdxl-lora",weight_name="cereal_box_sdxl_v1.safetensors",adapter_name="cereal"
)
pipeline("bears, pizza bites").images[0]
更多用法如设置权重等见官方文档对LoRA的介绍。
ControlNet
ControlNet 是一个适配器,可实现可控生成,例如生成处于特定姿势的猫的图像或遵循特定猫的草图中的线条。它的工作原理是添加一个较小的 “零卷积” 层网络,并逐步训练这些层,以避免破坏原始模型。原始模型参数将被冻结,以避免重新训练。
ControlNet 以额外的视觉信息或“结构控制”(精明的边缘、深度图、人体姿势等)为条件,这些信息或“结构控制”可以与文本提示相结合,以生成由视觉输入引导的图像。
使用open-cv生成边缘图像:
import cv2
import numpy as np
from PIL import Image
from diffusers.utils import load_imageoriginal_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/non-enhanced-prompt.png"
)image = np.array(original_image)low_threshold = 100
high_threshold = 200image = cv2.Canny(image, low_threshold, high_threshold)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
canny_image = Image.fromarray(image)
将该边缘图像传递给管道,使用参数controlnet_conditioning_scale
可确定要分配给控件的权重。
import torch
from diffusers.utils import load_image
from diffusers import FluxControlNetPipeline, FluxControlNetModel
# 加载controlnet
controlnet = FluxControlNetModel.from_pretrained("InstantX/FLUX.1-dev-Controlnet-Canny", torch_dtype=torch.bfloat16
)
# 设置管道模型与controlnet
pipeline = FluxControlNetPipeline.from_pretrained("black-forest-labs/FLUX.1-dev", controlnet=controlnet, torch_dtype=torch.bfloat16
).to("cuda")prompt = """
A photorealistic overhead image of a cat reclining sideways in a flamingo pool floatie holding a margarita.
The cat is floating leisurely in the pool and completely relaxed and happy.
"""pipeline(prompt, control_image=canny_image,controlnet_conditioning_scale=0.5,num_inference_steps=50, guidance_scale=3.5,
).images[0]
训练扩散模型
该部分尚无需使用,所以仅挂个官方训练无条件生成模型链接占位符,大致流程就是使用数据集,裁剪图像尺寸到与输出通道一致,设置一个优化器和一个学习率调度器,随后设置学习方法,开始循环训练。
总结
其实用起来并不复杂,就是前置知识太多。
本本文介绍了diifusers库生成图像的部分使用方法,包括管道,调度器和模型的使用,如何手动构建图像生成过程,大致流程是:加载模型,创建文本,生成噪声,循环去噪,解码图像。
如果仅仅需要生成图像,并对图像质量没有要求的话,使用快速管道传prompt
就能实现,要实现部分可控只需调用几个插件,如特定类型的用LoRA
,指定骨架或形状的用controlnet
。但若要保证图像质量,并且训练专属于自己的模型就复杂了,可能需要在多次调用管道,并且自行编写循环去噪方法。
目前也仅仅是对使用方法过了一遍,后续还需要更多时间实践摸索。