一、 Diffusion Model and GAN

简介

Diffusion Model(扩散模型)和 GAN(生成对抗网络)是当今生成式人工智能领域两大最主流的模型。它们的目标相同——生成高质量的数据(如图像、音频),但实现路径和特性却截然不同。

下图清晰地对比了两者在工作原理上的核心差异:
在这里插入图片描述

核心原理对比

GAN:

  • 核心角色
    • 生成器:一个“伪造者”,接收随机噪声,目标是生成以假乱真的图像来欺骗判别器。
    • 判别器:一个“鉴定专家”,接收真实图像和生成器生成的图像,目标是准确区分它们是“真实的”还是“伪造的”。
  • 训练过程:如上图所示,这是一个对抗性的最小最大博弈。生成器和判别器在动态竞争中共同进化,直到生成器能产生足以骗过判别器的高质量图像。

Diffusion Model:一个“渐进去噪”的过程

  • 核心过程
    1. 前向扩散:在训练阶段,对一张真实图像逐步添加高斯噪声,经过数百步后,最终将其完全破坏成一个纯粹的随机噪声。这个过程是固定的,无需学习。
    2. 反向去噪:如上图所示,模型(通常是一个U-Net)需要学习如何将这个过程逆转。它从随机噪声开始,一步步地、循序渐进地预测并去除噪声,最终还原出一张清晰的图像。

优劣对比

特性 GAN Diffusion Model
训练稳定性 。著名的“模式崩溃”问题,即生成器只学会生成有限的几种样本,多样性不足。训练过程容易失衡。 。训练目标明确且稳定,就是预测噪声。没有对抗博弈,不易崩溃。
生成质量 非常高。在相当长的时间里,GAN生成的图像在清晰度和逼真度上都是标杆。 极高。目前SOTA,尤其在细节、纹理和创造性方面表现惊人,作品往往更具“艺术感”。
生成速度 。前向传播一次,即可生成一张图像。 。需要多次(通常50-1000步)迭代去噪,采样速度慢。
多样性 一般(尤其是有模式崩溃时)。 非常好。能产生非常多样化和富有创造性的结果。
控制性 通过条件GAN、StyleGAN等可以实现较好控制。 极强。非常适合基于文本提示词、草图等条件的生成,是当前文生图主流。

代表模型与应用场景

GAN
  • StyleGAN:由NVIDIA推出,在人脸生成等领域达到惊人效果,能精细控制图像风格。
  • CycleGAN:用于无配对数据的图像到图像的转换(如将马变成斑马)。
  • 应用:人脸生成、图像风格迁移、图像超分辨率、数据增强。
Diffusion Model
  • Stable Diffusion:当前最流行的开源文生图模型,通过在潜在空间进行扩散,大幅降低了计算成本。
  • DALL-E 2/3:OpenAI的文生图模型,核心也是扩散模型。
  • Midjourney:其背后技术也基于扩散模型。
  • 应用文生图、图生图、图像编辑、3D生成、分子结构生成。

总结与关系

模型 优势 劣势
GAN 生成速度快,图像锐利 训练不稳定,多样性可能不足
Diffusion Model 训练稳定,生成质量高,多样性好,控制性强 生成速度慢,计算开销大

发展趋势
Diffusion Model 凭借其卓越的生成质量和训练稳定性,在2022年后成为了生成式AI的主流,尤其是在需要与自然语言紧密结合的创意生成领域。然而,其生成速度慢是亟待解决的瓶颈。因此,当前的研究热点之一就是 “蒸馏”更快的采样器,旨在用更少的步数生成高质量的图像,甚至出现了将 Diffusion 思想与 GAN 结合的研究,取长补短。

简单来说,GAN是闪电般迅速但难以驾驭的赛马,而Diffusion Model则是稳定可靠、精雕细琢的工匠。 目前,后者正推动着AIGC浪潮的蓬勃发展。


二、Diffusion Model 中的Classifier-Free Guidance(CFG)的核心机制。

Classifier-Free Guidance(分类器自由引导)是作用于【从噪声还原到图片】的“去噪过程”中的一种引导技术。

不是一个前向的“图片加噪”过程。

详细解释

为了理解CFG,我们首先需要理解标准的扩散模型在做什么。

1. 标准扩散模型的去噪过程:

