第四章 PyTorch 模型模組

第四章簡介

上一章介紹了資料相關的DatasetDataLoadertransforms,已經能把資料從磁片中有序的讀取並處理以及載入成batch形式。接下來就需要一個強大的模型來處理它,本章就針對模型部分進行展開,這也是深度學習最核心的地方,其中包括一個模型如何創建各網路層、各網路層如何搭建、參數如何管理與初始化、如何截取某些層的特徵圖等一系列問題。

  • 首先介紹核心類——Module
  • 再介紹常用的模組容器——Containers
  • 接著講解常用網路層的使用
  • 再學習module常用函數與hook函數應用
  • 最後介紹權重初始化方法——nn.init

4.1 Module & parameter

Module初認識

深度學習通常通過深度神經網路實現,這些網路由多個層組成,我們通常稱之為模型(Model),一個模型包含很多個網路層,多個網路層拼接構建成一個模型。在pytorch中模型是一個Module,各網路層、模組也是Module,本小節就介紹模型/模組的抽象——Module。後續不加以說明的話,模型、模組、網路層都可指代Module

Module是所有神經網路的基類,所有的模型都必須繼承於Module類,並且它可以嵌套,一個Module裡可以包含另外一個Module。要想理解清楚這句話就必須清楚瞭解一個Module是如何工作的。在第二章我們就構建了一個Module——TinnyCNN,第三章講解transform的時候也用到了Module,並且知道它的前向傳播具體執行是在forward()函數當中,其實Module定義了一些列屬性來管理模組的功能,分別用8個有序字典進行管理,分別是:

self._modules = OrderedDict()

self._parameters = OrderedDict()

self._buffers = OrderedDict()

self._backward_hooks = OrderedDict()

self._forward_hooks = OrderedDict()

self._forward_pre_hooks = OrderedDict()

self._state_dict_hooks = OrderedDict()

self._load_state_dict_pre_hooks = OrderedDict()

Copy

它們的作用分別是:

  • modules : 存儲管理nn.Module
  • parameters: 存儲管理nn.Parameter
  • buffers:存儲管理緩衝屬性,如BN層中的running_mean
  • *_hooks:存儲管理鉤子函數

講到這,大家估計很懵,因為與前面接觸到的內容完全搭不上邊。但是這些又是Module的核心知識點,為了降低大家的學習曲線斜率,在這裡暫且只需要知道一個Module有這些關鍵屬性用於管理Module,以及在哪裡找到它們——debug模式下的Protected Attributes看到它們的詳情。 

第四章 PyTorch 模型模組 4.1 Module &

forward函數

除了八大核心屬性之外,還有一個函數不得不瞭解,那就是forward函數,forward之于Module等價于getitem之於Datasetforward函數是模型每次調用的具體實現,所有的模型必須實現forward函數,否則調用時會報錯

Traceback (most recent call last):

  File "E:/pytorch-tutorial-2nd/code/chapter-2/02_COVID_19_cls.py", line 150, in <module>

    main()

  File "E:/pytorch-tutorial-2nd/code/chapter-2/02_COVID_19_cls.py", line 111, in main

    outputs = model(data)

  File "D:\Anaconda_data\envs\pytorch_1.10_gpu\lib\site-packages\torch\nn\modules\module.py", line 1102, in _call_impl

    return forward_call(*input, **kwargs)

  File "D:\Anaconda_data\envs\pytorch_1.10_gpu\lib\site-packages\torch\nn\modules\module.py", line 201, in _forward_unimplemented

    raise NotImplementedError

NotImplementedError

Process finished with exit code 1

Copy

到這裡,總結一下Module

  • Module是所有模型的基類
  • 每個module8個字典管理它的核心屬性
  • 一個module可以包含多個子module
  • 一個module相當於一個運算,必須實現forward函數

一個模型的創建

下面通過簡單的代碼慢慢去熟悉Module,回顧TinnyCNN的創建與使用,可以總結出一個模型的創建需要考慮兩個要素:

  1. 構建子模組:構建網路所需要的網路層,如卷積層,池化層,全聯接層等等
  2. 拼接子模組:在forward函數中定義需要執行的功能,即將子模組以一定的方式拼接起來,完成對資料的前向傳播

模型的創建就像搭積木,首先收集到需要的基礎部件,是三角形、正方形還是六邊形,然後以一定的方式拼接起來,如果要一個屋子就先放正方形,然後放三角形。如果需要一個汽車就先放兩個圓形,再放一個長方形。

同理,模型搭建也是,先知道有哪些網路層是需要的,那麼再init函數裡進行初始化,先讓此類獲得這些網路層可用。具體如何用,需要在forward函數中寫清楚。就像下面這個圖一樣。 

第四章 PyTorch 模型模組 4.1 Module &

知道了Module有哪些關鍵屬性,以及一個模型如何創建,下面回顧2.2小結的COVID-19分類代碼,採用debug方式觀察TinnyCNN的創建——model = TinnyCNN(2) 以及它的推理: outputs = model(data)

TinnyCNN的創建

代碼在:code/chapter-2/02_COVID_19_cls.py

模型產生實體的代碼是這行:

model = TinnyCNN(2)

Copy

我們打下中斷點,採用debug運行,進行分析如下:

  • 進入 TinnyCNN 類的init函數:這裡進行初始化,可以看到第一行就是調用父類的init函數,父類是Module,因此我們繼續step into進去看看;
  • 來到 Module類的init函數:這裡會初始化那8個有序字典,以及一些關鍵屬性,如training等。我們跳出去;
  • 回到TinnyCNN 類的init函數:父類init函數結束,就來到自訂的元件定義部分,這裡我們需要一個卷積層、一個全連接層供搭積木使用。這裡的nn.Conv2d也是一個module,大家可以自行step into進去觀察它的創建,這裡暫且知道它是一個module即可。同理,nn.Linear也是。init函數裡收集了需要搭積木的元件,下面跳出去。
  • 回到主代碼:model = TinnyCNN(2),這樣一個模型就創建好了,我們可以看到model下面就有了這些屬性: 

