🤗 Diffusers UNet2DModel 完全指南:2D UNet 架构、配置参数与扩散系统应用
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
UNet2DModel是 🤗 Diffusers 中最基础也最重要的扩散模型组件之一:它接收带噪样本与时间步,输出与输入同尺寸的去噪预测结果,是图像扩散系统中实际执行去噪过程的骨干网络。本文以 docs/source/en/api/models/unet2d.md 为骨架,结合 unet_2d.py、unet_2d_blocks.py 与 test_models_unet_2d.py 等仓库源码,系统讲解该模型的架构原理、全部构造参数、前向传播流程、块级实现细节及从零训练与加载使用的完整实战方案,读完后你将能够熟练地实例化、定制、加载并训练自己的 2D UNet 扩散骨干。
UNet2DModel 在扩散系统中的定位
UNet(U-Net)最初由 Ronneberger 等人提出,用于生物医学图像分割(对应论文 U-Net: Convolutional Networks for Biomedical Image Segmentation)。它之所以在 🤗 Diffusers 中被广泛采用,关键在于输出图像与输入图像尺寸一致——扩散过程需要网络对任意时刻的带噪样本给出同分辨率的预测,UNet 的对称编码器-解码器结构天然满足这一要求。
在扩散系统中,UNet2DModel负责「实际执行扩散过程」:给定一个带噪样本sample和当前去噪步timestep,网络预测出对应的噪声(或目标样本),调度器(Scheduler)据此逐步去除噪声,最终还原出干净图像。因此它是扩散系统的核心组件之一。
🤗 Diffusers 中的 UNet 家族根据维度数量与是否为条件模型演化出多种变体,从源码 src/diffusers/models/unets 目录可以清晰看到:
| 模型文件 | 维度 / 用途 |
|---|---|
unet_2d.py | 2D 无条件 UNet(本文主角) |
unet_2d_condition.py | 2D 条件 UNet(支持文本/图像交叉注意力,用于 Stable Diffusion 等) |
unet_1d.py | 1D UNet(如音频、价值函数场景) |
unet_3d_condition.py | 3D 条件 UNet(视频扩散) |
unet_motion_model.py | 动画运动模块 |
unet_spatio_temporal_condition.py | 时空条件 UNet(SVD 等视频模型) |
其中UNet2DModel是无条件 2D 模型的代表,也是理解其他变体的最佳起点。
原论文摘要
原文档引用了论文摘要,其核心思想可概括为:网络由一个**收缩路径(contracting path)捕捉上下文语义,配合一个对称扩张路径(expanding path)**实现精确定位,从而在极少量标注样本下即可端到端训练,且在 ISBI 神经结构分割挑战与 2015 ISBI 细胞追踪挑战中大幅超越此前最优的滑窗卷积网络;512×512 图像分割在当时的 GPU 上耗时不足一秒。
架构总览:收缩路径、瓶颈与扩张路径
从 unet_2d.py 的构造函数可以看到,UNet2DModel由以下五大部分拼接而成:
- 输入卷积
conv_in:nn.Conv2d(in_channels, block_out_channels[0], kernel_size=3, padding=1),将输入图像投影到首个下采样块的通道数; - 时间(及可选类别)嵌入:
time_proj+time_embedding,把标量时间步编码为可注入网络的嵌入向量; - 下采样路径
down_blocks:由get_down_block逐块构建,逐级压缩空间分辨率、扩张通道数; - 中间块
mid_block:UNetMidBlock2D,在最低分辨率处做最深的特征处理(可含自注意力); - 上采样路径
up_blocks:由get_up_block逐块构建,结合跳跃连接(skip connection)逐步恢复分辨率,最终经conv_norm_out(GroupNorm)、conv_act(SiLU)与conv_out输出与输入同尺寸的结果。
默认配置下,block_out_channels=(224, 448, 672, 896)意味着网络沿下采样方向将通道从 224 逐级扩张到 896,形成典型的「漏斗形」结构;解码器再镜像式地恢复通道与分辨率,并通过跳跃连接把编码器各层特征拼接到对应解码层,实现精确重建。
UNet2DModel 全部构造参数详解
UNet2DModel继承自ModelMixin与ConfigMixin,所有构造参数经@register_to_config自动存入self.config(见 unet_2d.py),因此既可用于from_pretrained加载,也可序列化为模型配置文件。下面按功能分组讲解全部参数(默认值与语义均取自源码 docstring)。
输入输出尺寸
| 参数 | 默认值 | 说明 |
|---|---|---|
sample_size | None | 输入/输出样本的高宽(int 或(h, w)元组)。注意维度必须是2 ** (len(block_out_channels) - 1)的整数倍,否则下采样次数无法整除分辨率。 |
in_channels | 3 | 输入样本通道数,RGB 图像为 3,潜空间训练时为 4(见下文 LDM 配置)。 |
out_channels | 3 | 输出通道数。 |
center_input_sample | False | 是否将输入样本中心化到 [-1, 1]。开启时forward第一步执行sample = 2 * sample - 1.0。 |
时间嵌入
| 参数 | 默认值 | 说明 |
|---|---|---|
time_embedding_type | "positional" | 时间嵌入类型,可选"positional"(正弦位置编码,Timesteps)、"fourier"(高斯傅里叶投影GaussianFourierProjection,NCSN++ 使用)、"learned"(可学习的nn.Embedding,需配合num_train_timesteps)。 |
time_embedding_dim | None | 时间嵌入维度,默认取block_out_channels[0] * 4。 |
freq_shift | 0 | 傅里叶/位置时间嵌入的频率偏移。 |
flip_sin_to_cos | True | 是否将正弦位置编码翻转为 cos。 |
从源码 unet_2d.py 可见三种嵌入的具体实现:fourier时timestep_input_dim = 2 * block_out_channels[0];positional与learned时输入维度为block_out_channels[0]。嵌入经TimestepEmbedding投影到time_embed_dim后注入各 ResNet/注意力块。
块结构与通道配置(网络骨架)
| 参数 | 默认值 | 说明 |
|---|---|---|
down_block_types | ("DownBlock2D", "AttnDownBlock2D", "AttnDownBlock2D", "AttnDownBlock2D") | 各下采样块类型构成的元组。 |
mid_block_type | "UNetMidBlock2D" | 中间块类型,仅支持UNetMidBlock2D或None(去掉中间块)。 |
up_block_types | ("AttnUpBlock2D", "AttnUpBlock2D", "AttnUpBlock2D", "UpBlock2D") | 各上采样块类型构成的元组。 |
block_out_channels | (224, 448, 672, 896) | 每个块的输出通道数,长度必须等于down_block_types/up_block_types的长度。 |
layers_per_block | 2 | 每个块内包含的 ResNet 层数(上采样块实际为layers_per_block + 1层,见 unet_2d.py)。 |
mid_block_scale_factor | 1 | 中间块的输出缩放因子,NCSN++ 配置中设为sqrt(2)。 |
downsample_padding | 1 | 下采样卷积的 padding 值。 |
downsample_type | "conv" | 下采样方式,可选"conv"或"resnet"。 |
upsample_type | "conv" | 上采样方式,可选"conv"或"resnet"。 |
add_attention | True | 是否在中间块中加入注意力层。 |
构造函数中有两条显式校验:down_block_types与up_block_types长度必须一致;block_out_channels长度必须与down_block_types一致,否则抛出ValueError(见 unet_2d.py)。
归一化、激活与正则
| 参数 | 默认值 | 说明 |
|---|---|---|
dropout | 0.0 | 各块中的 dropout 概率。 |
act_fn | "silu" | ResNet 块激活函数,如silu、swish、mish等。 |
attention_head_dim | 8 | 单个注意力头的维度;置None时回退为输出通道数。中间块按in_channels // attention_head_dim计算注意力头数。 |
norm_num_groups | 32 | ResNet 块 GroupNorm 的组数;置None时输出层回退为min(block_out_channels[0] // 4, 32)。 |
attn_norm_num_groups | None | 中间块注意力层的 GroupNorm 组数;为None时仅当resnet_time_scale_shift="default"才创建该层,并使用norm_num_groups。 |
norm_eps | 1e-5 | 归一化的 epsilon。 |
resnet_time_scale_shift | "default" | ResNet 块的时间尺度偏移方式,可选"default"或"scale_shift"(对应ResnetBlock2D的time_embedding_norm)。 |
类别条件(class conditioning)
| 参数 | 默认值 | 说明 |
|---|---|---|
class_embed_type | None | 类别嵌入类型,可选None、"timestep"或"identity",其嵌入最终与时间嵌入相加。 |
num_class_embeds | None | 可学习类别嵌入矩阵的输入维度(当class_embed_type=None且类别数非空时,创建nn.Embedding(num_class_embeds, time_embed_dim))。 |
num_train_timesteps | None | 训练时间步总数,time_embedding_type="learned"时作为nn.Embedding的第一维。 |
类别条件的运行时行为见 unet_2d.py:若模型配置了类别嵌入而forward未传class_labels,或未配置嵌入却传了class_labels,都会抛出ValueError;class_embed_type="timestep"时类别标签先经time_proj投影再嵌入。
前向传播(forward)完整流程
forward(sample, timestep, class_labels=None, return_dict=True)的执行逻辑在 unet_2d.py 中分为六个阶段:
- 输入中心化:若
center_input_sample=True,执行sample = 2 * sample - 1.0; - 时间编码:把标量/张量形式的
timestep规整为批量张量,广播到sample.shape[0]维度(该写法兼容 ONNX/Core ML 导出),经time_proj与time_embedding得到emb,并转为模型当前 dtype(如 fp16);若启用类别条件则把class_emb与emb相加; - 预处理:保存
skip_sample = sample用于跳跃连接,然后conv_in投影通道; - 下采样:逐块执行
down_block(hidden_states=sample, temb=emb),收集每块的残差样本res_samples存入down_block_res_samples;对于带skip_conv的 Skip 系列块,还会同步更新skip_sample; - 中间块:
mid_block(sample, emb),在最低分辨率做最深层处理; - 上采样与后处理:每个上采样块从
down_block_res_samples尾部取出对应数量的残差样本做跳跃连接拼接;最后依次经过conv_norm_out(GroupNorm)→conv_act(SiLU)→conv_out,若存在skip_sample则加到输出上;当time_embedding_type="fourier"时,还会把输出除以重塑后的timesteps。
return_dict=False时返回普通元组(sample,),否则返回UNet2DOutput。
输出结构 UNet2DOutput
UNet2DOutput是一个@dataclass,继承自BaseOutput(见 unet_2d.py),包含唯一字段:
sample:torch.Tensor,形状(batch_size, num_channels, height, width),即最后一层输出的隐藏状态。
使用时既可以通过output.sample访问,也可以像元组一样索引,例如测试中的model(noise, timestep).sample。
块级实现:unet_2d_blocks 中的块类型
UNet2DModel本身不定义卷积块,而是通过工厂函数get_down_block/get_up_block与UNetMidBlock2D组装(见 unet_2d_blocks.py)。源码中支持的 2D 块类型包括:
- 普通 ResNet 块:
DownBlock2D/UpBlock2D(纯 ResNet + 下/上采样); - 带自注意力块:
AttnDownBlock2D/AttnUpBlock2D(ResNet + 空间自注意力); - 跨注意力块:
CrossAttnDownBlock2D/CrossAttnUpBlock2D(用于条件模型的 Transformer 注意力,构造时必须提供cross_attention_dim,否则报错); - Skip 跳跃块:
SkipDownBlock2D/AttnSkipDownBlock2D/SkipUpBlock2D/AttnSkipUpBlock2D(带skip_conv,与 NCSN++ 深度特征保持一致); - 编解码器块:
DownEncoderBlock2D/AttnDownEncoderBlock2D/UpDecoderBlock2D/AttnUpDecoderBlock2D; - K 系列块:
KDownBlock2D/KCrossAttnDownBlock2D/KUpBlock2D/KCrossAttnUpBlock2D; - ResNet 采样块:
ResnetDownsampleBlock2D/ResnetUpsampleBlock2D。
UNetMidBlock2D(unet_2d_blocks.py)内部由resnets与attentions两个ModuleList交替堆叠:先过一个 ResNet,再循环执行「注意力 → ResNet」。当resnet_time_scale_shift="spatial"时改用ResnetBlockCondNorm2D做空间条件归一化;注意力层基于Attention实现,带残差连接、bias 与upcast_softmax=True,并支持梯度检查点(gradient_checkpointing)。
实战一:加载预训练权重与推理
UNet2DModel继承ModelMixin,支持from_pretrained/save_pretrained全套接口。仓库测试 test_models_unet_2d.py 给出了可验证的加载与推理范式(如TestUNetLDMModel):
import torch from diffusers import UNet2DModel # 从 Hub 加载预训练 UNet(LDM 风格 4 通道潜空间模型) model = UNet2DModel.from_pretrained("fusing/unet-ldm-dummy-update") model.eval() noise = torch.randn(1, model.config.in_channels, model.config.sample_size, model.config.sample_size) timestep = torch.tensor([10] * noise.shape[0]) with torch.no_grad(): output = model(noise, timestep).sample print(output.shape) # torch.Size([1, 4, 32, 32]),与输入同尺寸from_pretrained默认走accelerate的low_cpu_mem_usage=True路径以节省内存,测试test_from_pretrained_accelerate_wont_change_results验证了该加载方式与常规加载的结果在rtol=1e-3内一致。测试还对比了输出切片与参考张量(test_output_pretrained),可用于校验本地实现是否正确。
除了独立使用,UNet2DModel也被用作其他模型的子模块:例如 consistency_decoder_vae.py 中,一致性解码器 VAE 的decoder_unet就是一个UNet2DModel。
实战二:从零训练一个 UNet2DModel
官方案例:蝴蝶生成
官方教程 basic_training.md 展示了在 Smithsonian 蝴蝶数据集子集上从零训练UNet2DModel的经典配置:
from diffusers import UNet2DModel model = UNet2DModel( sample_size=config.image_size, # 目标图像分辨率 in_channels=3, # RGB 图像为 3 out_channels=3, layers_per_block=2, # 每个 UNet 块内的 ResNet 层数 block_out_channels=(128, 128, 256, 256, 512, 512), down_block_types=( "DownBlock2D", # 普通 ResNet 下采样块 "DownBlock2D", "DownBlock2D", "DownBlock2D", "AttnDownBlock2D", # 带空间自注意力的下采样块 "DownBlock2D", ), up_block_types=( "UpBlock2D", "AttnUpBlock2D", # 带空间自注意力的上采样块 "UpBlock2D", "UpBlock2D", "UpBlock2D", "UpBlock2D", ), )训练前建议先核对输入输出形状一致:
sample_image = dataset[0]["images"].unsqueeze(0) print("Input shape:", sample_image.shape) # [1, 3, 128, 128] print("Output shape:", model(sample_image, timestep=0).sample.shape) # [1, 3, 128, 128]随后配合DDPMScheduler加噪并计算损失:noise_pred = model(noisy_image, timesteps).sample,损失为noise_pred与真实噪声的 MSE。推理阶段则由调度器从纯噪声出发逐步调用模型去噪。
训练脚本中的默认模型
examples/unconditional_image_generation/train_unconditional.py 在未提供--model_config_name_or_path时,以args.resolution为分辨率、采用与教程一致的(128, 128, 256, 256, 512, 512)通道配置和「5 个 DownBlock2D + 1 个 AttnDownBlock2D / 1 个 AttnUpBlock2D + 5 个 UpBlock2D」组合初始化模型——这是官方无条件图像生成训练的默认骨干,可直接参考该脚本的完整训练管线(含 accelerate、EMA、checkpoint 保存与 Hub 推送)。
三种典型配置对照(来自测试)
仓库测试 test_models_unet_2d.py 覆盖了UNet2DModel的三种代表性配置,可作为定制网络的设计参考:
| 配置类 | 特点 | 关键参数 |
|---|---|---|
Unet2DModelTesterConfig | 通用小模型 | block_out_channels=(4, 8),("DownBlock2D", "AttnDownBlock2D")对称结构 |
UNetLDMModelTesterConfig | LDM 潜空间 | in_channels=4, out_channels=4,全DownBlock2D/UpBlock2D无注意力 |
NCSNppModelTesterConfig | 分数匹配(NCSN++) | time_embedding_type="fourier",SkipDownBlock2D/AttnSkipDownBlock2D等 Skip 系列块,norm_num_groups=None,mid_block_scale_factor=sqrt(2) |
其中 NCSN++ 配置对应预训练模型google/ncsnpp-celebahq-256(256×256 分辨率),测试验证了其输出切片的数值正确性。三种配置的训练测试还分别断言了梯度检查点所覆盖的块集合(如通用配置为{"AttnUpBlock2D", "AttnDownBlock2D", "UNetMidBlock2D", "UpBlock2D", "DownBlock2D"}),说明梯度检查点、内存优化(MemoryTesterMixin)等能力对UNet2DModel均开箱可用。
常见问题与约束
- 分辨率约束:
sample_size必须是2 ** (len(block_out_channels) - 1)的整数倍,否则下采样阶段的特征图尺寸无法被整除,导致上采样无法精确恢复; - 块数量一致性:
down_block_types、up_block_types、block_out_channels三者长度必须对齐,构造函数会直接抛ValueError; - 类别条件配套使用:配置了
num_class_embeds后必须同时传class_labels,反之亦然; - 注意力头维度:
attention_head_dim=None时中间块会回退到in_channels,但源码建议显式指定以避免歧义; - 潜空间 vs 像素空间:直接生成图像用
in_channels=3;在 VAE 潜空间训练(如 LDM)时用in_channels=4。
小结
UNet2DModel是 🤗 Diffusers 中 2D 无条件扩散骨干的标准实现:它以 U-Net 的收缩-扩张对称结构为核心,通过down_block_types/up_block_types/block_out_channels等参数即可灵活定制网络形态,并完整支持时间嵌入(positional / fourier / learned)、类别条件、梯度检查点、内存优化与from_pretrained生态。无论是从零训练无条件生成模型(参考 train_unconditional.py),还是作为潜空间去噪网络嵌入更大的系统,理解本文所述参数与前向流程都是使用 🤗 Diffusers 构建扩散应用的第一步。
【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考