在反向扩散(从噪声生成图像)的每一步,模型(U-Net)会接收以下输入:

  • x_t:当前时间步 t 的带噪图像
  • t:当前时间步(时间嵌入)
  • c:条件信息(如文本提示词 “a photo of an astronaut”)

模型的任务是预测噪声 ε,或者直接预测去噪后的图像 x_{t-1}。这个过程是条件性的,因为它依赖于你的文本提示 c

2. Classifier-Free Guidance 的巧妙之处:

CFG的核心思想是:为了更好地遵循条件,我们先要理解“无条件”是什么样子。

它在训练时要求模型同时学习两个任务

  • 条件预测ε_c = model(x_t, t, c) → 给定提示词,图像应该是什么样的。
  • 无条件预测ε_∅ = model(x_t, t, ∅) → 随机的“空”提示(如 [PAD] token),代表“无条件的”或“平均的”图像应该是什么样的。

3. 在采样(生成)时如何工作:

在从噪声生成图像的每一步,CFG并不直接使用条件预测 ε_c。而是计算一个**“引导后的预测噪声”**:

ε_guided = ε_∅ + guidance_scale * (ε_c - ε_∅)

让我们来拆解这个公式:

  • (ε_c - ε_∅):这部分是 “条件信息带来的方向” 。它代表了“因为你的提示词,图像应该朝哪个方向改变”。
  • guidance_scale引导尺度,是一个超参数(通常为7.5)。
    • guidance_scale = 1 时,ε_guided = ε_c,就是标准的条件生成。
    • guidance_scale > 1 时,模型会被强烈地推向提示词 c 所描述的方向,远离那种“平均的、模糊的”图像
  • ε_∅ + ...:最终,我们在无条件预测的基础上,加上一个放大的“条件方向”,得到引导后的噪声预测。

一个生动的比喻

想象一位画家(扩散模型)在根据你的描述(提示词 c)作画。

  1. 没有CFG:画家直接画他理解的“宇航员”。
  2. 有CFG
    • 画家首先在脑子里想象一个随机的、平均的、没有任何特点的人(这就是无条件预测 ε_∅)。
    • 然后,他再想象一个具体的宇航员(条件预测 ε_c)。
    • 最后,他画的是:那个普通人 + 一个被放大的“宇航员特质”(比如更明显的宇航服、头盔、太空背景)。

这个“放大特质”的过程,使得最终生成的“宇航员”更像一个宇航员,细节更清晰,与提示词的关联性更强。

总结

过程 作用阶段 目标
前向扩散(加噪) 训练阶段 将真实图像破坏成噪声,为训练提供数据。
反向去噪(图像生成) 推理/生成阶段 从噪声一步步重建图像。
Classifier-Free Guidance 反向去噪过程的每一步 引导去噪的方向,使生成的结果与条件(如文本提示)更匹配,质量更高。

所以,CFG是一个在生成过程中使用的、通过结合条件与无条件预测来增强生成效果的先进技术。它是当今Stable Diffusion、DALL-E 2/3等顶级文生图模型高质量输出的关键所在。


三、 Diffusion Model 中的加噪过程

好的,我们来详细讲解扩散模型中的加噪过程。这个过程在学术上被称为 “前向扩散过程”

首先,请理解一个核心概念:加噪过程是一个固定的、预先定义好的数学过程,它不包含任何需要学习的模型参数。 它发生在训练阶段,其唯一目的是为模型制造训练数据。

核心思想:渐进式破坏

加噪过程的目标是,通过一系列步骤 t = 1, 2, ..., T(T通常是1000步),将一张清晰的原始图像 x₀ 逐步破坏,最终变成一个完全随机的、符合高斯分布的噪声 x_T

这个过程是确定的,意味着对于同一张 x₀,在每一步 t 加噪后的结果 x_t 都是可以精确计算出来的。

数学实现:重参数化技巧

加噪过程最经典的实现方式是使用一个线性插值公式,它巧妙地融合了原始图像和随机噪声。这个过程可以通过下图来直观理解:

flowchart TD
    A[“原始图像 x₀”] --> B[“采样一步噪声 ε<br>从标准高斯分布”]
    C[“从0到T的<br>噪声调度表”] --> D[“根据步骤t<br>计算α_bar_t”]
    
    B & D --> E[“应用加噪公式<br>x_t = √ᾱ_t * x₀ + √(1-ᾱ_t) * ε”]
    E --> F[“第t步的<br>带噪图像 x_t”]

