PyTorch中的nn.ModuleList和nn.Sequential

nn.Sequential

nn.Sequential裏面的所構造的模塊是要按照順序進行排列的,必須確保前一個模塊的輸出大小和下一個模塊的輸入大小是一致的。

import torch
import torch.nn as nn
import torch.nn.functional as F
class net_seq(nn.Module):
    def __init__(self):
        super(net2, self).__init__()
        self.seq = nn.Sequential(
                        nn.Conv2d(1,20,5),
                         nn.ReLU(),
                          nn.Conv2d(20,64,5),
                       nn.ReLU()
                       )      
    def forward(self, x):
        return self.seq(x)
net_seq = net_seq()
print(net_seq)
#net_seq(
#  (seq): Sequential(
#    (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (1): ReLU()
#    (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (3): ReLU()
#  )
#)

nn.Sequential也可以使用OrderedDict來指定每個module的名字,而不是採用默認的命名方式(按序號 0,1,2,3…)。

from collections import OrderedDict

class net_seq(nn.Module):
    def __init__(self):
        super(net_seq, self).__init__()
        self.seq = nn.Sequential(OrderedDict([
                        ('conv1', nn.Conv2d(1,20,5)),
                         ('relu1', nn.ReLU()),
                          ('conv2', nn.Conv2d(20,64,5)),
                       ('relu2', nn.ReLU())
                       ]))
    def forward(self, x):
        return self.seq(x)
net_seq = net_seq()
print(net_seq)
#net_seq(
#  (seq): Sequential(
#    (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (relu1): ReLU()
#    (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (relu2): ReLU()
#  )
#)

nn.Sequential

nn.ModuleList,它是一個儲存不同 module,並自動將每個 module 的 parameters 添加到網絡之中的容器。你可以把任意 nn.Module 的子類 (比如 nn.Conv2d, nn.Linear 之類的) 加到這個 list 裏面,方法和 Python 自帶的 list 一樣,無非是 extend,append 等操作。但不同於一般的 list,加入到 nn.ModuleList 裏面的 module 是會自動註冊到整個網絡上的,同時 module 的 parameters 也會自動添加到整個網絡中。若使用python的list,則會出問題。

class net_modlist(nn.Module):
    def __init__(self):
        super(net_modlist, self).__init__()
        self.modlist = nn.ModuleList([
                       nn.Conv2d(1, 20, 5),
                       nn.ReLU(),
                        nn.Conv2d(20, 64, 5),
                        nn.ReLU()
                        ])

    def forward(self, x):
        for m in self.modlist:
            x = m(x)
        return x

net_modlist = net_modlist()
print(net_modlist)
#net_modlist(
#  (modlist): ModuleList(
#    (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
#    (1): ReLU()
#    (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
#    (3): ReLU()
#  )
#)
for param in net_modlist.parameters():
    print(type(param.data), param.size())
#<class 'torch.Tensor'> torch.Size([20, 1, 5, 5])
#<class 'torch.Tensor'> torch.Size([20])
#<class 'torch.Tensor'> torch.Size([64, 20, 5, 5])
#<class 'torch.Tensor'> torch.Size([64])

可以看到,這個網絡權重 (weithgs) 和偏置 (bias) 都在這個網絡之內。接下來看看另一個作爲對比的網絡,它使用 Python 自帶的 list:

class net_modlist(nn.Module):
    def __init__(self):
        super(net_modlist, self).__init__()
        self.modlist = [
                       nn.Conv2d(1, 20, 5),
                       nn.ReLU(),
                        nn.Conv2d(20, 64, 5),
                        nn.ReLU()
                        ]

    def forward(self, x):
        for m in self.modlist:
            x = m(x)
        return x

net_modlist = net_modlist()
print(net_modlist)
#net_modlist()
for param in net_modlist.parameters():
    print(type(param.data), param.size())
#None

顯然,使用 Python 的 list 添加的卷積層和它們的 parameters 並沒有自動註冊到我們的網絡中。當然,我們還是可以使用 forward 來計算輸出結果。但是如果用其實例化的網絡進行訓練的時候,因爲這些層的parameters不在整個網絡之中,所以其網絡參數也不會被更新,也就是無法訓練。

nn.Sequential與nn.ModuleList的區別

不同點1:
nn.Sequential內部實現了forward函數,因此可以不用寫forward函數。而nn.ModuleList則沒有實現內部forward函數。如果完全直接用 nn.Sequential,確實是可以的,但這麼做的代價就是失去了部分靈活性,不能自己去定製 forward 函數裏面的內容了。一般情況下 nn.Sequential 的用法是來組成卷積塊 (block),然後像拼積木一樣把不同的 block 拼成整個網絡,讓代碼更簡潔,更加結構化。

不同點2:
nn.Sequential可以使用OrderedDict對每層進行命名,上面已經闡述過了;

不同點3:
nn.Sequential裏面的模塊按照順序進行排列的,所以必須確保前一個模塊的輸出大小和下一個模塊的輸入大小是一致的。而nn.ModuleList 並沒有定義一個網絡,它只是將不同的模塊儲存在一起,這些模塊之間並沒有什麼先後順序可言。網絡的執行順序是根據 forward 函數來決定的。若將forward函數中幾行代碼互換,使輸入輸出之間的大小不一致,則程序會報錯。此外,爲了使代碼具有更高的可讀性,最好把ModuleList和forward中的順序保持一致。

不同點4:
有的時候網絡中有很多相似或者重複的層,我們一般會考慮用 for 循環來創建它們,而不是一行一行地寫,比如:

layers = [nn.Linear(10, 10) for i in range(5)]

那麼這裏我們使用ModuleList:

class net4(nn.Module):
    def __init__(self):
        super(net4, self).__init__()
        layers = [nn.Linear(10, 10) for i in range(5)]
        self.linears = nn.ModuleList(layers)

    def forward(self, x):
        for layer in self.linears:
            x = layer(x)
        return x

net = net4()
print(net)
# net4(
#   (linears): ModuleList(
#     (0): Linear(in_features=10, out_features=10, bias=True)
#     (1): Linear(in_features=10, out_features=10, bias=True)
#     (2): Linear(in_features=10, out_features=10, bias=True)
#   )
# )

參考選自: 知乎.

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