动手学深度学习 5.12 稠密连接网络(DenseNet)
前言
从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。
《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/
《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch
(由于有时候公式太多,可能会直接贴图片)
ResNet中的跨层连接设计引申出了数个后续工作。本节我们介绍其中的一个:稠密连接网络(DenseNet) [1]。 它与ResNet的主要区别如图5.10所示。

与ResNet的主要区别在于,DenseNet里模块B的输出不是像ResNet那样和模块A的输出相加,而是在通道维上连结。这样模块A的输出可以直接传入模块B后面的层。
DenseNet的主要构建模块是稠密块(dense block)和过渡层(transition layer)。前者定义了输入和输出是如何连结的,后者则用来控制通道数,使之不过大。
1. 稠密块
DenseNet使用了ResNet改良版的“批量归一化、激活和卷积”结构,我们首先在conv_block函数里实现这个结构。
1 | def conv_block(in_channels, out_channels): |
稠密块由多个conv_block组成,每块使用相同的输出通道数。但在前向计算时,我们将每块的输入和输出在通道维上连结。

简单理解模型组成
第一层 net( input , ouput)
第二层 net( input + output , output )
第三层 net( input + 2*output , output )
每层的作为输入的上一层输出都会叠加一次output。前向计算同理。
在下面的例子中,我们定义一个有2个输出通道数为10的卷积块。使用通道数为3的输入时,我们会得到通道数为3+2×10 = 23的输出。卷积块的通道数控制了输出通道数相对于输入通道数的增长,因此也被称为增长率(growth
rate)。
1 | blk = DenseBlock(2, 3, 10) |

2. 过渡层
由于每个稠密块都会带来通道数的增加,使用过多则会带来过于复杂的模型。过渡层用来控制模型复杂度。
它通过1×1卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽,从而进一步降低模型复杂度。
1 | def transition_block(in_channels, out_channels): |
对上一个例子中稠密块的输出使用通道数为10的过渡层。此时输出的通道数减为10,高和宽均减半。
1 | blk = transition_block(23, 10) |

3. DenseNet模型
我们来构造DenseNet模型。DenseNet首先使用同ResNet一样的单卷积层和最大池化层。
1 | net = nn.Sequential( |
类似于ResNet接下来使用的4个残差块,DenseNet使用的是4个稠密块。
同ResNet一样,我们可以设置每个稠密块使用多少个卷积层。这里我们设成4,从而与上一节的ResNet-18保持一致。
稠密块里的卷积层通道数(即增长率)设为32,所以每个稠密块将增加128个通道。
1 | num_channels, growth_rate = 64, 32 # num_channels为当前的通道数 |
同ResNet一样,最后接上全局池化层和全连接层来输出。
1 | net.add_module("BN", nn.BatchNorm2d(num_channels)) |
我们尝试打印每个子模块的输出维度确保网络无误:
1 | X = torch.rand((1, 1, 96, 96)) |

4. 获取数据并训练模型
由于这里使用了比较深的网络,本节里我们将输入高和宽从224降到96来简化计算。
1 | batch_size = 256 |
