Skip to content

필사 모드: 生成式 AI 与 Diffusion 模型完全指南:从 Stable Diffusion、ControlNet 到视频生成

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言

2022 年 Stable Diffusion 发布后,图像生成 AI 迎来了大众化的时代。但是,能真正回答「为什么图像会从噪声中浮现出来」这个问题的人却并不多。

本指南从GAN 到 Consistency Models梳理生成模型的谱系,并完整解剖 DDPM 的数学、Stable Diffusion 的内部结构、ControlNet、LoRA fine-tuning,以及 Sora 这类视频生成模型。


1. 生成模型谱系:GAN → VAE → Flow → Diffusion → Consistency

1.1 GAN(Generative Adversarial Network,2014)

Ian Goodfellow 提出的 GAN,通过生成器(Generator)与判别器(Discriminator)之间的对抗博弈进行学习。

  • 优点:图像生成质量高,采样速度快
  • 缺点:训练不稳定(mode collapse),多样性不足
# 基本 GAN 结构
import torch
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self, latent_dim=100, img_size=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, img_size * img_size * 3),
            nn.Tanh()
        )

    def forward(self, z):
        return self.net(z).view(-1, 3, 64, 64)

1.2 VAE(Variational Autoencoder,2013)

VAE 让编码器学习潜在空间中的分布,再由解码器从该分布中采样以重建图像。

损失函数:L=E[logp(xz)]DKL(q(zx)p(z))\mathcal{L} = \mathbb{E}[\log p(x|z)] - D_{KL}(q(z|x) \| p(z))

  • 优点:潜在空间可解释,训练稳定
  • 缺点:样本质量比 GAN 更模糊(blurry)

1.3 Normalizing Flow(2015~)

Flow 模型通过堆叠可逆变换(invertible transformation),把简单分布转换为复杂分布。

p(x)=p(z)detf1xp(x) = p(z) \left|\det \frac{\partial f^{-1}}{\partial x}\right|

  • 优点:可精确计算 likelihood
  • 缺点:网络结构受限(要求可逆性),内存效率低

1.4 Diffusion Models(2020~)

Diffusion 对数据逐步添加噪声,再学习其逆过程。它结合了 score matching 与 SDE 理论,是当前生成模型中的最高水平。

1.5 Consistency Models(2023)

Consistency Models 解决了 Diffusion 采样速度慢的问题。它学习一个一致性函数,能在任意噪声水平下直接映射到原始数据


2. Diffusion 数学:DDPM、Score Matching、SDE

2.1 DDPM Forward Process

DDPM(Denoising Diffusion Probabilistic Models)的正向过程,对原始数据 x0x_0 在 T 个步骤中逐步添加高斯噪声。

q(xtxt1)=N(xt;1βtxt1,βtI)q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t} x_{t-1}, \beta_t I)

将其累积后,可以在任意时间步 t 直接采样:

q(xtx0)=N(xt;αˉtx0,(1αˉt)I)q(x_t | x_0) = \mathcal{N}(x_t; \sqrt{\bar{\alpha}_t} x_0, (1-\bar{\alpha}_t) I)

其中 αˉt=s=1t(1βs)\bar{\alpha}_t = \prod_{s=1}^{t}(1-\beta_s)

import torch
import torch.nn.functional as F

class DDPMScheduler:
    def __init__(self, num_timesteps=1000, beta_start=1e-4, beta_end=0.02):
        self.T = num_timesteps
        # 线性噪声调度
        self.betas = torch.linspace(beta_start, beta_end, num_timesteps)
        self.alphas = 1.0 - self.betas
        self.alpha_bar = torch.cumprod(self.alphas, dim=0)

    def add_noise(self, x0, noise, t):
        """对 x0 添加 t 步噪声(reparameterization trick)"""
        sqrt_alpha_bar = self.alpha_bar[t] ** 0.5
        sqrt_one_minus = (1 - self.alpha_bar[t]) ** 0.5
        # 调整 shape 以便广播
        sqrt_alpha_bar = sqrt_alpha_bar.view(-1, 1, 1, 1)
        sqrt_one_minus = sqrt_one_minus.view(-1, 1, 1, 1)
        return sqrt_alpha_bar * x0 + sqrt_one_minus * noise

2.2 DDPM Reverse Process

逆向过程由神经网络预测每一步的噪声:

pθ(xt1xt)=N(xt1;μθ(xt,t),σt2I)p_\theta(x_{t-1} | x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \sigma_t^2 I)

