利用Pytorch实现ResNet网络
主要是为了学习Pytorch构建神经网络的基本方法,参考自«深度学习框架Pytorch:入门与实践»一书,作者陈云

1.什么是ResNet网络

ResNet(Deep Residual Network)深度残差网络,是由Kaiming He等人提出的一种新的卷积神经网络结构,其最重要的特点就是网络大部分是由如图一所示的残差单元组成,许多参数相同的残差单元连接起来组成BasicBlock,而许多BasicBlock组合起来再加上预处理层和最后的全连接分类层就组成了ResNet网络
利用Pytorch实现ResNet34网络

图一 残差单元

1.1 ResNet34网络的具体构造

具体构造如下图所示:

利用Pytorch实现ResNet34网络

图2:ResNet34具体结构

除了最开始的卷积池化和最后的池化全连接外,网络中有许多结构相似的单元,这些重复单元的共同点就是有个跨层直连的shortcut。

不同层数的ResNet的配置清单:
利用Pytorch实现ResNet34网络

1.2 ResNet34网络的原理与优点

目前不太理解,待补充。。。。。。

2.Pytorch代码实现

2.1 实现代码

import torch as t
from torch import  nn
from torch.nn import functional as F


class ResidualBlock(nn.Module):
    def __init__(self,inchanel,outchanel,stride=1,shortcut=None):
        super(ResidualBlock,self).__init__()
        self.left=nn.Sequential(
            nn.Conv2d(inchanel,outchanel,3,stride,1,bias=False),
            nn.BatchNorm2d(outchanel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchanel,outchanel,3,1,1,bias=False),
            nn.BatchNorm2d(outchanel)
        )
        self.right=shortcut
    def forward(self, x):
        out=self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)

class ResNet34(nn.Module):
    """
    实现主module:ResNet34
    ResNet34包含多个layer,每个layer又包含多个residual block
    用子model实现residual block,用__make_layer__函数实现layer
    """
    def __init__(self,num_classes=1000):
        """
        构建ResNet34网络的各层结构
        :param num_classes:
        """
        super(ResNet34,self).__init__()
        self.pre=nn.Sequential(
            nn.Conv2d(3,64,7,2,3,bias=True),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3,2,1)
        )
        self.layer1 = self.__make_layer__(64,128,3)
        self.layer2 = self.__make_layer__(128,256,4,stride=2)
        self.layer3 = self.__make_layer__(256,512,6,stride=2)
        self.layer4 = self.__make_layer__(512,512,3,stride=2)
        self.fc = nn.Linear(512,num_classes)

    def __make_layer__(self,inchannel,outchannel,block_num,stride=1):
        """
        构建layer,包含多个residual block
        :param inchannel:
        :param outchannel:
        :param block_num:
        :param stride:
        :return:
        """
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,1,stride,bias=False),
            nn.BatchNorm2d(outchannel)
        )
        layers = []
        layers.append(ResidualBlock(inchannel,outchannel,stride,shortcut))
        for i in range(1,block_num):
            layers.append(ResidualBlock(inchannel,outchannel))
        return  nn.Sequential(*layers)

    def forward(self, x):
        x = self.pre(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x,7)
        x = x.view(x.size(0),-1)
        return self.fc(x)

2.2 整体思路

根据ResNet网络的特点,我们将模型分成两部分,主Model和子Model,子Model实现由多个残差单元组成的一个layer,主Model将最开始的卷积池化和最后的池化全连接以及中间的多个拥有不同参数的layer组合起来,形成ResNet网络。

2.3 代码分析

2.3.1 ResidualBlock 部分

class ResidualBlock(nn.Module):
    def __init__(self,inchanel,outchanel,stride=1,shortcut=None):
        super(ResidualBlock,self).__init__()
        self.left=nn.Sequential(
            nn.Conv2d(inchanel,outchanel,3,stride,1,bias=False),
            nn.BatchNorm2d(outchanel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchanel,outchanel,3,1,1,bias=False),
            nn.BatchNorm2d(outchanel)
        )
        self.right=shortcut
    def forward(self, x):
        out=self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)
  1. class ResidualBlock(nn.Module)
    如果想要用Pytorch实现一个模型,必须要先继承nn.Module类,这样我们就可以不手动实现反向传播的过程,只需要实现初始化模型部分以及前向传播部分即可

  2. __init__

    该函数负责实现模型的初始化,模型中的各个参数,各神经层需要在该函数中设置

    可以看到在该层中我们首先使用了一个super(ResidualBlock,self).__init__(),以此来调用nn.Module的构造函数,还有一种调用方式是nn.Module.__init__(self)

    在构造函数中我们可以定义可学习参数或者子Module。如果是定义一个可学习参数,必须封装成Parameter,例如nn.Parameter(torch.randn(out._features))Parameter是一种特殊的Variable ,但是是默认是需要求导的。

    另外,在这里我们还使用了nn.Sequential以及其他的一些在nn包里面实现的Module

    • nn.Sequential
      Sequential类似于一个列表,只不过列表里面的内容是一个一个的Module,假设我们定义了一个Sequential,对于这个Sequential传入一个input,Sequential会按内部模型定义的顺序处理这个input,最终返回一个output给我们

    • torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros')
      实现了一个简单的二维卷积层,参数含义如下:

      • in_channels 输入的通道数
      • out_channels 输出的通道数
      • kernel_size 卷积核的大小
      • stride 卷积核移动的步数
      • padding 图像矩阵每个维度填充0的个数
    • nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

      实现一个二维的批标准化
      用来保证每一个隐藏层的激活输入都具有相同的分布,BN就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布,解决梯度消失的问题
      具体可以参考

      • num_features 定义了特征数 即(N,C,W,H)中的C
  3. forward(self, x)
    实现该模型前向传播的过程,最终返回结果值

2.3.2 ResNet部分

class ResNet34(nn.Module):
    """
    实现主module:ResNet34
    ResNet34包含多个layer,每个layer又包含多个residual block
    用子model实现residual block,用__make_layer__函数实现layer
    """
    def __init__(self,num_classes=1000):
        """
        构建ResNet34网络的各层结构
        :param num_classes:
        """
        super(ResNet34,self).__init__()
        self.pre=nn.Sequential(
            nn.Conv2d(3,64,7,2,3,bias=True),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3,2,1)
        )
        self.layer1 = self.__make_layer__(64,128,3)
        self.layer2 = self.__make_layer__(128,256,4,stride=2)
        self.layer3 = self.__make_layer__(256,512,6,stride=2)
        self.layer4 = self.__make_layer__(512,512,3,stride=2)
        self.fc = nn.Linear(512,num_classes)

    def __make_layer__(self,inchannel,outchannel,block_num,stride=1):
        """
        构建layer,包含多个residual block
        :param inchannel:
        :param outchannel:
        :param block_num:
        :param stride:
        :return:
        """
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,1,stride,bias=False),
            nn.BatchNorm2d(outchannel)
        )
        layers = []
        layers.append(ResidualBlock(inchannel,outchannel,stride,shortcut))
        for i in range(1,block_num):
            layers.append(ResidualBlock(inchannel,outchannel))
        return  nn.Sequential(*layers)

    def forward(self, x):
        x = self.pre(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x,7)
        x = x.view(x.size(0),-1)
        return self.fc(x)

实现了ResNet模型的主要部分,将许多ResidualBlock整合在一起,大部分地方的实现方法和ResidualBlock Module差不多
这里实现了一个__make_layer__方法用来拼接任意数量的具有相同参数的残差单元,返回layer