系統學習Pytorch筆記四:模型創建Module、模型容器Containers及AlexNet網絡搭建

Pytorch官方英文文檔:https://pytorch.org/docs/stable/torch.html?
Pytorch中文文檔:https://pytorch-cn.readthedocs.io/zh/latest/

1. 寫在前面

疫情在家的這段時間,想系統的學習一遍Pytorch基礎知識,因爲我發現雖然直接Pytorch實戰上手比較快,但是關於一些內部的原理知識其實並不是太懂,這樣學習起來感覺很不踏實, 對Pytorch的使用依然是模模糊糊, 跟着人家的代碼用Pytorch玩神經網絡還行,也能讀懂,但自己親手做的時候,直接無從下手,啥也想不起來, 我覺得我這種情況就不是對於某個程序練得不熟了,而是對Pytorch本身在自己的腦海根本沒有形成一個概念框架,不知道它內部運行原理和邏輯,所以自己寫的時候沒法形成一個代碼邏輯,就無從下手。 這種情況即使背過人家這個程序,那也只是某個程序而已,不能說會Pytorch, 並且這種背程序的思想本身就很可怕, 所以我還是習慣學習知識先有框架(至少先知道有啥東西)然後再通過實戰(各個東西具體咋用)來填充這個框架。 而這個系列的目的就是在腦海中先建一個Pytorch的基本框架出來, 學習知識,知其然,知其所以然才更有意思 😉

今天是該系列的第四篇,通過前面的Pytorch數據讀取機制(DataLoader)與圖像預處理模塊(transforms),已經整理完了Pytorch的數據模塊的一些內容,今天正式進入模型模塊的學習,進行模型的創建和模型容器的知識學習。首先學習模型的創建步驟和nn.Module的相關細節, 然後學習搭建模型的容器Containers,這裏麪包括nn.Sequential, nn.ModuleList, nn.ModuleDict, 它們各自有各自的特點和應用場景。 最後我們分析一個經典的網絡AlexNet。

注意,本系列都默認已經安裝了Cuda,搭建好了Pytorch環境,如果你電腦是Windows,並且沒有裝Pytorch,那麼巧了, 我之前寫過一篇怎麼搭建環境,可以先看看 Pytorch入門+實戰系列一:Windows下的Pytorch環境手把手搭建 😉

大綱如下

  • Pytorch模型的建立
  • Pytorch的容器
  • AlexNet網絡的構建
  • 總結回顧

2. Pytorch模型的創建

在學習Pytorch的模型創建之前,我們依然是回顧一下模型創建到底是以什麼樣的邏輯存在的, 上一次,我們已經整理了機器模型學習的五大模塊,分別是數據,模型,損失函數,優化器,迭代訓練:

在這裏插入圖片描述
這裏的模型創建是模型模塊的一個分支,和數據模塊一樣,我們先看一下模型模塊的具體內容:
在這裏插入圖片描述
瞭解了上面這些框架,有利於把知識進行整合起來,到底學習的內容屬於哪一塊我們從上面的模型創建開始,學習網絡層的構建和拼接。

2.1 模型的創建步驟

在模型創建步驟之前,我們先來進行一個分析,下面是我們在人民幣二分類任務過程中用到的LeNet模型,我們可以看一下LeNet的組成。
在這裏插入圖片描述
上面是LeNet的模型計算圖,由邊和節點組成,節點就是表示每個數據, 而邊就是數據之間的運算。 我們可以發現,LeNet是一個很大的網絡,接收輸入,然後經過運算得到輸出, 在LeNet的內部,又分爲很多個子網絡層進行拼接組成,這些子網絡層之間連接配合,最終完成我們想要的運算。

所以通過上面的分析,我們可以得到構建我們模型的兩大要素:

  1. 構建子模塊(比如LeNet裏面的卷積層,池化層,全連接層)
  2. 拼接子模塊(有了子模塊,我們把子模塊按照一定的順序,邏輯進行拼接起來得到最終的LeNet模型)

