社群計算機視覺課程文件

GoogLeNet

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

GoogLeNet

在本章中,我們將介紹一種名為 GoogLeNet 的卷積架構。

概述

Inception 架構是一種為計算機視覺任務(如分類和檢測)設計的卷積神經網路 (CNN),其效率突出。它包含不到 700 萬個引數,比其前身小得多,比 AlexNet 小 9 倍,比 VGG16 小 22 倍。該架構在 ImageNet 2014 挑戰賽中獲得認可,Google 的改編版 GoogLeNet(向 LeNet 致敬)在效能上設定了新的基準,同時比以前的領先方法使用了更少的引數。

架構創新

在 Inception 架構出現之前,AlexNet 和 VGG 等模型展示了更深層網路結構的優勢。然而,更深層的網路通常涉及更多的計算步驟,並可能導致過擬合和梯度消失等問題。Inception 架構提供了一種解決方案,使得可以訓練具有更少浮點引數的複雜 CNN。

Inception“網路中的網路”模組

在先前的網路中,例如 AlexNet 或 VGG,基本塊是卷積層本身。然而,Lin 等人於 2013 年引入了網路中的網路概念,認為單個卷積不一定是正確的基石。它應該更復雜。因此,受此啟發,Inception 模型作者決定使用一個更復雜的構建塊,稱為 Inception 模組,其名稱恰如其分地取自著名電影——“盜夢空間”(夢中夢)。

Inception 模組堅持使用不同核大小的卷積濾波器進行多尺度特徵提取。對於任何輸入特徵圖,它應用一個1×11 \times 1卷積、一個 3x3 卷積和一個 5x5 卷積並行進行。除了卷積,還應用了最大池化操作。所有這四種操作都以相同空間維度的方式進行填充和步幅。這些特徵被連線起來,並形成下一階段的輸入。參見圖 1。

inception_naive

圖 1:樸素 Inception 模組

正如我們所看到的,在多個尺度上應用具有更大核大小(例如 5x5)的多個卷積,可以大幅增加引數數量。當輸入特徵大小(通道大小)增加時,這個問題更加突出。因此,當我們深入網路堆疊這些“Inception 模組”時,計算量將大幅增加。簡單的解決方案是在計算需求似乎增加的地方減少特徵數量。計算量大的主要痛點是卷積層。特徵維度透過計算成本低廉的1×11 \times 1卷積在 3x3 和 5x5 卷積之前進行縮減。讓我們用一個例子來看。

我們想將一個特徵圖從S×S×128 S \times S \times 128 S×S×256 S \times S \times 256 透過 5x5 卷積。引數數量(不包括偏置)為 5*5*128*256 = 819,200。然而,如果我們首先透過一個1×11 \times 1卷積將特徵維度減少到 64,那麼引數數量(不包括偏置)是1×1×128×64+5×5×64×256=8,192+409,600=417,792 1\times 1\times 128\times 64 + 5\times 5\times 64\times 256 = 8,192 + 409,600 = 417,792 這意味著引數數量減少了幾乎一半!

我們還希望在與輸出特徵圖連線之前,減少最大池化層的輸出特徵。因此,我們再新增一個1×1 1\times 1 在最大池化層之後進行卷積。我們還在每個1×1 1\times 1 卷積後新增 ReLU 啟用函式,以增加模組的非線性和複雜性。參見圖 2。

inception_reduced

圖 2:Inception 模組

此外,由於在多個尺度上並行進行卷積操作,我們確保了更多的操作,而無需深入網路,從本質上緩解了梯度消失問題。

平均池化

在先前的網路中,如 AlexNet 或 VGG,最後的層將是幾個全連線層。這些全連線層,由於其大量的單元,將構成網路中的大部分引數。例如,VGG16 中 89% 的引數位於最後三個全連線層中。AlexNet 中 95% 的引數位於最後的全連線層中。這種需求可以歸因於卷積層不一定足夠複雜的假設。

然而,有了 Inception 模組,我們不再需要全連線層,簡單的空間維度平均池化就足夠了。這也源於“網路中的網路”論文。然而,GoogLeNet 包含了一個全連線層。他們報告了 top-1 準確率提高了 0.6%。

GoogLeNet 在全連線層中只有 15% 的引數。

輔助分類器

隨著引入計算量較小的1×1 1 \times 1 卷積以及用平均池化代替多個全連線層,該網路的引數顯著減少,這意味著我們可以新增更多層並深入網路。然而,堆疊層可能導致梯度消失問題,即梯度在反向傳播到網路初始層時變得越來越小並接近零。

該論文引入了輔助分類器——從中間層分支出幾個小型分類器,並將這些分類器的損失新增到總損失中(權重較小)。這確保了靠近輸入的層也能接收到大小適中的梯度。

