残差网络ResNet源码解析——Pytorch版本

更新时间:2023-05-05 12:20:46 阅读: 评论:0

残差⽹络ResNet源码解析——Pytorch版本
⼀、⽹络结构
残差指的是什么? 其中ResNet提出了两种mapping:⼀种是identity mapping,指的就是图1中”弯弯的曲线”,另⼀种residual mapping,指的就是除了”弯弯的曲线“那部分,所以最后的输出是 y=F(x)+x identity mapping顾名思义,就是指本⾝,也就是公式中的xx,⽽residual mapping指的是“差”,也就是y−x,所以残差指的就是F(x)部分。为什么ResNet可以解决“随着⽹络加深,准确率不下降”的问题? 理论上,对于“随着⽹络加深,准确率下降”的问题,Resnet提供了两种选择⽅式,也就是identity mapping和residual mapping,如果⽹络已经到达最优,继续加深⽹络,residual mapping将被push为0,只剩下identity mapping,这样理论上⽹络⼀直处于最优状态了,⽹络的性能也就不会随着深度增加⽽降低了。ResNet结构:
它使⽤了⼀种连接⽅式叫做“shortcut connection”,顾名思义,shortcut就是“抄近道”的意思,看下图我们就能⼤致理解:
图1 Shortcut Connection
这是⽂章⾥⾯的图,我们可以看到⼀个“弯弯的弧线“这个就是所谓的”shortcut connection“,也是⽂中提到identity mapping,这张图也诠释了ResNet的真谛,当然⼤家可以放⼼,真正在使⽤的ResNet模块并不是这么单⼀,⽂章中就提出了两种⽅式:
这两种结构分别针对ResNet18/ResNet34(左图)和ResNet50/101/152(右图),⼀般称整个结构为⼀个”building block“。其中右图⼜称为”bottleneck design”,⽬的⼀⽬了然,就是为了降低参数的数⽬,第⼀个1x1的卷积把256维channel降到64维,然后在最后通过1x1卷积恢复,整体上⽤的参数数⽬:1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632,⽽不使⽤bottleneck的话就是两个3x3x256的卷积,参数数⽬: 3x3x256x256x2 = 1179648,差了16.94倍。
对于常规ResNet,可以⽤于34层或者更少的⽹络中,对于Bottleneck Design的ResNet通常⽤于更深的如101这样的⽹络中,⽬的是减少计算和参数量(实⽤⽬的)
如图1所⽰,如果F(x)和x的channel个数不同怎么办,因为F(x)和x是按照channel维度相加的,channel不同怎么相加呢? 针对channel个数是否相同,要分成两种情况考虑,如下图:
图3 两种Shortcut Connection⽅式
如图3所⽰,我们可以清楚的”实线“和”虚线“两种连接⽅式,
实线的的Connection部分(”第⼀个粉⾊矩形和第三个粉⾊矩形“)都是执⾏3x3x64的卷积,他们的channel个数⼀致,所以采⽤计算⽅式:
y=F(x)+x
虚线的的Connection部分(”第⼀个绿⾊矩形和第三个绿⾊矩形“)分别是3x3x64和3x3x128的卷积操作,他们的channel个数不同(64和128),所以采⽤计算⽅式:
y=F(x)+Wx
其中W是卷积操作,⽤来调整x的channel维度的;
⼆、源码解析
残差⽹络代码详解
1. 模块调⽤
1import torchvision
2
3"""
4如果你需要⽤预训练模型,设置pretrained=True
5如果你不需要⽤预训练模型,设置pretrained=Fal,默认是Fal,你可以不写
6"""
7model = snet50(pretrained=True)
8model = snet50()
9
10# 你也可以导⼊dennet模型。且不需要是预训练的模型
11model = dels.dennet169(pretrained=Fal)
2. 源码解析
以导⼊resnet50为例,介绍具体导⼊模型时候的源码。
运⾏ model = snet50(pretrained=True)的时候,是通过models包下的resnet.py脚本进⾏的,源码如下:
⾸先是导⼊必要的库,其中model_zoo是和导⼊预训练模型相关的包,另外all变量定义了可以从外部import的函数名或类名。这也是前⾯为什么可以⽤snet50()来调⽤的原因。
model_urls这个字典是预训练模型的下载地址。
as nn
2import math
3import del_zoo as model_zoo
4
5__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
6          'resnet152']
7
8model_urls = {
9    'resnet18': '/models/resnet18-5c106cde.pth',
10    'resnet34': '/models/resnet34-333f7ec4.pth',
11    'resnet50': '/models/resnet50-19c8e357.pth',
12    'resnet101': '/models/resnet101-5d3b4d8f.pth',
13    'resnet152': '/models/resnet152-b121ed2d.pth',
14}
接下来就是resnet50这个函数了,参数pretrained默认是Fal。
1. model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)是构建⽹络结构,Bottleneck是另外⼀个构建bottleneck的类,在ResNet⽹络结构
的构建中有很多重复的⼦结构,这些⼦结构就是通过Bottleneck类来构建的,后⾯会介绍。
2. 如果参数pretrained是True,那么就会通过model_zoo.py中的load_url函数根据model_urls字典下载或导⼊相应的预训练模型。
3. 通过调⽤model的load_state_dict⽅法⽤预训练的模型参数来初始化你构建的⽹络结构,这个⽅法就是PyTorch中通⽤的⽤⼀个模型
的参数初始化另⼀个模型的层的操作。load_state_dict⽅法还有⼀个重要的参数是strict,该参数默认是True,表⽰预训练模型的层和你的⽹络结构层严格对应相等(⽐如层名和维度)。
1def resnet50(pretrained=Fal, **kwargs):
2    model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
3    if pretrained:
4        model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))
5    return model
其他resnet18、resnet101等函数和resnet50基本类似。
差别主要是在:
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字典的键不⼀样,对应不同的预训练模型。因此接下来分别看看如何构建⽹络结构和如何导⼊预训练模型。
1# pretrained (bool): If True, returns a model pre-trained on ImageNet
2
3def resnet18(pretrained=Fal, **kwargs):
4    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
5    if pretrained:
6        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
7    return model
8
9def resnet101(pretrained=Fal, **kwargs):
10    model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs)
11    if pretrained:
12        model.load_state_dict(model_zoo.load_url(model_urls['resnet101']))
13    return model
3. ResNet类
继承PyTorch中⽹络的基类:Module :
构建ResNet⽹络是通过ResNet这个类进⾏的。
其次主要的是重写初始化__init__()和forward()。
__init __()中主要是定义⼀些层的参数。
forward()中主要是定义数据在层之间的流动顺序,也就是层的连接顺序。
另外还可以在类中定义其他私有⽅法⽤来模块化⼀些操作,⽐如这⾥的_make_layer()是⽤来构建ResNet⽹络中的4个blocks。
_make_layer():
第⼀个输⼊block是Bottleneck或BasicBlock类,
第⼆个输⼊是该blocks的输出channel,
第三个输⼊是每个blocks中包含多少个residual⼦结构,因此layers这个列表就是前⾯resnet50的[3, 4, 6, 3]。
_make_layer()⽅法中⽐较重要的两⾏代码是:
1、layers.append(block(lf.inplanes, planes, stride, downsample)),该部分是将每个blocks的第⼀个residual结构保存在layers列表
中。
2、 for i in range(1, blocks): layers.append(block(lf.inplanes, planes)),该部分是将每个blocks的剩下residual 结构保存在layers列
表中,这样就完成了⼀个blocks的构造。
这两⾏代码中都是通过Bottleneck这个类来完成每个residual的构建,接下来介绍Bottleneck类。
1class ResNet(nn.Module):
2    def __init__(lf, block, layers, num_class=1000):
3        lf.inplanes = 64
4        super(ResNet, lf).__init__()
5        # ⽹络输⼊部分
6        lf.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,bias=Fal)
7        lf.bn1 = nn.BatchNorm2d(64)
8        lf.relu = nn.ReLU(inplace=True)
9        lf.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
10        # 中间卷积部分
11        lf.layer1 = lf._make_layer(block, 64, layers[0])
12        lf.layer2 = lf._make_layer(block, 128, layers[1], stride=2)
13        lf.layer3 = lf._make_layer(block, 256, layers[2], stride=2)
14        lf.layer4 = lf._make_layer(block, 512, layers[3], stride=2)
15        # 平均池化和全连接层
16        lf.avgpool = nn.AvgPool2d(7, stride=1)
17        lf.fc = nn.Linear(512 * pansion, num_class)
18
19        for m dules():
20            if isinstance(m, nn.Conv2d):
21                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
22                m.al_(0, math.sqrt(2. / n))
23            elif isinstance(m, nn.BatchNorm2d):
24                m.weight.data.fill_(1)
25                m._()
26
27    def _make_layer(lf, block, planes, blocks, stride=1):
28        downsample = None
29        if stride != 1 or lf.inplanes != planes * pansion:
30            downsample = nn.Sequential(
31                nn.Conv2d(lf.inplanes, planes * pansion,
32                          kernel_size=1, stride=stride, bias=Fal),
33                nn.BatchNorm2d(planes * pansion),
34            )
35
36        layers = []
37        layers.append(block(lf.inplanes, planes, stride, downsample))
38        lf.inplanes = planes * pansion
39        for i in range(1, blocks):
40            layers.append(block(lf.inplanes, planes))
41
42        return nn.Sequential(*layers)
43
44    def forward(lf, x):
45        x = lf.conv1(x)
46        x = lf.bn1(x)
47        x = lf.relu(x)
48        x = lf.maxpool(x)
49
50        x = lf.layer1(x)
51        x = lf.layer2(x)
52        x = lf.layer3(x)
53        x = lf.layer4(x)
54
55        x = lf.avgpool(x)
56        x = x.view(x.size(0), -1)
57        x = lf.fc(x)
58        return x
如上class ResNet(nn.Module)代码详解如下:

本文发布于:2023-05-05 12:20:46,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/531911.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模型   训练   结构   卷积   参数   构建   连接   加深
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图