Contents
- 1 Introduction
- 2 稠密块
- 3 过渡层
- 4 DenseNet模型
- 5 获取数据并训练
1 Introduction
与ResNet的主要区别在于,DenseNet里模块B的输出不是像ResNet那样和模块A的输出相加,而是在通道维上连结。这样模块A的输出可以直接传入模块B后面的层。在这个设计里,模块A直接跟模块B后面的所有层连接在了一起。这也是它被称为“稠密连接”的原因。
如果用公式表示的话,传统的网络在 [公式] 层的输出为:
而对于ResNet,增加了来自上一层输入的identity函数:
在DenseNet中,会连接前面所有层作为输入:
DenseNet的主要构建模块是稠密块(dense block)和过渡层(transition layer)。前者定义了输入和输出是如何连结的,后者则用来控制通道数,使之不过大。
在DenseBlock中,各个层的特征图大小一致,可以在channel维度上连接。DenseBlock中的非线性组合函数H(·)采用的是BN+ReLU+3×3 Conv的结构,如下图所示。另外值得注意的一点是,与ResNet不同,所有DenseBlock中各个层卷积之后均输出k个特征图,即得到的特征图的channel数为 k,或者说采用 k 个卷积核。k 在DenseNet称为growth rate,这是一个超参数。一般情况下使用较小的k (比如12),就可以得到较佳的性能。假定输入层的特征图的channel数为 k0,那么l 层输入的channel数为 k0+k(l-1) ,因此随着层数增加,尽管 k设定得较小,DenseBlock的输入会非常多,不过这是由于特征重用所造成的,每个层仅有 k个特征是自己独有的。
2 稠密块
import timeimport torchfrom torch import nn, optimimport torch.nn.functional as Fimport syssys.path.append(\"..\")import d2lzh_pytorch as d2ldevice = torch.device(\'cuda\' if torch.cuda.is_available() else \'cpu\')def conv_block(in_channels, out_channels):blk = nn.Sequential(nn.BatchNorm2d(in_channels),nn.ReLU(),nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))return blk
# 稠密块由多个conv_block组成,每块使用相同的输出通道数。但在前向计算时,将每块的输入和输出在通道维上连结。class DenseBlock(nn.Module):def __init__(self, num_convs, in_channels, out_channels):super(DenseBlock, self).__init__()net = []for i in range(num_convs):in_c = in_channels + i * out_channelsnet.append(conv_block(in_c, out_channels))self.net = nn.ModuleList(net)self.out_channels = in_channels + num_convs * out_channels # 计算输出通道数def forward(self, X):for blk in self.net:Y = blk(X)X = torch.cat((X, Y), dim=1) # 在通道维上将输入和输出连结return X
3 过渡层
# 由于每个稠密块连结都会带来通道数的增加,使用过多则会带来过于复杂的模型。过渡层用来控制模型复杂度。它通过1×1卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽,从而进一步降低模型复杂度。def transition_block(in_channels, out_channels):blk = nn.Sequential(nn.BatchNorm2d(in_channels),nn.ReLU(),nn.Conv2d(in_channels, out_channels, kernel_size=1),nn.AvgPool2d(kernel_size=2, stride=2))return blk
4 DenseNet模型
DenseNet首先使用同ResNet一样的单卷积层和最大池化层。
类似于ResNet接下来使用的4个残差块,DenseNet使用的是4个稠密块。同ResNet一样,可以设置每个稠密块使用多少个卷积层,这里设成4,与ResNet-18保持一致。稠密块里的卷积层通道数(即增长率)设为32,所以每个稠密块将增加128个通道。
最后接上全局池化层和全连接层来输出。
net = 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))num_channels, growth_rate = 64, 32 # num_channels为当前的通道数num_convs_in_dense_blocks = [4, 4, 4, 4]for i, num_convs in enumerate(num_convs_in_dense_blocks):DB = DenseBlock(num_convs, num_channels, growth_rate)net.add_module(\"DenseBlosk_%d\" % i, DB)# 上一个稠密块的输出通道数num_channels = DB.out_channels# 在稠密块之间加入通道数减半的过渡层if i != len(num_convs_in_dense_blocks) - 1:net.add_module(\"transition_block_%d\" % i, transition_block(num_channels, num_channels // 2))num_channels = num_channels // 2net.add_module(\"BN\", nn.BatchNorm2d(num_channels))net.add_module(\"relu\", nn.ReLU())net.add_module(\"global_avg_pool\", d2l.GlobalAvgPool2d()) # GlobalAvgPool2d的输出: (Batch, num_channels, 1, 1)net.add_module(\"fc\", nn.Sequential(d2l.FlattenLayer(), nn.Linear(num_channels, 10)))
5 获取数据并训练
batch_size = 256# 如出现“out of memory”的报错信息,可减小batch_size或resizetrain_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)lr, num_epochs = 0.001, 5optimizer = torch.optim.Adam(net.parameters(), lr=lr)d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
参考原文:《动手学深度学习(pyTorch)》
参考知乎:小白将
欢迎关注【OAOA】