【深度学习】U-Net系列(一·补):U-Net网络结构深度剖析

🔖 系列导航:[前置知识] → [U-Net架构详解] → [网络结构深度剖析] → [医学图像应用] → [完整实战项目] → [FAQ与调优] → [变体与改进]

📌 关键词:网络结构、特征图变化、参数计算、数据流、Graphviz架构图


1. 前言

本文是 U-Net 架构详解的补充篇,将从数据流动特征图尺寸变化参数量计算等角度深入剖析 U-Net 的每一层结构,帮助读者建立清晰的网络全貌认知。


2. U-Net 完整架构图(Graphviz)

以下是使用 Graphviz DOT 语言绘制的 U-Net 完整架构图,可在支持 Graphviz 的环境中渲染:

digraph UNet {
    // 全局设置
    rankdir=TB;
    node [shape=box, style="rounded,filled", fontname="Arial"];
    edge [fontname="Arial", fontsize=10];

    // 颜色定义
    // 编码器: 蓝色系
    // 解码器: 绿色系
    // 跳跃连接: 橙色虚线
    // 池化/上采样: 红色

    // 输入
    input [label="Input\n572×572×1", fillcolor="#E8E8E8"];

    // 编码器第1层
    subgraph cluster_enc1 {
        label="Encoder Block 1";
        style=dashed;
        color="#4A90D9";
        enc1_conv1 [label="Conv 3×3\n572→570\n1→64", fillcolor="#AED6F1"];
        enc1_bn1 [label="BN + ReLU", fillcolor="#AED6F1"];
        enc1_conv2 [label="Conv 3×3\n570→568\n64→64", fillcolor="#AED6F1"];
        enc1_bn2 [label="BN + ReLU", fillcolor="#AED6F1"];
    }

    // 编码器第2层
    subgraph cluster_enc2 {
        label="Encoder Block 2";
        style=dashed;
        color="#4A90D9";
        pool1 [label="MaxPool 2×2\n568→284", fillcolor="#F1948A"];
        enc2_conv1 [label="Conv 3×3\n284→282\n64→128", fillcolor="#85C1E9"];
        enc2_bn1 [label="BN + ReLU", fillcolor="#85C1E9"];
        enc2_conv2 [label="Conv 3×3\n282→280\n128→128", fillcolor="#85C1E9"];
        enc2_bn2 [label="BN + ReLU", fillcolor="#85C1E9"];
    }

    // 编码器第3层
    subgraph cluster_enc3 {
        label="Encoder Block 3";
        style=dashed;
        color="#4A90D9";
        pool2 [label="MaxPool 2×2\n280→140", fillcolor="#F1948A"];
        enc3_conv1 [label="Conv 3×3\n140→138\n128→256", fillcolor="#5DADE2"];
        enc3_bn1 [label="BN + ReLU", fillcolor="#5DADE2"];
        enc3_conv2 [label="Conv 3×3\n138→136\n256→256", fillcolor="#5DADE2"];
        enc3_bn2 [label="BN + ReLU", fillcolor="#5DADE2"];
    }

    // 编码器第4层
    subgraph cluster_enc4 {
        label="Encoder Block 4";
        style=dashed;
        color="#4A90D9";
        pool3 [label="MaxPool 2×2\n136→68", fillcolor="#F1948A"];
        enc4_conv1 [label="Conv 3×3\n68→66\n256→512", fillcolor="#3498DB"];
        enc4_bn1 [label="BN + ReLU", fillcolor="#3498DB"];
        enc4_conv2 [label="Conv 3×3\n66→64\n512→512", fillcolor="#3498DB"];
        enc4_bn2 [label="BN + ReLU", fillcolor="#3498DB"];
    }

    // 瓶颈层
    subgraph cluster_bottleneck {
        label="Bottleneck";
        style=dashed;
        color="#8E44AD";
        pool4 [label="MaxPool 2×2\n64→32", fillcolor="#F1948A"];
        bn_conv1 [label="Conv 3×3\n32→30\n512→1024", fillcolor="#D7BDE2"];
        bn_bn1 [label="BN + ReLU", fillcolor="#D7BDE2"];
        bn_conv2 [label="Conv 3×3\n30→28\n1024→1024", fillcolor="#D7BDE2"];
        bn_bn2 [label="BN + ReLU", fillcolor="#D7BDE2"];
    }

    // 解码器第1层
    subgraph cluster_dec1 {
        label="Decoder Block 1";
        style=dashed;
        color="#27AE60";
        up1 [label="UpConv 2×2\n28→56\n1024→512", fillcolor="#F5B041"];
        crop1 [label="Crop & Concat\n56×56\n512+512→1024", fillcolor="#FAD7A0"];
        dec1_conv1 [label="Conv 3×3\n56→54\n1024→512", fillcolor="#ABEBC6"];
        dec1_bn1 [label="BN + ReLU", fillcolor="#ABEBC6"];
        dec1_conv2 [label="Conv 3×3\n54→52\n512→512", fillcolor="#ABEBC6"];
        dec1_bn2 [label="BN + ReLU", fillcolor="#ABEBC6"];
    }

    // 解码器第2层
    subgraph cluster_dec2 {
        label="Decoder Block 2";
        style=dashed;
        color="#27AE60";
        up2 [label="UpConv 2×2\n52→104\n512→256", fillcolor="#F5B041"];
        crop2 [label="Crop & Concat\n104×104\n256+256→512", fillcolor="#FAD7A0"];
        dec2_conv1 [label="Conv 3×3\n104→102\n512→256", fillcolor="#82E0AA"];
        dec2_bn1 [label="BN + ReLU", fillcolor="#82E0AA"];
        dec2_conv2 [label="Conv 3×3\n102→100\n256→256", fillcolor="#82E0AA"];
        dec2_bn2 [label="BN + ReLU", fillcolor="#82E0AA"];
    }

    // 解码器第3层
    subgraph cluster_dec3 {
        label="Decoder Block 3";
        style=dashed;
        color="#27AE60";
        up3 [label="UpConv 2×2\n100→200\n256→128", fillcolor="#F5B041"];
        crop3 [label="Crop & Concat\n200×200\n128+128→256", fillcolor="#FAD7A0"];
        dec3_conv1 [label="Conv 3×3\n200→198\n256→128", fillcolor="#58D68D"];
        dec3_bn1 [label="BN + ReLU", fillcolor="#58D68D"];
        dec3_conv2 [label="Conv 3×3\n198→196\n128→128", fillcolor="#58D68D"];
        dec3_bn2 [label="BN + ReLU", fillcolor="#58D68D"];
    }

    // 解码器第4层
    subgraph cluster_dec4 {
        label="Decoder Block 4";
        style=dashed;
        color="#27AE60";
        up4 [label="UpConv 2×2\n196→392\n128→64", fillcolor="#F5B041"];
        crop4 [label="Crop & Concat\n392×392\n64+64→128", fillcolor="#FAD7A0"];
        dec4_conv1 [label="Conv 3×3\n392→390\n128→64", fillcolor="#2ECC71"];
        dec4_bn1 [label="BN + ReLU", fillcolor="#2ECC71"];
        dec4_conv2 [label="Conv 3×3\n390→388\n64→64", fillcolor="#2ECC71"];
        dec4_bn2 [label="BN + ReLU", fillcolor="#2ECC71"];
    }

    // 输出层
    output_conv [label="Conv 1×1\n388×388\n64→n_classes", fillcolor="#E8E8E8"];
    output [label="Output\n388×388×n_classes", fillcolor="#E8E8E8"];

    // 连接 - 编码器
    input -> enc1_conv1;
    enc1_conv1 -> enc1_bn1 -> enc1_conv2 -> enc1_bn2;
    enc1_bn2 -> pool1;
    pool1 -> enc2_conv1 -> enc2_bn1 -> enc2_conv2 -> enc2_bn2;
    enc2_bn2 -> pool2;
    pool2 -> enc3_conv1 -> enc3_bn1 -> enc3_conv2 -> enc3_bn2;
    enc3_bn2 -> pool3;
    pool3 -> enc4_conv1 -> enc4_bn1 -> enc4_conv2 -> enc4_bn2;
    enc4_bn2 -> pool4;

    // 连接 - 瓶颈
    pool4 -> bn_conv1 -> bn_bn1 -> bn_conv2 -> bn_bn2;

    // 连接 - 解码器
    bn_bn2 -> up1 -> crop1;
    crop1 -> dec1_conv1 -> dec1_bn1 -> dec1_conv2 -> dec1_bn2;
    dec1_bn2 -> up2 -> crop2;
    crop2 -> dec2_conv1 -> dec2_bn1 -> dec2_conv2 -> dec2_bn2;
    dec2_bn2 -> up3 -> crop3;
    crop3 -> dec3_conv1 -> dec3_bn1 -> dec3_conv2 -> dec3_bn2;
    dec3_bn2 -> up4 -> crop4;
    crop4 -> dec4_conv1 -> dec4_bn1 -> dec4_conv2 -> dec4_bn2;
    dec4_bn2 -> output_conv -> output;

    // 跳跃连接(橙色虚线)
    enc4_bn2 -> crop1 [style=dashed, color="#E67E22", label="skip"];
    enc3_bn2 -> crop2 [style=dashed, color="#E67E22", label="skip"];
    enc2_bn2 -> crop3 [style=dashed, color="#E67E22", label="skip"];
    enc1_bn2 -> crop4 [style=dashed, color="#E67E22", label="skip"];
}