训练目标是让添加的噪声与预测的噪声之间的 MSE 最小化:

L=Et,x0,ϵ[ϵϵθ(αˉtx0+1αˉtϵ,t)2]\mathcal{L} = \mathbb{E}_{t, x_0, \epsilon}\left[\|\epsilon - \epsilon_\theta(\sqrt{\bar{\alpha}_t} x_0 + \sqrt{1-\bar{\alpha}_t}\epsilon, t)\|^2\right]

def ddpm_training_step(model, scheduler, x0, optimizer):
    batch_size = x0.shape[0]
    # 随机采样时间步
    t = torch.randint(0, scheduler.T, (batch_size,))
    # 采样高斯噪声
    noise = torch.randn_like(x0)
    # 添加噪声(forward process)
    xt = scheduler.add_noise(x0, noise, t)
    # 预测噪声
    predicted_noise = model(xt, t)
    # MSE 损失
    loss = F.mse_loss(predicted_noise, noise)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    return loss.item()

2.3 Score Matching 视角

Score function 是数据分布对数概率的梯度

sθ(x)=xlogpθ(x)s_\theta(x) = \nabla_x \log p_\theta(x)

Diffusion 模型的噪声预测,实际上等价于学习 score function:

ϵθ(xt,t)1αˉtxtlogq(xt)\epsilon_\theta(x_t, t) \approx -\sqrt{1-\bar{\alpha}_t} \cdot \nabla_{x_t} \log q(x_t)

2.4 SDE 视角(Stochastic Differential Equation)

Song Yang 提出的 SDE 框架,把 Diffusion 推广到连续时间。

正向 SDE:dx=f(x,t)dt+g(t)dWdx = f(x,t)dt + g(t)dW

逆向 SDE:dx=[f(x,t)g(t)2xlogpt(x)]dt+g(t)dWˉdx = [f(x,t) - g(t)^2 \nabla_x \log p_t(x)]dt + g(t)d\bar{W}

借助这个框架,可以用统一的视角理解 DDPM、SMLD(NCSN)以及 ODE 采样器。


3. Stable Diffusion 内部结构

3.1 整体架构

Stable Diffusion 由三个核心组件构成:

  1. VAE(Variational Autoencoder):像素空间 ↔ 潜在空间的转换
  2. U-Net:在潜在空间中预测噪声
  3. CLIP Text Encoder:把文本提示转换为嵌入向量
from diffusers import StableDiffusionPipeline
import torch

# 使用基础 pipeline
pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16
).to("cuda")

# 生成图像
image = pipe(
    prompt="a serene mountain landscape at sunset, photorealistic",
    negative_prompt="blurry, low quality, distorted",
    num_inference_steps=30,
    guidance_scale=7.5,
    width=512,
    height=512
).images[0]

image.save("output.png")

3.2 为什么要用潜在空间(Latent Space)?

如果直接在像素空间上运行 Diffusion,需要处理 512x512x3 = 786,432 维。而 SD 的 VAE 将其压缩到 64x64x4 = 16,384 维

  • 计算成本:约减少 48 倍
  • 质量损失:得益于 VAE 的 perceptual loss,损失被降到最低
# 可视化 VAE 潜在空间
from diffusers import AutoencoderKL
from PIL import Image
import torchvision.transforms as T

vae = AutoencoderKL.from_pretrained("stabilityai/sd-vae-ft-mse")
vae = vae.to("cuda").eval()

transform = T.Compose([T.Resize((512, 512)), T.ToTensor(),
                        T.Normalize([0.5], [0.5])])

img = transform(Image.open("input.png")).unsqueeze(0).to("cuda")
with torch.no_grad():
    # 像素 → 潜在(编码)
    latent = vae.encode(img).latent_dist.sample()
    latent = latent * vae.config.scaling_factor
    print(f"潜在空间尺寸: {latent.shape}")  # [1, 4, 64, 64]

3.3 CLIP Text Encoder

CLIP 是用图像-文本对训练出来的模型。在 SD 中只用作文本编码器,把提示词转换为 77 个 token × 768 维的嵌入向量。

from transformers import CLIPTextModel, CLIPTokenizer

tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
text_encoder = CLIPTextModel.from_pretrained("openai/clip-vit-large-patch14")

prompt = "a fantasy castle in the clouds"
tokens = tokenizer(prompt, padding="max_length", max_length=77,
                   return_tensors="pt")