这个流程中的关键因素是 噪声调度表,它决定了在每一步 t 时,图像和噪声的混合比例。具体公式为:

x_t = √ᾱ_t * x₀ + √(1 - ᾱ_t) * ε

让我们来拆解这个公式的每个部分:

  • x₀:原始清晰的图像。
  • ε:从标准高斯分布(均值为0,方差为1)中随机采样的一份噪声。ε ~ N(0, I)
  • ᾱ_t:这是噪声调度表在步骤 t 的值,它是一个介于0和1之间的数。它代表了原始图像信号的权重
    • t=0 时,ᾱ_0 ≈ 1,这意味着 x₀ 的权重为1,噪声权重为0。
    • t=T 时,ᾱ_T ≈ 0,这意味着 x₀ 的权重为0,噪声权重为1。
  • √ᾱ_t√(1 - ᾱ_t):分别是原始图像和噪声的系数。它们的平方和为1,这是为了保持 x_t 的方差稳定。

这个公式的妙处在于: 它允许我们一步到位地计算出任何步骤 t 的加噪结果 x_t,而无需真正地从第1步、第2步……一步步地模拟过来。这极大地提高了训练数据准备的效率。

一个具体的数值例子

假设:

  • 原始图像 x₀ 的某个像素值为 1.0
  • 随机噪声 ε 的对应值为 0.5
  • 在步骤 t=500 时,调度表给出 ᾱ_500 = 0.4

那么,在 t=500 步的加噪结果计算如下:
x_500 = √0.4 * 1.0 + √(1-0.4) * 0.5
x_500 = 0.632 * 1.0 + 0.775 * 0.5
x_500 = 0.632 + 0.387 = 1.019

可以看到,原始的像素值 1.0 被噪声 0.5 “污染”了,变成了 1.019

加噪过程的最终目的

你可能会问,这个看似破坏性的过程有什么用?它的目的是为去噪模型制造训练样本。

在训练时,我们会:

  1. 随机选择一个时间步 t(例如,在1到1000之间随机选一个数)。
  2. 对一张训练图像 x₀ 应用加噪公式,得到 x_t
  3. (x_t, t, ε) 作为一个训练样本喂给模型(U-Net)。
  4. 模型的任务:根据带噪图像 x_t 和时间步 t,去预测我们当初加入的噪声 ε

通过在海量数据上重复这个过程,模型就学会了如何从任何程度的噪声中,一步步地还原出清晰的图像。而这个“还原”的过程,就是神奇的图像生成过程。

总结

特性 描述
阶段 训练阶段
目的 为去噪模型制造训练数据 (x_t, t, ε)
可学习性 ,这是一个固定的数学公式
关键公式 x_t = √ᾱ_t * x₀ + √(1 - ᾱ_t) * ε
关键超参数 噪声调度表(决定了 ᾱ_t 如何从1衰减到0)

简单来说,加噪过程就是一位“老师”,它负责制造出各种难度的“考题”(带噪图像 x_t),而模型则是“学生”,通过学习来练习如何解这些题(预测噪声 ε)。 当这个学生学成之后,我们就可以给它一份纯粹的噪声(x_T),让它自己一步步地“解”出一张全新的图像。


四、Diffusion Model 中的反向去噪过程核心 U-Net

在扩散模型中,反向去噪过程的核心是一个名为 U-Net 的神经网络

这个U-Net并不是凭空产生的,它是一个在计算机视觉领域,尤其是在图像分割任务中久经考验的经典架构。扩散模型(如Stable Diffusion、DALL-E)对其进行了精心的改造和利用。

核心网络:U-Net

在这里插入图片描述

我们可以通过上图来理解这个U-Net在反向去噪过程中的工作原理:

U-Net 在扩散模型中的关键设计

为了完成去噪任务,这个U-Net具备以下几个关键设计:

1. 输入与输出
  • 输入
    • 带噪图像 x_t:当前时间步的噪声图片。
    • 时间步 t:告诉模型当前处于去噪过程的哪一步。这是通过正弦位置编码可学习的位置嵌入来实现的,然后被投影并融入到网络的各个部分(例如,通过加法或注意力机制)。
    • 条件信息 c(可选):对于文生图等任务,这是文本提示词。它通过交叉注意力机制被注入到U-Net中。
  • 输出
    • 预测出的、应该从 x_t 中减去的噪声 ε_θ
    • 或者,直接预测去噪后的图像 x_{t-1}。这两种目标是等价的。