下面還是以紙幣二分類的任務去看看如何進行LeNet模型的構建,依然是使用代碼調試:
在這裏插入圖片描述
上一次是數據模塊部分,這一次,我們進入模型模塊的學習,這裏可以看到,使用的模型是LeNet, 我們模型這一行打上斷點,然後進行debug調試,看看這個LeNet是怎麼搭建起來的:
在這裏插入圖片描述
程序運行到斷點,點擊步入,就進入了lenet.py文件,在這裏面有個LeNet類,繼承了nn.Module。 並且我們發現在它的__init__方法裏面實現了各個子模塊的構建。所以構建模型的第一個要素 – 子模塊的構建就是在這裏。

下面的一個問題,就是我們知道了子模塊是定義在LeNet模型__init__方法裏面,那麼這些子模塊的拼接是在哪裏定義呢? 你可能一下子會說出來, 當然是在forward裏面了, 如果真的是這樣,那說明你會用Pytorch了。 如果不知道,那麼我們可以想想, 我們定義的模型是在哪用到的呢? 只要用到了模型,必然會知道它是怎麼拼接的,所以我們就從模型的訓練那進行調試,看看是不是真的調用了forward方法進行模型拼接。
在這裏插入圖片描述
主程序的模型訓練部分,我們在outputs=net(inputs)打上斷點,因爲這裏開始是模型的訓練部分, 而這一行代碼正是前向傳播, 我們進行步入,看看這個函數的運行原理:
在這裏插入圖片描述
我們發現進入了module.py裏面的一個__call__函數, 因爲我們的LeNet是繼承於Module的。在這裏我們會發現有一行是調用了LeNet的forward方法。我們把鼠標放在這一行,然後運行到這裏,再步入,就會發現果真是調用了LeNet的forward方法:
在這裏插入圖片描述

所以,我們基於這個例子,就基本上理清楚了上面構建模型的兩個要素:

  1. 構建子模塊, 這個是在自己建立的模型(繼承nn.Module)的__init__()方法
  2. 拼接子模塊, 這個是在模型的forward()方法中

在模型的概念當中,我們有一個非常重要的概念叫做nn.Module, 我們所有的模型,所有的網絡層都是繼承於這個類的。所以我們非常有必要了解nn.Module這個類。

2.2 nn.Module類

在介紹nn.Module之前,我們先介紹與其相關的幾個模塊, 建立一個框架出來,看看Module這個模塊在以一個什麼樣的邏輯存在, 這樣的目的依然是把握宏觀。
在這裏插入圖片描述
torch.nn: 這是Pytorch的神經網絡模塊, 這裏的Module就是它的子模塊之一,另外還有幾個與Module並列的子模塊, 這些子模塊協同工作,各司其職。

今天的重點是nn.Module這個模塊, 這裏面是所有網絡層的基類,管理有關網絡的屬性。

在nn.Module中,有8個重要的屬性, 用於管理整個模型,他們都是以有序字典的形式存在着:
在這裏插入圖片描述

  • _parameters: 存儲管理屬於nn.Parameter類的屬性,例如權值,偏置這些參數
  • _modules: 存儲管理nn.Module類, 比如LeNet中,會構建子模塊,卷積層,池化層,就會存儲在modules中
  • _buffers: 存儲管理緩衝屬性, 如BN層中的running_mean, std等都會存在這裏面
  • ***_hooks: 存儲管理鉤子函數(5個與hooks有關的字典,這個先不用管)

今天學習的重點是前2個, _parameters and _modules, 下面通過LeNet模型代碼來觀察nn.Module的創建以及它對屬性管理的一個機制。這裏依然開啓調試機制, 先在pythonnet = LeNet(classes=2)前打上斷點,然後debug步入到LeNet。
在這裏插入圖片描述
我們可以看到LeNet是繼承於nn.Module的,所以LeNet也是一個Module,我們看看__init__方法中的第一行,是實現了父類函數調用的功能,在這裏也就是調用了nn.Module的初始化函數。我們進入這一個nn.Module的__init__
在這裏插入圖片描述
從Module類裏面的初始化方法中,看到了會調用_construct方法實現了8個有序字典的一個初始化操作,也就是通過第一行代碼,我們的LeNet模型就有了8個有序字典屬性,去幫助我們存儲後面的變量和參數。

