resnet 代碼解讀

resnet 和 vgg 是重要的backbone大诸,而且許多網(wǎng)絡(luò)結(jié)構(gòu)都是以vgg 和resnet為原型進(jìn)行創(chuàng)作抵卫,充分的理解這兩個(gè)基本網(wǎng)絡(luò)結(jié)構(gòu)的代碼是十分重要的,本次就詳細(xì)解讀一下這兩個(gè)基本網(wǎng)絡(luò)結(jié)構(gòu)代碼,代碼來(lái)自 torchvision.models :

1 resnet代碼:

隨便建立一個(gè) .py 文件架专,然后:

from torchvision.models import *

m1 = resnet34()

就可以找到resnet的pytorch源碼。下面先看一下resnet的層次結(jié)構(gòu)圖:

resnet 層次結(jié)構(gòu)圖.png

注意觀察一下玄帕,resnet18 部脚、34、50裤纹、101委刘、152的是非常相似的丧没,都是分為5個(gè)stage(stage是什么一查便知;而且通常分辨率降低同時(shí)我們會(huì)增加通道數(shù)目锡移,因?yàn)橐话阏J(rèn)為深層的網(wǎng)絡(luò)可以提取出更加豐富的語(yǔ)義信息呕童。隨著網(wǎng)絡(luò)的加深一般我們會(huì)讓分辨率降低而讓通道數(shù)增加:也就是一般認(rèn)為通道是攜帶高級(jí)語(yǔ)義信息的基本單位,隨著網(wǎng)絡(luò)越深淆珊,提取的語(yǔ)義信息越豐富夺饲,需要用來(lái)表達(dá)語(yǔ)義信息的單位也就越來(lái)越多,所以需要通道數(shù)目越多施符。)往声。

開始都是一個(gè)卷積接著一個(gè)maxpooling,只不過(guò)在后面的每個(gè)stage中戳吝,resnet18 浩销、34、50听哭、101撼嗓、152的卷積層個(gè)數(shù)是不一樣的。他們都是通過(guò)往上堆疊一個(gè)個(gè)的基本模塊欢唾,然后使得網(wǎng)絡(luò)達(dá)到較深的層數(shù)且警。

較為淺層的resnet中(resnet1,resnet34)中使用的基本模塊叫BasicBlock,它由兩個(gè) (3, 3, out_plane)的Conv2d 堆疊而成礁遣。在使用這個(gè)BasicBlock時(shí)候斑芜,只需要根據(jù) 堆疊具體參數(shù):輸入輸出通道數(shù)目,堆疊幾個(gè)BasicBlock祟霍,就能確定每個(gè)stage中basicblock的基本使用情況杏头;在較為深層的resnet中(resnt50,resnet101沸呐,resnet152)醇王,既能增加模塊深度,又能減少參數(shù)量崭添,使用的是一種瓶頸結(jié)構(gòu)Bottleneck寓娩,它由 (1,1, ) ,(3,3)呼渣,(1,1)堆疊而成棘伴,使用方法和BasicBlock基本類似。

在2,3,4,5個(gè)stage中屁置,resnet18的在各個(gè)stage中使用的基本模塊的數(shù)目為:[2,2,2,2]焊夸;resnet34的在各個(gè)stage中的基本模塊的數(shù)目為:[3,4,6,3];resnet50的在各個(gè)stage中的基本模塊的數(shù)目為:[3,4,6,3]蓝角;resnet101的在各個(gè)stage中的基本模塊的數(shù)目為:[3,4,23,3]阱穗;resnet18的在各個(gè)stage中的基本模塊的數(shù)目為:[3,8,36,3]饭冬;

下面以 resnet18 和 resnet 50 為代表詳細(xì)解釋代碼:

(1)resnet 18建立(寫完resnet18的建立發(fā)現(xiàn)根本沒(méi)有必要寫resnet 50了,哈哈哈哈哈揪阶。伍伤。。遣钳。。麦乞。蕴茴。。)

