Pytorch模型訓練(1) - 模型定義

《模型定義》

 本系列來總結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 初始化組件:_ 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)

  注意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))

2 模型定義–CPN實例

2.1 CPN結構

CPN模型結構如下:
在這裏插入圖片描述
 首先找到pytorch-cpn源碼,以256×192.model爲例,打開train.pytrain.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對象記錄該對象擁有屬性和方法的一個內置屬性,它是一個字典類型

  • network.pynetwork.py中,有這麼一句

     __all__ = ['CPN50', 'CPN101']
    

 這玩意是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   #返回模型

  這裏就存在上文中,提到的模型中可以有嵌套的子模型,res50就是CPN一個子模型,而res50內部也有它的子模型;但無論怎麼嵌套,每個子模型的生成都是按照模型三要素的套路構建的

  這裏我們又分兩步來看模型的構成,先看res50

2.2.1 res50

   A)這裏調用resnet50,我們先跳到resnet.pyresnet.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構建後面模型

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

   這裏將繼續調用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)

  將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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章