經典網絡結構(四):GoogLeNet (Inception v1~v4)

本節參考:《深度學習之PyTorch物體檢測實戰》
《DIVE INTO DEEP LEARNING》、吳恩達深度學習視頻

Inceptionv1

Inception塊

(彩蛋:得名於同名電影《盜夢空間》(Inception) – “We need to go deeper”,GoogLenet 同時也向 Lenet 致敬)

在這裏插入圖片描述
(1×11 \times 1 / 3×33 \times 3 / 5×55\times 5 / MaxPoolMaxPool怎麼選? Inception:小孩子才做選擇,我全都要)

Inception塊裏有4條並行的線路。前3條線路使用窗口大小分別是 1×13×35×51×1 、 3×3 和 5×5 的卷積層來抽取不同空間尺寸下的信息,其中中間2個線路會對輸入先做 1×11×1 卷積來減少輸入通道數(即之前的文章裏提到的 bottleneck layer,相比其他大小的卷積核,利用1×11×1 卷積來減少輸入通道數能顯著降低計算成本),以降低模型複雜度,在類似的測試精度下,GoogLenet的計算複雜度往往更低。第四條線路則使用 3×33×3 最大池化層,後接 1×11×1 卷積層來改變通道數。4條線路都使用了合適的填充來使輸入與輸出的高和寬一致。最後我們將每條線路的輸出在通道維上連結,並輸入接下來的層中去。

Inception塊中可以自定義的超參數每個層的輸出通道數,我們以此來控制模型複雜度。

class Inceptionv1(nn.Module):
    def __init__(self, in_dim, c1, c2, c3, c4):
        super(Inceptionv1, self).__init__()
        self.branch1x1 = nn.Sequential(
            nn.Conv2d(in_dim, c1, 1),
            nn.ReLU(inplace=True)
        )
        self.branch3x3 = nn.Sequential(
            nn.Conv2d(in_dim, c2[0], 1),
            nn.ReLU(inplace=True),
            nn.Conv2d(c2[0], c2[1], 3, padding=1),
            nn.ReLU(inplace=True)
        )
        self.branch5x5 = nn.Sequential(
            nn.Conv2d(in_dim, c3[0], 1),
            nn.ReLU(inplace=True),
            nn.Conv2d(c3[0], c3[1], 5, padding=2),
            nn.ReLU(inplace=True)
        )
        self.branch_pool = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            nn.Conv2d(in_dim, c4, 1),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        b1 = self.branch1x1(x)
        b2 = self.branch3x3(x)
        b3 = self.branch5x5(x)
        b4 = self.branch_pool(x)
        output = torch.cat((b1, b2, b3, b4), dim=1)
        return output

GoogLeNet模型

在這裏插入圖片描述
GoogLeNet跟VGG一樣,在主體卷積部分中使用5個模塊(block),每個模塊之間使用步幅爲2的 3×33×3 最大池化層來減小輸出高寬。

  • 第一模塊使用一個64通道的 7×77×7 卷積層
  • 第二模塊使用2個卷積層:首先是64通道的 1×11×1 卷積層,然後是將通道增大3倍的 3×33×3 卷積層。它對應Inception塊中的第二條線路
  • 第三模塊串聯2個完整的Inception塊。第一個Inception塊的輸出通道數爲 64+128+32+32=25664+128+32+32=256 ,其中4條線路的輸出通道數比例爲 64:128:32:32=2:4:1:164:128:32:32=2:4:1:1 。其中第二、第三條線路先分別將輸入通道數減小至 96/192=1/296/192=1/216/192=1/1216/192=1/12 後,再接上第二層卷積層。第二個Inception塊輸出通道數增至 128+192+96+64=480128+192+96+64=480 ,每條線路的輸出通道數之比爲 128:192:96:64=4:6:3:2128:192:96:64=4:6:3:2 。其中第二、第三條線路先分別將輸入通道數減小至 128/256=1/2128/256=1/232/256=1/832/256=1/8 (Inception塊的通道數分配之比是在ImageNet數據集上通過大量的實驗得來的)
  • 第四模塊更加複雜。它串聯了5個Inception塊,其輸出通道數分別是 192+208+48+64=512192+208+48+64=512160+224+64+64=512160+224+64+64=512128+256+64+64=512128+256+64+64=512112+288+64+64=528112+288+64+64=528256+320+128+128=832256+320+128+128=832 。這些線路的通道數分配和第三模塊中的類似,首先是含 3×33×3 卷積層的第二條線路輸出最多通道,其次是僅含 1×11×1 卷積層的第一條線路,之後是含 5×55×5 卷積層的第三條線路和含 3×33×3 最大池化層的第四條線路。其中第二、第三條線路都會先按比例減小通道數。這些比例在各個Inception塊中都略有不同。
  • 第五模塊有輸出通道數爲 256+320+128+128=832256+320+128+128=832384+384+128+128=1024384+384+128+128=1024 的兩個Inception塊。其中每條線路的通道數的分配思路和第三、第四模塊中的一致,只是在具體數值上有所不同。需要注意的是,第五模塊的後面緊跟輸出層,該模塊同NiN一樣使用全局平均池化層來將每個通道的高和寬變成1。最後我們將輸出變成二維數組後接上一個輸出個數爲標籤類別數的全連接層。