with torch.no_grad():
    text_emb = text_encoder(tokens.input_ids)[0]
print(f"文本嵌入尺寸: {text_emb.shape}")  # [1, 77, 768]

3.4 CFG(Classifier-Free Guidance)

CFG 用来调节条件生成的强度。guidance_scale 越高,越严格地遵循提示词;越低,多样性越高。

ϵguided=ϵuncond+w(ϵcondϵuncond)\epsilon_{guided} = \epsilon_{uncond} + w \cdot (\epsilon_{cond} - \epsilon_{uncond})


4. LoRA 与 DreamBooth Fine-tuning

4.1 LoRA(Low-Rank Adaptation)原理

LoRA 不直接更新整个权重矩阵 WRd×kW \in \mathbb{R}^{d \times k},而是用两个低秩矩阵的乘积来表示其变化量:

W=W+ΔW=W+BAW' = W + \Delta W = W + BA

其中 BRd×rB \in \mathbb{R}^{d \times r}ARr×kA \in \mathbb{R}^{r \times k}rmin(d,k)r \ll \min(d, k)

通常取 r=4~16,相对于全部参数只训练 0.1~1%

from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
from peft import LoraConfig, get_peft_model
import torch

# LoRA 配置
lora_config = LoraConfig(
    r=16,                          # 秩
    lora_alpha=32,                 # 缩放参数
    target_modules=["to_q", "to_v", "to_k", "to_out.0"],
    lora_dropout=0.05,
    bias="none",
)

# 把 LoRA 应用到模型上
pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5"
)
unet_lora = get_peft_model(pipe.unet, lora_config)
unet_lora.print_trainable_parameters()
# 可训练参数: 约 3M / 总参数: 约 860M(约占 0.3%)

4.2 DreamBooth Fine-tuning

DreamBooth 只用 3~10 张图像就能学习特定对象,并用稀有 token(例如「sks」)作为该对象的标识符。

from diffusers import DiffusionPipeline
import torch

# 加载 DreamBooth 训练好的模型
pipe = DiffusionPipeline.from_pretrained(
    "./dreambooth-sks-dog",  # 训练好的 checkpoint
    torch_dtype=torch.float16
).to("cuda")

# 用 "sks dog" 生成特定的狗
images = pipe(
    "a photo of sks dog in the Eiffel Tower",
    num_inference_steps=50,
    guidance_scale=7.5
).images

5. ControlNet 与 IP-Adapter

5.1 ControlNet 架构

ControlNet 复制 U-Net 的编码器部分,构建出一个独立的控制网络,并用zero convolution来保护 SD 原始权重。

支持的条件类型:

  • Depth map:空间深度信息
  • Canny edge:保留轮廓线
  • OpenPose:控制人体姿态
  • Scribble:从粗略草图生成精细图像
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from diffusers.utils import load_image
import torch
import cv2
import numpy as np

# 加载 ControlNet 模型(Canny edge)
controlnet = ControlNetModel.from_pretrained(
    "lllyasviel/sd-controlnet-canny",
    torch_dtype=torch.float16
)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    controlnet=controlnet,
    torch_dtype=torch.float16
).to("cuda")

# 提取 Canny edge
image = load_image("input.png")
image_np = np.array(image)
low_threshold, high_threshold = 100, 200
canny_image = cv2.Canny(image_np, low_threshold, high_threshold)
canny_image = canny_image[:, :, None]  # 增加通道
canny_image = np.concatenate([canny_image] * 3, axis=2)

# ControlNet 推理
result = pipe(
    prompt="a beautiful landscape, detailed, 8k",
    image=canny_image,
    num_inference_steps=30,
    controlnet_conditioning_scale=1.0,
).images[0]

5.2 IP-Adapter 与 InstantID

IP-Adapter 把参考图像的风格/内容与文本提示一起作为条件使用。

InstantID 仅凭一张人脸照片,就能在生成多样风格的同时保持一致的身份特征。它是 ControlNet(姿态控制)与 IP-Adapter(面部特征)结合而成的结构。


6. 高级图像编辑:InstructPix2Pix

InstructPix2Pix 用文本指令来编辑图像,能理解「把马变成斑马」这样的指令。

from diffusers import StableDiffusionInstructPix2PixPipeline
import torch
from diffusers.utils import load_image

