稠密块
深入理解稠密连接网络中的稠密块结构,掌握DenseNet的核心设计思想和实现方法

稠密连接网络 (DenseNet) 在残差网络 (ResNet) 跳跃连接的恒等映射基础上进行了扩展。
1. 稠密块的结构
1.1 稠密连接原理
网络中的每层不仅只接受来自上一层的输入,而将上方所有层的输出全部拼接在一起作为层的输入(而非逐元素相加),这种结构被称为稠密块 (dense block)。

若用 表示原始数据,分别表示从第 1 层到第 层的输出,表示数据在通道维度上的拼接, 表示在 层执行的一系列批量归一化、激活、卷积操作, 表示每层通道数的增长率 (growth rate),则经过稠密连接 (dense connection) 在第层稠密块的输出 为:
1.2 稠密连接的优势
1.3 与残差块的对比
为了限制网络参数规模,避免通道数无限增长,需在两个稠密块之间使用过渡层 (transition layer) 连接。

2. 思路与代码实现
2.1 实现思路
稠密块的实现思路如下:
-
使用 PyTorch,通过继承
nn.Module实现DenseBlock稠密块,并重载__init__()方法自定义模块结构、重载forward()方法自定义数据流向; -
提供
in_channels和layers_num参数,分别作为稠密块的输入通道数和稠密块中堆叠的稠密层数,使用参数growth_rate控制每个稠密层的特征图通道数增量(增长率 ); -
对于每个稠密层:
-
借鉴了 ResNet v2 中的预激活架构,即由 BatchNorm → ReLU → 3×3 卷积层组成;
-
由于需要动态创建并手动执行前向传播的逻辑,使用
nn.ModuleList实例存储每个稠密层,保证输入与通道数等于初始输入的通道数加上之前所有层的增长率累积,输出通道数即为增长率; -
在向前传播中,遍历
**nn.ModuleList**实例中的每个稠密层,在通道维度上将新特征与输入特征拼接。
2.2 代码实现
import torch
from torch import Tensor, nn
class DenseBlock(nn.Module):
def __init__(self, in_channels: int, layers_num: int, growth_rate: int):
"""
稠密块
由多组 “BatchNorm → ReLU → Conv” 结构(稠密层,DenseLayer)组成,每循环一次这样的结构,通道数增长 k。输出结果在通道上完成拼接。
:param in_channels: 输入特征图的通道数
:param layers_num: 堆叠的稠密层 (DenseLayer) 数量
:param growth_rate: 增长率 (k)。每个稠密层输出的新特征图通道数,将与先前层的拼接
"""
super().__init__()
self.growth_rate = growth_rate
# 根据需要堆叠的稠密层数,动态创建
self.dense_layers = nn.ModuleList([
self._get_dense_layer(in_channels + growth_rate * i)
for i in range(layers_num)
])
def _get_dense_layer(self, connected_channels: int) -> nn.Sequential:
"""
返回单个稠密层实例
BatchNorm → ReLU → Conv(3x3)
:param connected_channels: 该稠密层的输入通道数,等于初始输入的通道数加上之前所有层的增长率累积
"""
dense_layer = nn.Sequential(
nn.BatchNorm2d(connected_channels),
nn.ReLU(),
nn.Conv2d(connected_channels, self.growth_rate, kernel_size=3, padding=1, bias=False)
)
return dense_layer
def forward(self, x: Tensor) -> Tensor:
for layer in self.dense_layers:
out = layer(x)
x = torch.cat((x, out), dim=1) # 在通道维度拼接
return x
2.3 模型测试
使用torchinfo库的summary函数执行更便利的输出维度测试:
from torchinfo import summary
model = DenseBlock(in_channels=3, layers_num=2, growth_rate=10)
summary(model, input_size=(4, 3, 8, 8))
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
DenseBlock [4, 23, 8, 8] --
├─ModuleList: 1-1 -- --
│ └─Sequential: 2-1 [4, 10, 8, 8] --
│ │ └─BatchNorm2d: 3-1 [4, 3, 8, 8] 6
│ │ └─ReLU: 3-2 [4, 3, 8, 8] --
│ │ └─Conv2d: 3-3 [4, 10, 8, 8] 270
│ └─Sequential: 2-2 [4, 10, 8, 8] --
│ │ └─BatchNorm2d: 3-4 [4, 13, 8, 8] 26
│ │ └─ReLU: 3-5 [4, 13, 8, 8] --
│ │ └─Conv2d: 3-6 [4, 10, 8, 8] 1,170
==========================================================================================
Total params: 1,472
Trainable params: 1,472
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 0.37
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.07
Params size (MB): 0.01
Estimated Total Size (MB): 0.08
==========================================================================================
使用一个具有 2 层稠密层的稠密块,当输入通道数为 3,增长率为 10 时,输出通道数为 3 + 10×2 = 23。此外,特征图大小保持不变。
总结
本文介绍了稠密连接网络中稠密块的核心概念:
- 稠密连接原理:通过通道拼接而非逐元素相加实现跨层连接
- 设计优势:提高特征复用率、促进梯度流动、减少参数量
- 实现要点:使用 ModuleList 动态创建稠密层,实现特征图的通道拼接
- 过渡层的必要性:防止通道数无限增长,控制网络复杂度
如果这篇记录对你有帮助,可以留下一句回应。

DISCUSSION
讨论与回应
欢迎补充细节、指出问题,或分享与这篇文章有关的经验。
昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。