DenseNet
论文传送门 https://arxiv.org/abs/1608.06993 , 这篇论文是 CVPR 2017 的最佳论文.
resnet 一文里说了, resnet 是具有里程碑意义的. densenet 就是受 resnet 的启发提出的模型.
resnet 中是把不同层的 feature map 相应元素的值直接相加. 而 densenet 是将 channel 维上的 feature map 直接 concat 在一起, 从而实现了 feature 的复用. 如下所示:
注意, 是连接 dense block 内输出层前面所有层的输出, 不是只有输出层的前一层
网络结构
首先实现 DenseBlock
先解释几个名词
bottleneck layer
即上图中红圈的 1x1 卷积核.主要目的是对输入在 channel 维度做降维. 减少运算量.
卷积核的数量为 4k,k 为该 layer 输出的 feature map 的数量 (也就是 3x3 卷积核的数量)
growth rate
即上图中黑圈处 3x3 卷积核的数量. 假设 3x3 卷积核的数量为 k, 则每个这种 3x3 卷积后, 都得到一个 channel=k 的输出. 假如一个 denseblock 有 m 组这种结构, 输入的 channel 为 n 的话, 则做完一次连接操作后得到的输出的 channel 为 n + k + k +...+k = n+m*k.所以又叫做 growth rate.
conv
论文里的 conv 指的是 BN-ReLU-Conv
实现 DenseBlock DenseLayer
- class DenseLayer(nn.Module):
- def __init__(self,in_channels,bottleneck_size,growth_rate):
- super(DenseLayer,self).__init__()
- count_of_1x1 = bottleneck_size
- self.bn1 = nn.BatchNorm2d(in_channels)
- self.relu1 = nn.ReLU(inplace=True)
- self.conv1x1 = nn.Conv2d(in_channels,count_of_1x1,kernel_size=1)
- self.bn2 = nn.BatchNorm2d(count_of_1x1)
- self.relu2 = nn.ReLU(inplace=True)
- self.conv3x3 = nn.Conv2d(count_of_1x1,growth_rate,kernel_size=3,padding=1)
- def forward(self,*prev_features):
- # for f in prev_features:
- # print(f.shape)
- input = torch.cat(prev_features,dim=1)
- # print(input.device,input.shape)
- # for param in self.bn1.parameters():
- # print(param.device)
- # print(list())
- bottleneck_output = self.conv1x1(self.relu1(self.bn1(input)))
- out = self.conv3x3(self.relu2(self.bn2(bottleneck_output)))
- return out
首先是 1x1 卷积, 然后是 3x3 卷积. 3x3 卷积核的数量即 growth_rate,bottleneck_size 即 1x1 卷积核数量. 论文里是 bottleneck_size=4xgrowth_rate 的关系. 注意 forward 函数的实现
- def forward(self,*prev_features):
- # for f in prev_features:
- # print(f.shape)
- input = torch.cat(prev_features,dim=1)
- # print(input.device,input.shape)
- # for param in self.bn1.parameters():
- # print(param.device)
- # print(list())
- bottleneck_output = self.conv1x1(self.relu1(self.bn1(input)))
- out = self.conv3x3(self.relu2(self.bn2(bottleneck_output)))
- return out
我们传进来的是一个元祖, 其含义是 [block 的输入, layer1 输出, layer2 输出,...]. 前面说过了, 一个 dense block 内的每一个 layer 的输入是前面所有 layer 的输出和该 block 的输入在 channel 维度上的连接. 这样就使得不同 layer 的 feature map 得到了充分的利用.
tips:
函数参数带 * 表示可以传入任意多的参数, 这些参数被组织成元祖的形式, 比如
- ## var-positional parameter
- ## 定义的时候, 我们需要添加单个星号作为前缀
- def func(arg1, arg2, *args):
- print arg1, arg2, args
- ## 调用的时候, 前面两个必须在前面
- ## 前两个参数是位置或关键字参数的形式
- ## 所以你可以使用这种参数的任一合法的传递方法
- func("hello", "Tuple, values is:", 2, 3, 3, 4)
- ## Output:
- ## hello Tuple, values is: (2, 3, 3, 4)
- ## 多余的参数将自动被放入元组中提供给函数使用
- ## 如果你需要传递元组给函数
- ## 你需要在传递的过程中添加 * 号
- ## 请看下面例子中的输出差异:
- func("hello", "Tuple, values is:", (2, 3, 3, 4))
- ## Output:
- ## hello Tuple, values is: ((2, 3, 3, 4),)
- func("hello", "Tuple, values is:", *(2, 3, 3, 4))
- ## Output:
- ## hello Tuple, values is: (2, 3, 3, 4)
- DenseBlock
- class DenseBlock(nn.Module):
- def __init__(self,in_channels,layer_counts,growth_rate):
- super(DenseBlock,self).__init__()
- self.layer_counts = layer_counts
- self.layers = []
- for i in range(layer_counts):
- curr_input_channel = in_channels + i*growth_rate
- bottleneck_size = 4*growth_rate #论文里设置的 1x1 卷积核是 3x3 卷积核的4倍.
- layer = DenseLayer(curr_input_channel,bottleneck_size,growth_rate).cuda()
- self.layers.append(layer)
- def forward(self,init_features):
- features = [init_features]
- for layer in self.layers:
- layer_out = layer(*features) #注意参数是 * features 不是 features
- features.append(layer_out)
- return torch.cat(features, 1)
一个 Dense Block 由多个 Layer 组成. 这里注意 forward 的实现, init_features 即该 block 的输入, 然后每个 layer 都会得到一个输出. 第 n 个 layer 的输入由输入和前 n-1 个 layer 的输出在 channel 维度上连接组成.
最后, 该 block 的输出为各个 layer 的输出为输入以及各个 layer 的输出在 channel 维度上连接而成.
TransitionLayer
很显然, dense block 的计算方式会使得 channel 维度过大, 所以每一个 dense block 之后要通过 1x1 卷积在 channel 维度降维.
- class TransitionLayer(nn.Sequential):
- def __init__(self, in_channels, out_channels):
- super(TransitionLayer, self).__init__()
- self.add_module('norm', nn.BatchNorm2d(in_channels))
- self.add_module('relu', nn.ReLU(inplace=True))
- self.add_module('conv', nn.Conv2d(in_channels, out_channels,kernel_size=1, stride=1, bias=False))
- self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))
- Dense.NET
dense.NET 的基本组件我们已经实现了. 下面就可以实现 dense.NET 了.
- class DenseNet(nn.Module):
- def __init__(self,in_channels,num_classes,block_config):
- super(DenseNet,self).__init__()
- self.conv1 = nn.Sequential(
- nn.Conv2d(in_channels,64,kernel_size=7,stride=2,padding=3),
- nn.BatchNorm2d(64),
- nn.ReLU(inplace=True)
- )
- self.pool1 = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
- self.dense_block_layers = nn.Sequential()
- block_in_channels = in_channels
- growth_rate = 32
- for i,layers_counts in enumerate(block_config):
- block = DenseBlock(in_channels=block_in_channels,layer_counts=layers_counts,growth_rate=growth_rate)
- self.dense_block_layers.add_module('block%d' % (i+1),block)
- block_out_channels = block_in_channels + layers_counts*growth_rate
- transition = TransitionLayer(block_out_channels,block_out_channels//2)
- if i != len(block_config): #最后一个 dense block 后没有 transition layer
- self.dense_block_layers.add_module('transition%d' % (i+1),transition)
- block_in_channels = block_out_channels // 2 #更新下一个 dense block 的 in_channels
- self.avg_pool = nn.AdaptiveAvgPool2d(output_size=(1,1))
- self.fc = nn.Linear(block_in_channels,num_classes)
- def forward(self,x):
- out = self.conv1(x)
- out = self.pool1(x)
- for layer in self.dense_block_layers:
- out = layer(out)
- # print(out.shape)
- out = self.avg_pool(out)
- out = torch.flatten(out,start_dim=1) #相当于 out = out.view((x.shape[0],-1))
- out = self.fc(out)
- return out
首先和 resnet 一样, 首先是 7x7 卷积接 3x3,stride=2 的最大池化, 然后就是不断地 dense block + tansition.得到 feature map 以后用全局平均池化得到 n 个 feature.然后给全连接层做分类使用.
可以用
- X=torch.randn(1,3,224,224).cuda()
- block_config = [6,12,24,16]
- net = DenseNet(3,10,block_config)
- net = net.cuda()
- out = net(X)
- print(out.shape)
测试一下, 输出如下, 可以看出 feature map 的变化情况. 最终得到 508x7x7 的 feature map.全局平均池化后, 得到 508 个特征, 通过线性回归得到 10 个类别.
- torch.Size([1, 195, 112, 112])
- torch.Size([1, 97, 56, 56])
- torch.Size([1, 481, 56, 56])
- torch.Size([1, 240, 28, 28])
- torch.Size([1, 1008, 28, 28])
- torch.Size([1, 504, 14, 14])
- torch.Size([1, 1016, 14, 14])
- torch.Size([1, 508, 7, 7])
- torch.Size([1, 10])
总结:
核心就是 dense block 内每一个 layer 都复用了之前的 layer 得到的 feature map, 因为底层细节的 feature 被复用, 所以使得模型的特征提取能力更强. 当然坏处就是计算量大, 显存消耗大.
来源: https://www.cnblogs.com/sdu20112013/p/12269817.html