pytorch模型定義常用函式以及resnet模型修改案例

MapleTx 發表於 2022-05-29
PyTorch

模型定義常用函式

利用nn.Parameter()設計新的層

import torch
from torch import nn

class MyLinear(nn.Module):
  def __init__(self, in_features, out_features):
    super().__init__()
    self.weight = nn.Parameter(torch.randn(in_features, out_features))
    self.bias = nn.Parameter(torch.randn(out_features))

  def forward(self, input):
    return (input @ self.weight) + self.bias

nn.Sequential

一個有序的容器,神經網路模組將按照在傳入構造器的順序依次被新增到計算圖中執行,同時以神經網路模組為元素的有序字典也可以作為傳入引數。Sequential適用於快速驗證結果,簡單易讀,但使用Sequential也會使得模型定義喪失靈活性,比如需要在模型中間加入一個外部輸入時就不適合用Sequential的方式實現。

net = nn.Sequential(
   ('fc1',MyLinear(4, 3)),
   ('act',nn.ReLU()),
   ('fc2',MyLinear(3, 1))
)

nn.ModuleList()

ModuleList 接收一個子模組(或層,需屬於nn.Module類)的列表作為輸入,然後也可以類似List那樣進行append和extend操作。同時,子模組或層的權重也會自動新增到網路中來。

net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
net.append(nn.Linear(256, 10)) # # 類似List的append操作
print(net[-1])  # 類似List的索引訪問
print(net)
Linear(in_features=256, out_features=10, bias=True)
ModuleList(
  (0): Linear(in_features=784, out_features=256, bias=True)
  (1): ReLU()
  (2): Linear(in_features=256, out_features=10, bias=True)
)

要特別注意的是,nn.ModuleList 並沒有定義一個網路,它只是將不同的模組儲存在一起。ModuleList中元素的先後順序並不代表其在網路中的真實位置順序,需要經過forward函式指定各個層的先後順序後才算完成了模型的定義。具體實現時用for迴圈即可完成:

class model(nn.Module):
  def __init__(self, ...):
    super().__init__()
    self.modulelist = ...
    ...
    
  def forward(self, x):
    for layer in self.modulelist:
      x = layer(x)
    return x

nn.ModuleDict()

ModuleDict和ModuleList的作用類似,只是ModuleDict能夠更方便地為神經網路的層新增名稱。

net = nn.ModuleDict({
    'linear': nn.Linear(784, 256),
    'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) # 新增
print(net['linear']) # 訪問
print(net.output)
print(net)
Linear(in_features=784, out_features=256, bias=True)
Linear(in_features=256, out_features=10, bias=True)
ModuleDict(
  (act): ReLU()
  (linear): Linear(in_features=784, out_features=256, bias=True)
  (output): Linear(in_features=256, out_features=10, bias=True)
)

ModuleList和ModuleDict在某個完全相同的層需要重複出現多次時,非常方便實現,可以”一行頂多行“;當我們需要之前層的資訊的時候,比如 ResNets 中的殘差計算,當前層的結果需要和之前層中的結果進行融合,一般使用 ModuleList/ModuleDict 比較方便。

nn.Flatten

展平輸入的張量: 28x28 -> 784

input = torch.randn(32, 1, 5, 5)
m = nn.Sequential(
    nn.Conv2d(1, 32, 5, 1, 1),
    nn.Flatten()
)
output = m(input)
output.size()

模型修改案例

有了上面的一些常用方法,我們可以修改現有的一些開源模型,這裡通過介紹修改模型層、新增額外輸入的案例來幫助我們更好地理解。

修改模型層

以pytorch官方視覺庫torchvision預定義好的模型ResNet50為例,探索如何修改模型的某一層或者某幾層。我們先看看模型的定義:

import torchvision.models as models
net = models.resnet50()
print(net)
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): Bottleneck(
      (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
..............
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=2048, out_features=1000, bias=True)
)


為了適配ImageNet,fc層輸出是1000,若需要用這個resnet模型去做一個10分類的問題,就應該修改模型的fc層,將其輸出節點數替換為10。另外,我們覺得一層全連線層可能太少了,想再加一層。可以做如下修改:

from collections import OrderedDict
classifier = nn.Sequential(OrderedDict([('fc1', nn.Linear(2048, 128)),
                          ('relu1', nn.ReLU()), 
                          ('dropout1',nn.Dropout(0.5)),
                          ('fc2', nn.Linear(128, 10)),
                          ('output', nn.Softmax(dim=1))
                          ]))
    
net.fc = classifier # 將模型(net)最後名稱為“fc”的層替換成了我們自己定義的名稱為“classifier”的結構

新增外部輸入

有時候在模型訓練中,除了已有模型的輸入之外,還需要輸入額外的資訊。比如在CNN網路中,我們除了輸入影像,還需要同時輸入影像對應的其他資訊,這時候就需要在已有的CNN網路中新增額外的輸入變數。基本思路是:將原模型新增輸入位置前的部分作為一個整體,同時在forward中定義好原模型不變的部分、新增的輸入和後續層之間的連線關係,從而完成模型的修改。

我們以torchvision的resnet50模型為基礎,任務還是10分類任務。不同點在於,我們希望利用已有的模型結構,在倒數第二層增加一個額外的輸入變數add_variable來輔助預測。具體實現如下:

class Model(nn.Module):
    def __init__(self, net):
        super(Model, self).__init__()
        self.net = net
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        self.fc_add = nn.Linear(1001, 10, bias=True)
        self.output = nn.Softmax(dim=1)
        
    def forward(self, x, add_variable):
        x = self.net(x)
        # add_variable (batch_size, )->(batch_size, 1)
        x = torch.cat((self.dropout(self.relu(x)), add_variable.unsqueeze(1)),1)
        x = self.fc_add(x)
        x = self.output(x)
        return x

修改好的模型結構進行例項化,就可以使用

import torchvision.models as models
net = models.resnet50()
model = Model(net).cuda()
# 使用時輸入兩個inputs
outputs = model(inputs, add_var)

參考資料:

Pytorch與深度學習自查手冊3-模型定義 | 冬於的部落格 (ifwind.github.io)

[https://datawhalechina.github.io/thorough-pytorch/第五章/5.1 PyTorch模型定義的方式.html](https://datawhalechina.github.io/thorough-pytorch/第五章/5.1 PyTorch模型定義的方式.html)