pytorch实现ResNet结构的实例代码


Posted in Python onMay 17, 2021

1.ResNet的创新

现在重新稍微系统的介绍一下ResNet网络结构。 ResNet结构首先通过一个卷积层然后有一个池化层,然后通过一系列的残差结构,最后再通过一个平均池化下采样操作,以及一个全连接层的得到了一个输出。ResNet网络可以达到很深的层数的原因就是不断的堆叠残差结构而来的。

1)亮点

网络中的亮点 :

  • 超深的网络结构( 突破1000 层)
  • 提出residual 模块
  • 使用Batch Normalization 加速训练( 丢弃dropout)

但是,一般来说,并不是一直的加深神经网络的结构就会得到一个更好的结果,一般太深的网络会出现过拟合的现象严重,可能还没有一些浅层网络要好。

pytorch实现ResNet结构的实例代码

2)原因

其中有两个原因:

  • 梯度消失或梯度爆炸

当层数过多的时候,假设每一层的误差梯度都是一个小于1的数值,当进行方向传播的过程中,每向前传播一层,都要乘以一个小于1的误差梯度,当网络越来越深时,所成的小于1的系数也就越来越多,此时梯度便越趋近于0,这样梯度便会越来越小。这便会造成梯度消失的现象。

而当所成的误差梯度是一个大于1的系数,而随着网络层数的加深,梯度便会越来越大,这便会造成梯度爆炸的现象。

  • 退化问题(degradation problem)

当解决了梯度消失或者梯度爆炸的问题之后,其实网络的效果可能还是不尽如意,还可能有退化问题。为此,ResNet提出了残差结构来解决这个退化问题。 也正是因为有这个残差的结构,所以才可以搭建这么深的网络。

pytorch实现ResNet结构的实例代码

2.ResNet的结构

残差结构如图所示

pytorch实现ResNet结构的实例代码

作图是针对ResNet-18/34层浅层网络的结构,右图是ResNet-50/101/152层深层网络的结构,其中注意:主分支与shortcut 的输出特征矩阵shape。

一下表格为网络的一些主要参数

pytorch实现ResNet结构的实例代码

可以看见,不同层数的网络结构其实框架是类似的,不同的至少堆叠的残差结构的数量。

1)浅层的残差结构

pytorch实现ResNet结构的实例代码

需要注意,有些残差结构的ShortCut是实线,而有的是虚线,这两者是不同的。对于左图来说,ShortCut是实线,这表明输入与输出的shape是一样的,所以可以直接的进行相加。而对于右图来说,其输入的shape与输出的shape是不一样的,这时候需要调整步长stribe与kernel size来使得两条路(主分支与捷径分支)所处理好的shape是一模一样的。

2)深层的残差结构

pytorch实现ResNet结构的实例代码

同样的,需要注意,主分支与shortcut 的输出特征矩阵shape必须相同,同样的通过步长来调整。

但是注意原论文中:

右侧虚线残差结构的主分支上、第一个1x1卷积层的步距是2,第二个3x3卷积层的步距是1.

而在pytorch官方实现的过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,这样能够在ImageNet的top1上提升大概0.5%的准确率。

所以在conv3_x,conv4_x,conv5_x中所对应的残差结构的第一层,都是指虚线的残差结构,其他的残差结构是实线的残差结构。

3)总结

对于每个大模块中的第一个残差结构,需要通过虚线分支来调整残差结构的输入与输出是同一个shape。此时使用了下采样的操作函数。
对于每个大模块中的其他剩余的残差结构,只需要通过实线分支来调整残差网络结构,因为其输出和输入本身就是同一个shape的。

对于第一个大模块的第一个残差结构,其第二个3x3的卷积中,步长是1的,而其他的三个大模块的步长均为2.
在每一个大模块的维度变换中,主要是第一个残差结构使得shape减半,而模块中其他的残差结构都是没有改变shape的。也真因为没有改变shape,所以这些残差结构才可以直接的通过实线进行相加。

3.Batch Normalization

