抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Diffusion

扩散原理

生成模型的目标是:给定一组数据,构建一个分布,生成新的数据

在物理学中很多微观过程都是时间可逆的,如果能知道当前系统的状态,理论上可以求出上一时间的状态。受此启发,如果我们知道从一幅画上如何一步步加噪声,也许能学会如何从噪声出发一步步去噪声得到一幅画。

扩散模型是一类概率生成模型,定义了两个马尔可夫过程:

  1. 前向过程:一个固定的马尔可夫链,将数据分布逐步添加高斯噪声,转为一种已知的先验分布(通常为标准高斯分布)
  2. 反向过程:一个参数化的马尔可夫链,从先验分布开始逐步降噪,最终生成数据分布的样本

扩散模型将一个复杂的抽样转为一系列的简单抽样,简化了学习

高斯扩散

$$
x_{t+1}=x_t + \eta_t ,\quad \eta_t \sim N(0, \sigma^2)
$$

一个假设:反向过程中,每一次去噪声的结果,仍是高斯分布。

于是这个分布可以用一组均值和方差表示,神经网络只需要预测这组参数,就能还原出$x_0$

DDPM

去噪扩散概率模型

DDPM,一个常用的构建反向采样器的方法,将复杂的变分推断问题简化为一个简单的去噪任务

DDPM的简化:

  1. 固定前向过程,前向加噪时使用一个预先设定的固定方差的高斯噪声,无需学习,只需要专注学习反向过程
  2. 简化反向过程,反向去噪时每次都是用固定方差的高斯噪声,模型只需要学习预测高斯分布的均值
  3. 重参数化目标,从学习预测图像转为预测噪声,极大地稳定了训练过程

相关概念

马尔可夫过程:核心特征是无记忆性,系统的未来状态仅于当前状态有关,而与历史状态(先前的状态)无关

最优传输理论:一种研究如何以最小成本将一个概率分布转移为另一种概率分布

对数似然下界(ELBO):用于求一个复杂概率分布的后验分布,由于直接计算后验分布十分困难,所以引入变分推断来近似

信噪比(SNR):信息/噪声

U-Net

U-Net最初是一个医学图像分割模型,当时的需求是神经网络既要理解全局语义信息(这是什么器官),又要精确定位像素。传统的Encoder-Decoder架构当特征图被压缩到最小时,大量空间信息被丢失了,而U-Net通过引入跳跃连接(下图灰色箭头),让高分辨率的信息直接拷贝到后续部分。

U型的网络传递全局语义信息,跳跃连接传递局部高分辨像素信息

UNet

U-Net的优点:

  1. 对称性的架构,能保证输出和输入在空间上严格对齐
  2. 多尺度的处理能力,在扩散过程中,前期高噪声时主要进行全局结构和语义重建,后期需要进行精修细节和清晰度,U-Net可以兼顾全局和局部
  3. 高效,大部分复杂计算(如自注意力)在低分辨率的特征图中,在高分辨率下只做简单操作

ViT

ViT将Transformer架构引入到CV领域,核心思想是将输入图片切分为一个个小patch,为这些patch标注类别token,将这些pathc和类别一同送入Transformer中,生成分类

DiT的输入是一个个带噪声的图像patch和条件token(如位置和时间步数),送入Transformer后得到输出token,解码得到每个patch对应的噪声

Flow Matching

应用

T2I

from diffusers import DiffusionPipeline

pipe_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 DiffusionPipeline

pipe_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 torch
from PIL import Image
from diffusers import StableDiffusionXLImg2ImgPipeline

pipe_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 # 控制生成图像与输入图像的相似程度,范围为0到1
).images[0]

output_image.save(f"outputs/1.jpg")

I2I LoRA Controlnet

直接使用I2I LoRA效果并不好,对原图的控制能力比较弱,可以配合使用Controlnet使用

import os
import cv2
import torch
import numpy as np
from PIL import Image
from diffusers import StableDiffusionXLControlNetImg2ImgPipeline, ControlNetModel

output_folder = "outputs"
os.makedirs(output_folder, exist_ok=True)

# 加载模型和Controlnet
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)
# 生成 edges
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

# train.sh
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
# inference.py
import os
import hashlib
import argparse
import torch
from diffusers import AutoPipelineForText2Image
from safetensors.torch import load_file

if __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()

# Load the model
model = AutoPipelineForText2Image.from_pretrained(args.model_id, torch_dtype=torch.bfloat16).to("cuda")
# Load LoRA weights
model.load_lora_weights(args.lora_name, weight_name="pytorch_lora_weights.safetensors")
model.set_adapters("default_0", args.lora_scale)

# Inference
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》

评论