ResNet

本文详细介绍了ResNet模型中的两种核心结构——BasicBlock和Bottleneck,包括它们的卷积层设计、下采样方式以及在不同版本ResNet中的应用。BasicBlock适用于较浅的网络,而Bottleneck因其参数效率高更适合深网。通过创建残差块和ResNet网络的步骤,展示了如何构建不同深度的ResNet模型。
摘要由CSDN通过智能技术生成

欢迎访问我的博客首页


1. BasicBlock 和 Bottleneck


  resnet-18 和 resnet-34 基于 BasicBlock,层数更多的 ResNet 基于 Bottleneck。BasicBlock 包含两个 3x3 的卷积和一个利用 1x1 的卷积实现的下采样。Bottleneck 包含一个 1x1 的卷积 (为深度可分离卷积准备通道)、一个深度可分离卷积 (一个 3x3 的卷积DW、一个 1x1 的卷积PW) 和一个利用 1x1 的卷积实现的下采样。相比 BasicBlock,Bottleneck 的参数更少,适合在层数更多的 ResNet 中使用。

def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=dilation, groups=groups, bias=False, dilation=dilation)

def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)

1.1 BasicBlock


  相比 Bottleneck,BasicBlock 的结构教简单,所以它用于创建层数较少的 resnet-18、resnet-34。

BasicBlock
图   1.1 r e s n e t − 18 和 r e s n e t − 34 中 使 用 B a s i c B l o c k 创 建 的 4 组 残 差 块 图\ 1.1\quad resnet-18 和 resnet-34 中使用 BasicBlock 创建的 4 组残差块  1.1resnet18resnet34使BasicBlock4

  创建 resnet-18 时 [ n 1 , n 2 , n 3 , n 4 ] = [ 2 , 2 , 2 , 2 ] [n1, n2, n3, n4] = [2, 2, 2, 2] [n1,n2,n3,n4]=[2,2,2,2],创建 resnet-34 时 [ n 1 , n 2 , n 3 , n 4 ] = [ 3 , 4 , 6 , 3 ] [n1, n2, n3, n4] = [3, 4, 6, 3] [n1,n2,n3,n4]=[3,4,6,3]。灰色区域表示这一块需要重复若干次,比如 resnet-34 的 n2=4 则第 2 列包含 1 个不是灰色的残差块和 3 个灰色的残差块。下面是 BasicBlock 的代码。

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(BasicBlock, self).__init__()
        # 1.参数。
        self.stride = stride
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        if groups != 1 or base_width != 64:
            raise ValueError('BasicBlock only supports groups=1 and base_width=64')
        if dilation > 1:
            raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
        # 2.两个卷积层和一个下采样层。当步长不为1时,self.conv1和self.downsample都会进行下采样。
        # 2.1 卷积层。
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True)
        # 2.2 卷积层。
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = norm_layer(planes)
        # 2.3 下采样层(有1x1的卷积层实现)。
        self.downsample = downsample

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

  由第 11 行和第 13 行可知,BasicBlock 不支持分组卷积和空洞卷积。

1.2 Bottleneck


  Bottleneck 用于创建 resnet-50 等更深层的 ResNet。

