1. 池化層
在卷積網絡中, 通常會在卷積層之間增加池化(Pooling) 層, 以降低特征圖的參數量, 提升計算速度, 增加感受野, 是一種降采樣操作。池化是一種較強的先驗, 可以使模型更關注全局特征而非局部出現的位置, 這種降維的過程可以保留一些重要的特征信息, 提升容錯能力, 並且還能在一定程度上起到防止過擬合的作用 。
在物體檢測中, 常用的池化有最大值池化(Max Pooling) 與平均值池化(Average Pooling) 。 池化層有兩個主要的輸入參數, 即核尺寸kernel_size與步長stride。 如圖3.7所示為一個核尺寸與步長都為2的最大值池化過程, 以左上角為例, 9、 20、 15與26進行最大值池化, 保留26。
下面是PyTorch對於池化層的實現。

1 import torch 2 from torch import nn 3 4 max_pooling = nn.MaxPool2d(2, stride=2) 5 aver_pooling = nn.AvgPool2d(2, stride=2) 6 7 input = torch.randn(1, 1, 4, 4) 8 print(input) 9 >> tensor([[[[ 1.2237, -0.8173, -0.2594, 0.1698], 10 [-0.1023, 0.6973, -0.6429, 0.8561], 11 [-0.3660, 0.1269, 0.2488, 0.0576], 12 [ 0.0859, 0.1622, -0.0725, -0.0237]]]]) 13 14 # 池化主要需要兩個參數, 第一個參數代表池化區域大小, 第二個參數表示步長 15 out_max = max_pooling(input) 16 print(out_max) 17 >> tensor([[[[1.2237, 0.8561], 18 [0.1622, 0.2488]]]]) 19 20 # 調用最大值池化與平均值池化, 可以看到size從[1, 1, 4, 4]變為了[1, 1, 2, 2] 21 out_aver = aver_pooling(input) 22 print(out_aver) 23 >> tensor([[[[0.2503, 0.0309], 24 [0.0023, 0.0525]]]])
2. Dropout層
在深度學習中, 當參數過多而訓練樣本又比較少時, 模型容易產生過擬合現象。 過擬合是很多深度學習乃至機器學算法的通病, 具體表現為在訓練集上預測准確率高, 而在測試集上准確率大幅下降。 2012年, Hinton等人提出了Dropout算法, 可以比較有效地緩解過擬合現象的發生, 起到一定正則化的效果。
Dropout的基本思想如圖3.8所示, 在訓練時, 每個神經元以概率p保留, 即以1-p的概率停止工作, 每次前向傳播保留下來的神經元都不同, 這樣可以使得模型不太依賴於某些局部特征, 泛化性能更強。 在測試時, 為了保證相同的輸出期望值, 每個參數還要乘以p。 當然還有另外一種計算方式稱為Inverted Dropout, 即在訓練時將保留下的神經元乘以1/p, 這樣測試時就不需要再改變權重。
至於Dropout為什么可以防止過擬合, 可以從以下3個方面解釋。
·多模型的平均: 不同的固定神經網絡會有不同的過擬合, 多個取平均則有可能讓一些相反的擬合抵消掉, 而Dropout每次都是不同的神經元失活, 可以看做是多個模型的平均, 類似於多數投票取勝的策略。
·減少神經元間的依賴: 由於兩個神經元不一定同時有效, 因此減少了特征之間的依賴, 迫使網絡學習有更為魯棒的特征, 因為神經網絡不應該對特定的特征敏感, 而應該從眾多特征中學習更為共同的規律,這也起到了正則化的效果。
·生物進化: Dropout類似於性別在生物進化中的角色, 物種為了適應環境變化, 在繁衍時取雄性和雌性的各一半基因進行組合, 這樣可以適應更復雜的新環境, 避免了單一基因的過擬合, 當環境發生變化時也不至於滅絕。
在PyTorch中使用Dropout非常簡單, 示例如下:

import torch from torch import nn # PyTorch將元素置0來實現Dropout層, 第一個參數為置0概率, 第二個為是否原地操作 dropout = nn.Dropout(0.5, inplace=False) input = torch.randn(2, 64, 7, 7) output = dropout(input) print(output.shape)
Dropout被廣泛應用到全連接層中, 一般保留概率設置為0.5, 而在較為稀疏的卷積網絡中則一般使用下一節將要介紹的BN層來正則化模型, 使得訓練更穩定。
3. BN層
為了追求更高的性能, 卷積網絡被設計得越來越深, 然而網絡卻變得難以訓練收斂與調參。 原因在於, 淺層參數的微弱變化經過多層線性變換與激活函數后會被放大, 改變了每一層的輸入分布, 造成深層的網絡需要不斷調整以適應這些分布變化, 最終導致模型難以訓練收斂。
由於網絡中參數變化導致的內部節點數據分布發生變化的現象被稱做ICS(Internal Covariate Shift) 。 ICS現象容易使訓練過程陷入飽和區, 減慢網絡的收斂。 前面提到的ReLU從激活函數的角度出發, 在一定程度上解決了梯度飽和的現象, 而2015年提出的BN層, 則從改變數據分布的角度避免了參數陷入飽
和區。 由於BN層優越的性能, 其已經是當前卷積網絡中的“標配”。
BN層首先對每一個batch的輸入特征進行白化操作, 即去均值方差過程。 假設一個batch的輸入數據為x: B={ x1,…,xm} , 首先求該batch數據的均值與方差, 如式(3-5) 和式(3-6) 所示。
以上公式中, m代表batch的大小, μB為批處理數據的均值, σ2B為批處理數據的方差。 在求得均值方差后, 利用式(3-7) 進行去均值方差操作:
白化操作可以使輸入的特征分布具有相同的均值與方差, 固定了每一層的輸入分布, 從而加速網絡的收斂。 然而, 白化操作雖然從一定程度上避免了梯度飽和, 但也限制了網絡中數據的表達能力, 淺層學到的參數信息會被白化操作屏蔽掉, 因此, BN層在白化操作后又增加了一個線性變換操作, 讓數據盡可能地恢復本身的表達能力, 如公式(3-7) 和公式(3-8) 所示。
公式(3-8) 中, γ與β為新引進的可學習參數, 最終的輸出為yi。
BN層可以看做是增加了線性變換的白化操作, 在實際工程中被證明了能夠緩解神經網絡難以訓練的問題。 BN層的優點主要有以下3點:
·緩解梯度消失, 加速網絡收斂。 BN層可以讓激活函數的輸入數據落在非飽和區, 緩解了梯度消失問題。 此外, 由於每一層數據的均值與方差都在一定范圍內, 深層網絡不必去不斷適應淺層網絡輸入的變化,實現了層間解耦, 允許每一層獨立學習, 也加快了網絡的收斂。
·簡化調參, 網絡更穩定。 在調參時, 學習率調得過大容易出現震盪與不收斂, BN層則抑制了參數微小變化隨網絡加深而被放大的問題, 因此對於參數變化的適應能力更強, 更容易調參。
·防止過擬合。 BN層將每一個batch的均值與方差引入到網絡中, 由於每個batch的這兩個值都不相同, 可看做為訓練過程增加了隨機噪音, 可以起到一定的正則效果, 防止過擬合。
在測試時, 由於是對單個樣本進行測試, 沒有batch的均值與方差,通常做法是在訓練時將每一個batch的均值與方差都保留下來, 在測試時使用所有訓練樣本均值與方差的平均值。
PyTorch中使用BN層很簡單, 示例如下:

1 import torch 2 from torch import nn 3 # 使用BN層需要傳入一個參數為num_features, 即特征的通道數 4 bn = nn.BatchNorm2d(64) 5 print(bn) 6 >> BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) 7 8 # eps為公式中的є, momentum為均值方差的動量, affine為添加可學習參數 9 input = torch.randn(4, 64, 224, 224) 10 output = bn(input) 11 # BN層不改變輸入、 輸出的特征大小 12 print(output.shape) 13 >> torch.Size([4, 64, 224, 224])
盡管BN層取得了巨大的成功, 但仍有一定的弊端, 主要體現在以下兩點:
·由於是在batch的維度進行歸一化, BN層要求較大的batch才能有效地工作, 而物體檢測等任務由於占用內存較高, 限制了batch的大小, 這會限制BN層有效地發揮歸一化功能。
·數據的batch大小在訓練與測試時往往不一樣。 在訓練時一般采用滑動來計算平均值與方差, 在測試時直接拿訓練集的平均值與方差來使用。 這種方式會導致測試集依賴於訓練集, 然而有時訓練集與測試集的數據分布並不一致。
因此, 我們能不能避開batch來進行歸一化呢? 答案是可以的, 最新的工作GN(Group Normalization) 從通道方向計算均值與方差, 使用更 為靈活有效, 避開了batch大小對歸一化的影響。
具體來講, GN先將特征圖的通道分為很多個組, 對每一個組內的參數做歸一化, 而不是batch。 GN之所以能夠工作的原因, 筆者認為是在特征圖中, 不同的通道代表了不同的意義, 例如形狀、 邊緣和紋理等, 這些不同的通道並不是完全獨立地分布, 而是可以放到一起進行歸一化分析。
4. 全連接層
全連接層(Fully Connected Layers) 一般連接到卷積網絡輸出的特征圖后邊, 特點是每一個節點都與上下層的所有節點相連, 輸入與輸出都被延展成一維向量, 因此從參數量來看全連接層的參數量是最多的,如圖3.9所示。
在物體檢測算法中, 卷積網絡的主要作用是從局部到整體地提取圖像的特征, 而全連接層則用來將卷積抽象出的特征圖進一步映射到特定維度的標簽空間, 以求取損失或者輸出預測結果。
在第2章中的感知機例子即是使用了三層的全連接網絡進行分類,PyTorch使用全連接層需要指定輸入的與輸出的維度。 示例如下:

import torch from torch import nn # 第一維表示一共有4個樣本 input = torch.randn(4, 1024) linear = nn.Linear(1024, 4096) output = linear(input) print(input.shape) >> torch.Size([4, 1024]) print(output.shape) >> torch.Size([4, 4096])
然而, 隨着深度學習算法的發展, 全連接層的缺點也逐漸暴露了出來, 最致命的問題在於其參數量的龐大。 在此以VGGNet為例說明, 其第一個全連接層的輸入特征為7×7×512=25088個節點, 輸出特征是大小為4096的一維向量, 由於輸出層的每一個點都來自於上一層所有點的權重相加, 因此這一層的參數量為25088×4096≈108。 相比之下, VGGNet最后一個卷積層的卷積核大小為3×3×512×512≈2.4×106, 全連接層的參數量是這一個卷積層的40多倍。
大量的參數會導致網絡模型應用部署困難, 並且其中存在着大量的參數冗余, 也容易發生過擬合的現象。 在很多場景中, 我們可以使用全局平均池化層(Global Average Pooling, GAP) 來取代全連接層, 這種思想最早見於NIN(Network in Network) 網絡中, 總體上, 使用GAP有如下3點好處:
·利用池化實現了降維, 極大地減少了網絡的參數量。
·將特征提取與分類合二為一, 一定程度上可以防止過擬合。
·由於去除了全連接層, 可以實現任意圖像尺度的輸入。