3. 特征图尺寸变化详解

3.1 原始 U-Net(Valid 卷积)

原始论文使用无填充卷积,每次 3×3 卷积会使尺寸减 2:

阶段 输入尺寸 输出尺寸 通道数
输入 - - 572×572 1
Enc1 Conv×2 572×572 568×568 64
Enc2 Pool + Conv×2 568×568 280×280 128
Enc3 Pool + Conv×2 280×280 136×136 256
Enc4 Pool + Conv×2 136×136 64×64 512
Bottleneck Pool + Conv×2 64×64 28×28 1024
Dec1 UpConv + Conv×2 28×28 52×52 512
Dec2 UpConv + Conv×2 52×52 100×100 256
Dec3 UpConv + Conv×2 100×100 196×196 128
Dec4 UpConv + Conv×2 196×196 388×388 64
输出 Conv 1×1 388×388 388×388 n_classes

3.2 现代 U-Net(Same 卷积)

现代实现使用 padding=1 保持尺寸:

阶段 输入尺寸 输出尺寸 通道数
输入 - 256×256 3
Enc1 256×256 256×256 64
Enc2 128×128 128×128 128
Enc3 64×64 64×64 256
Enc4 32×32 32×32 512
Bottleneck 16×16 16×16 1024
Dec1 32×32 32×32 512
Dec2 64×64 64×64 256
Dec3 128×128 128×128 128
Dec4 256×256 256×256 64
输出 256×256 256×256 n_classes

