Diffusion
扩散原理
生成模型的目标是:给定一组数据,构建一个分布,生成新的数据
在物理学中很多微观过程都是时间可逆的,如果能知道当前系统的状态,理论上可以求出上一时间的状态。受此启发,如果我们知道从一幅画上如何一步步加噪声,也许能学会如何从噪声出发一步步去噪声得到一幅画。
扩散模型是一类概率生成模型,定义了两个马尔可夫过程:
前向过程:一个固定的马尔可夫链,将数据分布逐步添加高斯噪声,转为一种已知的先验分布(通常为标准高斯分布)
反向过程:一个参数化的马尔可夫链,从先验分布开始逐步降噪,最终生成数据分布的样本
扩散模型将一个复杂的抽样转为一系列的简单抽样,简化了学习
高斯扩散
$$
x_{t+1}=x_t + \eta_t ,\quad \eta_t \sim N(0, \sigma^2)
$$
一个假设:反向过程中,每一次去噪声的结果,仍是高斯分布。
于是这个分布可以用一组均值和方差表示,神经网络只需要预测这组参数,就能还原出$x_0$
DDPM
去噪扩散概率模型
DDPM,一个常用的构建反向采样器的方法,将复杂的变分推断问题简化为一个简单的去噪任务
DDPM的简化:
固定前向过程,前向加噪时使用一个预先设定的固定方差的高斯噪声,无需学习,只需要专注学习反向过程
简化反向过程,反向去噪时每次都是用固定方差的高斯噪声,模型只需要学习预测高斯分布的均值
重参数化目标,从学习预测图像转为预测噪声,极大地稳定了训练过程
相关概念
马尔可夫过程:核心特征是无记忆性,系统的未来状态仅于当前状态有关,而与历史状态(先前的状态)无关
最优传输理论:一种研究如何以最小成本将一个概率分布转移为另一种概率分布
对数似然下界(ELBO):用于求一个复杂概率分布的后验分布,由于直接计算后验分布十分困难,所以引入变分推断来近似
信噪比(SNR):信息/噪声
U-Net
U-Net最初是一个医学图像分割模型,当时的需求是神经网络既要理解全局语义信息(这是什么器官),又要精确定位像素。传统的Encoder-Decoder架构当特征图被压缩到最小时,大量空间信息被丢失了,而U-Net通过引入跳跃连接(下图灰色箭头),让高分辨率的信息直接拷贝到后续部分。
U型的网络传递全局语义信息,跳跃连接传递局部高分辨像素信息
U-Net的优点:
对称性的架构,能保证输出和输入在空间上严格对齐
多尺度的处理能力,在扩散过程中,前期高噪声时主要进行全局结构和语义重建,后期需要进行精修细节和清晰度,U-Net可以兼顾全局和局部
高效,大部分复杂计算(如自注意力)在低分辨率的特征图中,在高分辨率下只做简单操作
ViT
ViT将Transformer架构引入到CV领域,核心思想是将输入图片切分为一个个小patch,为这些patch标注类别token,将这些pathc和类别一同送入Transformer中,生成分类
DiT的输入是一个个带噪声的图像patch和条件token(如位置和时间步数),送入Transformer后得到输出token,解码得到每个patch对应的噪声
Flow Matching
应用
T2I
from diffusers import DiffusionPipelinepipe_id = "stabilityai/stable-diffusion-xl-base-1.0" pipe = DiffusionPipeline.from_pretrained(pipe_id, torch_dtype=torch.float16).to("cuda" ) prompt = "a blue hair gril" image = pipe(prompt, num_inference_steps=45 , guidance_scale=7.5 , height=1024 , width=1024 ).images[0 ] image.save("output.jpg" )
T2I LoRA
LoRA可以改变模型画风
from diffusers import DiffusionPipelinepipe_id = "stabilityai/stable-diffusion-xl-base-1.0" pipe = DiffusionPipeline.from_pretrained(pipe_id, torch_dtype=torch.float16).to("cuda" ) pipe.load_lora_weights("sd-gbf-lora" ) prompt = "a blue hair gril" lora_scale = 0.9 image = pipe(prompt, num_inference_steps=45 , guidance_scale=7.5 , cross_attention_kwargs={"scale" : lora_scale}, height=1024 , width=1024 ).images[0 ] image.save("output.jpg" )
I2I LoRA
将图片转为LoRA画风
import torchfrom PIL import Imagefrom diffusers import StableDiffusionXLImg2ImgPipelinepipe_id = "stabilityai/stable-diffusion-xl-base-1.0" pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained(pipe_id, torch_dtype=torch.float16).to("cuda" ) pipe.load_lora_weights("sd-gbf-lora" ) input_image_path = "examples/lubi.jpg" input_image = Image.open (input_image_path).convert("RGB" ) prompt = "gbfhero" negative_prompt = "low quality, bad quality" with torch.no_grad(): output_image = pipe( prompt=prompt, negative_prompt=negative_prompt, guidance_scale=7.5 , cross_attention_kwargs={"scale" : 0.9 }, height=1024 , width=1024 , image=input_image, strength=0.5 ).images[0 ] output_image.save(f"outputs/1.jpg" )
I2I LoRA Controlnet
直接使用I2I LoRA效果并不好,对原图的控制能力比较弱,可以配合使用Controlnet使用
import osimport cv2import torchimport numpy as npfrom PIL import Imagefrom diffusers import StableDiffusionXLControlNetImg2ImgPipeline, ControlNetModeloutput_folder = "outputs" os.makedirs(output_folder, exist_ok=True ) pipe_id = "stabilityai/stable-diffusion-xl-base-1.0" controlnet_id = "diffusers/controlnet-canny-sdxl-1.0" controlnet = ControlNetModel.from_pretrained(controlnet_id, torch_dtype=torch.float16) pipe = StableDiffusionXLControlNetImg2ImgPipeline.from_pretrained(pipe_id, controlnet=controlnet, torch_dtype=torch.float16).to("cuda" ) pipe.load_lora_weights("sd-gbf-lora3" ) input_image_path = "examples/leishen.jpeg" input_image = Image.open (input_image_path).convert("RGB" ) np_image = np.array(input_image) np_image = cv2.Canny(np_image, 100 , 200 ) np_image = np_image[:, :, None ] np_image = np.concatenate([np_image, np_image, np_image], axis=2 ) canny_image = Image.fromarray(np_image) canny_image.save(f'{output_folder} /tmp_edge.png' ) prompt = "gbfhero, clean background" negative_prompt = "low quality, bad quality" lora_scale = 0.9 image = pipe(prompt, negative_prompt=negative_prompt, guidance_scale=7.5 , cross_attention_kwargs={"scale" : lora_scale}, controlnet_conditioning_scale=0.5 , image=input_image, strength=0.9 , control_image=canny_image, height=1024 , width=1024 ).images[0 ] image.save(f"{output_folder} /5.jpg" )
训练
Flux DreamBooth LoRA
advanced_diffusion_training
基于代码train_dreambooth_lora_flux_advanced.py
export MODEL_NAME="black-forest-labs/FLUX.1-dev" export DATASET_NAME="Reuben-Sun/ATDAN-pictures" export DATASET_PATH="ATDAN-pictures" export OUTPUT_DIR="Flux-LoRA-ATDAN" export CUDA_VISIBLE_DEVICES='0,1,2,3' accelerate launch \ --num_processes=4 \ --num_machines=1 \ --multi_gpu \ train_dreambooth_lora_flux_advanced.py \ --pretrained_model_name_or_path=$MODEL_NAME \ --instance_data_dir=$DATASET_PATH \ --instance_prompt="ATDAN" \ --validation_prompt="ATDAN, a vibrant blue and white anime girl with long white hair and a crown on her head. Her eyes are glowing with a yellow hue, adding a pop of color to the scene. Her hands are clasped in front of her face, while her hands are held together in a praying position. The background is dark, creating a stark contrast to the girl's outfit" \ --validation_epochs=20 \ --output_dir=$OUTPUT_DIR \ --mixed_precision="bf16" \ --resolution=1024 \ --train_batch_size=2 \ --repeats=1 \ --report_to="tensorboard" \ --gradient_accumulation_steps=1 \ --gradient_checkpointing \ --learning_rate=1e-4 \ --optimizer="adamW" \ --lr_scheduler="constant" \ --lr_warmup_steps=0 \ --rank=32 \ --lora_alpha=16 \ --max_train_steps=4000 \ --checkpointing_steps=200 \ --checkpoints_total_limit=10 \ --seed=42 \ --guidance_scale=1.0
import osimport hashlibimport argparseimport torchfrom diffusers import AutoPipelineForText2Imagefrom safetensors.torch import load_fileif __name__ == "__main__" : parser = argparse.ArgumentParser(description="Inference script for Flux LoRA model" ) parser.add_argument("--model_id" , type =str , default='black-forest-labs/FLUX.1-dev' ) parser.add_argument("--lora_name" , type =str , required=True ) parser.add_argument('--lora_scale' , type =float , default=1.0 , help ='Scale for LoRA weights' ) parser.add_argument("--prompt" , type =str , required=True ) parser.add_argument("--steps" , type =int , default=25 ) parser.add_argument("--seed" , type =int , default=0 ) parser.add_argument("--guidance_scale" , type =float , default=7.5 ) args = parser.parse_args() model = AutoPipelineForText2Image.from_pretrained(args.model_id, torch_dtype=torch.bfloat16).to("cuda" ) model.load_lora_weights(args.lora_name, weight_name="pytorch_lora_weights.safetensors" ) model.set_adapters("default_0" , args.lora_scale) with torch.no_grad(): generator = torch.Generator(device=model.device).manual_seed(args.seed) output_image = model(prompt=args.prompt, num_inference_steps=args.steps, guidance_scale=args.guidance_scale, generator=generator).images[0 ] output_image.save('output.png' )
参考
diffusion_tutorial
《Step-by-Step Diffusion: An Elementary Tutorial》