GluonCV ------ rsenet.py

1. 辅助函数(helpers)

@des: 	封装nn.Conv2D,便捷地生成步幅可控的3×3卷积块。
@p1:	输出通道数
@p2:	步幅
@p3:	输入通道数
def _conv3x3(channels, stride, in_channels):
    return nn.Conv2D(channels, kernel_size=3, strides=stride, padding=1,
                     use_bias=False, in_channels=in_channels)

2. 基本残差单元

一共分为四种,用于resnet18、resnet34的BasicBlock和用于resnet50、resnet101、resnet152的BottleneckV1。以及根据残差块的结构分为V1和V2。
resnet结构参见:resnet-n
残差块结构参见:resnetV1 and resnetV2
在gluoncv源码中采用4个类进行表示。下面介绍其中一个,其他类似。

@des: 	cov3*3 + BN + Relu + conv3*3 + BN
@p1:	输出通道数
@p2:	首个小2d卷积的步幅(决定了输出形状)
@p3:	是否降采样(当输入和输出通道数不相等的时候需要)
@p4:	输入通道数(默认为0,表示根据输入自适应)
@p5:	trick
@p6:	trick
@p7:	Normalization层
@p8:	Normalization层参数
class BasicBlockV1(HybridBlock):
    def __init__(self, channels, stride, downsample=False, in_channels=0,
                 last_gamma=False, use_se=False, norm_layer=BatchNorm, norm_kwargs=None, **kwargs):
        super(BasicBlockV1, self).__init__(**kwargs)
        self.body = nn.HybridSequential(prefix='')
        ##--第一个卷积块是可指定步幅的,也就决定了输出的尺寸。
        self.body.add(_conv3x3(channels, stride, in_channels))
        self.body.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
        self.body.add(nn.Activation('relu'))
        self.body.add(_conv3x3(channels, 1, channels))
        ##--多半是个trick
        if not last_gamma:
            self.body.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
        else:
            self.body.add(norm_layer(gamma_initializer='zeros',
                                     **({} if norm_kwargs is None else norm_kwargs)))
		##--多半又是个trick
        if use_se:
            self.se = nn.HybridSequential(prefix='')
            self.se.add(nn.Dense(channels // 16, use_bias=False))
            self.se.add(nn.Activation('relu'))
            self.se.add(nn.Dense(channels, use_bias=False))
            self.se.add(nn.Activation('sigmoid'))
        else:
            self.se = None
		##--当上述第一个3*3的卷积块stride不是1时,就需要这样一个降采样层去将分支大小变成一致,之后才可以相加。
        if downsample:
            self.downsample = nn.HybridSequential(prefix='')
            self.downsample.add(nn.Conv2D(channels, kernel_size=1, strides=stride,
                                          use_bias=False, in_channels=in_channels))
            self.downsample.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
        else:
            self.downsample = None

2. 网络(Nets)

一个ResNet网络由多个卷积层(layers)构成,一个卷积层由多个残差块(block)构成,一个残差块由多个残差基本单元(BasicBlock)构成。

@des: 	根据不同的resnet结构构建不同的Net
@p1:	残差基本单元类型
@p2:	每个卷积层内(总共4个)含有的残差块数量(列表)
@p3:	每个卷积层输出的通道数(列表)
@p4:	最终的分类数
@p5:	trick
@p6:	trick
@p7:	trick
@p8:	Normalization层
@p8:	Normalization层参数
class ResNetV1(HybridBlock):
    def __init__(self, block, layers, channels, classes=1000, thumbnail=False,
                 last_gamma=False, use_se=False, norm_layer=BatchNorm, norm_kwargs=None, **kwargs):
        super(ResNetV1, self).__init__(**kwargs)
        ##--第一个channel为第一个残差block的输入通道
        assert len(layers) == len(channels) - 1
        with self.name_scope():
            self.features = nn.HybridSequential(prefix='')
            if thumbnail:
                self.features.add(_conv3x3(channels[0], 1, 0))
            else:
                self.features.add(nn.Conv2D(channels[0], 7, 2, 3, use_bias=False))
                self.features.add(norm_layer(**({} if norm_kwargs is None else norm_kwargs)))
                self.features.add(nn.Activation('relu'))
                self.features.add(nn.MaxPool2D(3, 2, 1))

            for i, num_layer in enumerate(layers):
            	##--残差块步幅stage1不会改变数据形状,其他stage则会缩小数据形状并增加通道数。
                stride = 1 if i == 0 else 2
                self.features.add(self._make_layer(block, num_layer, channels[i+1],
                                                   stride, i+1, in_channels=channels[i],
                                                   last_gamma=last_gamma, use_se=use_se,
                                                   norm_layer=norm_layer, norm_kwargs=norm_kwargs))
            self.features.add(nn.GlobalAvgPool2D())

            self.output = nn.Dense(classes, in_units=channels[-1])
	@des: 	构建卷积层
	@p1:	残差基本单元类型
	@p2:	该卷积层残差块数量
	@p3:	该卷积层输出通道数
	@p4:	残差块首个残差基本单元的输入strid
	@p5:	卷积层的一个序列,每个卷积层是一个stage。
	@p6:	该卷积层输入通道数
	@p7:	trick
	@p8:	trick
	@p9:	Normalization层
	@p10:	Normalization层参数	
    def _make_layer(self, block, layers, channels, stride, stage_index, in_channels=0,
                    last_gamma=False, use_se=False, norm_layer=BatchNorm, norm_kwargs=None):
        layer = nn.HybridSequential(prefix='stage%d_'%stage_index)
        with layer.name_scope():
        	##--首个残差基本单元使用可控步幅用以调整数据形状。
            layer.add(block(channels, stride, channels != in_channels, in_channels=in_channels,
                            last_gamma=last_gamma, use_se=use_se, prefix='',
                            norm_layer=norm_layer, norm_kwargs=norm_kwargs))
            ##--后面的残差基本单元都使用固定步幅,因此数据形状不发生改变
            for _ in range(layers-1):
                layer.add(block(channels, 1, False, in_channels=channels,
                                last_gamma=last_gamma, use_se=use_se, prefix='',
                                norm_layer=norm_layer, norm_kwargs=norm_kwargs))
        return layer

    def hybrid_forward(self, F, x):
        x = self.features(x)
        x = self.output(x)

        return x

3. 指定方式

1、构建resnet_spec字典,可以用resnet-n的编号指定不同resnet的网络结构。
2、构建resnet versions列表指定版本。这样idx=0则是版本1,idx=1则是版本2。
3、构建block versions字典列表,此时resnet_spec字典的第一个value可以作为key。

resnet_spec = {18: ('basic_block', [2, 2, 2, 2], [64, 64, 128, 256, 512]),
               34: ('basic_block', [3, 4, 6, 3], [64, 64, 128, 256, 512]),
               50: ('bottle_neck', [3, 4, 6, 3], [64, 256, 512, 1024, 2048]),
               101: ('bottle_neck', [3, 4, 23, 3], [64, 256, 512, 1024, 2048]),
               152: ('bottle_neck', [3, 8, 36, 3], [64, 256, 512, 1024, 2048])}
resnet_net_versions = [ResNetV1, ResNetV2]
resnet_block_versions = [{'basic_block': BasicBlockV1, 'bottle_neck': BottleneckV1},
                         {'basic_block': BasicBlockV2, 'bottle_neck': BottleneckV2}]

4. 构造网络

@des: 	构建残差网络
@p1:	残差网络版本-resnet_net_versions列表中指定
@p2:	层数-resnet_spec字典中指定
@p3:	该卷积层输出通道数
@p4:	残差块首个残差基本单元的输入strid
@p5:	卷积层的一个序列,每个卷积层是一个stage。
@p6:	该卷积层输入通道数
def get_resnet(version, num_layers, pretrained=False, ctx=cpu(),
               root='~/.mxnet/models', use_se=False, **kwargs):
    assert num_layers in resnet_spec, \
        "Invalid number of layers: %d. Options are %s"%(
            num_layers, str(resnet_spec.keys()))
    ##--数据结构的巧妙!仅仅通过一个int就指定了3个参数!
    block_type, layers, channels = resnet_spec[num_layers]
    assert 1 <= version <= 2, \
        "Invalid resnet version: %d. Options are 1 and 2."%version
    resnet_class = resnet_net_versions[version-1]
    block_class = resnet_block_versions[version-1][block_type]
    ##--使用网络类构建网络
    net = resnet_class(block_class, layers, channels, use_se=use_se, **kwargs)
    ##--加载resnet的预训练参数
    if pretrained:
        from .model_store import get_model_file
        if not use_se:
        	##--首先在root目录中要有为resnet%d_v%d'%(num_layers, version)-short_hash.params文件
        	##--get_model_file()首先会根据传参的名字生成一个short_hash,结合成上述文件名。
        	##--将文件名和预设的sha1对比,通过则返回该文件路径,否则从网络上下载。
            net.load_parameters(get_model_file('resnet%d_v%d'%(num_layers, version),
                                               tag=pretrained, root=root), ctx=ctx)
        else:
            net.load_parameters(get_model_file('se_resnet%d_v%d'%(num_layers, version),
                                               tag=pretrained, root=root), ctx=ctx)
        ##--社么鬼
        from ..data import ImageNet1kAttr
        attrib = ImageNet1kAttr()
        net.synset = attrib.synset
        net.classes = attrib.classes
        net.classes_long = attrib.classes_long
    return net

5. 定向接口

没什么好解释的

def resnet18_v1(**kwargs):
    return get_resnet(1, 18, use_se=False, **kwargs)
..
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值