pipe = StableDiffusionInstructPix2PixPipeline.from_pretrained(
    "timbrooks/instruct-pix2pix",
    torch_dtype=torch.float16,
    safety_checker=None
).to("cuda")

image = load_image("horse.png")
result = pipe(
    "turn the horse into a zebra",
    image=image,
    num_inference_steps=30,
    image_guidance_scale=1.5,  # 对原图的保真度
    guidance_scale=7.5          # 文本指令强度
).images[0]

7. 视频生成:Sora、CogVideoX

7.1 Sora 的技术创新

OpenAI 的 Sora 采用Video Diffusion Transformer结构,把视频当作「spacetime patches」的序列来处理。核心创新:

  1. Spatial-temporal attention:空间与时间维度同时进行注意力计算
  2. Variable resolution training:在多种分辨率/帧率上训练
  3. Recaptioning:提升视频字幕的质量

7.2 保持时间一致性

视频生成中最大的挑战是时间一致性(temporal consistency)。

  • Motion prior:学习自然运动的分布
  • Cross-frame attention:跨帧共享特征
  • Optical flow guidance:用光流控制运动
from diffusers import CogVideoXPipeline
import torch

pipe = CogVideoXPipeline.from_pretrained(
    "THUDM/CogVideoX-5b",
    torch_dtype=torch.bfloat16
).to("cuda")

video = pipe(
    prompt="A serene lake with rippling water, birds flying overhead",
    num_videos_per_prompt=1,
    num_inference_steps=50,
    num_frames=49,
    guidance_scale=6,
).frames[0]

8. 音乐/音频生成

8.1 MusicGen(Meta)

MusicGen 是一个基于语言模型、根据文本生成音乐的系统。

from audiocraft.models import MusicGen
import torchaudio

model = MusicGen.get_pretrained("facebook/musicgen-large")
model.set_generation_params(duration=30)  # 生成 30 秒

descriptions = ["happy jazz piano with upbeat rhythm"]
wav = model.generate(descriptions)
torchaudio.save("music.wav", wav[0].cpu(), sample_rate=32000)

8.2 AudioLM 架构

Google 的 AudioLM 使用分层的分词方式:

  • Semantic tokens(w2v-BERT):语义信息
  • Coarse acoustic tokens(SoundStream):粗粒度音响
  • Fine acoustic tokens(SoundStream):细粒度音响

8.3 VALL-E 语音合成

Microsoft 的 VALL-E 仅凭 3 秒语音样本,就能复刻说话人的声音。它像语言模型一样,以自回归方式生成语音编解码器 token。


9. 生产环境部署

9.1 diffusers 库优化

from diffusers import StableDiffusionPipeline
import torch

pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16
)

# 内存优化
pipe.enable_attention_slicing()           # 注意力切片
pipe.enable_vae_slicing()                 # VAE 切片
pipe.enable_model_cpu_offload()           # CPU 卸载

# xformers 加速(如已安装)
try:
    pipe.enable_xformers_memory_efficient_attention()
    print("xformers 已启用")
except:
    print("未找到 xformers,使用默认注意力机制")

9.2 调用 ComfyUI API

import json
import urllib.request
import urllib.parse

def queue_prompt(prompt_workflow, server_address="127.0.0.1:8188"):
    """通过 ComfyUI API 执行工作流"""
    p = {"prompt": prompt_workflow}
    data = json.dumps(p).encode("utf-8")
    req = urllib.request.Request(
        f"http://{server_address}/prompt",
        data=data,
        headers={"Content-Type": "application/json"}
    )
    with urllib.request.urlopen(req) as response:
        return json.loads(response.read())

# ComfyUI 工作流(JSON 格式)
workflow = {
    "1": {
        "class_type": "CheckpointLoaderSimple",
        "inputs": {"ckpt_name": "v1-5-pruned-emaonly.ckpt"}
    },
    "2": {
        "class_type": "CLIPTextEncode",
        "inputs": {
            "text": "a beautiful sunset over mountains",
            "clip": ["1", 1]
        }
    },
    "3": {
        "class_type": "KSampler",
        "inputs": {
            "model": ["1", 0],
            "positive": ["2", 0],
            "negative": ["4", 0],
            "latent_image": ["5", 0],
            "seed": 42,
            "steps": 30,
            "cfg": 7.5,
            "sampler_name": "euler",
            "scheduler": "karras",
            "denoise": 1.0
        }
    }
}

