pytorch中forwod函數(shù)在父類中的調(diào)用方式解讀
pytorch forwod函數(shù)在父類中的調(diào)用
問題背景
最近在研究Detetron2的代碼結(jié)構(gòu)時(shí),發(fā)現(xiàn)有些網(wǎng)絡(luò)代碼里面沒有forward函數(shù),卻照樣可以推理,深入挖掘之后,發(fā)現(xiàn)其將forword函數(shù)都寫在了同一個(gè)父類里面。
這就牽涉到了下面這個(gè)問題,子類中沒有forward函數(shù),只有父類中有forward函數(shù),這樣能不能正常調(diào)用網(wǎng)絡(luò)。
import torch.nn as nn class Network1(nn.Module): ? ? def __init__(self): ? ? ? ? super().__init__() ? ? def forward(self,x): ? ? ? ? return x class Network2(Network1): ? ? def __init__(self): ? ? ? ? super().__init__() data = [1,2,3] model = Network2().eval() output = model(data) print(output)
輸出結(jié)果如下:
[1,2,3]
pytorch forward方法調(diào)用原理
在使用Pytorch自定義網(wǎng)絡(luò)模型的時(shí)候,我們需要繼承nn.Module這個(gè)類,然后定義forward方法來實(shí)現(xiàn)前向轉(zhuǎn)播。
如下圖的一個(gè)自定義的網(wǎng)絡(luò)模型
首先該網(wǎng)絡(luò)模型的初始化方法__init__需要繼承父類nn.Module的初始化方法,用語句super().init()實(shí)現(xiàn)。
并在初始化方法里面,定義了卷積、BN、激活函數(shù)等。接下來定義forward方法,將整個(gè)網(wǎng)絡(luò)連接起來。
有了上面的定義,我們可以實(shí)例化一個(gè)對(duì)象,例如:
fire2 = Fire(96, 128,16,64,64)
實(shí)現(xiàn)前向傳播,使用 y= fire2(x) 其中x是該網(wǎng)絡(luò)的輸入,y是輸出,實(shí)現(xiàn)了forward方法的額功能。
這里就會(huì)有人感到奇怪,forward作為Fire這個(gè)類的方法,使用的時(shí)候不應(yīng)該是 y= fire2.forward(x)嗎。
這里為什么一個(gè)類的實(shí)例可以當(dāng)做方法直接使用?這是因?yàn)檫@個(gè)Fire類繼承的父類nn.Module里面定義了__call__方法。
一個(gè)類如果定義了__call__方法,則該類的實(shí)例就可以作為一個(gè)方法那樣直接使用。
例如下列代碼[1]
class A(): def __call__(self): print('i can be called like a function') a = A() a()
就會(huì)執(zhí)行print函數(shù),打印其中搞的文字。這里需要區(qū)別的是,實(shí)例化的時(shí)候,類的名稱后面括號(hào)可以傳遞參數(shù),例如前面實(shí)例化Fire的時(shí)候,傳遞in_channel,out_channel等參數(shù)。
但是要利用__call__的特性,是在實(shí)例名后面的括號(hào)中傳遞參數(shù),例如上面的例子a(),這里雖然沒有參數(shù),但是也可以改變__call__的定義使之可以傳遞參數(shù)。
回到網(wǎng)絡(luò)模型的內(nèi)容上來。翻看nn.Module的部分源碼[2],可以發(fā)現(xiàn),nn.Module里面果然定義了__call__,并且傳遞了參數(shù)*input。在__call__的定義中國,調(diào)用了self.forward。
這里其實(shí)還有一個(gè)點(diǎn)值得注意。其實(shí)nn.Module里面并沒有定義forward,但他卻調(diào)用self.forward,嚴(yán)格來說,他是“想要”調(diào)用self.forward。
如果我們沒有定義一個(gè)類,例如Fire,來繼承nn.Module,并且在這個(gè)類里面定義forward,那么nn.Module中__call__下面的self.forward就是無效的。
這意味著,父類中__call__下面調(diào)用的函數(shù),可以在繼承他的子類中定義。
下面給出一個(gè)簡單的例子。
class father(): def __call__(self): self.forward() print('I''m the father!') class child(father): def forward(self): print('Forward!') F=father() C=child()
這里定義了父類father,并定義了繼承他的一個(gè)子類child。此外還進(jìn)行了他們的實(shí)例化。
顯然,在father的__call__方法下面,調(diào)用了self.forward,但是沒有定義。child在繼承了father之后,定義了forward。
首先,這段代碼不會(huì)報(bào)錯(cuò),即使father的__call__下面的self.forward并沒有定義,這也是前面我說的,雖然沒有定義forward,但是可以理解為他“想要”調(diào)用self.forward。
那么在child記成了father之后,進(jìn)行了forward的定義,這使得child本身可以調(diào)用forward。
在上面這段代碼的基礎(chǔ)上,如果我們執(zhí)行F(),匯報(bào)下面這一段錯(cuò)誤,這解釋了forward沒有定義,只是“想要”調(diào)用self.forward。
如果我們執(zhí)行C(),則如下圖輸出。
顯然,在child中補(bǔ)充了forward的定義,就可以成功調(diào)用。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
用Python做的數(shù)學(xué)四則運(yùn)算_算術(shù)口算練習(xí)程序(后添加減乘除)
這篇文章主要介紹了用Python做的數(shù)學(xué)四則運(yùn)算_算術(shù)口算練習(xí)程序(后添加減乘除),需要的朋友可以參考下2016-02-02Python動(dòng)態(tài)聲明變量賦值代碼實(shí)例
這篇文章主要介紹了Python動(dòng)態(tài)聲明變量賦值代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12python 實(shí)現(xiàn)返回一個(gè)列表中出現(xiàn)次數(shù)最多的元素方法
今天小編就為大家分享一篇python 實(shí)現(xiàn)返回一個(gè)列表中出現(xiàn)次數(shù)最多的元素方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-06-06Python Traceback異常代碼排錯(cuò)利器使用指南
這篇文章主要為大家介紹了Python Traceback異常代碼排錯(cuò)利器使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01python生成每日?qǐng)?bào)表數(shù)據(jù)(Excel)并郵件發(fā)送的實(shí)例
今天小編就為大家分享一篇python生成每日?qǐng)?bào)表數(shù)據(jù)(Excel)并郵件發(fā)送的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02在Python中畫圖(基于Jupyter notebook的魔法函數(shù))
這篇文章主要介紹了在Python中畫圖(基于Jupyter notebook的魔法函數(shù)),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10在Python中通過threading模塊定義和調(diào)用線程的方法
由于著名的GIL的存在,Python中雖然能創(chuàng)建多條線程,但卻不能同時(shí)執(zhí)行...anyway,這里我們還是來學(xué)習(xí)一下在Python中通過threading模塊定義和調(diào)用線程的方法2016-07-07