ResNet34學習筆記+用pytorch手寫實現

看懂ResNet,需要理解兩個點:shortcut的處理,以及網絡結構

理解1——Identity Mapping by Shortcuts(快捷恆等映射)

我們每隔幾個堆疊層採用殘差學習。構建塊如圖2所示。在本文中我們考慮構建塊正式定義爲

x和y是考慮的層的輸入和輸出向量。函數F(x,Wi)表示要學習的殘差映射。圖2中的例子有兩層,F=W2σ(W1x)中σ表示ReLU[29],爲了簡化忽略偏置項。F+x操作通過快捷連接和各個元素相加來執行。在相加之後我們採納了第二種非線性(即σ(y),看圖2)。

公式(1)中的快捷連接既沒有引入外部參數又沒有增加計算複雜度。這不僅在實踐中有吸引力,而且在簡單網絡和殘差網絡的比較中也很重要。我們可以公平地比較同時具有相同數量的參數,相同深度,寬度和計算成本的簡單/殘差網絡(除了不可忽略的元素加法之外)。

方程(1)中x和F的維度必須是相等的。如果不是這種情況(例如,當更改輸入/輸出通道時),我們可以對快捷連接執行線性投影Ws(進行卷積操作)來匹配維度:

我們也可以在方程(1)中使用方陣Ws。但是我們將通過實驗表明,恆等映射足以解決退化問題,並且是合算的,因此Ws僅在匹配維度時使用。

 

理解2——網絡架構:

 

簡單網絡:我們簡單網絡的基準(圖3,中間)主要受到VGG網絡[40](圖3,左圖)的啓發。卷積層主要有3×3的濾波器,並遵循兩個簡單的設計規則:(i)對於相同的輸出特徵圖尺寸,每層具有相同數量的濾波器;(ii)如果特徵圖尺寸減半,則濾波器數量加倍,以便保持每層的時間複雜度。我們直接用步長爲2的卷積層進行下采樣。網絡以全局平均池化層和具有softmax的1000維全連接層結束。圖3(中間)的加權層總數爲34。

殘差網絡。 基於上述的簡單網絡,我們插入快捷連接(圖3,右),將網絡轉換爲其對應的殘差版本。當輸入和輸出具有相同的維度時(圖3中的實線連接)時,可以直接使用恆等快捷連接(方程(1))當維度增加(圖3中的虛線連接)時,我們考慮兩個選項:(A)快捷連接仍然執行恆等映射,額外填充零輸入以增加維度。此選項不會引入額外的參數;(B)方程(2)中的投影快捷連接Ws用於匹配維度(由1×1卷積完成)。對於這兩個選項,當快捷連接跨越兩種尺寸的特徵圖時,它們執行時步長爲2。

B比A好一點,所以代碼裏用的是B。

文章後面比較了optionA,B,C 。恆等和投影快捷連接我們已經表明沒有參數,恆等快捷連接有助於訓練。接下來我們調查投影快捷連接(方程2)。我們比較了三個選項:(A) 零填充快捷連接用來增加維度,所有的快捷連接是沒有參數的(與表2和圖4右相同);(B)投影快捷連接用來增加維度,其它的快捷連接是恆等的;(C)所有的快捷連接都是投影。

表3顯示,所有三個選項都比對應的簡單網絡好很多。選項B比A略好。我們認爲這是因爲A中的零填充確實沒有殘差學習。選項C比B稍好,我們把這歸因於許多(十三)投影快捷連接引入了額外參數。但A/B/C之間的細微差異表明,投影快捷連接對於解決退化問題不是至關重要的。因爲我們在本文的剩餘部分不再使用選項C,以減少內存/時間複雜性和模型大小。恆等快捷連接對於不增加下面介紹的瓶頸結構的複雜性尤爲重要。

表1。ImageNet架構。構建塊顯示在括號中,以及構建塊的堆疊數量。下采樣通過步長爲2的conv3_1, conv4_1和conv5_1執行。

ResNet34實現代碼:

import torch.nn as nn
import torch
from torch.nn import functional as F

class ResidualBlock(nn.Module):
    #實現子module:Residual Block
    def __init__(self, in_ch, out_ch, stride=1, shortcut=None):
        super(ResidualBlock,self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(in_ch,out_ch,3,stride,padding=1,bias=False),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace = True),#inplace = True原地操作
            nn.Conv2d(out_ch,out_ch,3,stride=1,padding=1,bias=False),
            nn.BatchNorm2d(out_ch)
            )
        self.right = shortcut
        
    def forward(self,x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)
        
class ResNet34(nn.Module):#224x224x3
    #實現主module:ResNet34
    def __init__(self, num_classes=1):
        super(ResNet34,self).__init__()
        self.pre = nn.Sequential(
                nn.Conv2d(3,64,7,stride=2,padding=3,bias=False),# (224+2*p-)/2(向下取整)+1,size減半->112
                nn.BatchNorm2d(64),#112x112x64
                nn.ReLU(inplace = True),
                nn.MaxPool2d(3,2,1)#kernel_size=3, stride=2, padding=1
                )#56x56x64
        
        #重複的layer,分別有3,4,6,3個residual block
        self.layer1 = self.make_layer(64,64,3)#56x56x64,layer1層輸入輸出一樣,make_layer裏,應該不用對shortcut進行處理,但是爲了統一操作。。。
        self.layer2 = self.make_layer(64,128,4,stride=2)#第一個stride=2,剩下3個stride=1;28x28x128
        self.layer3 = self.make_layer(128,256,6,stride=2)#14x14x256
        self.layer4 = self.make_layer(256,512,3,stride=2)#7x7x512
        #分類用的全連接
        self.fc = nn.Linear(512,num_classes)
        
    def make_layer(self,in_ch,out_ch,block_num,stride=1):
        #當維度增加時,對shortcut進行option B的處理
        shortcut = nn.Sequential(#首個ResidualBlock需要進行option B處理
                nn.Conv2d(in_ch,out_ch,1,stride,bias=False),#1x1卷積用於增加維度;stride=2用於減半size;爲簡化不考慮偏差
                nn.BatchNorm2d(out_ch)
                )
        layers = []
        layers.append(ResidualBlock(in_ch,out_ch,stride,shortcut))
        
        for i in range(1,block_num):
            layers.append(ResidualBlock(out_ch,out_ch))#後面的幾個ResidualBlock,shortcut直接相加
        return nn.Sequential(*layers)
        
    def forward(self,x):    #224x224x3
        x = self.pre(x)     #56x56x64
        x = self.layer1(x)  #56x56x64
        x = self.layer2(x)  #28x28x128
        x = self.layer3(x)  #14x14x256
        x = self.layer4(x)  #7x7x512
        x = F.avg_pool2d(x,7)#1x1x512
        x = x.view(x.size(0),-1)#將輸出拉伸爲一行:1x512
        x = self.fc(x)    #1x1
        # nn.BCELoss:二分類用的交叉熵,用的時候需要在該層前面加上 Sigmoid 函數
        return nn.Sigmoid()(x)#1x1,將結果化爲(0~1)之間

 

Reference:

ResNet原文

ResNet網絡結構

ResNet論文翻譯——中文版

Pytorch學習(三)--用50行代碼搭建ResNet

 

 

 

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