# 5.9 含并行連結的網絡(GoogLeNet)
在2014年的ImageNet圖像識別挑戰賽中,一個名叫GoogLeNet的網絡結構大放異彩 [1]。它雖然在名字上向LeNet致敬,但在網絡結構上已經很難看到LeNet的影子。GoogLeNet吸收了NiN中網絡串聯網絡的思想,并在此基礎上做了很大改進。在隨后的幾年里,研究人員對GoogLeNet進行了數次改進,本節將介紹這個模型系列的第一個版本。
## 5.9.1 Inception 塊
GoogLeNet中的基礎卷積塊叫作Inception塊,得名于同名電影《盜夢空間》(Inception)。與上一節介紹的NiN塊相比,這個基礎塊在結構上更加復雜,如圖5.8所示。
:-: 
<div align=center>圖5.8 Inception塊的結構</div>
由圖5.8可以看出,Inception塊里有4條并行的線路。前3條線路使用窗口大小分別是``$ 1\times 1 $`、`$ 3\times 3 $`和`$ 5\times 5 $`的卷積層來抽取不同空間尺寸下的信息,其中中間2個線路會對輸入先做`$ 1\times 1 $` 卷積來減少輸入通道數,以降低模型復雜度。第四條線路則使用`$ 3\times 3 $`最大池化層,后接 `$ 1\times 1 $`卷積層來改變通道數。4條線路都使用了合適的填充來使輸入與輸出的高和寬一致。最后我們將每條線路的輸出在通道維上連結,并輸入接下來的層中去。
Inception塊中可以自定義的超參數是每個層的輸出通道數,我們以此來控制模型復雜度。
``` python
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class Inception(nn.Module):
# c1 - c4為每條線路里的層的輸出通道數
def __init__(self, in_c, c1, c2, c3, c4):
super(Inception, self).__init__()
# 線路1,單1 x 1卷積層
self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
# 線路2,1 x 1卷積層后接3 x 3卷積層
self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 線路3,1 x 1卷積層后接5 x 5卷積層
self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 線路4,3 x 3最大池化層后接1 x 1卷積層
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_c, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
return torch.cat((p1, p2, p3, p4), dim=1) # 在通道維上連結輸出
```
## 5.9.2 GoogLeNet模型
GoogLeNet跟VGG一樣,在主體卷積部分中使用5個模塊(block),每個模塊之間使用步幅為2的`$ 3\times 3 $`最大池化層來減小輸出高寬。第一模塊使用一個64通道的`$ 7\times 7 $`卷積層。
``` python
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
```
第二模塊使用2個卷積層:首先是64通道的`$ 1\times 1 $`卷積層,然后是將通道增大3倍的`$ 3\times 3 $`卷積層。它對應Inception塊中的第二條線路。
``` python
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
```
第三模塊串聯2個完整的Inception塊。第一個Inception塊的輸出通道數為`$ 64+128+32+32=256 $`,其中4條線路的輸出通道數比例為`$ 64:128:32:32=2:4:1:1 $`。其中第二、第三條線路先分別將輸入通道數減小至`$ 96/192=1/2 $`和`$ 16/192=1/12 $`后,再接上第二層卷積層。第二個Inception塊輸出通道數增至`$ 128+192+96+64=480 $`,每條線路的輸出通道數之比為`$ 128:192:96:64 = 4:6:3:2 $`。其中第二、第三條線路先分別將輸入通道數減小至`$ 128/256=1/2 $`和`$ 32/256=1/8 $`。
``` python
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
```
第四模塊更加復雜。它串聯了5個Inception塊,其輸出通道數分別是`$ 192+208+48+64=512 $`、`$ 160+224+64+64=512 $`、`$ 128+256+64+64=512 $`、`$ 112+288+64+64=528 $`和`$ 256+320+128+128=832 $`。這些線路的通道數分配和第三模塊中的類似,首先含`$ 3\times 3 $`卷積層的第二條線路輸出最多通道,其次是僅含`$ 1\times 1 $`卷積層的第一條線路,之后是含`$ 5\times 5 $`卷積層的第三條線路和含`$ 3\times 3 $` 最大池化層的第四條線路。其中第二、第三條線路都會先按比例減小通道數。這些比例在各個Inception塊中都略有不同。
``` python
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
```
第五模塊有輸出通道數為`$ 256+320+128+128=832 $`和`$ 384+384+128+128=1024 $`的兩個Inception塊。其中每條線路的通道數的分配思路和第三、第四模塊中的一致,只是在具體數值上有所不同。需要注意的是,第五模塊的后面緊跟輸出層,該模塊同NiN一樣使用全局平均池化層來將每個通道的高和寬變成1。最后我們將輸出變成二維數組后接上一個輸出個數為標簽類別數的全連接層。
``` python
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
d2l.GlobalAvgPool2d())
net = nn.Sequential(b1, b2, b3, b4, b5,
d2l.FlattenLayer(), nn.Linear(1024, 10))
```
GoogLeNet模型的計算復雜,而且不如VGG那樣便于修改通道數。本節里我們將輸入的高和寬從224降到96來簡化計算。下面演示各個模塊之間的輸出的形狀變化。
``` python
net = nn.Sequential(b1, b2, b3, b4, b5, d2l.FlattenLayer(), nn.Linear(1024, 10))
X = torch.rand(1, 1, 96, 96)
for blk in net.children():
X = blk(X)
print('output shape: ', X.shape)
```
輸出:
```
output shape: torch.Size([1, 64, 24, 24])
output shape: torch.Size([1, 192, 12, 12])
output shape: torch.Size([1, 480, 6, 6])
output shape: torch.Size([1, 832, 3, 3])
output shape: torch.Size([1, 1024, 1, 1])
output shape: torch.Size([1, 1024])
output shape: torch.Size([1, 10])
```
## 5.9.3 獲取數據和訓練模型
我們使用高和寬均為96像素的圖像來訓練GoogLeNet模型。訓練使用的圖像依然來自Fashion-MNIST數據集。
``` python
batch_size = 128
# 如出現“out of memory”的報錯信息,可減小batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
```
輸出:
```
training on cuda
epoch 1, loss 0.0087, train acc 0.570, test acc 0.831, time 45.5 sec
epoch 2, loss 0.0032, train acc 0.851, test acc 0.853, time 48.5 sec
epoch 3, loss 0.0026, train acc 0.880, test acc 0.883, time 45.4 sec
epoch 4, loss 0.0022, train acc 0.895, test acc 0.887, time 46.6 sec
epoch 5, loss 0.0020, train acc 0.906, test acc 0.896, time 43.5 sec
```
## 小結
* Inception塊相當于一個有4條線路的子網絡。它通過不同窗口形狀的卷積層和最大池化層來并行抽取信息,并使用`$ 1\times 1 $`卷積層減少通道數從而降低模型復雜度。
* GoogLeNet將多個設計精細的Inception塊和其他層串聯起來。其中Inception塊的通道數分配之比是在ImageNet數據集上通過大量的實驗得來的。
* GoogLeNet和它的后繼者們一度是ImageNet上最高效的模型之一:在類似的測試精度下,它們的計算復雜度往往更低。
## 參考文獻
[1] Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., & Anguelov, D. & Rabinovich, A.(2015). Going deeper with convolutions. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 1-9).
[2] Ioffe, S., & Szegedy, C. (2015). Batch normalization: Accelerating deep network training by reducing internal covariate shift. arXiv preprint arXiv:1502.03167.
[3] Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J., & Wojna, Z. (2016). Rethinking the inception architecture for computer vision. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (pp. 2818-2826).
[4] Szegedy, C., Ioffe, S., Vanhoucke, V., & Alemi, A. A. (2017, February). Inception-v4, inception-resnet and the impact of residual connections on learning. In Proceedings of the AAAI Conference on Artificial Intelligence (Vol. 4, p. 12).
-----------
> 注:除代碼外本節與原書此節基本相同,[原書傳送門](https://zh.d2l.ai/chapter_convolutional-neural-networks/googlenet.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