4.2 Module容器——Containers

容器的概念出現在日常生活的方方面面,每天喝水的杯子用來裝一些水,書包用來裝一些辦公用品,衣櫃用來裝一些衣服。因此,我們可以很容易地抽象出容器的概念,它用於將一些物品放置在某個地方,並進行有效的管理和使用。

在深度學習模型裡面,有一些網路層需要放在一起使用,如 conv + bn + relu 的組合。Module的容器是將一組操作捆綁在一起的工具,在pytorch官方文檔中把Module也定義為Containers,或許是因為“Modules can also contain other Modules”

對於Module類可查看4.1小結,這裡詳細介紹兩個常用的容器SequentialModuleList,同時介紹ModuleDictParameterListParameterDict

Sequential

sequentialpytorch裡使用最廣泛的容器,它的作用是將一系列網路層按固定的先後順序串起來,當成一個整體,調用時資料從第一個層按循序執行到最後一個層。回顧一下transformsCompose就可以體會到按順序的含義了。

sequential可以直接傳module,也可以傳OrderedDictOrderedDict可以讓容器裡的每個module都有名字,方便調用。

請看兩段官方代碼:

model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )
 
# Using Sequential with OrderedDict. This is functionally the
# same as the above code
model = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ]))

來看一個實際案例:

AlexNet是新一代CNN的開山之作,也是這一輪深度學習潮流裡,電腦視覺任務的開山之作。對於現代CNN,通常會把前面的卷積層、池化層稱為特徵提取部分,最後的全連接層當作分類器,這一點在代碼編寫上將有所體現。

例如,在D:\Anaconda_data\envs\pytorch_1.10_gpu\Lib\site-packages\torchvision\models\alexnet.py 下的AlexNet,它把前面的卷積和池化都放到Sequential這個容器當中,並且命名為self.features。在最後還有一個名為self.classifierSequential容器,它包含3Linear層及啟動函數、Dropout。這裡正如下圖所示,把模型大卸兩塊。

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

​ <<AI人工智慧 PyTorch自學>> 4.2 Modul

Sequential的調用

Sequential容器把一堆module包起來之後,它在forward中是如何使用的呢? 下麵請看配套代碼,主要採用debug模式,觀察Alexnetforward中,兩個Sequential容器是如何forward的,同時也要看看Alexnet這個模型的屬性。

  • output = model(fake_input) 設置中斷點,step into,然後進入熟悉的_call_impl,關於module內部的代碼這裡直接略過,不熟悉的朋友請回到4.1小節閱讀。
  • 這裡直接跳到Alexnet類的forward函數,第一行就是執行x = self.features(x),繼續step into觀察這個sequential容器是如何工作的,進入它
  • 來到了Module類下的_call_impl函數:沒錯,又來到了_call_impl,因為sequential它也是一個module,因此在調用self.features的時候,會進入_call_impl 下面需要大家有耐心的進入self.featuresforward函數,其實就是1102行進去;
  • 來到Sequential類的forward函數,它十分簡潔,如下所示:
def forward(self, input):
​    for module in self:
​        input = module(input)
​    return input

這段代碼是不是十分的熟悉呢? transforms當中也是這樣去實現一個Compose裡的變換的。

從此處可知道,Sequential類的功能是將一系列網路層按固定的先後順序串起來,當成一個整體,調用時資料從第一個層按循序執行到最後一個層,各層之間的資料必須能對接起來。

接著回到 Alexnetforward函數下,觀察一下Alexnet這個module的屬性

<<AI人工智慧 PyTorch自學>> 4.2 Modul

重點看_modules屬性,它有3key-value,其中有2個是Sequential類,因為Sequential屬於module類,繼續展開一個Sequential來看看

<<AI人工智慧 PyTorch自學>> 4.2 Modul

可以看到該容器下的一系列網路層,並且是排了序的,這些對於後續理解網路結構、理解網路權重載入的key是十分重要的

ModuleList

ModuleList 是將各個網路層放到一個列表中,便於反覆運算的形式調用。

ModuleListpython List的區別

這裡注意是列表而不是列表,因為ModuleList管理的modulepythonList管理的module是有不同的,大家是否還記得modulesetattr函數?在那裡會對類屬性進行判斷管理,只有ModuleList裡的網路層才會被管理,如果是List裡的網路層則不會被管理,也就不能反覆運算更新了。

ModuleList 使用示例

假設要構建一個10層的全連接網路,如果用Sequential,那就要手寫10nn.Linear,而用ModuleList是這樣的:

    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
            # self.linears = [nn.Linear(10, 10) for i in range(10)]    # 觀察model._modules,將會是空的
 
        def forward(self, x):
            for sub_layer in self.linears:
                x = sub_layer(x)
            return x

需要對python list進行一個ModuleList封裝,這樣才可以在model_modules屬性下看到創建的10Linear層。

推薦大家看看class ModuleList(Module)的實現,裡邊並不會像Sequential那樣提供forward,即管理的網路層由使用者自行調用,可以for迴圈全用,也可以通過if判斷,有條件的選擇部分網路層使用。同時ModuleList也提供了類似List的方法,insert\append\extend等。

ModuleDict

ModuleList可以像pythonList一樣管理各個module,但對於索引而言有一些不方便,因為它沒有名字,需要記住是第幾個元素才能定位到指定的層,這在深度神經網路中有一點不方便。

ModuleDict就是可以像pythonDict一樣為每個層賦予名字,可以根據網路層的名字進行選擇性的調用網路層。

請看代碼

    class MyModule2(nn.Module):
        def __init__(self):
            super(MyModule2, self).__init__()
            self.choices = nn.ModuleDict({
                    'conv': nn.Conv2d(3, 16, 5),
                    'pool': nn.MaxPool2d(3)
            })
            self.activations = nn.ModuleDict({
                    'lrelu': nn.LeakyReLU(),
                    'prelu': nn.PReLU()
            })
 
        def forward(self, x, choice, act):
            x = self.choices[choice](x)
            x = self.activations[act](x)
            return x

ParameterList & ParameterDict

除了Module有容器,Parameter也有容器。與ModuleListModuleDict類似的,Paramter也有ListDict,使用方法一樣,這裡就不詳細展開,可以參考Module的容器。

可以看兩段官方文檔代碼感受一下

class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.params = nn.ParameterDict({
                'left': nn.Parameter(torch.randn(5, 10)),
                'right': nn.Parameter(torch.randn(5, 10))
        })
 
    def forward(self, x, choice):
        x = self.params[choice].mm(x)
        return x
 
# ParameterList
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.params = nn.ParameterList([nn.Parameter(torch.randn(10, 10)) for i in range(10)])
 
    def forward(self, x):
        # ParameterList can act as an iterable, or be indexed using ints
        for i, p in enumerate(self.params):
            x = self.params[i // 2].mm(x) + p.mm(x)
        return x

小結

隨著深度神經網路拓撲結構越來越複雜,網路模組多,雜,亂,因此需要Module容器來管理、組織各個網路層,便於forward函數中調用。

使用頻率最高的是Sequential,其次是ModuleList,其餘的均為進階用法,在各類魔改網路中才會涉及。

這裡深刻理解Sequential的機制、理解一個module是如何把Sequential裡的module管理到自己的_modules屬性中,對於後續使用模型是非常重要的。

熟悉了Module類,各種容器封裝,下一小節將介紹一些常用的網路層,如卷積、池化、全連接、啟動函數等。

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 HCHUNGW 的頭像
    HCHUNGW

    HCHUNGW的部落格

    HCHUNGW 發表在 痞客邦 留言(0) 人氣()