在原論文中還有一個細節:如上圖所示黃色後接白色的兩個方框爲全連接層接softmax,可以看到除了最後的輸出以外,從隱藏層中也引出了兩個該結構,通過引入這兩個輔助分類器(Auxiliary Classifier),在第3個與第6個Inception塊輸出後執行softmax並計算損失,在訓練時和最後的損失一起回傳。下面引用一下吳恩達在深度學習視頻中的解釋:
It ensures that the features computed even in the hidden units are not too bad for predicting the output class of an image. (have a regularization effect and help prevent this network from overfitting)

class GoogLenetv1(nn.Module):
    def __init__(self, in_dim, class_num):
        super(GoogLenetv1, self).__init__()
        self.b1 = nn.Sequential(
            nn.Conv2d(in_dim, 64, kernel_size=7, stride=2, padding=3),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3, stride=2, padding=1),
        )
        self.b2 = nn.Sequential(
            nn.Conv2d(64, 64, 1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 192, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3, stride=2, padding=1),
        )
        self.b3 = nn.Sequential(
            Inceptionv1(192, 64, (96, 128), (16, 32), 32),
            Inceptionv1(256, 128, (128, 192), (32, 96), 64),
            nn.MaxPool2d(3, stride=2, padding=1),
        )
        self.b4 = nn.Sequential(
            Inceptionv1(480, 192, (96, 208), (16, 48), 64),
            Inceptionv1(512, 160, (112, 224), (24, 64), 64),
            Inceptionv1(512, 128, (128, 256), (24, 64), 64),
            Inceptionv1(512, 112, (144, 288), (32, 64), 64),
            Inceptionv1(528, 256, (160, 320), (32, 128), 128),
            nn.MaxPool2d(3, stride=2, padding=1),
        )
        self.b5 = nn.Sequential(
            Inceptionv1(832, 256, (160, 320), (32, 128), 128),
            Inceptionv1(832, 384, (192, 384), (48, 128), 128),
            nn.AdaptiveAvgPool2d(1),
        )
        self.net = nn.Sequential()
        for i in range(1, 6):
            exec('self.net.add_module(str(i), self.b' + str(i) + ')')
        self.fc = nn.Linear(1024, class_num)
        
    def forward(self, x):
        output = self.net(x)
        output = output.view(-1, 1024) 
        output = self.fc(output)
        
        return output

Inceptionv2

參考:https://www.jianshu.com/p/4e5b3e652639

Inceptionv2的基礎模塊結構

在這裏插入圖片描述
下面再搭建一個Inceptionv2的基礎模塊結構:

class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding=0):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=padding)
        self.bn = nn.BatchNorm2d(out_channels, eps=0.001)
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        return F.relu(x, inplace=True)

