DenseNet

介绍

DenseNet,全称为Densely Connected Convolutional Networks,即密集连接卷积网络,是一种深度卷积神经网络模型。它由Gao Huang等人于2017年提出,旨在改善神经网络中的信息流动和梯度的传播。

原理

DenseNet的主要特点是其“密集连接”机制,即每一层的输入是前面所有层输出的并集,而其输出又被直接传递给后面所有层作为输入。这种连接方式使得网络中的每一层都直接与其它层相连,实现了特征的重用,减少了参数数量,并增强了梯度的传播。

具体来说,DenseNet由多个Dense Block组成,每个Dense Block内部包含多个卷积层。在每个Dense Block中,每一层的输入是前面所有层输出的拼接(concat,与ResNet不同,ResNet是将同一通道上的同一个位置的元素相加,而DenseNet则是将同一个高宽在通道上进行拼接而不是相加),输出则直接传递给后续所有的层。此外,DenseNet还采用了过渡层(Transition Layer)来连接不同的Dense Block,过渡层通常包含一个\(1\times1\)的卷积层和一个\(2\times2\)的平均池化层,用于降低特征图的尺寸和通道数。

DenseNet
DenseNet

DenseNet表现优异的原因主要归功于其独特的密集连接机制,这种设计带来了多个优势:

  1. 特征重用:在DenseNet中,每一层的输入都是前面所有层输出的拼接,这意味着每一层都可以直接访问到网络早期层的特征。这种特征重用减少了需要学习的特征数量,使得网络更加参数高效。
  2. 缓解梯度消失问题:由于每一层都直接连接到损失函数,梯度可以直接流回每一层,从而缓解了深度神经网络中常见的梯度消失问题。这有助于加快训练速度并提高训练稳定性。
  3. 多样化的感受野:每一层接收到的输入包含了不同尺度的特征图,这些特征图来自于不同深度的层,因此每一层都能够感知到多样化的感受野,这有助于网络学习更丰富的特征表示。
  4. 正则化:DenseNet的结构本身具有一定的正则化效果,因为每一层都需要从多个不同的特征图中学习有用的信息,这迫使网络学习更加鲁棒的特征。
  5. 参数效率高:由于特征的重用,DenseNet相比其他深度卷积网络模型(如ResNet)具有更少的参数。

网络结构

DenseNet网络结构主要由DenseBlock和Transition组成:

DenseNet

对于每个DenseBlock:

DenseBlock

在DenseBlock中,每个层的特征图大小一致,可以在channel维度上连接,DenseBlock中每一个block中的非线性组合采用的是BN + ReLU + \(3\times3\) Conv的结构。DenseBlock中各个层卷积之后均输出\(k\)个特征图,即得到的特征图的channel数为\(k\)\(k\)在DenseNet被称为growth rate,一般情况下使用较小的\(k\)(比如12),就可以得到比较好的性能。假定一个DenseBlock输入的特征图的通道数为\(k_0\),那么第\(l\)个block输入的特征图的通道数为\(k_0 + k(l-1)\),因此随着层数的增加,就算\(k\)设定的较小,DenseBlock的输入会非常多,不过这是由于特征重用造成的,每个层仅有\(k\)个特征是自己独有的。

由于后面层的输入通道数会非常多,DenseBlock内部可以采用 Bottleneck 层来减少计算量,主要是在原有的结构中添加\(1\times1\) Conv,如图所示,称为DenseNet-B结构,其中\(1\times1\) Conv得到\(4k\)个特征图,作用是降低特征数量,从而提升计算效率。

DenseNet-B

对于Transition层,它主要是连接两个相邻的DenseBlock,并且降低特征图大小,Transition层包括BN、ReLU、一个\(1\times1\)卷积和一个\(2\times2\)的AvgPooling:

DenseNet

Transition层可以起到压缩模型的作用,假定Transition的上接DenseBlock得到的特征图通道数为\(m\),Transition可以产生\(\theta m\)个特征,其中\(\theta\in(0,1]\)是压缩系数。当\(\theta=1\)时,特征个数通过Transition层没有变化;当压缩系数小于1时,这种结构称为Transition-C。对于使用Bottleneck层的DenseBlock结构和压缩系数小于1的Transition组合的结构成为DenseNet-BC

性能

相比于ResNet,DenseNet在更少的参数上实现了更好的性能:

性能对比
性能对比

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class DenseLayer(nn.Module):
def __init__(self, in_channels, growth_rate, bottleneck_size=4):
super().__init__()
self.bn1 = nn.BatchNorm2d(in_channels)
self.conv1 = nn.Conv2d(in_channels, bottleneck_size * growth_rate, kernel_size=1, bias=False)
self.bn2 = nn.BatchNorm2d(bottleneck_size * growth_rate)
self.conv2 = nn.Conv2d(bottleneck_size * growth_rate, growth_rate, kernel_size=3, padding=1, bias=False)

def forward(self, x):
out = self.conv1(F.relu(self.bn1(x)))
out = self.conv2(F.relu(self.bn2(out)))
return torch.cat([x, out], dim=1)

class DenseBlock(nn.Module):
def __init__(self, in_channels, growth_rate, num_layers):
super().__init__()
self.layers = nn.ModuleList()
for i in range(num_layers):
self.layers.append(DenseLayer(in_channels + growth_rate * i, growth_rate))

def forward(self, x):
for layer in self.layers:
x = layer(x)
return x

class TransitionLayer(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.bn = nn.BatchNorm2d(in_channels)
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
self.pool = nn.AvgPool2d(kernel_size=2)

def forward(self, x):
out = self.conv(F.relu(self.bn(x)))
out = self.pool(out)
return out

class DenseNet(nn.Module):
def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_classes=10):
super().__init__()
self.growth_rate = growth_rate
self.num_classes = num_classes

# Initial convolution
self.conv1 = nn.Conv2d(3, growth_rate * 2, kernel_size=3, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(growth_rate * 2)

# Dense blocks and transition layers
self.blocks = nn.ModuleList()
self.transitions = nn.ModuleList()
num_channels = growth_rate * 2
for i, num_layers in enumerate(block_config):
self.blocks.append(DenseBlock(num_channels, growth_rate, num_layers))
num_channels += num_layers * growth_rate
if i != len(block_config) - 1:
self.transitions.append(TransitionLayer(num_channels, num_channels // 2))
num_channels = num_channels // 2

# Final batch norm
self.bn2 = nn.BatchNorm2d(num_channels)

self.flatten = nn.Flatten()

# Linear layer
self.linear = nn.Linear(num_channels, num_classes)

def forward(self, x):
out = self.conv1(x)
out = F.relu(self.bn1(out))

for block, transition in zip(self.blocks, self.transitions):
out = block(out)
out = transition(out)

# Process last dense block
# 因为self.transitions会比self.blocks少一个,zip(self.blocks, self.transitions)无法得到self.blocks中的最后一个元素
out = self.blocks[-1](out)
out = F.relu(self.bn2(out))
out = F.adaptive_avg_pool2d(out, (1, 1))
out = self.flatten(out)
out = self.linear(out)
return out

# Example of creating a DenseNet model
model = DenseNet()

DenseNet
https://blog.shinebook.net/2025/03/15/人工智能/理论基础/深度学习/DenseNet/
作者
X
发布于
2025年3月15日
许可协议