2. 架构特点为何适合去噪
  • 编码器-解码器结构 with 跳跃连接
    • 编码器(下采样):通过卷积和池化层,逐步压缩图像尺寸,增加通道数,以捕捉图像的全局上下文和语义信息(例如,图片中有一个“物体”)。
    • 解码器(上采样):通过转置卷积或插值,逐步恢复图像尺寸和细节,以生成清晰的像素
    • 跳跃连接:这是U-Net的灵魂。它将编码器对应层的特征图直接拼接到解码器。这确保了在恢复细节时,网络不会丢失在编码器中学到的低级特征(如边缘、纹理),从而能生成高质量、高分辨率的输出。
3. 核心组件:注意力机制

现代扩散模型的U-Net通常集成了自注意力层交叉注意力层,通常以 Transformer Block 的形式存在。

  • 自注意力层:让图像的不同区域之间进行信息交互。这使得模型能够理解图像的全局结构,例如,为了正确生成一只猫,它需要知道猫头应该在猫身的上方。
  • 交叉注意力层:这是条件生成的关键。它将文本条件(如 “a cute cat” 的文本嵌入)注入到U-Net中。在解码器的某些层,U-Net的特征会作为 Query,而文本嵌入作为 KeyValue。这使得模型在生成图像时,能够“关注”相关的文本描述,从而确保生成的图片与提示词一致。

总结

特性 描述
网络类型 U-Net,一种编码器-解码器架构,带有跳跃连接。
主要输入 带噪图像 x_t,时间步 t
条件输入 文本提示词(通过交叉注意力机制注入)。
主要输出 预测的噪声 ε_θ
核心能力 多尺度特征融合(得益于跳跃连接)和全局上下文理解(得益于注意力机制)。

可以这样理解:这个U-Net是一个**“智能去噪滤波器”**。它不仅仅是一个简单的滤波器,而是一个能够理解时间步(知道该去除多少噪声)、理解图像内容(知道在生成什么物体)、并能听从文本指令(条件生成)的、极其强大的神经网络。正是它的存在,才使得从随机噪声中一步步“幻化”出逼真图像成为可能。


五、基于PyTorch U-Net实现代码:

import torch
import torch.nn as nn
import torch.nn.functional as F

class DoubleConv(nn.Module):
    """(卷积 => [BN] => ReLU) * 2"""
    def __init__(self, in_channels, out_channels, mid_channels=None):
        super().__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

class Down(nn.Module):
    """下采样模块:最大池化 + DoubleConv"""
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)

class Up(nn.Module):
    """上采样模块"""
    def __init__(self, in_channels, out_channels, bilinear=True):
        super().__init__()
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1, x2):
        x1 = self.up(x1)
        # 计算填充差异
        diffY = x2.size()[2] - x1.size()[2]
        diffX = x2.size()[3] - x1.size()[3]
        
        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                        diffY // 2, diffY - diffY // 2])
        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)

class OutConv(nn.Module):
    """输出卷积层"""
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

    def forward(self, x):
        return self.conv(x)

