pytorch教程resnet.py的实现文件源码分析

目录

  • 调用pytorch内置的模型的方法
  • 解读模型源码Resnet.py
    • 包含的库文件
    • 该库定义了6种Resnet的网络结构
    • 每种网络都有训练好的可以直接用的.pth参数文件
    • Resnet中大多使用3*3的卷积定义如下
    • 如何定义不同大小的Resnet网络
      • 定义Resnet18
      • 定义Resnet34
    • Resnet类
      • 网络的forward过程
        • 残差Block连接是如何实现的

        调用pytorch内置的模型的方法
        import torchvisionmodel = torchvision.models.resnet50(pretrained=True)

        这样就导入了resnet50的预训练模型了。如果只需要网络结构,不需要用预训练模型的参数来初始化
        那么就是:
        model = torchvision.models.resnet50(pretrained=False)

        如果要导入densenet模型也是同样的道理
        比如导入densenet169,且不需要是预训练的模型:
        model = torchvision.models.densenet169(pretrained=False)

        由于pretrained参数默认是False,所以等价于:
        model = torchvision.models.densenet169()

        不过为了代码清晰,最好还是加上参数赋值。

        解读模型源码Resnet.py
        包含的库文件
        import torch.nn as nnimport mathimport torch.utils.model_zoo as model_zoo


        该库定义了6种Resnet的网络结构
        包括
        __all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50','resnet101','resnet152']


        每种网络都有训练好的可以直接用的.pth参数文件
        __all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50','resnet101','resnet152']


        Resnet中大多使用3*3的卷积定义如下
        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)

        该函数继承自nn网络中的2维卷积,这样做主要是为了方便,少写参数参数由原来的6个变成了3个
        输出图与输入图长宽保持一致

        如何定义不同大小的Resnet网络
        Resnet类是一个基类,
        所谓的"Resnet18", ‘resnet34', ‘resnet50', ‘resnet101', 'resnet152'只是Resnet类初始化的时候使用了不同的参数,理论上我们可以根据Resnet类定义任意大小的Resnet网络
        下面先看看这些不同大小的Resnet网络是如何定义的

        定义Resnet18
        def resnet18(pretrained=False, **kwargs):"""Constructs a ResNet-18 model.Args:pretrained (bool):If True, returns a model pre-trained on ImageNet"""model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet18'])) return model


        定义Resnet34
        def resnet34(pretrained=False, **kwargs):"""Constructs a ResNet-34 model.Args:pretrained (bool): If True, returns a model pre-trained on ImageNet"""model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)if pretrained:model.load_state_dict(model_zoo.load_url(model_urls['resnet34'])) return model

        我们发现Resnet18和Resnet34的定义几乎是一样的,下面我们把Resnet18,Resnet34,Resnet50,Resnet101,Resnet152,不一样的部分写在一块进行对比
        model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)#Resnet18model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs)#Resnet34model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)#Eesnt50model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)#Resnet101model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs)#Resnet152

        代码看起来非常的简洁工整,
        其他resnet18、resnet101等函数和resnet18基本类似,差别主要是在:
        1、构建网络结构的时候block的参数不一样,比如resnet18中是[2, 2, 2, 2],resnet101中是[3, 4, 23, 3]。
        2、调用的block类不一样,比如在resnet50、resnet101、resnet152中调用的是Bottleneck类,而在resnet18和resnet34中调用的是BasicBlock类,这两个类的区别主要是在residual结果中卷积层的数量不同,这个是和网络结构相关的,后面会详细介绍。
        3、如果下载预训练模型的话,model_urls字典的键不一样,对应不同的预训练模型。因此接下来分别看看如何构建网络结构和如何导入预训练模型。

        Resnet类
        构建ResNet网络是通过ResNet这个类进行的。ResNet类是继承PyTorch中网络的基类:torch.nn.Module。
        构建Resnet类主要在于重写 init() forward() 方法。
        我们构建的所有网络比如:VGGAlexnet等都需要重写这两个方法,这两个方法很重要
        看起来Resne类是整个文档的核心
        下面我们就要研究一下Resnet基类是如何实现的
        Resnet类采用了pytorch定义网络模型的标准结构,包含
        iinit()方法: 定义了网络的各个层
        forward()方法: 定义了前向传播过程
        这两个方法的用法,这个可以查看pytorch的官方文档就可以明白
        在Resnet类中,还包含一个自定义的方法make_layer()方法
        是用来构建ResNet网络中的4个blocks
        _make_layer方法的第一个输入block是BottleneckBasicBlock
        第二个输入是该blocks的输出channel
        第三个输入是每个blocks中包含多少个residual子结构,因此layers这个列表就是前面resnet50的[3, 4, 6, 3]。
        _make_layer方法中比较重要的两行代码是:
        layers.append(block(self.inplanes, planes, stride, downsample))

        该部分是将每个blocks的第一个residual结构保存在layers列表中。
        for i in range(1, blocks): layers.append(block(self.inplanes, planes))

        该部分是将每个blocks的剩下residual 结构保存在layers列表中,这样就完成了一个blocks的构造。这两行代码中都是通过Bottleneck这个类来完成每个residual的构建
        接下来介绍Bottleneck类
        class ResNet(nn.Module):def __init__(self, block, layers, num_classes=1000):self.inplanes = 64super(ResNet, self).__init__()self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.layer1 = self._make_layer(block, 64, layers[0])self.layer2 = self._make_layer(block, 128, layers[1], stride=2)self.layer3 = self._make_layer(block, 256, layers[2], stride=2)self.layer4 = self._make_layer(block, 512, layers[3], stride=2)self.avgpool = nn.AvgPool2d(7, stride=1)self.fc = nn.Linear(512 * block.expansion, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()def _make_layer(self, block, planes, blocks, stride=1):downsample = Noneif stride != 1 or self.inplanes != planes * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.inplanes, planes * block.expansion,kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(planes * block.expansion),)layers = []layers.append(block(self.inplanes, planes, stride, downsample))self.inplanes = planes * block.expansionfor i in range(1, blocks):layers.append(block(self.inplanes, planes))return nn.Sequential(*layers)def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return x

        下面我们分别看看这两个过程:

        网络的forward过程
        def forward(self, x):#x代表输入x = self.conv1(x)#进过卷积层1x = self.bn1(x)#bn1层x = self.relu(x)#relu激活x = self.maxpool(x)#最大池化x = self.layer1(x)#卷积块1x = self.layer2(x)#卷积块2x = self.layer3(x)#卷积块3x = self.layer4(x)#卷积块4x = self.avgpool(x)#平均池化x = x.view(x.size(0), -1)#二维变成变成一维向量x = self.fc(x)#全连接层return x

        里面的大部分我们都可以理解,只有layer1-layer4是Resnet网络自己定义的,
        它也是Resnet残差连接的精髓所在,我们来分析一下layer层是怎么实现的

        残差Block连接是如何实现的
        从前面的ResNet类可以看出,在构造ResNet网络的时候,最重要的是 BasicBlock这个类,因为ResNet是由residual结构组成的,而 BasicBlock类就是完成residual结构的构建。同样 BasicBlock还是继承了torch.nn.Module类,且重写了__init__()和forward()方法。从forward方法可以看出,bottleneck就是我们熟悉的3个主要的卷积层、BN层和激活层,最后的out += residual就是element-wise add的操作。
        这部分在 BasicBlock类中实现,我们看看这层是如何前向传播的
        def forward(self, x):residual = xout = 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:residual = self.downsample(x)out += residualout = self.relu(out)return out

        我画个流程图来表示一下
        pytorch教程resnet.py的实现文件源码分析
        文章图片

        画的比较丑,不过基本意思在里面了,
        根据论文的描述,x是否需要下采样由x与out是否大小一样决定,
        假如进过conv2和bn2后的结果我们称之为 P
        假设x的大小为wHchannel1
        如果P的大小也是wHchannel1
        则无需下采样
        out = relu(P + X)
        out的大小为W * H *(channel1+channel2),
        如果P的大小是W/2 * H/2 * channel
        则X需要下采样后才能与P相加,
        out = relu(P+ X下采样)
        out的大小为W/2 * H/2 * (channel1+channel2)
        BasicBlock类和Bottleneck类类似,前者主要是用来构建ResNet18和ResNet34网络,因为这两个网络的residual结构只包含两个卷积层,没有Bottleneck类中的bottleneck概念。因此在该类中,第一个卷积层采用的是kernel_size=3的卷积,就是我们之前提到的conv3x3函数。
        下面是BasicBlock类的完整代码
        class BasicBlock(nn.Module):expansion = 1def __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 = downsampleself.stride = stridedef forward(self, x):residual = xout = 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:residual = self.downsample(x)out += residualout = self.relu(out)return out

        【pytorch教程resnet.py的实现文件源码分析】 以上就是pytorch教程resnet.py的实现文件源码解读的详细内容,更多关于pytorch源码解读的资料请关注脚本之家其它相关文章!

          推荐阅读