4. 卷积块结构详解

4.1 双卷积块(DoubleConv)

每个编码器/解码器阶段都包含两次卷积:

Double Convolution Block

Input
H×W×C_in

Conv 3×3

BatchNorm

ReLU

Conv 3×3

BatchNorm

ReLU

Output
H×W×C_out

4.2 参数量计算

单个 3×3 卷积层参数

Params = C i n × C o u t × k 2 + C o u t \text{Params} = C_{in} \times C_{out} \times k^2 + C_{out} Params=Cin×Cout×k2+Cout

其中 k = 3 k=3 k=3 为卷积核大小, C o u t C_{out} Cout 为偏置项(若使用 BN 则无偏置)。

DoubleConv 参数量(无偏置):

Params d o u b l e = C i n × C m i d × 9 + C m i d × C o u t × 9 \text{Params}_{double} = C_{in} \times C_{mid} \times 9 + C_{mid} \times C_{out} \times 9 Paramsdouble=Cin×Cmid×9+Cmid×Cout×9

4.3 各层参数量统计

模块 输入通道 输出通道 参数量
Enc1 1 64 38,720
Enc2 64 128 221,440
Enc3 128 256 885,248
Enc4 256 512 3,539,968
Bottleneck 512 1024 14,158,848
Dec1 1024 512 7,079,424
Dec2 512 256 1,769,984
Dec3 256 128 442,624
Dec4 128 64 110,720
Output 64 2 130
总计 - - ~31M

5. 跳跃连接机制详解

5.1 为什么需要跳跃连接

没有跳跃连接的问题

下采样

空间信息丢失

上采样无法恢复细节

分割边界模糊

5.2 跳跃连接工作原理

跳跃连接详解

编码器特征
H_e × W_e × C

中心裁剪

Concatenate

解码器特征
H_d × W_d × C

融合特征
H_d × W_d × 2C

5.3 裁剪操作(原始 U-Net)

由于 valid 卷积导致编码器特征图更大,需要中心裁剪对齐:

offset = H e n c − H d e c 2 \text{offset} = \frac{H_{enc} - H_{dec}}{2} offset=2HencHdec