通過(guò)調(diào)用 已經(jīng)定義好的 resnet18()函數(shù)姐直,return 一個(gè)resnet18 model實(shí)例倦淀, 建立resnet18實(shí)例:

def resnet18(pretrained=False, **kwargs):
    """Constructs a ResNet-18 model.

    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
    return model

——————————————————————————————————

1)BasicBlock() 殘差塊 解釋

resnet18() 函數(shù)調(diào)用ResNet() 類,通過(guò)輸入初始化參數(shù):BasicBlock 声畏,[2,2,2,2]撞叽,實(shí)例化一個(gè)resnet18 model:
先看下ResNet()大類,輸入哪些初始化話參數(shù)就可以實(shí)例化為 resnet18 模型:BasicBlock

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x    # 其實(shí)這里不應(yīng)該叫residual插龄,應(yīng)該寫為:identity mapping = x愿棋,用identity mapping代替residual

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)

        return out

下面附上 基礎(chǔ)模塊BasicBlock 也就是 殘差塊residual block 的圖解釋:

residual block圖

BasicBlock要解決的一個(gè)重要問(wèn)題就是,identity mapping這個(gè)直連的維度 和 F(x) 輸出的維度不一樣無(wú)法直接相加的問(wèn)題:采用一個(gè)kernel=1的conv2d卷積核融合并降低通道信息均牢,如果H/W尺度也不一樣就設(shè)計(jì)stride糠雨。下面是在ResNet()定義中 定義的一個(gè)下采樣模塊,在BasicBlock實(shí)例化的時(shí)候作為了輸入?yún)?shù)徘跪。
basicblock 實(shí)例化圖

下采樣模塊定義圖

只要 stride>1 或者 輸入和輸出通道數(shù)目不同 都可以斷定 residul F(x)部分產(chǎn)生的 feature maps 相對(duì)于原來(lái)的feature maps的分辨率降低了甘邀,此時(shí)的 identity map 都要進(jìn)行下采樣。也就是identity map部分要和 residual 部分進(jìn)行相同的尺寸變換(包括H/W 和 channel)垮庐,這兩部分 的輸入輸出通道(planes)要相同松邪, stride(H/W)也相同

————————————————————————————————————————

2)制作stage的函數(shù) __make_layer() 解釋

注意在resnet18()函數(shù)中哨查,直接調(diào)用了BasicBlock類作為 實(shí)參逗抑,并沒(méi)有使用BasicBlock 實(shí)例:


.png

而是在實(shí)例化 ResNet()類的時(shí)候?qū)嵗?BasicBlock()類(這是定義的 ResNet()類 中的成員函數(shù)_make
_layer() 的代碼,下面先解釋完這個(gè)十分重要的成員函數(shù)代碼寒亥,然后再附上ResNet()類代碼):

圖片.png

_make_layer() 成員函數(shù)锋八,是用來(lái)制作每個(gè)stage中的網(wǎng)絡(luò)結(jié)構(gòu)的函數(shù),其的 形參 包含block, planes, blocks, stride 四個(gè)參數(shù):
block:基本模塊選擇誰(shuí)(前面提到過(guò)基本模塊包含 BasicBlock 和 Bottleneck兩個(gè)基本模塊)
planes:這是每個(gè)stage中护盈,與每個(gè)block的輸出通道相關(guān)的參數(shù)( 查看代碼就知道挟纱,如果使用的是BasicBlock那么planes就等于這個(gè)block的輸出通道數(shù)目,如果使用的是Bottleneck那么這個(gè)block的輸出通道數(shù)目就是planes的4倍)腐宋。

_make_layer()要解決:根據(jù)不同的基本block紊服,完成一個(gè)stage 網(wǎng)絡(luò)結(jié)構(gòu)的構(gòu)建檀轨。


3) __make_layer() 中用到的重要參數(shù) 類屬性expansion 和 成員變量self.inplanes

BasicBlock()(或Bottleneck())類中的類屬性expandsion,用來(lái)指定下一個(gè)BasicBlock的輸入通道是多少欺嗤。因?yàn)榫退阍趕tage中参萄,第一個(gè)block結(jié)束之后,下一個(gè)block的輸入通道數(shù)目已經(jīng)變化了煎饼,已經(jīng)不是 同一個(gè)stage 的 第一個(gè)block 的輸入通道數(shù)目讹挎。self.inplanes 的重要作用:self.inplanes一共有在block中兩次使用:


每個(gè)stage中(一個(gè)_make_layer()就是一個(gè)stage),第一次使用時(shí)吆玖,self.inplanes 等于上一個(gè)stage的輸出通道數(shù)筒溃,后面self.inplanes都等于同一個(gè)數(shù)目,就是每個(gè)block的輸出通道數(shù)目沾乘。

因?yàn)榉譃锽asicBlock()和Bottleneck() 兩個(gè)基本的block類怜奖,對(duì)應(yīng)不同深度的resnet,這兩種block最后的輸出通道是不一樣的翅阵,為了標(biāo)記這兩個(gè)類輸出通道數(shù)目的不同歪玲,設(shè)置了一個(gè)類屬性expansion。根據(jù)類屬性expansion和我們指定的輸出通道參數(shù)planes掷匠,可以確定對(duì)于這兩種block 結(jié)束之后的輸出通道數(shù)目滥崩。

BasicBlock()代碼
Bottleneck()代碼
圖片.png

———————————————————————————————————————————

4) Resnet類 代碼

說(shuō)完了上面的部分resnet的代碼已經(jīng)沒(méi)有完全可以看懂了。
ResNet() 類代碼:

class ResNet(nn.Module):

    def __init__(self, block, layers, num_classes=1000):
        self.inplanes = 64   # 每一個(gè)block的輸入通道數(shù)目
        super(ResNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()

    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x

整體思路就是把每個(gè)stage保存為一個(gè)單元讹语。
在制作每一個(gè)stage的時(shí)候夭委,把這stage中的 每個(gè)Basicblock,按照順序append到一個(gè) 列表layer 中募强,當(dāng)添加完這個(gè)stage中的所有block株灸,把這個(gè)列表放入nn.Sequential()中,就把構(gòu)建好的這個(gè)stage網(wǎng)絡(luò)模型放到了計(jì)算圖中擎值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慌烧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鸠儿,更是在濱河造成了極大的恐慌屹蚊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件进每,死亡現(xiàn)場(chǎng)離奇詭異汹粤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)田晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門嘱兼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人贤徒,你說(shuō)我怎么就攤上這事芹壕』闼模” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵踢涌,是天一觀的道長(zhǎng)通孽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)睁壁,這世上最難降的妖魔是什么背苦? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮潘明,結(jié)果婚禮上行剂,老公的妹妹穿的比我還像新娘。我一直安慰自己钉疫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布巢价。 她就那樣靜靜地躺著牲阁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壤躲。 梳的紋絲不亂的頭發(fā)上城菊,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音碉克,去河邊找鬼凌唬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛漏麦,可吹牛的內(nèi)容都是我干的客税。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼撕贞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼更耻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捏膨,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤秧均,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后号涯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體目胡,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年链快,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了誉己。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡域蜗,死狀恐怖巫延,靈堂內(nèi)的尸體忽然破棺而出效五,到底是詐尸還是另有隱情,我是刑警寧澤炉峰,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布畏妖,位于F島的核電站,受9級(jí)特大地震影響疼阔,放射性物質(zhì)發(fā)生泄漏戒劫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一婆廊、第九天 我趴在偏房一處隱蔽的房頂上張望迅细。 院中可真熱鬧,春花似錦淘邻、人聲如沸茵典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)统阿。三九已至,卻和暖如春筹我,著一層夾襖步出監(jiān)牢的瞬間扶平,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工蔬蕊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留结澄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓岸夯,卻偏偏與公主長(zhǎng)得像麻献,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猜扮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容