当前位置:   article > 正文

Pytorch模型训练(1) - 模型定义_pytorch定义模型以及训练

pytorch定义模型以及训练

《模型定义》

 本系列来总结Pytorch训练中的模型结构一些内容,包括模型的定义,模型参数化初始化方法,模型的保存与加载等

0 博客目录

Pytorch模型训练(0) - CPN源码解析
Pytorch模型训练(1) - 模型定义
Pytorch模型训练(2) - 模型初始化
Pytorch模型训练(3) - 模型保存与加载
Pytorch模型训练(4) - Loss Function
Pytorch模型训练(5) - Optimizer
Pytorch模型训练(6) - 数据加载

1 模型定义–三要素

1.1 Pytorch模型的定义都需要继承nn.module这个类

  读者可以在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.
    """
    。。。。。太长,省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.2 初始化组件:_ init _(self)

  这个初始化函数,就是将我们需要的组件(如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)
  • 1
  • 2
  • 3
  • 4

  注意2:这里只负责创建组件,并不决定组件之间的关系;就像我们组装电脑,这里只负责将电脑需要的配件(GPU,CPU,主板,电源,内存等)找齐全,并没有进行组装
  注意3:这里的组件摆放也是有一定顺序的,并不是杂乱无章,方便组装;还是拿组装电脑来说,这些配件摆放应该有一定的顺序或逻辑在,方便组装人员组装

1.3 组件组装:forward(self, x)

  这个函数就将刚才的组件按照我们的模型结构组装成模型,这里决定这些组件之间的关系;就像组装电脑一样,CPU有它的位置,GPU也有它自己的位置,最终形成我们想要的电脑

def forward(self, x):
      x = F.relu(self.conv1(x))
      return F.relu(self.conv2(x))
  • 1
  • 2
  • 3

2 模型定义–CPN实例

2.1 CPN结构

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

 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']
    
    • 1

 这玩意是python特性,就是指,该network模块只放出两个接口’CPN50’和’CPN101’;这有点像C/C++中,函数接口一样,模块中可能有很多属性和方法,但只放出这两个供外部使用

2.2 CPN50

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   #返回模型
  • 1
  • 2
  • 3
  • 4

  这里就存在上文中,提到的模型中可以有嵌套的子模型,res50就是CPN一个子模型,而res50内部也有它的子模型;但无论怎么嵌套,每个子模型的生成都是按照模型三要素的套路构建的

  这里我们又分两步来看模型的构成,先看res50

2.2.1 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

   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]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

   这里初始化时,传入了参数:Bottleneck,它又是一个子模块;它的构建也类似,也是三要素原则,就不粘贴了;也就是说Bottleneck由某些层构成,res50由Bottleneck和部分层构成

  再回到CPN50,来看CPN构建后面模型

2.2.2 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

   这里将继续调用globalNet和refineNet两个类分别构建子模块global_net,refine_net;我这就不粘贴出来了,总之他们的逻辑都是三要素原则
   至于其中的具体用到什么层,怎样设计参数,这就是我们模型设计的事,这里只是按照模型设计用python代码,调用pytorch封装好的实现函数,翻译出来。

  至此,我们的模型就构建完成了。

3 模型定义–细节补充

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)
  • 1
  • 2
  • 3

  将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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号