我們跳回去, 繼續往下運行第二行代碼,建立第一個子模塊卷積層,
在這裏插入圖片描述
我們使用步入,進入到nn.Conv2d這個卷積層,我們會發現class Conv2d(_ConvNd):也就是這個類是繼承於_ConvNd的,在Conv2d的初始化方法中,依然是先調用父類的初始化方法, 我們進入這個類
在這裏插入圖片描述
我們發現_ConvNd也是繼承Module這個類的,並且初始化方法中也是用了super調用了父類的初始化方法。 所以這就說明Conv2d這個子模塊是一個Module,並且也有那8個有序字典的屬性

然後我們再跳回去,準備運行第三行代碼:
在這裏插入圖片描述
這時候第二行代碼運行完了,也就是我們在LeNet中建立了第一個子模塊Conv2d, 那麼我們可以看到LeNet的_modules這個有序字典中,就記錄了這個子模塊的信息。 因爲這個Conv2d也是一個Module,所以它也有8個有序字典,但是它下面的_modules裏面就是空的了,畢竟它沒有子模塊了。 但是它的_parameters這個字典裏面不是空的,因爲它有參數,這裏會記錄權重和偏置的參數信息。 還是點開上面的箭頭看看吧:
在這裏插入圖片描述
通過上面的調試,我們就看清楚了LeNet這個Module實現了一個子網絡層的構建,並且把它存儲到了_modules這個字典中進行管理。

下面通過構建第二個網絡層來觀察LeNet是如何將這個子模塊Conv2d存儲到這個_modules字典裏面的? 上面只是看了存進去了,但是我們不知道是咋存進去的啊? 這樣也很不爽, 那就繼續調試着, 繼續stepinto步入第三行的nn.Conv2d
在這裏插入圖片描述
這次我們進來,先啥也不幹, 直接跳回去
在這裏插入圖片描述
這時候我們會發現LeNet這個Module的_modules字典中依然只有conv1,沒有出現conv2, 這是因爲目前只是通過初始化函數實現了一個Conv2d的一個實例化,還沒有賦值到我們的conv2中,只是構建了這麼一個網絡層,下一步纔是賦值到conv2這個上面,所以一個子模塊的初始化和賦值是兩步走的,第一步初始化完了之後,會被一個函數進行攔截,然後再進行第二步賦值, 那麼這個函數是啥呢? 它又想幹啥呢? 我們可以再次stepinto一下,進入這個函數,這次就直接到了這個函數裏面(注意我們上面的第一次stepinto是跳到了__init__裏面去初始化)
在這裏插入圖片描述
這次到了__setattr__這就是那個攔截的函數了,我們可以看看在幹什麼事情,這個方法接收了一個value, 然後會判斷value是什麼樣的類型, 如果是參數,就會保存到參數的有序字典,如果是Module,就會保存到模型的有序字典裏面。 而這裏的conv2d
在這裏插入圖片描述
是一個Module,所以這個會存入到_modules字典中, name就是這裏的conv2。所以我們再跳回來就會發現_modules字典中有了conv2了。
在這裏插入圖片描述
這樣一步一步的運行下去。

這就是nn.Module構建屬性的一個機制了,簡單回顧一下,我們是先有一個大的Module繼承nn.Module這個基類, 比如上面的LeNet,然後這個大的Module裏面又可以有很多的子模塊,這些子模塊同樣也是繼承於nn.Module, 在這些Module的__init__方法中,會先通過調用父類的初始化方法進行8個屬性的一個初始化。 然後在構建每個子模塊的時候,其實分爲兩步,第一步是初始化,然後被__setattr__這個方法通過判斷value的類型將其保存到相應的屬性字典裏面去,然後再進行賦值給相應的成員。 這樣一個個的構建子模塊,最終把整個大的Module構建完畢。

