CondenseNet总结

1711_CondenseNet

图:img

img

网络描述:

CondenseNet是作者在DenseNet基础上进行改进,集合了分组卷积、稠密连接、剪枝等。
实验证明,CondenseNet比前沿的MobileNet和ShuffleNet性能更好。
作者认为,DenseNet对特征的利用存在冗余,即每一层不必接收前面所有层的特征输出。为了降低这些冗余,作者提出了一种剪枝机制。之前的文章中,剪枝多是在网络训练之后,按照连接的权值大小或者其他连接重要性评估参数进行剪枝。==本文的剪枝采用了另外一种策略,即在训练的过程中进行,在训练时学习一个稀疏的网络。此外,作者将卷积核分为多个组,在训练的过程中,逐渐较少每个组内不重要的参数连接,对卷积核进行修剪。==值得注意的是,这种分组的模式不是固定的,而是网络自己学习到的。

Figure1中左边图是DenseNet的结构。第三层的11 Conv主要起到channel缩减的作用,第五层的33 Conv生成k个channel的输出。Figure1中间图是CondenseNet在训练时候的结构,Permute层的作用是为了降低引入11 L-Cconv对结果的不利影响,实现的是channel之间的调换过程。需要注意的是原来11 Conv替换成了11 L-Conv(learned group convolution),原来的33 Conv替换成了33 G-Conv(group convolution)。Figure1中右边图是CondenseNet测试时候的结构,其中的index层的作用在于feature selection,具体要选择哪些feature map,是在Figure1中间图中训练完的时候就确定的,因此index层只是一个类似0-1操作。另外要注意由于添加了Index层,所以原来Figure1中间图中的11 L-Conv层在Figure1右边图中替换成常规的卷积group层:1*1 G-Conv,这是训练和测试时候的一个不同点。

特点,优点:

本文中主要用到了三大策略,分别是自学习分组卷积、索引层、指数递增k值等结构改进。

(1)自学习分组卷积机制
针对上图的1x1卷积,由于其输入通道数较多,所以作者自然而然的想到了采用分组卷积来降低计算量,如上图中(训练阶段)、右(测试阶段)所示。而如何分组是一个问题。作者尝试了顺序分组和随机分组几种方法,发现顺序分组带来较大的精度损失。作者推测可能是因为密集连接的每一层接收的是前面所有输出特征的串联,串联的特征图存在内在的顺序,按照所谓的排序顺序进行分组破坏了这种内在顺序。若采用随机分组,效果好一点但是还是难以避免精度损失的问题。
==作者提出了learned group convolution,即自学习分组卷积。==学习分组的过程如下图所示。
如图,学习分组的过程分为多个阶段,分别是浓缩阶段、优化阶段、测试阶段。其中,浓缩阶段分为两步(或多步),

第一步为常规的卷积训练。在训练过程中加入了稀疏正则化,即loss函数中采用group Lasso进行正则。这样的好处是,学习到的参数可以呈现结构化稀疏的分布,后续对权值进行剪枝时不会带来太大的精度损失。这一点还是很好理解的,剪去一个权值为0的连接对网络没有精度影响。

第二步为剪枝,如前所述,剪枝基于通道进行,确定了分组数后,每组输出通道内对应的输入通道相同,稀疏度也相同。即,每组内由原来的全通道连接模式剪去相同数量的连接数。剪去的数量是按照一定比例的,后文有提到。

在优化阶段,在此基础上进一步进行剪枝,最终达到预设的比例。
img

剪枝完成后,每组内对应的残留的输入通道个数,与所有输入通道数的比例,即为浓缩因子C。如图中,输入通道为12个,设置C为3的话,每组应保留三分之一的通道,即12/3=4。并且修剪的过程也是由C来定义的。给定浓缩因子C,浓缩阶段包含C-1步,其中第一步是常规稀疏正则化训练,剩余的C-2步进行修剪。每个浓缩阶段,剪去1/C的通道数。在优化阶段,再剪掉1/C。所以,到了训练阶段,只剩下1/C通道了。
2)索引层
除了分组卷积之外,作者还添加了一个索引层,便于后续卷积层利用现有框架的常规分组卷积进行计算。下图很好理解了。