F c r o p p e d = F e n c [ offset : offset + H d e c , offset : offset + W d e c ] F_{cropped} = F_{enc}[\text{offset}:\text{offset}+H_{dec}, \text{offset}:\text{offset}+W_{dec}] Fcropped=Fenc[offset:offset+Hdec,offset:offset+Wdec]

5.4 跳跃连接对比

方法 操作 特点
U-Net Concatenate 保留完整信息,通道数翻倍
ResNet Add 参数少,需通道数相同
DenseNet 密集Concat 特征复用最充分

6. 上采样层详解

6.1 转置卷积(原始 U-Net)

转置卷积 2×2, stride=2

输入
H × W × C

转置卷积

输出
2H × 2W × C/2

转置卷积参数

Params = C i n × C o u t × k 2 = 1024 × 512 × 4 = 2 , 097 , 152 \text{Params} = C_{in} \times C_{out} \times k^2 = 1024 \times 512 \times 4 = 2,097,152 Params=Cin×Cout×k2=1024×512×4=2,097,152

6.2 双线性插值 + 卷积(现代实现)

双线性上采样

输入
H × W × C

Bilinear ×2

2H × 2W × C

Conv 1×1

输出
2H × 2W × C/2

6.3 两种方法对比

方法 参数量 计算量 效果
转置卷积 可学习,可能棋盘效应
双线性+卷积 平滑,无棋盘效应

7. 数据流完整追踪

7.1 前向传播流程

以输入 256×256×1 的灰度图像为例(same padding):

前向传播数据流

输入 256×256×1

Enc1: 256×256×64

Pool: 128×128×64

Enc2: 128×128×128

Pool: 64×64×128

Enc3: 64×64×256

Pool: 32×32×256

Enc4: 32×32×512

Pool: 16×16×512

Bottleneck: 16×16×1024

Up: 32×32×512

Cat: 32×32×1024

Dec1: 32×32×512

Up: 64×64×256

Cat: 64×64×512

Dec2: 64×64×256

Up: 128×128×128

Cat: 128×128×256

Dec3: 128×128×128

Up: 256×256×64

Cat: 256×256×128

Dec4: 256×256×64

输出: 256×256×n_classes

7.2 显存占用估算

阶段 特征图大小 显存占用(float32, batch=1)
Enc1 256×256×64 16 MB
Enc2 128×128×128 8 MB
Enc3 64×64×256 4 MB
Enc4 32×32×512 2 MB
Bottleneck 16×16×1024 1 MB
前向总计 - ~50 MB
训练(含梯度) - ~150 MB

8. 感受野分析

8.1 什么是感受野

感受野(Receptive Field):输出特征图上一个像素对应输入图像的区域大小。

8.2 U-Net 感受野计算

每层感受野递推公式:

R F l = R F l − 1 + ( k l − 1 ) × ∏ i = 1 l − 1 s i RF_l = RF_{l-1} + (k_l - 1) \times \prod_{i=1}^{l-1} s_i RFl=RFl1+(kl1)×i=1l1si

感受野大小
Enc1 Conv2 5×5
Enc2 Conv2 14×14
Enc3 Conv2 32×32
Enc4 Conv2 68×68
Bottleneck 140×140

8.3 感受野的意义

感受野与分割

小感受野

捕获局部纹理

大感受野

捕获全局上下文

多尺度融合

精确分割


9. 计算复杂度分析

9.1 FLOPs 计算

单个卷积层 FLOPs:

FLOPs = 2 × H o u t × W o u t × C i n × C o u t × k 2 \text{FLOPs} = 2 \times H_{out} \times W_{out} \times C_{in} \times C_{out} \times k^2 FLOPs=2×Hout×Wout×Cin×Cout×k2

9.2 各层 FLOPs 统计(256×256 输入)

模块 FLOPs
Enc1 1.2G
Enc2 1.5G
Enc3 1.5G
Enc4 1.5G
Bottleneck 1.5G
Dec1 1.5G
Dec2 1.5G
Dec3 1.5G
Dec4 1.2G
总计 ~13G

10. 总结

10.1 U-Net 结构要点

组件 结构 作用
编码器 4个下采样阶段 提取多尺度特征
瓶颈层 最深层1024通道 高级语义特征
解码器 4个上采样阶段 恢复空间分辨率
跳跃连接 4条 Concat 融合细节信息

10.2 关键数值

指标 数值
参数量 ~31M
FLOPs (256×256) ~13G
最大感受野 140×140
下采样倍数 16×

📚 下一篇【深度学习】U-Net系列(二):U-Net在医学图像分割中的应用

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