#稠密连接网络
如图所示,ResNet和DenseNet的关键区别在于,DenseNet输出是连接(用图中的[,]表示)而不是如ResNet的简单相加。
def conv_block(input_channels, num_channels):
return nn.Sequential(
nn.BatchNorm2d(input_channels), nn.ReLU(), # 这是卷积块的第一部分,包括批量归一化层(nn.BatchNorm2d)和ReLU激活函数。
nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1)) # 这是卷积块的第二部分,包括一个卷积层(nn.Conv2d)。卷积层用于对特征图进行卷积操作,其中参数包括输入通道数(input_channels)、输出通道数(num_channels)、卷积核大小(kernel_size,这里是3x3)、以及填充(padding,这里是1)。
一个稠密块由多个卷积块组成,每个卷积块使用相同数量的输出通道。 然而,在前向传播中,我们将每个卷积块的输入和输出在通道维上连结。
class DenseBlock(nn.Module):
def __init__(self, num_convs, input_channels, num_channels): # num_convs:指定了稠密块中包含多少个卷积块,input_channels:输入通道数,表示输入特征图的通道数。num_channels:输出通道数,表示每个卷积块的输出通道数。
super(DenseBlock, self).__init__()
layer = [] # 创建一个名为layer的空列表,用于存储稠密块中的每个卷积块。
for i in range(num_convs):
layer.append(conv_block(
num_channels * i + input_channels, num_channels)) # 每个卷积块使用conv_block函数来创建,其中输入通道数是num_channels * i + input_channels,其中i表示当前卷积块的索引。初始时,i为0,因此输入通道数为input_channels。
self.net = nn.Sequential(*layer) # 在这里,我们使用nn.Sequential容器将所有卷积块组合在一起,形成一个包含多个卷积块的子网络。这个子网络被存储在self.net中。
def forward(self, X):
for blk in self.net:
Y = blk(X) #
# 连接通道维度上每个块的输入和输出
X = torch.cat((X, Y), dim=1)
return X # 在前向传播过程中,我们遍历稠密块中的每个卷积块(blk),将输入X通过卷积块 blk 进行前向传播,得到输出 Y。然后,我们将输入 X 和输出 Y 在通道维度上连接起来,形成新的输入 X,以便在下一个卷积块中使用。这个过程重复了num_convs次。
稠密块没有改变图像的空间尺寸(高度和宽度),但增加了输出通道的数量,使每个卷积块的输出被连接到了最终输出。这种设计使得每个卷积块都能访问之前卷积块的输出,从而加强了特征的重用和模型的深度。
由于每个稠密块都会带来通道数的增加,使用过多则会过于复杂化模型。 而过渡层可以用来控制模型复杂度。 它通过1×1卷积层来减小通道数,并使用步幅为2的平均汇聚层减半高和宽,从而进一步降低模型复杂度。