《模型定義》
本系列來總結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爲例,打開**
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對象記錄該對象擁有屬性和方法的一個內置屬性,它是一個字典類型
-
在中,有這麼一句
__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,我們先跳到
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