第四章 PyTorch 模型模組 4.1 Module &

  • 重點看紅框的三個內容,分別是convolution_layerfc_modules。前兩個沒啥好說的,是init函數中自訂的類屬性名稱,而第三個_modules什麼時候悄悄地記錄了我們自己定義的convolution_layerfc呢? 
  •  
  •  
  • 第四章 PyTorch 模型模組 4.1 Module &

 

這就需要大家瞭解一下python的基礎了,請看這行代碼:

self.convolution_layer = nn.Conv2d(1, 1, kernel_size=(3, 3))

Copy

在類屬性賦值的時候,即這行代碼中的“=”號,會調用類的__setattr__方法,在module.py1180行代碼是setatrr的實現,裡面會將“=”號右邊的值放到相應的地方去,如module會放到_modules裡,parameter會放到_parameters裡。

  • 至此,對於模型的創建流程有瞭解後,下面看看模型的推理是如何進行的,它可不是簡單的進入forward函數就完事了,中間還有複雜的協助工具,一起往下看。

TinnyCNN的推理

繼續採用debug,往下看。 先來到模型調用的地方:outputs = model(data),採用step into進入;

  • 來到Module類的call_impl函數:熟悉python的朋友就疑惑了,為什麼進入的是它而不是\_call__函數?(python規定可被調用的物件,其實現是在__call__\函數裡)其實並沒有錯,只Module類對call函數重命名了罷了,可以看到1148

__call__ : Callable[..., Any] = _call_impl

Copy

在早期版本的pytorch中還沒有這一層包裝,請各位專家指導一下為什麼採用這種方式?

_call_impl函數當中才會調用forward函數來實現資料的前向傳播,但module除了forward調用之外,還有一些協助工具,那就是一系列的hook函數的使用,這裡暫且放下,後續會展開hook函數的作用。這裡只需要關心怎麼進入forward的。如果沒有設置任何hook函數,則直接進行forward函數的調用

if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks

        or _global_forward_hooks or _global_forward_pre_hooks):

    return forward_call(*input, **kwargs)

Copy

step into 進入 return forward_call(input, *kwargs),就會發現來到了自訂的forward函數。

  • 來到TinnyCNN類的forward函數:這裡就是我們如何拼接網路層,組裝積木的地方了。

通常會在這裡調用其他module來完成資料的處理,例如使用nn.Conv2d來進行卷及操作,除了使用module物件,其它的數學運算、功能函數(如torch.nn.functionals裡的系列函數)、for迴圈等都是可以使用的。 值得說的一點是,一些啟動函數沒有可訓練參數,也不是module類,因此會在forward函數中直接調用,而不需要在init中初始化。比如 out = F.relu(self.conv1(x)) 中的F.relu

  • 最後要強調一點是:forward函數中需要注意前後層資料的格式,類似transforms的實現一樣,上一層的輸出一定要對得上下一層的輸入,否則會報錯,常見的報錯是Linear層接收到了不合適的資料。建議大家把TinnyCNNforward函數的第二行注釋掉:# x = x.view(x.size(0),-1),運行代碼並觀察錯誤,這個錯誤是大部分朋友都會遇到的:RuntimeError: mat1 and mat2 shapes cannot be multiplied (12x6 and 36x2)

到這裡一個模型的搭建以及前向推理就很清晰了,構建自己的網路只需要三步:

  1. 寫一個類繼承於Module
  2. init函數中把需要的網路層創建好
  3. forward函數中把模型如何搭建的規則寫好

Parameter

Module中有一個重要的物件——Parameter,參數。它繼承於Tensor,與Tensor差別不太大,主要作用是用來區分可訓練的參數與常規的Tensor

在這裡要做一下說明,權重、參數和超參數,它們的含義。一般情況下模型的權重就表示模型的參數,它們是可訓練的,通過反向傳播演算法不斷的更新;而超參數如卷積核大小、學習率、反覆運算次數是不能通過反向傳播演算法去更新的。很明顯Parameter就指模型的參數,如卷積層的卷積核權重和偏置,Linear層的權重和偏置,BN層的αβ等等。

Module中對於參數是採用_parameters 進行管理的,並且提供相應的api可以對module內所有參數進行調用與讀取。回顧2.2 COVID-19的優化器產生實體這行代碼:

  optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)

Copy

代碼中表示把model.parameters()返回的內容給優化器,讓優化器更新model.parameters(),從這裡可進一步理解parameter類的作用,以及各網路層它們的參數都會初始化為parameter類。

可以看看 D:\Anaconda_data\envs\pytorch_1.10_gpu\Lib\site-packages\torch\nn\modules\conv.py131行代碼

  self.weight = Parameter(torch.empty(

      (out_channels, in_channels // groups, *kernel_size), **factory_kwargs))

Copy

對默認的卷積核採用empty初始化數值,然後包裝成Parameter類。

小結

到這裡,一個簡單模型是如何創建、如何工作的我們就已經講解完了。但隨著深度神經網路的拓撲結構越來越複雜,層數越來越多,只靠上面的方法無法很好的構建神經網路,還需要借助一些容器把固定的模組封裝起來,迴圈地進行調用。下一節將介紹Module的容器,包括以下5

-

-

Sequential

A sequential container.

ModuleList

Holds submodules in a list.

ModuleDict

Holds submodules in a dictionary.

ParameterList

Holds parameters in a list.

Parameter

DictHolds parameters in a dictionary.

 

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

    HCHUNGW的部落格

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