pytorch笔记:04)resnet网络&解决输入图像大小问题

因为torchvision对resnet18-resnet152进行了封装实现,因而想跟踪下源码

首先看张核心的resnet层次结构图(图1),它诠释了resnet18-152是如何搭建的,其中resnet18和resnet34结构类似,而resnet50-resnet152结构类似。下面先看resnet18的源码
resnet层次图
图1

resnet18
首先是models.resnet18函数的调用

def resnet18(pretrained=False, **kwargs):
    """Constructs a ResNet-18 model.
    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    #[2, 2, 2, 2]和结构图[]X2是对应的
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained: #加载模型权重
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model

这里涉及到了一个BasicBlock类(resnet18和34),这样的一个结构我们称为一个block。在block内部conv都使用了padding,因此输入的in_img_size和out_img_size都是56x56,在图2右边的shortcut只需要改变输入的channel的大小,输入block的输入tensor和输出tensor就可以相加(详细内容)
BasicBlock
图2

事实上图2是Bottleneck类(用于resnet50-152,稍后分析),其和BasicBlock差不多,图3为图2的精简版(ps:可以把下图视为为一个box_block,即多个block叠加在一起,x3说明有3个上图一样的结构串起来):
Bottleneck
图3

BasicBlock类,可以对比结构图中的resnet18和resnet34,类中expansion =1,其表示block内部最后一个卷积的输出channel与第一个卷积的输出channel比值,即:
e x p a n s i o n = l a s t _ b l o c k _ c h a n n e l / f i r s t _ b l o c k _ c h a n n e l expansion= last\_block\_channel/first\_block\_channel expansion=last_block_channel/first_block_channel

def conv3x3(in_planes, out_planes, stride=1):
    "3x3 convolution with padding"
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)
                     
class BasicBlock(nn.Module):
    expansion = 1
	#inplanes其实就是channel,叫法不同
    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):
        residual = x

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

        out = self.conv2(out)
        out = self.bn2(out)
		#把shortcut那的channel的维度统一
        if self.downsample is not None:
            residual = self.downsample(x)

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

        return out

接下来是ResNet类,其和我们通常定义的模型差不多一个__init__()+forward(),代码有点长,我们一步步来分析:

  1. 参考前面的结构图,所有的resnet的第一个conv层都是一样的,输出channel=64
  2. 然后到了self.layer1 = self._make_layer(block, 64, layers[0]),这里的layers[0]=2,然后我们进入到_make_layer函数,由于stride=1或当前的输入channel和上一个块的输出channel一样,因而可以直接相加
  3. self.layer2 = self._make_layer(block, 128, layers[1], stride=2),此时planes=128而self.inplanes=64(上一个box_block的输出channel),此时channel不一致,需要对输出的x扩维后才能相加,而downsample 实现的就是该功能(ps:这里只有box_block中的第一个block需要downsample,为何?看图4)
  4. self.layer3 = self._make_layer(block, 256, layers[2], stride=2),此时planes=256而self.inplanes=128为,此时也需要扩维后才能相加,layer4 同理。
     图4
    图4

图4中下标2,3,4和上面的步骤对应,图中箭头旁数值表示block输入或者输出的channel数。
具体看图4-2,上

评论 59
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值