Part 8 DenseNet
约 736 字大约 2 分钟
2025-08-05
1 稠密块与 DenseNet 模型
ResNet 将函数展开为
f(x)=x+g(x)
更进一步,我们可以用泰勒公式将任何一个函数展开为超过两项的表达式
f(x)=f(0)+f′(0)x+2!f′′(0)x2+3!f′′′(0)x3+⋯
这就是 DenseNet 的思想。DenseNet 的输出是x展开式的连接,而非 ResNet 的相加。
x→[x,f1(x),f2([x,f1(x)]),f3([x,f1(x),f2([x,f1(x)])]),⋯]
最后将这些展开式结合到多层感知机中,再次减少特征的数量。DenseNet 这个名字由变量之间的“稠密连接”而得来,最后一层与之前的所有层紧密相连。
一个稠密块由多个卷积块构成,每个卷积块使用相同数量的输出通道。然后将输入通道和输出通道连接,作为稠密块的整体输出。因此稠密块的最终输出通道数量为输入通道数量和我们设置的输出通道数量之和。
卷积块的通道数控制了输出通道数相对于输入通道数的增长,因此也被称为增长率。
由于每个稠密块都会带来通道数的增加,使用过多会过于复杂化模型。过渡层可以用来控制模型复杂度,它通过 1×1 卷积层来减小通道数,并使用步幅为 2 的平均池化层减半尺寸,从而进一步降低模型复杂度。
2 DenseNet 的 PyTorch 实现
我们先来实现稠密块和过渡层:
def conv_block(input_channels, num_channels):
return nn.Sequential(
nn.BatchNorm2d(input_channels),
nn.ReLU(),
nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1),
)
class DenseBlock(nn.Module):
def __init__(self, num_convs, input_channels, num_channels):
super(DenseBlock, self).__init__()
layer = []
for i in range(num_convs):
layer.append(
conv_block(num_channels * i + input_channels, num_channels)
)
self.net = nn.Sequential(*layer)
def forward(self, X):
for blk in self.net:
Y = blk(X)
# 连接通道维度上每个块的输入和输出
X = torch.cat((X, Y), dim=1)
return X
def transition_block(input_channels, num_channels):
return nn.Sequential(
nn.BatchNorm2d(input_channels),
nn.ReLU(),
nn.Conv2d(input_channels, num_channels, kernel_size=1),
nn.AvgPool2d(kernel_size=2, stride=2),
)
与 ResNet 相同,DenseNet 的第一模块也使用相同的 64 通道的 7×7 卷积层和 3×3 的最大池化层。
b1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
)
接下来是四个稠密块,每个稠密块中有 4 个卷积层,增长率设为 32,即每个稠密块增加 128 个通道。
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
blks = []
for i, num_convs in enumerate(num_convs_in_dense_blocks):
blks.append(DenseBlock(num_convs, num_channels, growth_rate))
# 上一个稠密块的输出通道数
num_channels += num_convs * growth_rate
# 在稠密块之间添加一个转换层,使通道数量减半
if i != len(num_convs_in_dense_blocks) - 1:
blks.append(transition_block(num_channels, num_channels // 2))
num_channels = num_channels // 2
最后加上全局池化层和全连接层:
net = nn.Sequential(
b1,
*blks,
nn.BatchNorm2d(num_channels),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(num_channels, 10),
)
将学习率调整为 0.1,其余训练流程不变。