Bottleneck
图   1.2 r e s n e t − 50 等 更 深 层 的 R e s N e t 中 使 用 B o t t l e n e c k 创 建 的 4 组 残 差 块 图\ 1.2\quad resnet-50 等更深层的 ResNet 中使用 Bottleneck 创建的 4 组残差块  1.2resnet50ResNet使Bottleneck4

  作者提出的 ResNet 中 Bottleneck 在 1x1 的卷积 (self.conv1) 中实现下采样,而 torchvision 中的 Bottleneck 在 3x3 的卷积 (self.conv2) 中实现下采样。这时因为论文 Deep residual learning for image recognitionResNet V1.5 证明这样的效果更好。
  创建 resnet-50 时 [ n 1 , n 2 , n 3 , n 4 ] = [ 3 , 4 , 6 , 3 ] [n1, n2, n3, n4] = [3, 4, 6, 3] [n1,n2,n3,n4]=[3,4,6,3],创建 resnet-101 时 [ n 1 , n 2 , n 3 , n 4 ] = [ 3 , 4 , 23 , 3 ] [n1, n2, n3, n4] = [3, 4, 23, 3] [n1,n2,n3,n4]=[3,4,23,3],创建 resnet-152 时 [ n 1 , n 2 , n 3 , n 4 ] = [ 3 , 8 , 36 , 3 ] [n1, n2, n3, n4] = [3, 8, 36, 3] [n1,n2,n3,n4]=[3,8,36,3]。Bottleneck 包含 conv1x1、conv3x3、conv1x1 和一个可选的 downsample,下面是 Bottleneck 的代码。

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
                 base_width=64, dilation=1, norm_layer=None):
        super(Bottleneck, self).__init__()
        # 1.参数。
        self.stride = stride
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        width = int(planes * (base_width / 64.)) * groups
        # 2.三个卷积层和一个下采样层。当步长不为1时,self.conv2和self.downsample都会进行下采样。
        # 2.1 卷积层。
        self.conv1 = conv1x1(inplanes, width)
        self.bn1 = norm_layer(width)
        # 2.2 深度可分离卷积。
        self.conv2 = conv3x3(width, width, stride, groups, dilation)
        self.bn2 = norm_layer(width)
        self.conv3 = conv1x1(width, planes * self.expansion)
        self.bn3 = norm_layer(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        # 2.3 下采样层(有1x1的卷积层实现)。
        self.downsample = downsample

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

  Bottleneck 支持 depthwise 卷积和空洞卷积,但 group 和 dilation 的默认值都是 1,也就是默认使用常规卷积。

2. 创建残差块


  因为第一组残差块前使用了最大池化层,不再需要降采样,所以它和后三组残差块不太一样。从图 1.1 和图 1.2 可以看出,除第一组残差块外,其它组的第一个残差块都实现了下采样。
  ResNet 共有 4 组残差块,每组残差块中包含若干个残差块。不同版本 ResNet 的各残差组包含的残差块如下:

ResNet 版本resnet-18resnet-34resnet-50resnet-101resnet-151
blockBasicBlockBasicBlockBottleneckBottleneckBottleneck
残差组参数[2, 2, 2, 2][3, 4, 6, 3][3, 4, 6, 3][3, 4, 23, 3][3, 8, 36, 3]

  下面是创建 4 组残差块的代码,函数 _make_layer 根据残差组参数中 4 个参数中的一个参数,创建一组残差块。

self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2, dilate=replace_stride_with_dilation[0])  # 1/2.
self.layer3 = self._make_layer(block, 256, layers[2], stride=2, dilate=replace_stride_with_dilation[1])  # 1/2.
self.layer4 = self._make_layer(block, 512, layers[3], stride=2, dilate=replace_stride_with_dilation[2])  # 1/2.

def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
    norm_layer = self._norm_layer
    downsample = None
    previous_dilation = self.dilation
    if dilate:
        self.dilation *= stride
        stride = 1
    # 1.下采用层:缩小尺寸、通道乘4。
    if stride != 1 or self.inplanes != planes * block.expansion:
        downsample = nn.Sequential(
            conv1x1(self.inplanes, planes * block.expansion, stride),
            norm_layer(planes * block.expansion),
        )
    # 2.第一个block。
    layers = [block(self.inplanes, planes, stride, downsample, self.groups,
                    self.base_width, previous_dilation, norm_layer)]
    # 3.剩下的block。
    self.inplanes = planes * block.expansion
    for _ in range(1, blocks):
        # block输出的特征的通道不是planes而是planes * block.expansion。
        layers.append(block(self.inplanes, planes, groups=self.groups,
                            base_width=self.base_width, dilation=self.dilation,
                            norm_layer=norm_layer))
    return nn.Sequential(*layers)

3. 创建 ResNet


def build_res_net():
    return torchvision.models.resnet18(
        pretrained=False,  # 是否使用ImageNet上的预训练模型。
        progress=True,  # 是否显示下载预训练模型的进度条。
        num_classes=1000,  # 类别总数。
        zero_init_residual=False,  # 是否把每个Bottleneck和BasicBlock中的最后一个BN层的weight置0。
        groups=1,  # 在Bottleneck中使用分组卷积。
        width_per_group=64,  # 分组卷积中,每一组的通道数。
        replace_stride_with_dilation=None,  # 是否使用空洞卷积代替步长为2的卷积 。
        norm_layer=None  # 使用哪种归一化层。
    )

  上面创建 ResNet 使用的都是默认参数。通过调用下面的函数创建不同版本的 ResNet。

def _resnet(arch, block, layers, pretrained, progress, **kwargs):
    model = ResNet(block, layers, **kwargs)
    if pretrained:
        state_dict = load_state_dict_from_url(model_urls[arch], progress=progress)
        model.load_state_dict(state_dict)
    return model
    
def resnet18(pretrained=False, progress=True, **kwargs):
    return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, **kwargs)

def resnet34(pretrained=False, progress=True, **kwargs):
    return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, **kwargs)

def resnet50(pretrained=False, progress=True, **kwargs):
    return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, **kwargs)

def resnet101(pretrained=False, progress=True, **kwargs):
    return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress, **kwargs)

def resnet152(pretrained=False, progress=True, **kwargs):
    return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress, **kwargs)

  函数 _resnet 的第 1 个参数只是为了用对应版本的预训练参数初始化。第 2 个参数指定残差块,第 3 个参数指定网络结构,第 4、5 个参数指定是否使用预训练参数及下载预训练参数时是否显示进度条。

4. 参考


  1. ResNet 系列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值