Batch Normalization的目的是使我们的一批(Batch)特征矩阵feature map满足均值为0,方差为1的分布规律。

pytorch实现ResNet结构的实例代码

其中:
μ,σ_2在正向传播过程中统计得到
γ,β在反向传播过程中训练得到

Batch Normalization是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。通过该方法能够加速网络的收敛并提升准确率。

具体的相关原理见:Batch Normalization详解以及pytorch实验

4.参考代码

import torch
import torch.nn as nn

# 分类数目
num_class = 5
# 各层数目
resnet18_params = [2, 2, 2, 2]
resnet34_params = [3, 4, 6, 3]
resnet50_params = [3, 4, 6, 3]
resnet101_params = [3, 4, 23, 3]
resnet152_params = [3, 8, 36, 3]


# 定义Conv1层
def Conv1(in_planes, places, stride=2):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,stride=stride,padding=3, bias=False),
        nn.BatchNorm2d(places),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
    )


# 浅层的残差结构
class BasicBlock(nn.Module):
    def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 1):
        super(BasicBlock,self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling

        # torch.Size([1, 64, 56, 56]), stride = 1
        # torch.Size([1, 128, 28, 28]), stride = 2
        # torch.Size([1, 256, 14, 14]), stride = 2
        # torch.Size([1, 512, 7, 7]), stride = 2
        self.basicblock = nn.Sequential(
            nn.Conv2d(in_channels=in_places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(places * self.expansion),
        )

        # torch.Size([1, 64, 56, 56])
        # torch.Size([1, 128, 28, 28])
        # torch.Size([1, 256, 14, 14])
        # torch.Size([1, 512, 7, 7])
        # 每个大模块的第一个残差结构需要改变步长
        if self.downsampling:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(places*self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        # 实线分支
        residual = x
        out = self.basicblock(x)

        # 虚线分支
        if self.downsampling:
            residual = self.downsample(x)

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


# 深层的残差结构
class Bottleneck(nn.Module):

    # 注意:默认 downsampling=False
    def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 4):
        super(Bottleneck,self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling

        self.bottleneck = nn.Sequential(
            # torch.Size([1, 64, 56, 56]),stride=1
            # torch.Size([1, 128, 56, 56]),stride=1
            # torch.Size([1, 256, 28, 28]), stride=1
            # torch.Size([1, 512, 14, 14]), stride=1
            nn.Conv2d(in_channels=in_places,out_channels=places,kernel_size=1,stride=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            # torch.Size([1, 64, 56, 56]),stride=1
            # torch.Size([1, 128, 28, 28]), stride=2
            # torch.Size([1, 256, 14, 14]), stride=2
            # torch.Size([1, 512, 7, 7]), stride=2
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            # torch.Size([1, 256, 56, 56]),stride=1
            # torch.Size([1, 512, 28, 28]), stride=1
            # torch.Size([1, 1024, 14, 14]), stride=1
            # torch.Size([1, 2048, 7, 7]), stride=1
            nn.Conv2d(in_channels=places, out_channels=places * self.expansion, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(places * self.expansion),
        )

        # torch.Size([1, 256, 56, 56])
        # torch.Size([1, 512, 28, 28])
        # torch.Size([1, 1024, 14, 14])
        # torch.Size([1, 2048, 7, 7])
        if self.downsampling:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(places*self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        # 实线分支
        residual = x
        out = self.bottleneck(x)

        # 虚线分支
        if self.downsampling:
            residual = self.downsample(x)

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

        return out


class ResNet(nn.Module):
    def __init__(self,blocks, blockkinds, num_classes=num_class):
        super(ResNet,self).__init__()

        self.blockkinds = blockkinds
        self.conv1 = Conv1(in_planes = 3, places= 64)

        # 对应浅层网络结构
        if self.blockkinds == BasicBlock:
            self.expansion = 1
            # 64 -> 64
            self.layer1 = self.make_layer(in_places=64, places=64, block=blocks[0], stride=1)
            # 64 -> 128
            self.layer2 = self.make_layer(in_places=64, places=128, block=blocks[1], stride=2)
            # 128 -> 256
            self.layer3 = self.make_layer(in_places=128, places=256, block=blocks[2], stride=2)
            # 256 -> 512
            self.layer4 = self.make_layer(in_places=256, places=512, block=blocks[3], stride=2)

            self.fc = nn.Linear(512, num_classes)

        # 对应深层网络结构
        if self.blockkinds == Bottleneck:
            self.expansion = 4
            # 64 -> 64
            self.layer1 = self.make_layer(in_places = 64, places= 64, block=blocks[0], stride=1)
            # 256 -> 128
            self.layer2 = self.make_layer(in_places = 256,places=128, block=blocks[1], stride=2)
            # 512 -> 256
            self.layer3 = self.make_layer(in_places=512,places=256, block=blocks[2], stride=2)
            # 1024 -> 512
            self.layer4 = self.make_layer(in_places=1024,places=512, block=blocks[3], stride=2)

            self.fc = nn.Linear(2048, num_classes)

        self.avgpool = nn.AvgPool2d(7, stride=1)

        # 初始化网络结构
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # 采用了何凯明的初始化方法
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def make_layer(self, in_places, places, block, stride):

        layers = []

        # torch.Size([1, 64, 56, 56])  -> torch.Size([1, 256, 56, 56]), stride=1 故w,h不变
        # torch.Size([1, 256, 56, 56]) -> torch.Size([1, 512, 28, 28]), stride=2 故w,h变
        # torch.Size([1, 512, 28, 28]) -> torch.Size([1, 1024, 14, 14]),stride=2 故w,h变
        # torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 2048, 7, 7]), stride=2 故w,h变
        # 此步需要通过虚线分支,downsampling=True
        layers.append(self.blockkinds(in_places, places, stride, downsampling =True))

        # torch.Size([1, 256, 56, 56]) -> torch.Size([1, 256, 56, 56])
        # torch.Size([1, 512, 28, 28]) -> torch.Size([1, 512, 28, 28])
        # torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 1024, 14, 14])
        # torch.Size([1, 2048, 7, 7]) -> torch.Size([1, 2048, 7, 7])
        # print("places*self.expansion:", places*self.expansion)
        # print("block:", block)
        # 此步需要通过实线分支,downsampling=False, 每个大模块的第一个残差结构需要改变步长
        for i in range(1, block):
            layers.append(self.blockkinds(places*self.expansion, places))

        return nn.Sequential(*layers)


    def forward(self, x):

        # conv1层
        x = self.conv1(x)   # torch.Size([1, 64, 56, 56])

        # conv2_x层
        x = self.layer1(x)  # torch.Size([1, 256, 56, 56])
        # conv3_x层
        x = self.layer2(x)  # torch.Size([1, 512, 28, 28])
        # conv4_x层
        x = self.layer3(x)  # torch.Size([1, 1024, 14, 14])
        # conv5_x层
        x = self.layer4(x)  # torch.Size([1, 2048, 7, 7])

        x = self.avgpool(x) # torch.Size([1, 2048, 1, 1]) / torch.Size([1, 512])
        x = x.view(x.size(0), -1)   # torch.Size([1, 2048]) / torch.Size([1, 512])
        x = self.fc(x)      # torch.Size([1, 5])

        return x

def ResNet18():
    return ResNet(resnet18_params, BasicBlock)

def ResNet34():
    return ResNet(resnet34_params, BasicBlock)

def ResNet50():
    return ResNet(resnet50_params, Bottleneck)

def ResNet101():
    return ResNet(resnet101_params, Bottleneck)

def ResNet152():
    return ResNet(resnet152_params, Bottleneck)


if __name__=='__main__':
    # model = torchvision.models.resnet50()

    # 模型测试
    # model = ResNet18()
    # model = ResNet34()
    # model = ResNet50()
    # model = ResNet101()
    model = ResNet152()
    # print(model)

    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)

以上就是pytorch实现ResNet结构的实例代码的详细内容,更多关于pytorch ResNet结构的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python+pyqt实现12306图片验证效果
Oct 25 Python
Python实现的桶排序算法示例
Nov 29 Python
python format 格式化输出方法
Jul 16 Python
Python绘制KS曲线的实现方法
Aug 13 Python
Python爬虫基础之XPath语法与lxml库的用法详解
Sep 13 Python
Python设计模式之工厂方法模式实例详解
Jan 18 Python
Python使用sklearn库实现的各种分类算法简单应用小结
Jul 04 Python
Python识别快递条形码及Tesseract-OCR使用详解
Jul 15 Python
python判断一个对象是否可迭代的例子
Jul 22 Python
python 利用turtle库绘制笑脸和哭脸的例子
Nov 23 Python
python GUI库图形界面开发之PyQt5控件QTableWidget详细使用方法与属性
Feb 25 Python
详细总结Python常见的安全问题
May 21 Python
pytorch常用数据类型所占字节数对照表一览
May 17 #Python
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
Python编写可视化界面的全过程(Python+PyCharm+PyQt)
Pytorch 实现变量类型转换
Python进度条的使用
May 17 #Python
Python包管理工具pip的15 个使用小技巧
Python中json.dumps()函数的使用解析
May 17 #Python
You might like
PHP5与MySQL数据库操作常用代码 收集
2010/03/21 PHP
PHP实现一维数组转二维数组的方法
2015/02/25 PHP
将PHP从5.3.28升级到5.3.29时Nginx出现502错误
2015/05/09 PHP
php json_encode与json_decode详解及实例
2016/12/13 PHP
JS控件autocomplete 0.11演示及下载 1月5日已更新
2007/01/09 Javascript
Javascript 事件流和事件绑定
2009/07/16 Javascript
在javascript中对于DOM的加强
2013/04/11 Javascript
JS实现超精简响应鼠标显示二级菜单代码
2015/09/12 Javascript
Node.js静态服务器的实现方法
2018/02/28 Javascript
Vue项目安装插件并保存
2019/01/28 Javascript
Vue filter 过滤器、以及在table中的使用介绍
2020/09/07 Javascript
[05:26]TI10典藏宝瓶套装外观展示
2020/07/03 DOTA
Python  __getattr__与__setattr__使用方法
2008/09/06 Python
在Python的Tornado框架中实现简单的在线代理的教程
2015/05/02 Python
windows10下python3.5 pip3安装图文教程
2018/04/02 Python
使用 Python 实现简单的 switch/case 语句的方法
2018/09/17 Python
OpenCV HSV颜色识别及HSV基本颜色分量范围
2019/03/22 Python
python sklearn包——混淆矩阵、分类报告等自动生成方式
2020/02/28 Python
Python 如何批量更新已安装的库
2020/05/26 Python
pycharm 关闭search everywhere的解决操作
2021/01/15 Python
eBay法国购物网站:eBay.fr
2017/10/21 全球购物
GANT英国官方网上商店:甘特衬衫
2018/02/06 全球购物
《骆驼和羊》教学反思
2014/02/27 职场文书
对标管理实施方案
2014/03/12 职场文书
运输服务质量承诺书
2014/03/27 职场文书
高二学生评语大全
2014/04/25 职场文书
高中学生自我评价范文
2014/09/23 职场文书
导游词之蓬莱长岛
2019/12/17 职场文书
如何使用Python实现一个简易的ORM模型
2021/05/12 Python
Python scrapy爬取起点中文网小说榜单
2021/06/13 Python
MySql 8.0及对应驱动包匹配的注意点说明
2021/06/23 MySQL
mysql配置SSL证书登录的实现
2021/09/04 MySQL
Python echarts实现数据可视化实例详解
2022/03/03 Python
MySQL 外连接语法之 OUTER JOIN
2022/04/09 MySQL
一文教你快速生成MySQL数据库关系图
2022/06/28 Redis
Android实现图片九宫格
2022/06/28 Java/Android