ResNet

在这里插入图片描述
上图为ResNet的5个 基本结构,为了方便理解,此处以最简单的18-layer为例来展开:
首先我们知道ResNet中对于50层以下的构建块采用的是BasicBlock,而大于50的深层则采用的是Bottleneck,BasicBlock的构建代码如下:

class BasicBlock(nn.Module):
    expansion = 1
    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    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

首先我想解释下 BasicBlock 在ResNet 里代表的是什么,我们知道在图1所示,ResNet18由以下几部分组成,conv1,conv2_x,conv3_x,conv4_x,conv5_x,以及最后的全连接层,BasicBlock指的是 conv2_x,conv3_x,conv4_x,conv5_x里的这些具有重复结构的块,如Conv3_x层中具有两个([33,128]+[33,128])块,BasicBlock就指的是这些块,在ResNet中,通过重复调用BasicBlock方法就能方便的构建出这些块。

构建过程

在ResNet中通过BasicBlock构建一个完整网络利用的方法是_make-layer(通过循环创建块来创建层),其代码如下:

# block:基础块的类型,是BasicBlock,还是Bottleneck
# planes:当前块的输入输入通道数
# blocks:块的数目
def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        # downSample的作用于在残差连接时 将输入的图像的通道数变成和卷积操作的尺寸一致
        # 根据ResNet的结构特点,一般只在每层开始时进行判断
        if stride != 1 or self.inplanes != planes * block.expansion:
        	# 通道数恢复成一致
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                nn.BatchNorm2d(planes * block.expansion),
            )
        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes))
        return nn.Sequential(*layers)

为了方便理解,此处我分别对BasicBlock和Bottleneck根据结构进行推导下:

BasicBlock理解

Layer18–Conv2_x:假设我们的初始输入大小为2242243在conv1和max pool后尺寸变成:565664
则此时的self.inplanes=64,Conv2_x的创建代码为

# layer = [2,2,2,2]
self.layer1 = self._make_layer(Basicblock, 64, block_num=layer[0], stride=1)

stride=1 && 64 = 641(也就是self.inplanes = planes * block.expansion),无需downsample
在执行完成_make_layer后len(layers) = 2,结构均为(3
3,64)
然后是Conv3_x:此时的self.inplanes = 64,Conv3_x的代码为:

self.layer2 = self._make_layer(Basicblock, 128, block_num=layers[1], stride=2)

stride=2 需要downsample,此时downsample的主要修改是将通道由上层的64,变成本层的128,所以第一个BasicBlock的结构是:
64—>128,128—<128,第二个结构是,128—>128.128—>128,此阶段后的 self.inplanes=128,往后类似。也就是说,下一层的self.inplanes是上一层的最后的输出通道数。

Bottleneck理解

Layer50–Conv2_x:假设我们的初始输入大小为2242243在conv1和max pool后尺寸变成:565664
则此时的self.inplanes=64,Conv2_x的创建代码为

# layer = [3,4,6,3]
self.layer1 = self._make_layer(block,64,layer[0])

64 <> 644(也就是self.inplanes <> planes * block.expansion),需downsample
通道数 64—>64
4=256
第一个块的结构为:64—>64,64—>64,64—>256然后在残差连接时,使用downsample对上一层的输出进行调整,以方便进行连接,后续则无需downsample,然后进入下一层,与此类似。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