class UNet(nn.Module):
    """完整的U-Net架构"""
    def __init__(self, n_channels, n_classes, bilinear=True):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear

        # 编码器 (下采样路径)
        self.inc = DoubleConv(n_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        factor = 2 if bilinear else 1
        self.down4 = Down(512, 1024 // factor)
        
        # 解码器 (上采样路径)
        self.up1 = Up(1024, 512 // factor, bilinear)
        self.up2 = Up(512, 256 // factor, bilinear)
        self.up3 = Up(256, 128 // factor, bilinear)
        self.up4 = Up(128, 64, bilinear)
        self.outc = OutConv(64, n_classes)

    def forward(self, x):
        # 编码器
        x1 = self.inc(x)      # 64 channels
        x2 = self.down1(x1)   # 128 channels
        x3 = self.down2(x2)   # 256 channels
        x4 = self.down3(x3)   # 512 channels
        x5 = self.down4(x4)   # 1024 channels
        
        # 解码器 (包含跳跃连接)
        x = self.up1(x5, x4)  # 512 channels
        x = self.up2(x, x3)   # 256 channels
        x = self.up3(x, x2)   # 128 channels
        x = self.up4(x, x1)   # 64 channels
        
        # 输出层
        logits = self.outc(x)
        return logits

# 扩散模型专用的U-Net(包含时间步和条件输入)
class DiffusionUNet(nn.Module):
    """用于扩散模型的U-Net,支持时间步和条件输入"""
    def __init__(self, n_channels, n_classes, time_emb_dim=256, cond_emb_dim=512, bilinear=True):
        super(DiffusionUNet, self).__init__()
        
        # 时间步嵌入
        self.time_mlp = nn.Sequential(
            nn.Linear(time_emb_dim, time_emb_dim),
            nn.SiLU(),
            nn.Linear(time_emb_dim, time_emb_dim)
        )
        
        # 条件嵌入(用于文本提示等)
        self.cond_mlp = nn.Sequential(
            nn.Linear(cond_emb_dim, cond_emb_dim),
            nn.SiLU(),
            nn.Linear(cond_emb_dim, cond_emb_dim)
        )
        
        # 基础的U-Net
        self.unet = UNet(n_channels, n_classes, bilinear)
        
    def forward(self, x, time_emb, cond_emb=None):
        # 处理时间步嵌入
        t = self.time_mlp(time_emb)
        
        # 处理条件嵌入(如果有)
        if cond_emb is not None:
            c = self.cond_mlp(cond_emb)
            # 将条件信息融入到时间步嵌入中
            t = t + c
        
        # 将时间步信息添加到每个特征图中
        # 这里需要将时间步嵌入扩展到与特征图相同的空间维度
        batch_size, channels, height, width = x.shape
        t_expanded = t.unsqueeze(-1).unsqueeze(-1).repeat(1, 1, height, width)
        
        # 将时间步信息与输入连接
        x_with_time = torch.cat([x, t_expanded], dim=1)
        
        # 通过U-Net
        return self.unet(x_with_time)

# 测试代码
if __name__ == "__main__":
    # 基本U-Net测试
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    # 创建基本U-Net
    unet = UNet(n_channels=3, n_classes=1).to(device)
    
    # 创建扩散模型专用的U-Net
    diffusion_unet = DiffusionUNet(
        n_channels=3 + 256,  # 额外通道用于时间步嵌入
        n_classes=3,         # 输出RGB图像
        time_emb_dim=256,
        cond_emb_dim=512
    ).to(device)
    
    # 测试输入
    batch_size = 2
    x = torch.randn(batch_size, 3, 256, 256).to(device)
    time_emb = torch.randn(batch_size, 256).to(device)
    cond_emb = torch.randn(batch_size, 512).to(device)
    
    # 测试基本U-Net
    with torch.no_grad():
        output_basic = unet(x)
        print(f"Basic U-Net input shape: {x.shape}")
        print(f"Basic U-Net output shape: {output_basic.shape}")
        
        # 测试扩散U-Net
        output_diffusion = diffusion_unet(x, time_emb, cond_emb)
        print(f"Diffusion U-Net input shape: {x.shape}")
        print(f"Diffusion U-Net output shape: {output_diffusion.shape}")
    
    # 计算参数量
    def count_parameters(model):
        return sum(p.numel() for p in model.parameters() if p.requires_grad)
    
    print(f"Basic U-Net parameters: {count_parameters(unet):,}")
    print(f"Diffusion U-Net parameters: {count_parameters(diffusion_unet):,}")

这个U-Net实现包含以下关键特性:

主要组件:

  1. DoubleConv:双重卷积模块,包含卷积、批归一化和ReLU激活
  2. Down:下采样模块,使用最大池化减少空间维度
  3. Up:上采样模块,支持双线性插值或转置卷积
  4. OutConv:输出卷积层,将特征映射到目标类别数

架构特点:

  • 编码器-解码器结构:经典的U-Net对称架构
  • 跳跃连接:将编码器的特征与解码器对应层连接,保留空间信息
  • 渐进式下采样/上采样:逐步提取和恢复特征

扩散模型专用版本:

  • 时间步嵌入:将扩散过程的时间步信息融入网络
  • 条件嵌入:支持文本提示等条件输入
  • 灵活的输入输出:适应扩散模型的训练需求

使用示例:

# 基本用法
model = UNet(n_channels=3, n_classes=1)
output = model(input_tensor)

# 扩散模型用法
diffusion_model = DiffusionUNet(n_channels=3+256, n_classes=3)
output = diffusion_model(noisy_image, time_embedding, condition_embedding)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