唯一的问题是,如果浓缩因子小于分组数的话,中间的索引层将会包含比输入特征图更多的特征图。

img

3)结构改进
除了前面两点之外,作者还进行了另外两点新的改进,使之结构更简化,计算效率更高。如下图所示。

img

指数递增的增长率
上文提到,每层卷积层的输入通道数以k逐层递增,也就是说,前面每层传递给后面的卷积层的通道数都是k个。所谓远亲不如近邻,高层的卷积层可能更依赖于中高层的特征,而较少依赖于底层的特征。为了体现不同层的重要性,作者引入了指数递增的增长率k,同一个block内采用相同的k,随着block数增加,k值呈指数增长。从而强化近处层的连接,弱化较远层的连接。通过逐步增加增长率,后面某一层接收到底层的输出通道数少,而接收到近处层的输出通道数多。作者也承认,这样做是具有双面性的,一方面可以增强计算效率,一方面可能降低参数效率。
全密集连接
区别于DenseNet中的只有dense block内采用密集连接模式,所谓全密集连接即每一层都与前面其它层呈密集连接关系。如图所示。为了将不同尺寸的特征图级联起来,采用降采样进行分辨率的统一。

pytorch实现:
class _DenseLayer(nn.Module):
    def __init__(self, in_channels, growth_rate, args):
        super(_DenseLayer, self).__init__()
        self.group_1x1 = args.group_1x1
        self.group_3x3 = args.group_3x3
        ### 1x1 conv i --> b*k
        self.conv_1 = LearnedGroupConv(in_channels, args.bottleneck * growth_rate,
                                       kernel_size=1, groups=self.group_1x1,
                                       condense_factor=args.condense_factor,
                                       dropout_rate=args.dropout_rate)
        ### 3x3 conv b*k --> k
        self.conv_2 = Conv(args.bottleneck * growth_rate, growth_rate,
                           kernel_size=3, padding=1, groups=self.group_3x3)

    def forward(self, x):
        x_ = x
        x = self.conv_1(x)
        x = self.conv_2(x)
        return torch.cat([x_, x], 1)


class _DenseBlock(nn.Sequential):
    def __init__(self, num_layers, in_channels, growth_rate, args):
        super(_DenseBlock, self).__init__()
        for i in range(num_layers):
            layer = _DenseLayer(in_channels + i * growth_rate, growth_rate, args)
            self.add_module('denselayer_%d' % (i + 1), layer)


class _Transition(nn.Module):
    def __init__(self, in_channels, args):
        super(_Transition, self).__init__()
        self.pool = nn.AvgPool2d(kernel_size=2, stride=2)

    def forward(self, x):
        x = self.pool(x)
        return x

class CondenseNet(nn.Module):
    def __init__(self, args):

        super(CondenseNet, self).__init__()

        self.stages = args.stages
        self.growth = args.growth
        assert len(self.stages) == len(self.growth)
        self.args = args
        self.progress = 0.0
        if args.data in ['cifar10', 'cifar100']:
            self.init_stride = 1
            self.pool_size = 8
        else:
            self.init_stride = 2
            self.pool_size = 7

        self.features = nn.Sequential()
        ### Initial nChannels should be 3
        self.num_features = 2 * self.growth[0]
        ### Dense-block 1 (224x224)
        self.features.add_module('init_conv', nn.Conv2d(3, self.num_features,
                                                        kernel_size=3,
                                                        stride=self.init_stride,
                                                        padding=1,
                                                        bias=False))
        for i in range(len(self.stages)):
            ### Dense-block i
            self.add_block(i)
        ### Linear layer
        self.classifier = nn.Linear(self.num_features, args.num_classes)

        ### initialize
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                m.bias.data.zero_()
        return

    def add_block(self, i):
        ### Check if ith is the last one
        last = (i == len(self.stages) - 1)
        block = _DenseBlock(
            num_layers=self.stages[i],
            in_channels=self.num_features,
            growth_rate=self.growth[i],
            args=self.args,
        )
        self.features.add_module('denseblock_%d' % (i + 1), block)
        self.num_features += self.stages[i] * self.growth[i]
        if not last:
            trans = _Transition(in_channels=self.num_features,
                                args=self.args)
            self.features.add_module('transition_%d' % (i + 1), trans)
        else:
            self.features.add_module('norm_last',
                                     nn.BatchNorm2d(self.num_features))
            self.features.add_module('relu_last',
                                     nn.ReLU(inplace=True))
            self.features.add_module('pool_last',
                                     nn.AvgPool2d(self.pool_size))