class Inceptionv2(nn.Module):
    def __init__(self, in_dim, c1, c2, c3, c4):
        super(Inceptionv2, self).__init__()
        self.branch1 = BasicConv2d(in_dim, c1, 1, 0)
        self.branch2 = nn.Sequential(
            BasicConv2d(in_dim, c2[0], 1, 0),
            BasicConv2d(c2[0], c2[1], 3, 1)
        )
        self.branch3 = nn.Sequential(
            BasicConv2d(in_dim, c3[0], 1, 0),
            BasicConv2d(c3[0], c3[1], 3, 1),
            BasicConv2d(c3[1], c3[2], 3, 1)
        )
        self.branch4 = nn.Sequential(
            nn.AvgPool2d(3, stride=1, padding=1, count_include_pad=False),
            BasicConv2d(in_dim, c4, 1, 0)
        )
    def forward(self, x):
        x0 = self.branch1(x)
        x1 = self.branch2(x)
        x2 = self.branch3(x)
        x3 = self.branch4(x)
        out = torch.cat((x0, x1, x2, x3), 1)
        return out

可以看出,Inceptionv2基礎模塊結構與Inceptionv1相比,有下面幾個改動:

  • 在每個卷積層之後都加入了BN層,可以採用更大的學習率,能使學習更快地進行
  • 5×55\times5 卷積層換成了兩個 3×33\times3 卷積層,可以用更少的參數獲得相同的感受野
  • 將Max池化改爲Average池化

Inceptionv2的改進模塊結構

在上面所說的基礎上,最終的Inceptionv2還進行了以下的改進:
n×nn\times n的卷積運算分解爲1×n1\times nn×1n\times 1的兩個卷積運算。按照這種思路將3×33\times 3的卷積運算分解爲1×31\times 33×13\times 1的兩個卷積運算,這樣就進一步將計算成本降低了33%。作者通過測試發現非對稱卷積用在網絡中靠中間的層級纔有較好的效果(特別是feature map的大小在12x12~20x20之間時),Inceptionv2改進模塊如下圖所示
在這裏插入圖片描述
在這裏插入圖片描述

Inceptionv2的並行結構

Inceptionv2還使用並行結構來優化Pooling,使卷積核變得更寬而不是更深,解決表徵能力瓶頸問題。下面進行詳細介紹:
首先,作者在論文 Rethinking the Inception Architecture for Computer Vision 中提出了要按照一套合理的規則來優化Inception結構,具體如下:

  • 規則1:要防止出現特徵描述的瓶頸(representational bottleneck)。所謂特徵描述的瓶頸就是中間某層對特徵在空間維度進行較大比例的壓縮(比如使用pooling時),導致很多特徵丟失。雖然Pooling是CNN結構中必須的功能,但我們可以通過一些優化方法來減少Pooling造成的損失。
  • 規則2:特徵的數目越多收斂的越快。相互獨立的特徵越多,輸入的信息就被分解的越徹底,分解的子特徵間相關性低,子特徵內部相關性高,把相關性強的聚集在了一起會更容易收斂。規則2和規則1可以組合在一起理解,特徵越多能加快收斂速度,但是無法彌補Pooling造成的特徵損失,Pooling造成的representational bottleneck要靠其他方法來解決。
  • 規則3:可以壓縮特徵維度數,來減少計算量。inception-v1中提出的用1x1卷積先降維再作特徵提取就是利用這點。不同維度的信息有相關性,降維可以理解成一種無損或低損壓縮,即使維度降低了,仍然可以利用相關性恢復出原有的信息。
  • 規則4:整個網絡結構的深度和寬度(特徵維度數)要做到平衡。只有等比例的增大深度和維度才能最大限度的提升網絡的性能。

而使用並行結構來優化Pooling就是爲了解決規則1中提到的 representational bottleneck。representational bottleneck 的一種解決辦法就是在Pooling前用1x1卷積把特徵數加倍(見下圖右側),這種加倍可以理解加入了冗餘的特徵,然後再作Pooling就只是把冗餘的信息重新去掉,沒有減少信息量。這種方法有很好的效果但因爲加入了1x1卷積會極大的增大計算量。
在這裏插入圖片描述
替代的方法是使用兩個並行的支路,左路爲3×33\times 3,步長爲2的卷積,右路是Pooling,最後再在特徵維度拼合到一起(見下圖)也得到了兩倍的通道數。這種方法既有很好的效果,又沒有增大計算量。

