GoogLeNet: Going deeper with convolutions
1 前言
各位朋友大家好,歡迎來到月來客棧。在上一篇文章中,筆者花了很大的篇幅介紹完了GoogLeNet中的核心部分Inception模塊。其本質(zhì)上來說就是采用了不同尺度的卷積核對輸入進行特征提取,然后再將各個部分得到的結(jié)果進行組合,最后在輸入到下一層的網(wǎng)絡(luò)中。只是作者選擇了從另外一個相對晦澀的角度來進行切入,解釋了Inception結(jié)構(gòu)的目的以及合理性。
在接下來的這篇文章中,就讓我們來看看GoogLeNet是如何通過Inception堆疊形成的,并且還在ILSVRC2014上大獲成功。
2 GoogLeNet
首先我們需要知道的是在2014的ILSVR比賽中,谷歌使用的其實不止是一個模型,而是七個GooLeNet模型(每個模型均是以Inception為block構(gòu)成)的ensemble。總的來說其中六個模型的網(wǎng)絡(luò)結(jié)構(gòu)一模一樣,僅僅只是在訓練的時候采用了不同的采樣策略和輸入順序;而另外一個網(wǎng)絡(luò)結(jié)構(gòu)和其它的六個基本上差不多,只是在網(wǎng)絡(luò)的中間部分也插入了一些分類器。
We independently trained 7 versions of the same GoogLeNet model (including one wider version), and performed ensemble prediction with them. These models were trained with the same initialization and learning rate policies. They differed only in sampling methodologies and the randomized input image order.
2.1 Most Common Instance of Inception
現(xiàn)在我們先來看看最普通,也就是那六個結(jié)構(gòu)一樣的網(wǎng)絡(luò)模型。這個模型一共有22層(僅包含參數(shù)層),如果加上池化層就有27層。下面我們就來看看其到底長什么樣。
圖 1. 22層GoogLeNet參數(shù)圖如圖1所示就是這個最基本的GoogLeNet網(wǎng)絡(luò)結(jié)構(gòu)的參數(shù)表了。咋一看是不是根本沒看懂?很多人以為這張表對應(yīng)的就是論文中的那個網(wǎng)絡(luò)結(jié)構(gòu)圖,其實事實上并不是。在這里筆者就直接給出這個參數(shù)表所對應(yīng)的網(wǎng)絡(luò)圖。不過圖1中的params列給出的參數(shù)量好像有點問題(例如第一個卷積層的參數(shù)量應(yīng)該是7×7×3×64≈94007\times7\times3\times64\approx94007×7×3×64≈9400),有興趣的朋友也可以自己去研究一番。
圖 2. 22層GoogLeNet網(wǎng)絡(luò)結(jié)構(gòu)圖如圖2所示就是這個22層網(wǎng)絡(luò)的結(jié)構(gòu)圖了。在圖里,筆者幾乎標出了所有輸入輸出和參數(shù)的相關(guān)信息,其中S表示步長,P表示填充的圈數(shù),@后面的數(shù)字表示對應(yīng)卷積核的個數(shù)。看到?jīng)],還別說圖1中的這種表格形式應(yīng)該是最簡潔的描述這個網(wǎng)絡(luò)結(jié)構(gòu)的方式,只不過在沒弄懂之前看起來不知所謂。試想一下,如果是你的話你會以一種什么樣的形式來呈現(xiàn)這個網(wǎng)絡(luò)的參數(shù)設(shè)置?
在圖2所示的網(wǎng)絡(luò)中,所有卷積操作之后(注意是所有)都使用了ReLU激活函數(shù)。同時還可以發(fā)現(xiàn),在GoogLeNet網(wǎng)絡(luò)結(jié)構(gòu)中也采用了類似NIN中通過全局平均池化來處理輸出的方式,只不過這里還額外的加了一個線性層來方便處理不同的分類任務(wù)。并且作者還說到,將全連接改為全局平均后,top-1準確率還提高了0.6%0.6\%0.6%。
All the convolutions, including those inside the Inception modules, use rectified linear activation. All these reduction/projection layers use rectified linear activation as well.
以上就是這個22層GoogLeNet網(wǎng)絡(luò)模型的全部信息。接下來,我們再來看看ensemble中的另一個模型長什么樣。
2.2 Adding auxiliary classifiers of Inception
在論文中第5頁左下角作者說到,基于這樣一個觀點——在先前ILSVRC大賽中拔得頭籌的網(wǎng)絡(luò)模型都認為,處于網(wǎng)絡(luò)模型中間層的特征往往具有更強的判別能力,GoogLeNet也在網(wǎng)絡(luò)的中間部分額外的添加了兩個分類器。這種做法通常被認為是用于克服梯度消失以及增加網(wǎng)絡(luò)泛化能力的有效手段。也就是說,作者通過在圖2所示的網(wǎng)絡(luò)結(jié)構(gòu)中,額外加入了兩個分類層來構(gòu)成了ensemble中的第七個模型:
圖 3. 多分類層GoogLeNet如圖3所示就是根據(jù)圖2中的網(wǎng)絡(luò)所改進得到的,其中Dropout中的值表示丟掉神經(jīng)元的比例。從圖3可以看到,新增的兩個分類器分別將Inception(4a)和Inception(4d)的輸出作為輸入,然后進行分類。同時,在這個網(wǎng)絡(luò)訓練的過程中,整個模型的損失函數(shù)將會把三個部分的損失加在一起,但是額外的兩個輔助分類器僅僅只有30%的損失被累加到整體損失中。當模型訓練完成后,在實際的推理過程中,額外的兩個輔助分類器將會被忽略掉。雖然這樣做看起來會有很好的效果,但是作者說到,新增的這部分僅僅只給帶來了大約0.5%0.5\%0.5%準確率的提升。最后,當七個模型都被獨立的訓練完成后再采用集成的策略輸出最后的預(yù)測結(jié)果。當然,在這里我們肯定不能像論文中那樣來進行實驗了,所以后續(xù)我們還是會以前面使用過的數(shù)據(jù)集進行實驗。
3 實現(xiàn)
在這里,我們就以最普通也就是圖2中所示的網(wǎng)絡(luò)結(jié)構(gòu)為例進行實現(xiàn),然后再進行圖片的分類的任務(wù)。
3.1 前向傳播
從前面的介紹可知,GoogLeNet是由多個Inception模塊構(gòu)造而成,因此自然而然我們就會想到在實現(xiàn)的時候我們也可先寫一個Inception模塊,然后再復(fù)用即可。同時,我們可以發(fā)現(xiàn),在Inception內(nèi)部還有一個更小的單元就是四路卷積+ReLU的操作。因此,對于整個網(wǎng)絡(luò)的構(gòu)造來說,我們可以先實現(xiàn)最基本卷積小模塊,然后再基于卷積模塊實現(xiàn)Inception模塊,最后根據(jù)Inception來實現(xiàn)整個網(wǎng)絡(luò)。
3.1.1 基本卷積
在前面筆者介紹到,GoogLeNet中的每個卷積操作后都會跟上一個ReLU的操作,因此我們可以定義這么一個類:
class BasicConv2d(nn.Module):def __init__(self, in_channels, out_channels,kernel_size, stride=1, padding=0):super(BasicConv2d, self).__init__()self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False)self.relu = nn.ReLU(inplace=True)def forward(self, x):x = self.conv(x)return self.relu(x)這樣我們就完成了卷積模塊的定義。同時,由于BasicConv2d是繼承自nn.Module的,所以在后面我們也能像使用nn.Conv2d一樣來使用BasicConv2d。
3.1.2 Inception
在實現(xiàn)完卷積模塊后就可以開始實現(xiàn)Inception模塊了。從圖2可知,一個Inception模塊主要分為四條支路,所以我們在實現(xiàn)的時候也可以按四個部分來分別進行:
class Inception(nn.Module):def __init__(self, in_channels, ch1x1, ch3x3reduce, ch3x3, ch5x5reduce, ch5x5, pool_proj):super(Inception, self).__init__()conv_block = BasicConv2dself.branch1 = conv_block(in_channels, ch1x1, kernel_size=1)self.branch2 = nn.Sequential(conv_block(in_channels, ch3x3reduce, kernel_size=1),conv_block(ch3x3reduce, ch3x3, kernel_size=3, padding=1))self.branch3 = nn.Sequential(conv_block(in_channels, ch5x5reduce, kernel_size=1),conv_block(ch5x5reduce, ch5x5, kernel_size=5, padding=2))self.branch4 = nn.Sequential(nn.MaxPool2d(kernel_size=3, stride=1, padding=1),conv_block(in_channels, pool_proj, kernel_size=1))def forward(self, x):branch1 = self.branch1(x)branch2 = self.branch2(x)branch3 = self.branch3(x)branch4 = self.branch4(x)outputs = [branch1, branch2, branch3, branch4]return torch.cat(outputs, 1)如上就是整個Inception部分的實現(xiàn),其中:
- in_channels: 表示上一層輸入的通道數(shù);
- ch1x1:表示 1x1卷積的個數(shù);
- ch3x3reduce: 表示3x3卷積之前1x1卷積的個數(shù);
- ch3x3:表示3x3卷積的個數(shù);
- ch5x5reduce:表示5x5卷積之前1x1卷積的個數(shù)
- ch5x5:表示5x5卷積的個數(shù);
- pool_proj:表示池化后1x1卷積的個數(shù)。
在得到四個部分的輸出后,在融合到一起即可。
3.1.3 GoogLeNet
在做完前期的幾個準備工作后,我們就可以來一步步的實現(xiàn)GoogLeNet。根據(jù)圖2所示的結(jié)構(gòu),我們可以定義如下的一類:
class GoogLeNet(nn.Module):def __init__(self, num_classes=1000):super(GoogLeNet, self).__init__()conv_block, inception_block = BasicConv2d, Inceptionself.conv1 = conv_block(3, 64, kernel_size=7, stride=2, padding=3)self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.conv2 = conv_block(64,64, kernel_size=1, stride=1, padding=0)self.conv3 = conv_block(64,192, kernel_size=3, stride=1, padding=1)self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)# in_channels, ch1x1, ch3x3reduce, ch3x3, ch5x5reduce, ch5x5, pool_proj):self.inception3a = inception_block(192, 64, 96, 128, 16, 32, 32)self.inception3b = inception_block(256, 128, 128, 192, 32, 96, 64)self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.inception4a = inception_block(480, 192, 96, 208, 16, 48, 64)self.inception4b = inception_block(512, 160, 112, 224, 24, 64, 64)self.inception4c = inception_block(512, 128, 128, 256, 24, 64, 64)self.inception4d = inception_block(512, 112, 144, 288, 32, 64, 64)self.inception4e = inception_block(528, 256, 160, 320, 32, 128, 128)self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)self.inception5a = inception_block(832, 256, 160, 320, 32, 128, 128)self.inception5b = inception_block(832, 384, 192, 384, 48, 128, 128)self.avgpool = nn.AdaptiveAvgPool2d((1, 1))self.dropout = nn.Dropout(0.5)self.fc = nn.Linear(1024, num_classes)到這里,我們定義完了整個前向傳播中所需要用到的功能函數(shù),下面只需要在forward中使用這些部件完成整個前向傳播過程即可:
def forward(self, x):x = self.conv1(x)x = self.maxpool1(x) # torch.Size([1, 64, 56, 56])x = self.conv2(x)x = self.conv3(x)x = self.maxpool2(x)# torch.Size([1, 192, 28, 28])x = self.inception3a(x)x = self.inception3b(x)x = self.maxpool3(x)# torch.Size([1, 480, 14, 14])x = self.inception4a(x)x = self.inception4b(x)x = self.inception4c(x)x = self.inception4d(x)x = self.inception4e(x)x = self.maxpool4(x) # torch.Size([1, 832, 7, 7])x = self.inception5a(x)x = self.inception5b(x)x = self.avgpool(x)# torch.Size([1, 1024, 1, 1])x = torch.flatten(x, 1)x = self.dropout(x)x = self.fc(x) # torch.Size([1,1000])return x由于這部分代碼并沒有涉及到新的知識點,所以這里就不再贅述。隨便提一句的是,這些代碼筆者也是根據(jù)Pytorch官方提供的代碼修改而來。
3.2 訓練網(wǎng)絡(luò)
在原始論文中,作者使用的是ILSVRC2014中的數(shù)據(jù)集,但顯然我們一般不具備這樣的條件。因此,接下來我們會使用Fashion MNIST和CIFAR10這兩個數(shù)據(jù)集來進行實驗,并將得到的結(jié)果同之前的進行對比。在完成好GoogLeNet模型的定義后,我們就可以定義如下的一個類來訓練模型:
class MyModel:def __init__(self,batch_size=128,epochs=800,learning_rate=0.0001):self.batch_size = batch_sizeself.epochs = epochsself.learning_rate = learning_rateself.net = GoogLeNet(10)def train(self):train_iter, test_iter = load_dataset(batch_size=self.batch_size, resize=96)......if __name__ == '__main__':model = MyModel()model.train()由于這部分代碼同之前的相比幾乎沒有任何變化,所以就不貼在這里占用篇幅了,見示例代碼即可[1]。同時,因為這兩個數(shù)據(jù)集原始大小只有32×3232\times3232×32,所以這里我們將其resize到了96×9696\times9696×96的大小[2]。最后,需要注意的是Fashion MNIST數(shù)據(jù)集圖片的通道數(shù)為1,所以在使用它的時候需要將上面self.conv1 = conv_block(in_channels=3,...)中的in_channels設(shè)置為1。
3.3 訓練結(jié)果
如下表所示就是最近我們陸續(xù)介紹的這5個模型在Fashion MNIST和CIFAR10上的測試結(jié)果。當然,這種比法顯然是不公平的,因為前面的幾個模型都只是迭代了5輪,而由于后面的網(wǎng)絡(luò)越來越多因此也就使用了更多的迭代次數(shù)。但是可以明確的是,這些模型的表達能力肯定是從上到下依次提升的。
| LeNet5 | 0.8703 | 無 | 5 |
| AlexNet | 0.9043 | 無 | 5 |
| VGG19 | 0.9054 | 無 | 5 |
| NIN | 無 | 0.8643 | 800 |
| GoogLeNet | 0.9273 | 0.8357 | 60/800 |
雖然從上面的結(jié)果來看,GoogLeNet在CIFAR10上的結(jié)果還不如NIN的表現(xiàn),但這并不代表GoogLeNet就比NIN差。這可能是因為我們這里并沒有采用像論文中那樣的集成策略和訓練策略,同時也沒有對數(shù)據(jù)進行增強處理。但不得不承認的是,Inception的思想是非常值得借鑒與學習。
4 總結(jié)
在這篇文章中,筆者首先介紹了如何通過Inception來構(gòu)造GoogLeNet網(wǎng)絡(luò)模型;接著介紹了如何在基礎(chǔ)的GoogLeNet中再加入額外的兩個分類器;最后還介紹了如何通過Pytorch來實現(xiàn)GoogLeNet網(wǎng)絡(luò),并同時在Fashion MNIST和CIFAR10這兩個數(shù)據(jù)集上進行了實驗。同時,在讀完這篇論文后,我們最應(yīng)該掌握的兩個點就是Inception本身的這種結(jié)構(gòu)以及對于1×11\times11×1卷積的理解。在下一篇的文章中,我們將開始學習卷積網(wǎng)絡(luò)中的最最常用的一個操作Batch Normaliztion。
本次內(nèi)容就到此結(jié)束,感謝您的閱讀!如果你覺得上述內(nèi)容對你有所幫助,歡迎關(guān)注并傳播本公眾號!若有任何疑問與建議,請?zhí)砑庸P者微信’nulls8’或加群進行交流。青山不改,綠水長流,我們月來客棧見!
推薦閱讀
[1]Inception: Going deeper with convolutions
[2]VGG: Very deep convolutional networks for large-scale image recognitionNetwork in Network
[3]AlexNet: Imagenet classification with deep convolutional neural networks
總結(jié)
以上是生活随笔為你收集整理的GoogLeNet: Going deeper with convolutions的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电阻 电容表 电感表 频率表 测量套件
- 下一篇: 从Bold手环来谈谈无袖带血压计的技术