輔助分類器包含

  • 一個平均池化層,其5×5 5 \times 5 濾波器大小和步幅為 3。
  • 一個1×1 1 \times 1 具有 128 個濾波器的卷積,用於降維和修正線性啟用。
  • 一個具有 1024 個單元的全連線層和修正線性啟用。
  • 一個丟棄率為 70% 的 Dropout 層。
  • 一個帶有 softmax 損失的線性層作為分類器。

這些輔助分類器在推理時被移除。然而,使用輔助分類器獲得的收益微乎其微 (0.5%)。

googlenet_aux_clf

圖 3:一個輔助分類器

架構 - GoogLeNet

GoogLeNet 的完整架構如下圖所示。所有卷積,包括 inception 模組內部的卷積,都使用 ReLU 啟用函式。它以兩個卷積和最大池化塊開始。接著是兩個 inception 模組(3a 和 3b)和一個最大池化塊。然後是 5 個 inception 塊(4a、4b、4c、4d、4e)和一個最大池化。輔助分類器從 4a 和 4d 的輸出中取出。接著是兩個 inception 塊(5a 和 5b)。之後,使用平均池化和一個 128 個單元的全連線層。

googlenet_arch 圖 4:完整的 GoogLeNet 架構

程式碼

import torch
import torch.nn as nn


class BaseConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BaseConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x


class InceptionModule(nn.Module):
    def __init__(self, in_channels, n1x1, n3x3red, n3x3, n5x5red, n5x5, pool_proj):
        super(InceptionModule, self).__init__()

        self.b1 = nn.Sequential(
            nn.Conv2d(in_channels, n1x1, kernel_size=1),
            nn.ReLU(True),
        )

        self.b2 = nn.Sequential(
            BaseConv2d(in_channels, n3x3red, kernel_size=1),
            BaseConv2d(n3x3red, n3x3, kernel_size=3, padding=1),
        )

        self.b3 = nn.Sequential(
            BaseConv2d(in_channels, n5x5red, kernel_size=1),
            BaseConv2d(n5x5red, n5x5, kernel_size=5, padding=2),
        )

        self.b4 = nn.Sequential(
            nn.MaxPool2d(3, stride=1, padding=1),
            BaseConv2d(in_channels, pool_proj, kernel_size=1),
        )

    def forward(self, x):
        y1 = self.b1(x)
        y2 = self.b2(x)
        y3 = self.b3(x)
        y4 = self.b4(x)
        return torch.cat([y1, y2, y3, y4], 1)


class AuxiliaryClassifier(nn.Module):
    def __init__(self, in_channels, num_classes, dropout=0.7):
        super(AuxiliaryClassifier, self).__init__()
        self.pool = nn.AvgPool2d(5, stride=3)
        self.conv = BaseConv2d(in_channels, 128, kernel_size=1)
        self.relu = nn.ReLU(True)
        self.flatten = nn.Flatten()
        self.fc1 = nn.Linear(2048, 1024)
        self.dropout = nn.Dropout(dropout)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):
        x = self.pool(x)
        x = self.conv(x)
        x = self.flatten(x)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x


class GoogLeNet(nn.Module):
    def __init__(self, use_aux=True):
        super(GoogLeNet, self).__init__()

        self.use_aux = use_aux
        ## block 1
        self.conv1 = BaseConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.lrn1 = nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 2
        self.conv2 = BaseConv2d(64, 64, kernel_size=1)
        self.conv3 = BaseConv2d(64, 192, kernel_size=3, padding=1)
        self.lrn2 = nn.LocalResponseNorm(5, alpha=0.0001, beta=0.75)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 3
        self.inception3a = InceptionModule(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = InceptionModule(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 4
        self.inception4a = InceptionModule(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = InceptionModule(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = InceptionModule(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = InceptionModule(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = InceptionModule(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(3, stride=2, padding=1)

        ## block 5
        self.inception5a = InceptionModule(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = InceptionModule(832, 384, 192, 384, 48, 128, 128)

        ## auxiliary classifier
        if self.use_aux:
            self.aux1 = AuxiliaryClassifier(512, 1000)
            self.aux2 = AuxiliaryClassifier(528, 1000)

        ## block 6
        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, 1000)

    def forward(self, x):
        ## block 1
        x = self.conv1(x)
        x = self.maxpool1(x)
        x = self.lrn1(x)

        ## block 2
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.lrn2(x)
        x = self.maxpool2(x)

        ## block 3
        x = self.inception3a(x)
        x = self.inception3b(x)
        x = self.maxpool3(x)

        ## block 4
        x = self.inception4a(x)
        if self.use_aux:
            aux1 = self.aux1(x)
        x = self.inception4b(x)
        x = self.inception4c(x)
        x = self.inception4d(x)
        if self.use_aux:
            aux2 = self.aux2(x)
        x = self.inception4e(x)
        x = self.maxpool4(x)

        ## block 5
        x = self.inception5a(x)
        x = self.inception5b(x)

        ## block 6
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.dropout(x)
        x = self.fc(x)

        if self.use_aux:
            return x, aux1, aux2
        else:
            return x
< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.