赞
踩
《模型定义》
本系列来总结Pytorch训练中的模型结构一些内容,包括模型的定义,模型参数化初始化方法,模型的保存与加载等
Pytorch模型训练(0) - CPN源码解析
Pytorch模型训练(1) - 模型定义
Pytorch模型训练(2) - 模型初始化
Pytorch模型训练(3) - 模型保存与加载
Pytorch模型训练(4) - Loss Function
Pytorch模型训练(5) - Optimizer
Pytorch模型训练(6) - 数据加载
读者可以在Pytorch源码下/torch/nn/modules/module.py中找到这个类的实现:
class Module(object): r"""Base class for all neural network modules. 所有神经网络模型的基类 Your models should also subclass this class. 我们的模型需要继承这个类 Modules can also contain other Modules, allowing to nest them in a tree structure. You can assign the submodules as regular attributes:: 模块中可以包含其他模型,并将他们嵌套在树结构中,我们可以将子模块指定为常规属性 import torch.nn as nn import torch.nn.functional as F class Model(nn.Module): def __init__(self): super(Model, self).__init__() self.conv1 = nn.Conv2d(1, 20, 5) self.conv2 = nn.Conv2d(20, 20, 5) def forward(self, x): x = F.relu(self.conv1(x)) return F.relu(self.conv2(x)) Submodules assigned in this way will be registered, and will have their parameters converted too when you call :meth:`to`, etc. """ 。。。。。太长,省略
这个初始化函数,就是将我们需要的组件(如conv、pooling、Linear、BatchNorm等)全部进行初始化设置;
注意1:这里是参数初始化,也是组件注册,也就是说,我们设计的模型中将要用到那些层,这些层是什么,有什么操作,有什么参数,参数初始值等,如上面代码段中:
def __init__(self):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5)
self.conv2 = nn.Conv2d(20, 20, 5)
注意2:这里只负责创建组件,并不决定组件之间的关系;就像我们组装电脑,这里只负责将电脑需要的配件(GPU,CPU,主板,电源,内存等)找齐全,并没有进行组装
注意3:这里的组件摆放也是有一定顺序的,并不是杂乱无章,方便组装;还是拿组装电脑来说,这些配件摆放应该有一定的顺序或逻辑在,方便组装人员组装
这个函数就将刚才的组件按照我们的模型结构组装成模型,这里决定这些组件之间的关系;就像组装电脑一样,CPU有它的位置,GPU也有它自己的位置,最终形成我们想要的电脑
def forward(self, x):
x = F.relu(self.conv1(x))
return F.relu(self.conv2(x))
CPN模型结构如下:
首先找到pytorch-cpn源码,以256×192.model为例,打开
t
r
a
i
n
.
p
y
train.py
train.py**
from config import cfg #cfg是一些参数初始化定义,如运行路径,学习率,输出个数,batch大小,数据路径等等
#但在模型阶段需要关注几个参数:
1)model = 'CPN50'
2)output_shape = (64, 48)
3)num_class = 17
from networks import network #导入网络包
main函数中:
# create model
model = network.__dict__[cfg.model](cfg.output_shape, cfg.num_class, pretrained = True) #构建模型
model = torch.nn.DataParallel(model).cuda()
model = network._ dict _ [cfg.model](cfg.output_shape, cfg.num_class, pretrained = True) 解析:
调用network模块中CPN50这个方法,构建输出输出为(64,48),个数为17,使用预训练参数的模型
network. _ dict _:是python对象记录该对象拥有属性和方法的一个内置属性,它是一个字典类型
在 n e t w o r k . p y network.py network.py中,有这么一句
__all__ = ['CPN50', 'CPN101']
这玩意是python特性,就是指,该network模块只放出两个接口’CPN50’和’CPN101’;这有点像C/C++中,函数接口一样,模块中可能有很多属性和方法,但只放出这两个供外部使用
def CPN50(out_size,num_class,pretrained=True):
res50 = resnet50(pretrained=pretrained) #先调用resnet50,构建一个res50模型
model = CPN(res50, output_shape=out_size,num_class=num_class, pretrained=pretrained) #再用CPN构建模型,res50为一个参数
return model #返回模型
这里就存在上文中,提到的模型中可以有嵌套的子模型,res50就是CPN一个子模型,而res50内部也有它的子模型;但无论怎么嵌套,每个子模型的生成都是按照模型三要素的套路构建的。
这里我们又分两步来看模型的构成,先看res50
A)这里调用resnet50,我们先跳到 r e s n e t . p y resnet.py resnet.py
def resnet50(pretrained=False, **kwargs): """Constructs a ResNet-50 model. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) #先用ResNet类构建模型,注意这里参数有个Bottleneck if pretrained: #若使用预训练模型,则初始化model参数(这里有模型加载内容,先跳过) print('Initialize with pre-trained ResNet') from collections import OrderedDict state_dict = model.state_dict() pretrained_state_dict = model_zoo.load_url(model_urls['resnet50']) for k, v in pretrained_state_dict.items(): if k not in state_dict: continue state_dict[k] = v print('successfully load '+str(len(state_dict.keys()))+' keys') model.load_state_dict(state_dict) return model
B)再来看看ResNet这个类,怎么构建res50的
class ResNet(nn.Module): #三要素1:继承nn.Module def __init__(self, block, layers, num_classes=1000): #三要素2:初始化组件 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) 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): #三要素3:组件组装 x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x1 = self.layer1(x) x2 = self.layer2(x1) x3 = self.layer3(x2) x4 = self.layer4(x3) return [x4, x3, x2, x1]
这里初始化时,传入了参数:Bottleneck,它又是一个子模块;它的构建也类似,也是三要素原则,就不粘贴了;也就是说Bottleneck由某些层构成,res50由Bottleneck和部分层构成
再回到CPN50,来看CPN构建后面模型
class CPN(nn.Module): #三要素1:继承nn.Module
def __init__(self, resnet, output_shape, num_class, pretrained=True): #三要素2:初始化组件
super(CPN, self).__init__()
channel_settings = [2048, 1024, 512, 256] #参数设置
self.resnet = resnet #将刚才构建的res50传过来 resnet
self.global_net = globalNet(channel_settings, output_shape, num_class) #构建global_net
self.refine_net = refineNet(channel_settings[-1], output_shape, num_class) #构建refine_net
def forward(self, x):#三要素3:组件组装
res_out = self.resnet(x) #x作为resnet的输入,经resnet,输出res_out
global_fms, global_outs = self.global_net(res_out) #将res_out作为global_net的输入,并输出global_fms, global_outs
refine_out = self.refine_net(global_fms) #将global_fms作为refine_net的输入,并输出refine_out
return global_outs, refine_out #返回global_outs, refine_out
这里将继续调用globalNet和refineNet两个类分别构建子模块global_net,refine_net;我这就不粘贴出来了,总之他们的逻辑都是三要素原则;
至于其中的具体用到什么层,怎样设计参数,这就是我们模型设计的事,这里只是按照模型设计用python代码,调用pytorch封装好的实现函数,翻译出来。
至此,我们的模型就构建完成了。
1)模型类中存在其他函数
如ResNet类中的_make_layer,其他类中也可能有
A)它们是以一个下划线开头命名:表示它是一个受保护的方法,在本类或子类中可访问(当然python中没有绝对安全属性,不像C++)
B)它们常常是组件初始化的一种代码提炼
2)nn.Sequential
它也继承自Module类,它是一个顺序容器,见torch源码/torch/nn/modules/container.py;在模型构建时,它会将组件按照一定顺序保存起来;
在其中,组件内部(有些组件可能不是单个层)只有顺序关系,没有链接关系;比如一个bottleneck,对Resnet来说它是一个组件,它内部又有多个层(组件),这些层之间在Sequential里有顺序关系,但他们的链接关系是在bottleneck类的forward中实现的
组件之间的链接关系也在forward函数中确定
3)nn.ModuleList
它也继承自Module类,它是一个子模型容器,与python list类似,见torch源码/torch/nn/modules/container.py;
在模型构建中,常常用来存储整个Sequential容器,这些Sequential容器在ModuleList中,没有严格关系;比如CPN中的globalnet:
self.laterals = nn.ModuleList(laterals)
self.upsamples = nn.ModuleList(upsamples)
self.predict = nn.ModuleList(predict)
将3个子模块存在ModuleList中,它们之间没有严格关系;当然这样存也是为了创建结构清晰:
def forward(self, x):
global_fms, global_outs = [], []
for i in range(len(self.channel_settings)):
if i == 0:
feature = self.laterals[i](x[i])
else:
feature = self.laterals[i](x[i]) + up
global_fms.append(feature)
if i != len(self.channel_settings) - 1:
up = self.upsamples[i](feature)
feature = self.predict[i](feature)
global_outs.append(feature)
return global_fms, global_outs
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。