在這裏插入圖片描述

Inceptionv2整體結構

在這裏插入圖片描述
在這裏插入圖片描述

Inceptionv3

參考:https://www.jianshu.com/p/4e5b3e652639

使用標籤平滑(label smoothing)來對網絡輸出進行正則化

SoftmaxwithLoss層的輸出可以用下面公式表示:
在這裏插入圖片描述在這裏插入圖片描述
從上述公式可以反推出整個訓練過程收斂時Softmax的正確分類的輸入aka_k是無窮大,這是一種極其理想的情況,如果讓所有的輸入都產生這種極其理想的輸出,就會造成overfit(回想一下overfit的概念:能對所有的輸入進行最理想的分類,魯棒性差)。

所以爲了克服overfit,防止最終出來的正確分類yk=1y_k=1,在輸出yky_k時加了個參數deltadelta,生成新的yky_k^{'},用yky_k^{'}來計算loss

yk=(1ϵ)yk+ϵKy_k^{'} = (1 - \epsilon)y_k + \frac{\epsilon}{K}Kϵ0.1K爲類別數,\epsilon可取0.1舉個例子:假設輸出概率值爲[0, 0, 0, 1],那麼在經過 label smoothing之後,概率值就變成了[0.025, 0.025, 0.025, 0.925],相當於是施加了一個懲罰

關於分支分類器 Auxiliary Classifiers 的糾正

作者還對自己在GoogLeNet論文中提出的 分支分類器 Auxiliary Classifiers 的效果進行了糾正。Szegedy認爲自己當時的結論就是錯誤的,特別是靠近輸入側的那個Auxiliary Classifier,加不加完全沒區別,但如果在靠近輸出的那個Auxiliary Classifier的全連接層後加個BN,會起到正則化的作用,所以第二個Auxiliary Classifier還是可以保留。起正則化作用的原因Szegedy完全沒解釋。。。

Inceptionv4

論文鏈接:Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning
參考:https://blog.csdn.net/kxh123456/article/details/102828148

Inceptionv4將Inception的思想與殘差網絡進行了結合,顯著提升了訓練速度與模型準確率.論文中提出了三個網絡,分別叫Inception-v4、Inception-ResNet-v1以及Inception-ResNet-v2,其中Inception-v4中沒有使用殘差模塊,但是網絡深度比原來更深了。後兩個網絡則引入的殘差模塊,但是在設計上還是略有不同。

Scaling of the Residuals

在與前一層激活層求和時,縮小殘差有助於穩定訓練過程。一般我們會選擇固定的縮放因子,見圖20:
在這裏插入圖片描述

殘差模塊

在這裏插入圖片描述
在這裏插入圖片描述

Inception-v4

整體結構

在這裏插入圖片描述

網絡中的各個模塊

stem

在這裏插入圖片描述

Inception-A block

在這裏插入圖片描述

Inception-B block

在這裏插入圖片描述

Inception-C block

在這裏插入圖片描述

Reduction-A

在這裏插入圖片描述

Reduction-B

在這裏插入圖片描述

Inception-ResNet-V1

整體結構

在這裏插入圖片描述

網絡中的各個模塊

stem

在這裏插入圖片描述

Inception-ResNet-A

在這裏插入圖片描述

Inception-ResNet-B

在這裏插入圖片描述

Inception-ResNet-C

在這裏插入圖片描述

Reduction-A

在這裏插入圖片描述

Reduction-B

在這裏插入圖片描述

Inception-ResNet-V2

整體結構

在這裏插入圖片描述

網絡中的各個模塊

stem

在這裏插入圖片描述

Inception-ResNet-A

在這裏插入圖片描述

Inception-ResNet-B

在這裏插入圖片描述

Inception-ResNet-C

在這裏插入圖片描述

Reduction-A

在這裏插入圖片描述

Reduction-B

在這裏插入圖片描述

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