以下是CondenseNet的简单实现代码: ```python import torch import torch.nn as nn import torch.nn.functional as F def conv3x3(in_channels, out_channels, stride=1): """3x3 convolution with padding""" return nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) class Bottleneck(nn.Module): def __init__(self, in_channels, growth_rate): super(Bottleneck, self).__init__() self.bn1 = nn.BatchNorm2d(in_channels) self.conv1 = nn.Conv2d(in_channels, 4 * growth_rate, kernel_size=1, bias=False) self.bn2 = nn.BatchNorm2d(4 * growth_rate) self.conv2 = nn.Conv2d(4 * growth_rate, growth_rate, kernel_size=3, padding=1, bias=False) def forward(self, x): out = self.conv1(F.relu(self.bn1(x))) out = self.conv2(F.relu(self.bn2(out))) out = torch.cat([x, out], 1) return out class Transition(nn.Module): def __init__(self, in_channels, out_channels): super(Transition, self).__init__() self.bn = nn.BatchNorm2d(in_channels) self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) def forward(self, x): out = self.conv(F.relu(self.bn(x))) out = F.avg_pool2d(out, 2) return out class CondenseBlock(nn.Module): def __init__(self, in_channels, growth_rate, num_layers, drop_rate=0.0): super(CondenseBlock, self).__init__() self.num_layers = num_layers self.drop_rate = drop_rate self.layers = nn.ModuleList([Bottleneck(in_channels + i * growth_rate, growth_rate) for i in range(num_layers)]) def forward(self, x): for i in range(self.num_layers): if i == 0: out = self.layers[i](x) else: out = self.layers[i](out) if self.drop_rate > 0: out = F.dropout(out, p=self.drop_rate, training=self.training) out = torch.cat([x, out], 1) return out class CondenseNet(nn.Module): def __init__(self, growth_rate=12, block_config=(18, 18, 18), num_classes=10, drop_rate=0): super(CondenseNet, self).__init__() self.growth_rate = growth_rate # Initial convolution self.conv1 = nn.Conv2d(3, 2 * growth_rate, kernel_size=3, padding=1, bias=False) # First block self.block1 = CondenseBlock(2 * growth_rate, growth_rate, block_config[0], drop_rate=drop_rate) self.trans1 = Transition(2 * growth_rate + block_config[0] * growth_rate, int(1.5 * growth_rate)) # Second block self.block2 = CondenseBlock(int(1.5 * growth_rate), growth_rate, block_config[1], drop_rate=drop_rate) self.trans2 = Transition(int(1.5 * growth_rate) + block_config[1] * growth_rate, int(2.25 * growth_rate)) # Third block self.block3 = CondenseBlock(int(2.25 * growth_rate), growth_rate, block_config[2], drop_rate=drop_rate) # Final batch norm self.bn = nn.BatchNorm2d(int(2.25 * growth_rate)) # Linear layer self.linear = nn.Linear(int(2.25 * growth_rate), num_classes) def forward(self, x): out = self.conv1(x) out = F.max_pool2d(out, 3, stride=2, padding=1) out = self.block1(out) out = self.trans1(out) out = self.block2(out) out = self.trans2(out) out = self.block3(out) out = F.relu(self.bn(out)) out = F.avg_pool2d(out, 8) out = out.view(out.size(0), -1) out = self.linear(out) return out ``` 这里实现了CondenseNet网络的主要结构,包括Bottleneck、Transition和CondenseBlock三个模块。其中,Bottleneck表示CondenseBlock中的基本单元,Transition用于进行通道数调整,CondenseBlock则是CondenseNet的主要模块,由多个Bottleneck组成。在模型的前部分,使用了一个3x3卷积和一个最大池化层,而在最后一层则是一个全局平均池化层和一个全连接层。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值