MegEngine Python 層模塊串講(中)
在中,我們簡單介紹了在?MegEngine imperative
?中的各模塊以及它們的作用。對于新用戶而言可能不太了解各個(gè)模塊的使用方法,對于模塊的結(jié)構(gòu)和原理也是一頭霧水。Python
模塊串講將對?MegEngine
?的?python
?層相關(guān)模塊分別進(jìn)行更加深入的介紹,會(huì)涉及到一些原理的解釋和代碼解讀。Python
?層模塊串講共分為上、中、下三個(gè)部分,本文將介紹 Python
?層的?functional
、module
?和?optimizer
?模塊。理解并掌握這幾個(gè)模塊對于高效搭建神經(jīng)網(wǎng)絡(luò)非常重要。
Python 層計(jì)算接口 —— functional 模塊
我們在定義網(wǎng)絡(luò)結(jié)構(gòu)時(shí)經(jīng)常需要包含一些計(jì)算操作,這些計(jì)算操作就定義在?functional
?中。
functional
?中實(shí)現(xiàn)了各類計(jì)算函數(shù),包含對很多?op
?的封裝,供實(shí)現(xiàn)模型時(shí)調(diào)用。
functional
?中有些?op
?完全是由?Python
?代碼實(shí)現(xiàn),有些則需要調(diào)用?C++
?接口完成計(jì)算(沒錯(cuò),這里的計(jì)算就需要?MegDNN kernel
)。對于后者,需要有機(jī)制確保我們的實(shí)現(xiàn)能夠轉(zhuǎn)發(fā)到底層正確執(zhí)行,所以你在?functional
?的許多?op
?實(shí)現(xiàn)中會(huì)看到?builtin
?和?apply
:
builtin
builtin
?封裝了所有的?op
,我們在?functional
?中通過?builtin.SomeOp(param)
?的方式獲得一個(gè)算子?SomeOp
,param
?表示獲取?SomeOp
?需要的參數(shù)。apply
通過?
builtin
?獲取到?op
?后,需要調(diào)用?apply
?接口來調(diào)用?op
?的底層實(shí)現(xiàn)進(jìn)行計(jì)算。apply
?是在?Python
?層調(diào)用底層?op
?的接口,apply
?的第一個(gè)參數(shù)是?op
(通過?builtin
?獲得),后面的參數(shù)是執(zhí)行這個(gè)?op
?需要的參數(shù),都為?Tensor
。在?imperative
?中?op
?計(jì)算通過?apply(op, inputs)
?的方式向下傳遞并最終調(diào)用到?MegDNN?
中的?kernel
。
Functional
?中的許多?op
?都需要通過?builtin
?和?apply
?調(diào)用底層?MegDNN
?的?op
?來進(jìn)行計(jì)算操作。然而在實(shí)際的計(jì)算發(fā)生前,很多時(shí)候需要在?Python
?層做一些預(yù)處理。
來看下面這個(gè):
這里?concat
?方法先對輸入?tensor
?數(shù)量、device
?在?python
?層做了一些預(yù)處理,然后才調(diào)用?builtin
?和?apply
?向下轉(zhuǎn)發(fā)。
而對于??這個(gè)?op
,無需預(yù)處理直接向下傳遞即可:
對于實(shí)現(xiàn)了對應(yīng)?kernel
?的?op
,其在?imperative
?層的實(shí)現(xiàn)通常非常的短。
上面?concat
?和?diag
?的?apply
?調(diào)用會(huì)進(jìn)入??(https://github.com/MegEngine/MegEngine/blob/master/imperative/python/src/tensor.cpp#L79)函數(shù),并通過解析?Python
?中的參數(shù),將它們轉(zhuǎn)換成?C++
?中的對應(yīng)類型,然后調(diào)用?imperative::apply
,進(jìn)入?dispatch(https://github.com/MegEngine/MegEngine/blob/master/imperative/python/megengine/functional/tensor.py#L995)
?層。
部分?functional
?的?op
?不直接調(diào)用?py_apply
?而是有對應(yīng)的?cpp
?實(shí)現(xiàn),比如?:
這樣的實(shí)現(xiàn)往往是需要在調(diào)用?py_apply
?之前做一些預(yù)處理,但使用?python
?實(shí)現(xiàn)性能較差,所以我們將相關(guān)預(yù)處理以及?py_apply
?的邏輯在?C++
?層面實(shí)現(xiàn)。
本文主要介紹?Python
?層的方法,關(guān)于?C++
?部分的實(shí)現(xiàn)會(huì)在之后的文章進(jìn)行更深入的介紹。
在這里我們只需要知道,functional
?中包裝了所有關(guān)于?Tensor
?計(jì)算相關(guān)的接口,是所有計(jì)算的入口,實(shí)際的計(jì)算操作通常會(huì)被轉(zhuǎn)發(fā)到更底層的?C++
?實(shí)現(xiàn)。
用戶可以參考獲取所有?functional
?中的方法介紹。
模塊結(jié)構(gòu)的小型封裝版本 —— module 模塊
神經(jīng)網(wǎng)絡(luò)模型是由對輸入數(shù)據(jù)執(zhí)行操作的各種層(Layer
),或者說模塊(Module
)組成。
?用來定義網(wǎng)絡(luò)模型結(jié)構(gòu),用戶實(shí)現(xiàn)算法時(shí)要用組合模塊?Module (megengine/module)
?的方式搭建模型,定義神經(jīng)網(wǎng)絡(luò)時(shí)有些結(jié)構(gòu)經(jīng)常在模型中反復(fù)使用,將這樣的結(jié)構(gòu)封裝為一個(gè)?Module
,既可以減少重復(fù)代碼也降低了復(fù)雜模型編碼的難度。
一個(gè)?module
?類主要有兩類函數(shù):
__init__
:構(gòu)造函數(shù),定義了模型各個(gè)層的大小。用戶自定義的?Module
?都源自基類?,所以在構(gòu)造函數(shù)中一定要先調(diào)用?super().__init__()
,設(shè)置?Module
?的一些基本屬性。模型要使用的所有層 / 模塊都需要在構(gòu)造函數(shù)中聲明。
forward
:定義模型結(jié)構(gòu),實(shí)現(xiàn)前向傳播,也就是將數(shù)據(jù)輸入模型到輸出的過程。這里會(huì)調(diào)用?Functional (megengine/functional)
?中的函數(shù)進(jìn)行前向計(jì)算,forward
?表示的是模型實(shí)現(xiàn)的邏輯。
來看一個(gè)例子:
__init__
?表明模型中有一個(gè)參數(shù)?a
,它的初值是固定的,forward
?中實(shí)現(xiàn)了具體的計(jì)算邏輯,也就是對傳入的參數(shù)與?a
?進(jìn)行乘法運(yùn)算。
對于一些更復(fù)雜的計(jì)算操作(如卷積、池化等)就需要借助?functional
?中提供的方法來完成。
除了?__init__
?和?forward
,基類?class Module
?提供了很多屬性和方法,常用的有:
def buffers(self, recursive: bool = True, **kwargs) -> Iterable[Tensor]
:返回一個(gè)可迭代對象,遍歷當(dāng)前模塊的所有?buffers
;def parameters(self, recursive: bool = True, **kwargs) -> Iterable[Parameter]
:返回一個(gè)可迭代對象,遍歷當(dāng)前模塊所有的?parameters
;def tensors(self, recursive: bool = True, **kwargs) -> Iterable[Parameter]
:返回一個(gè)此?module
?的?Tensor
?的可迭代對象;def children(self, **kwargs) -> "Iterable[Module]"
:返回一個(gè)可迭代對象,該對象包括屬于當(dāng)前模塊的直接屬性的子模塊;def named_buffers(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Tensor]]
:返回當(dāng)前模塊中?key
?與?buffer
?的鍵值對的可迭代對象,這里?key
?是從該模塊至?buffer
?的點(diǎn)路徑(dotted path
);def named_parameters(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Parameter]]
:返回當(dāng)前模塊中?key
?與?parameter
?的鍵值對的可迭代對象,這里?key
?是從該模塊至?buffer
?的點(diǎn)路徑(dotted path
);def named_tensors(self, prefix: Optional[str] = None, recursive: bool = True, **kwargs) -> Iterable[Tuple[str, Tensor]]
:返回當(dāng)前模塊中?key
?與?Tensor
(buffer + parameter
) 的鍵值對的可迭代對象,這里?key
?是從該模塊至?Tensor
?的點(diǎn)路徑(dotted path
);def named_modules(self, prefix: Optional[str] = None, **kwargs) -> "Iterable[Tuple[str, Module]]"
:返回一個(gè)可迭代對象,該對象包括當(dāng)前模塊自身在內(nèi)的其內(nèi)部所有模塊組成的?key-module
?鍵-模塊對,這里?key
?是從該模塊至各子模塊的點(diǎn)路徑(dotted path
);def named_children(self, **kwargs) -> "Iterable[Tuple[str, Module]]"
:返回一個(gè)可迭代對象,該對象包括當(dāng)前模塊的所有子模塊(submodule
)與鍵(key
)組成的?key-submodule
?對,這里?key
?是子模塊對應(yīng)的屬性名;def state_dict(self, rst=None, prefix="", keep_var=False)
:返回模塊的狀態(tài)字典,狀態(tài)字典是一個(gè)保存當(dāng)前模塊所有可學(xué)習(xí)的?Tensor
?(buffer + parameter
)的字典。出于兼容性考慮,字典中的?value
?的數(shù)據(jù)結(jié)構(gòu)類型為?numpy.ndarray
?(而不是?Tensor
),并且不可修改,是只讀的;def load_state_dict(self, state_dict: Union[dict, Callable[[str, Tensor], Optional[np.ndarray]]], strict=True, )
:加載一個(gè)模塊的狀態(tài)字典,這個(gè)方法常用于模型訓(xùn)練過程的保存與加載。
值得一提的是,Parameters
?和?Buffer
?都是與?Module
?相關(guān)的?Tensor
,它們的區(qū)別可以理解為:
Parameter
?是模型的參數(shù),在訓(xùn)練過程中會(huì)通過反向傳播進(jìn)行更新,因此值是可能改變的,常見的有?weight
、bias
?等;Buffer
?是模型用到的統(tǒng)計(jì)量,不會(huì)在反向傳播過程中更新,常見的有?mean
、var
?等。
在?MegEngine
?的??下可以看到已經(jīng)有很多常見的?module
?實(shí)現(xiàn),用戶實(shí)現(xiàn)自己的模型可以根據(jù)需要復(fù)用其中的模塊。
使用 optimizer 模塊優(yōu)化模型參數(shù)
MegEngine
?中的??模塊實(shí)現(xiàn)了基于各種常見優(yōu)化策略的優(yōu)化器,為用戶提供了包括?SGD
、ADAM
?在內(nèi)的常見優(yōu)化器實(shí)現(xiàn)。這些優(yōu)化器能夠基于參數(shù)的梯度信息,按照算法所定義的策略執(zhí)行更新。
大部分情況下用戶不會(huì)自己實(shí)現(xiàn)優(yōu)化器,這里以?SGD
?優(yōu)化器為例,優(yōu)化神經(jīng)網(wǎng)絡(luò)模型參數(shù)的基本流程如下:
這里我們構(gòu)造了一個(gè)優(yōu)化器?
optimizer
,傳入?yún)?shù)是?model
?需要被優(yōu)化的?Parameter
,和?learning rate
;優(yōu)化器通過執(zhí)行?
step()
?方法進(jìn)行一次優(yōu)化;優(yōu)化器通過執(zhí)行?
clear_grad()
?方法清空參數(shù)梯度。為何要手動(dòng)清空梯度?
梯度管理器執(zhí)行?
backward()
?方法時(shí), 會(huì)將當(dāng)前計(jì)算所得到的梯度以累加的形式積累到原有梯度上,而不是直接做替換。 因此對于新一輪的梯度計(jì)算,通常需要將上一輪計(jì)算得到的梯度信息清空。 何時(shí)進(jìn)行梯度清空是由人為控制的,這樣可允許靈活進(jìn)行梯度的累積。
用戶也可以繼承?,實(shí)現(xiàn)自己的優(yōu)化器。
以上就是關(guān)于?functional,Module,optimizer
?的模塊的基本介紹,這幾個(gè)模塊是我們搭建模型訓(xùn)練的最核心的部分,熟悉這部分后,我們就可以高效搭建神經(jīng)網(wǎng)絡(luò)了。
附
更多 MegEngine 信息獲取,您可以:查看文檔:https://www.megengine.org.cn/doc/stable/zh/
GitHub 項(xiàng)目: https://github.com/MegEngine
加入 MegEngine 用戶交流 QQ 群:1029741705
歡迎參與 MegEngine 社區(qū)貢獻(xiàn),成為?Awesome MegEngineer:https://www.megengine.org.cn/community-AMGE,榮譽(yù)證書、定制禮品享不停。