pytorch教程resnet.py的实现文件源码分析
调用pytorch内置的模型的方法
import torchvision model = 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 nn import math import 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) #resnet18 model = resnet(basicblock, [3, 4, 6, 3], **kwargs) #resnet34 model = resnet(bottleneck, [3, 4, 6, 3], **kwargs) #eesnt50 model = resnet(bottleneck, [3, 4, 23, 3], **kwargs) #resnet101 model = 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()
方法。
我们构建的所有网络比如:vgg
,alexnet
等都需要重写这两个方法,这两个方法很重要
看起来resne类是整个文档的核心
下面我们就要研究一下resnet基类是如何实现的
resnet类采用了pytorch定义网络模型的标准结构,包含
iinit()
方法: 定义了网络的各个层forward()
方法: 定义了前向传播过程
这两个方法的用法,这个可以查看pytorch的官方文档就可以明白
在resnet类中,还包含一个自定义的方法make_layer()
方法
是用来构建resnet网络中的4个blocks
。
_make_layer方法的第一个输入block是bottleneck
或basicblock
类
第二个输入是该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 = 64 super(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_channels m.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 = none if 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.expansion for 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) #进过卷积层1 x = self.bn1(x) #bn1层 x = self.relu(x) #relu激活 x = self.maxpool(x) #最大池化 x = self.layer1(x) #卷积块1 x = self.layer2(x) #卷积块2 x = self.layer3(x) #卷积块3 x = self.layer4(x) #卷积块4 x = 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 = x out = 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 += residual out = self.relu(out) return out
我画个流程图来表示一下
画的比较丑,不过基本意思在里面了,
根据论文的描述,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 = 1 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) if self.downsample is not none: residual = self.downsample(x) out += residual out = self.relu(out) return out
以上就是pytorch教程resnet.py的实现文件源码解读的详细内容,更多关于pytorch源码解读的资料请关注其它相关文章!