result = queue_prompt(workflow)
print(f"Prompt ID: {result['prompt_id']}")

9.3 ONNX/TensorRT 优化

from diffusers import OnnxStableDiffusionPipeline
import numpy as np

# 用 ONNX 运行时推理(CPU/GPU 均可)
pipe = OnnxStableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    revision="onnx",
    provider="CUDAExecutionProvider",
)

image = pipe("a mountain lake at dawn").images[0]

10. 测验:生成式 AI 与 Diffusion 理解检查

Q1. DDPM 的 Forward Process 使用高斯噪声,数学上的理由是什么?

答案:中心极限定理(CLT)与高斯分布的再生性

解释:使用高斯噪声的理由有三个。第一,高斯分布对加法封闭(两个高斯分布之和仍是高斯分布)。第二,reparameterization trick 使得可以在任意时间步 t 直接采样:xt=αˉtx0+1αˉtϵx_t = \sqrt{\bar{\alpha}_t} x_0 + \sqrt{1-\bar{\alpha}_t} \epsilon。第三,根据中心极限定理,当 T 趋向无穷大时,任何分布都会收敛到标准高斯分布。

Q2. Stable Diffusion 中 U-Net 在潜在空间(latent space)而不是像素空间中运行,理由是什么?

答案:计算效率 + 语义压缩

解释:如果在像素空间(512x512x3)中运行 Diffusion,计算量会急剧膨胀。通过 VAE 压缩到 64x64x4 的潜在空间后,空间维度减少约 48 倍。此外,VAE 的潜在空间承载的是语义特征而不是像素级噪声,因此能用更少的步数生成高质量图像。

Q3. 相比全量权重 fine-tuning,LoRA 为什么更高效?

答案:通过低秩分解,把要更新的参数量降到最低

解释:更新整个权重矩阵 WRd×kW \in \mathbb{R}^{d \times k} 需要 d×kd \times k 个参数。LoRA 把它分解为 ΔW=BA\Delta W = BABRd×rB \in \mathbb{R}^{d \times r}ARr×kA \in \mathbb{R}^{r \times k}rd,kr \ll d, k),只训练 (d+k)×r(d+k) \times r 个参数。当 r=16、d=k=768 时,可以实现约98% 的参数削减。而且由于原始权重是固定的,可以替换不同的 LoRA 来切换风格。

Q4. ControlNet 的架构设计:它是如何接受额外条件输入的?

答案:Trainable copy + Zero convolution

解释:ControlNet 把 SD U-Net 的编码器模块复制出来,构建一个独立的控制网络。核心是zero convolution(初始权重为 0 的 1x1 卷积)——训练初期,控制信号的影响为 0,从而保护原始 SD 的质量。随着训练推进,zero convolution 的权重逐渐增大,控制效果也随之增强。depth map、edge map 等条件图像,会先经过一个独立的编码器处理。

Q5. Consistency Models 为什么能比 DDPM 减少采样步数?其原理是什么?

答案:学习一个能从任意时间步直接映射到原始数据的一致性函数

解释:DDPM 需要把 T=1000 步全部反向走完(即便用 DDIM 缩减,也要 20-50 步)。Consistency Models 学习一个一致性函数 fθ(xt,t)x0f_\theta(x_t, t) \approx x_0。这个函数要求:在同一条「trajectory」上的任意一点 xtx_t,都必须输出相同的 x0x_0(一致性条件)。因此仅需1~2 步就能完成高质量采样,比 DDPM 的 1000 步快 100~500 倍。


结语

Diffusion 模型兼具数学上的优雅与实用中的高性能,是当今生成式 AI 的核心。从 DDPM 的高斯数学,到 Stable Diffusion 的潜在空间、ControlNet 的控制机制、LoRA 的高效训练,再到 Sora 的视频生成——所有这些技术,都建立在同一个优美的数学框架之上。

推荐的下一步学习路径:

  1. 通读 DDPM 论文(Ho et al., 2020)
  2. 动手实践 HuggingFace diffusers 教程
  3. 亲自跑一遍 ControlNet、LoRA fine-tuning
  4. 用 ComfyUI 搭建自定义工作流

현재 단락 (1/298)

2022 年 Stable Diffusion 发布后,图像生成 AI 迎来了大众化的时代。但是,能真正回答「为什么图像会从噪声中浮现出来」这个问题的人却并不多。

작성 글자: 0원문 글자: 12,019작성 단락: 0/298