下面對nn.Module進行總結:

  • 一個module可以包含多個子module(LeNet包含卷積層,池化層,全連接層)
  • 一個module相當於一個運算, 必須實現forward()函數(從計算圖的角度去理解)
  • 每個module都有8個字典管理它的屬性(最常用的就是_parameters_modules

3. 模型容器Containers

上面我們學習的模型的搭建過程,包括兩個要素:構建子模塊和拼接子模塊, 在搭建模型中,還有一個非常重要的概念,那就是模型容器Containers。 下面我們就來看看這是個啥東西啊?依然是先觀察整體框架:
在這裏插入圖片描述
Containers這個容器裏面包含3個子模塊,分別是nn.Sequential, nn.ModuleList, nn.ModuleDict, 下面我們一一來看一看:

3.1 nn.Sequential

這是nn.module的容器,用於按順序 包裝一組網絡層。
我們知道, 在機器學習中,特徵工程部分是一個很重要的模塊,但是到了深度學習中,這部分的重要性就弱化了,深度學習中更偏向於讓網絡自己提取特徵,然後進行分類或者回歸任務, 所以就像上面的LeNet那樣,對於圖像的特徵,我們完全不需要人爲的設計, 只需要從前面加上卷積層讓網絡自己學習提取,後面加上幾個全連接層進行分類等任務。 所以在深度學習時代,也有習慣,以全連接層爲界限,將網絡模型劃分爲特徵提取模塊和分類模塊以便更好的管理網絡。

所以我們的LeNet模型,可以把前面的那部分劃分爲特徵提取部分,後面的全連接層爲模型部分。
在這裏插入圖片描述
下面我們通過代碼來觀察,使用sequential包裝一個LeNet,看看是怎麼做的:

	class LeNetSequential(nn.Module):
	    def __init__(self, classes):
	        super(LeNetSequential, self).__init__()
	        self.features = nn.Sequential(
	            nn.Conv2d(3, 6, 5),
	            nn.ReLU(),
	            nn.MaxPool2d(kernel_size=2, stride=2),
	            nn.Conv2d(6, 16, 5),
	            nn.ReLU(),
	            nn.MaxPool2d(kernel_size=2, stride=2),)
	
	        self.classifier = nn.Sequential(
	            nn.Linear(16*5*5, 120),
	            nn.ReLU(),
	            nn.Linear(120, 84),
	            nn.ReLU(),
	            nn.Linear(84, classes),)
	
	    def forward(self, x):
	        x = self.features(x)
	        x = x.view(x.size()[0], -1)
	        x = self.classifier(x)
	        return x

可以看到,我們的LeNet在這裏分成了兩大部分,第一部分是features模塊,用於特徵提取, 第二部分是classifier部分,用於分類。 每一部分都是各種網絡的堆疊,然後用sequential包裝起來。 然後它的forward函數也比較簡單, 只需要features處理輸出,然後形狀變換,然後classifier就搞定。

下面通過代碼調試,觀察通過Sequential搭建的LeNet裏面的一些屬性,並且看看Sequential是一個什麼樣的機制:

在這裏插入圖片描述
這次調試應該輕車熟路,打斷點,debug, 步入即可,這樣會到了LeNetSequential這個類裏面去, 我們通過super進行初始化,因爲這個繼承的也是nn.Module,所以肯定也是8個屬性字典,這個就不管了, stepover一步步的往下,到一個Sequential完成的位置停下來

在這裏插入圖片描述
然後,stepinto->stepout->stepinto, 進入container.py的Sequential這個類。會發現class Sequential(Module): , 這說明Sequential也是繼承與Module這個類的,所以它也會有那8個參數字典。
在這裏插入圖片描述
這樣,一直stepover, 直到第5個子模塊完事,這樣一個Sequential就建完了。 我們stepout回到主程序,然後往下執行,把第一個Sequential構建完畢。
在這裏插入圖片描述
下面的那個Sequential構建其實原理和第一個的一樣了,所以不再進行調試查看,簡單梳理一下Sequential的構建機制, 這個依然是繼承Module類,所以也是在__init__方法中先調用父類去初始化8個有序字典,然後再__init__裏面完成各個子模塊的參數存儲。 這樣,子模塊構建完成,還記得我們模型搭建的第一步嗎?

接下來,就是拼接子模塊, 這個是通過前向傳播函數完成的,所以下面我們看看Sequential是怎麼進行拼接子模塊的,依然是調試查看(這部分調試居多,因爲這些內部機制,光靠文字寫是沒法寫的,與其寫一大推迷迷糊糊,還不如截個圖來的痛快), 我們看前向傳播:
在這裏插入圖片描述
這時候步入這個net函數, 看看前向傳播的實現過程,

在這裏插入圖片描述
步入之後,就到了module.py的__call__函數, 就是在這裏面調用前向傳播的:
在這裏插入圖片描述
步入之後,我們跳到了LeNetSequential類的前向傳播函數,我們可以發現,完成第一個Sequential,也就是features的前向傳播,只用了一句代碼x = self.features(x),這句代碼竟然實現了6個子模塊的前向傳播,這是怎麼做到的呢? 在這行代碼步入一探究竟:
在這裏插入圖片描述
由於self.features是一個Sequential, 而Sequential也是繼承於Module,所以我們步入之後,依然是會跳到module.py的__call__函數, 我們還是stepout到前向傳播的那一行,然後步入看看Sequential的前向傳播。
在這裏插入圖片描述
從上面可以看出,在Sequential的前向傳播裏面,會根據之前定義時候的那個_module那個有序的參數字典,這裏面就是存的每個層的信息還記得嗎? 前向傳播的時候,就是遍歷這個東西, 得到每個子模塊,進行處理。 這裏要注意一下,這是個串聯機制,也就是上一層的輸出會是下一層的輸入。所以要注意上下模型輸入和輸出的對應關係,數據格式,形狀大小不要出錯。

這就是Sequential的forward的機制運行的步驟了,所以通過調試還是能把這個原理了解的很清楚的,下面的self.classifier的前向傳播也是如此,這裏不再過多贅述。

所以模型的拼接這塊也簡單梳理一下, 這一塊的拼接完全是Sequential自己實現的, 在Sequential定義的時候,會把每一層的子模塊的信息存入到它的_modules這個有序字典中,然後前向傳播的時候, Sequential的forward函數就會遍歷這個字典,把每一層拿出來然後處理數據,這是一個串行,上一層的輸出正好是下一層的輸入。這樣通過這個迭代就可以完成前向傳播過程。

下面完成調試,得到我們建好的LeNetSequential最終的結構:
在這裏插入圖片描述
在上一次的學習中,我們會發現網絡層是有名字的,比如conv1, conv2,這種,這樣可以通過名字去索引網絡層, 而這裏成了序號了,如果網絡層成千上百的話,很難通過序號去索引網絡層,這時候,我們可以對網絡層進行一個命名。 也就是第二種Sequential的使用方法:

	class LeNetSequentialOrderDict(nn.Module):
	    def __init__(self, classes):
	        super(LeNetSequentialOrderDict, self).__init__()
	
	        self.features = nn.Sequential(OrderedDict({
	            'conv1': nn.Conv2d(3, 6, 5),
	            'relu1': nn.ReLU(inplace=True),
	            'pool1': nn.MaxPool2d(kernel_size=2, stride=2),
	
	            'conv2': nn.Conv2d(6, 16, 5),
	            'relu2': nn.ReLU(inplace=True),
	            'pool2': nn.MaxPool2d(kernel_size=2, stride=2),
	        }))
	
	        self.classifier = nn.Sequential(OrderedDict({
	            'fc1': nn.Linear(16*5*5, 120),
	            'relu3': nn.ReLU(),
	
	            'fc2': nn.Linear(120, 84),
	            'relu4': nn.ReLU(inplace=True),
	
	            'fc3': nn.Linear(84, classes),
	        }))
	
	    def forward(self, x):
	        x = self.features(x)
	        x = x.view(x.size()[0], -1)
	        x = self.classifier(x)
	        return x

這裏面Sequential包裝的就是一個有序的字典, 字典中是網絡名:網絡層的形式。通過這個就可以對每一層網絡進行命名, 那它是什麼時候進行命名的呢? 當然還是在定義的時候,上面的某張圖片裏面我其實已經埋好了伏筆:
在這裏插入圖片描述
就是它了, 我們會看到, Sequential的初始化方法裏面有個判斷的,if後面其實就是判斷傳入的是不是個有序字典, 我們上次Sequential裏面直接是各個層,所以當時不滿足if,跳到了下面的else,那裏面是self.add_module(str(idx), module)這個很清楚了吧,就是不是有序字典,說明我們沒命名,那麼就用數字索引命名,然後加入到_module有序參數字典中。 而這次我們是構建了個有序字典,那麼就應該走if, 這裏面是self.add_module(key, module), 這一次我們命名了,所以就用我們的命名,把key(網絡名):value(網絡層)存入到_module有序參數字典中。這樣,我們搭建的網絡層就會有名字了。

下面對我們的Sequential進行一個總結:nn.Sequential是nn.module的容器, 用於按順序包裝一組網絡層

  • 順序性: 各網絡層之間嚴格按照順序構建,這時候一定要注意前後層數據的關係
  • 自帶forward(): 自答的forward裏,通過for循環依次執行前向傳播運算

3.2 nn.ModuleList

nn.ModuleList是nn.module的容器, 用於包裝一組網絡層, 以迭代方式調用網絡層, 主要方法:

  • append(): 在ModuleList後面添加網絡層
  • extend(): 拼接兩個ModuleList
  • insert(): 指定在ModuleList中位置插入網絡層

我們可以發現,這個方法的作用其實類似於我們的列表,只不過元素換成網絡層而已,下面我們學習ModuleList的使用,我們使用ModuleList來循環迭代的實現一個20個全連接層的網絡的構建。

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

    def forward(self, x):
        for i, linear in enumerate(self.linears):
            x = linear(x)
        return x

這一個就比較簡單了, ModuleList構建網絡層就可以使用列表生成式構建,然後前向傳播的時候也是遍歷每一層,進行計算即可。我們下面調試就是看看這個ModuleList的初始化,是怎麼把20個全連接層給連起來的。
在這裏插入圖片描述
可以看到這個modules是一個列表,裏面就是這20個全連接層。 前向傳播也比較簡單了,用的for循環獲取到每個網絡層,這裏就不調試了。

這樣就完成了一個20層的全連接層的網絡的實現。藉助nn.ModuleList只需要一行代碼就可以搞定。這就是nn.ModuleList的使用了,最重要的就是可以迭代模型,索引模型。

3.3 nn.ModuleDict

nn.ModuleDict是nn.module的容器, 用於包裝一組網絡層, 以索引方式調用網絡層, 主要方法:

  • clear(): 清空ModuleDict
  • items(): 返回可迭代的鍵值對(key-value pairs)
  • keys(): 返回字典的鍵(key)
  • values(): 返回字典的值(value)
  • pop(): 返回一對鍵值對, 並從字典中刪除

可以通過ModuleDict實現網絡層的選取, 我們看下面的代碼:

class ModuleDict(nn.Module):
    def __init__(self):
        super(ModuleDict, self).__init__()
        self.choices = nn.ModuleDict({
            'conv': nn.Conv2d(10, 10, 3),
            'pool': nn.MaxPool2d(3)
        })

        self.activations = nn.ModuleDict({
            'relu': nn.ReLU(),
            'prelu': nn.PReLU()
        })

    def forward(self, x, choice, act):
        x = self.choices[choice](x)
        x = self.activations[act](x)
        return x
    
net = ModuleDict()
fake_img = torch.randn((4, 10, 32, 32))
output = net(fake_img, 'conv', 'relu')    # 在這裏可以選擇我們的層進行組合
print(output)

這個理解起來應該比較好理解了, 前面通過self.choices這個ModuleDict可以選擇卷積或者池化, 而下面通過self.activations這個ModuleDict可以選取是用哪個激活函數, 這個東西在選擇網絡層的時候挺實用,比如要做時間序列預測的時候,我們往往會用到GRU或者LSTM, 我們就可以通過這種方式來對比哪種網絡的效果好。 而具體選擇哪一層是前向傳播那完成,會看到多了兩個參數。也是比較簡單的。

到這裏我們就學習了三個容器, nn.Sequential, nn.ModuleList, nn.ModuleDict。 下面總結一下它們的應用場合:
在這裏插入圖片描述

下面就來研究一個網絡模型了,這個是Pytorch提供的, 叫做AlexNet網絡模型。

4. AlexNet構建

這是一個劃時代的卷積神經網絡,2012年在ImageNet分類任務中獲得了冠軍,開創了卷積神經網絡的新時代。 AlexNet的特點如下:

  • 採用ReLu: 替換飽和激活函數, 減輕梯度消失
  • 採用LRN(Local Response Normalization): 對數據歸一化,減輕梯度消失(後面被Batch歸一化取代了)
  • Dropout: 提高全連接層的魯棒性,增加網絡的泛化能力
  • Data Augmentation: TenCrop, 色彩修改

下面就看看AlexNet的結構:
在這裏插入圖片描述
下面看看AlexNet的源代碼:

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

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

它這個就是用Sequential進行搭建的,分三部分, 第一部分是一個Sequential,由一系列的卷積池化模塊構成,目的是提取圖像的特徵, 然後是一個全局的池化層把特徵進行整合,最後有一個Sequential是全連接層組成的,用於模型的分類。 這樣就完成了AlexNet網絡的搭建, forward函數彙總也是非常簡單了,這裏就不再詳細贅述了。

到這裏就基本上完成了這篇文章的內容,不知道你發現了嗎? 如果你理解了這篇文章的內容,就會發現理解像AlexNet這樣的網絡構建非常簡單, 當然在Pytorch的models模塊中還有很多其他的經典網絡,像googlenet, vgg, ResNet等很多,學習了今天的知識之後,這些模型的構建都可以去查看了。 不管這些模型多麼複雜, 構建依然還是我們的nn.Module, Sequential, ModuleList, ModuleDict的這些內容去構建的,所以我們依然可以看懂這些網絡的邏輯。
在這裏插入圖片描述

5. 總結

今天的學習內容結束, 這次的內容有點多,並且還非常的重要,所以依然是快速梳理總結, 今天的內容主要是分爲3大塊, 第一塊就是Pytorch模型的構建步驟有兩個子模塊的構建和拼接, 然後就是學習了非常重要的一個類叫做nn.Module,這個是非常重要的,後面的模型搭建中我們都得繼承這個類,這就是祖宗級別的人物了。這裏面有8個重要的參數字典,其中_parameters_modules更是重中之重,所以以LeNet爲例,通過代碼調試的方式重點學習了LeNet的構建過程和細節部分。

第二塊是我們的模型容器Containers部分,這裏面先學習了nn.Sequential, 這個是順序搭建每個子模塊, 常用於block構建,依然是通過代碼調試看了它的初始化和自動前向傳播機制。 然後是nn.ModuleList, 這個類似於列表,常用於搭建結構相同的網絡子模塊, 特點就是可迭代。 最後是nn.ModuleDict, 這個的特點是索引性,類似於我們的python字典,常用於可選擇的網絡層。

第三塊就是根據上面的所學分析了一個非常經典的卷積神經網絡AlexNet的構建過程, 當然也可以分析其他的經典網絡構建的源代碼了。當然,只是可以去看懂網絡結構的代碼,不要奢求一下子就會寫,閉着眼就能自己敲出來, 如果你看一遍就能敲出這麼複雜的網絡結構,那就是超神了,祝賀你。 反正我目前是做不到, 如果想達到用Pytorch寫神經網絡的地步,就需要後面多多實踐,還是那句話,無他,唯手熟爾!😉

下面依然是一張思維導圖把知識拎起來:
在這裏插入圖片描述
今天的知識就到這裏,下一次我們就從這一次的基礎上更進一步,學習幾個比較重要的子模塊,比如卷積,池化, 全連接等。繼續rush!😉

PS: 本次學習視頻來自B站https://www.bilibili.com/video/BV1EE41177ot?from=search&seid=13894259699897815176, 時間長了有可能被和諧了。 所有代碼鏈接:

鏈接:https://pan.baidu.com/s/1c5EYdd0w8j6w3g54KTxJJA
提取碼:k7rh

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