卷積神經網絡中的各種池化操作

池化操作(Pooling)是CNN中非常常見的一種操作,Pooling層是模仿人的視覺系統對數據進行降維,池化操作通常也叫做子採樣(Subsampling)或降採樣(Downsampling),在構建卷積神經網絡時,往往會用在卷積層之後,通過池化來降低卷積層輸出的特徵維度,有效減少網絡參數的同時還可以防止過擬合現象。

主要功能有以下幾點:

  1. 抑制噪聲,降低信息冗餘
  2. 提升模型的尺度不變性、旋轉不變形
  3. 降低模型計算量
  4. 防止過擬合

一提到池化操作,大部分人第一想到的就是maxpool和avgpool,實際上還有很多種池化操作。

大部分pooling操作滿足上圖的模型,假設輸入大小爲ii, 輸出大小爲oo, kernel size簡稱kk, stride簡稱ss,滿足以下公式:
o=iks+1 o=\lfloor\frac{i-k}{s} \rfloor+1

1. 最大/平均池化

最大池化就是選擇圖像區域中最大值作爲該區域池化以後的值,反向傳播的時候,梯度通過前向傳播過程的最大值反向傳播,其他位置梯度爲0。

使用的時候,最大池化又分爲重疊池化和非重疊池化,比如常見的stride=kernel size的情況屬於非重疊池化,如果stride<kernel size 則屬於重疊池化。重疊池化相比於非重疊池化不僅可以提升預測精度,同時在一定程度上可以緩解過擬合。

重疊池化一個應用的例子就是yolov3-tiny的backbone最後一層,使用了一個stride=1, kernel size=2的maxpool進行特徵的提取。

>>> import torch
>>> import torch.nn.functional as F
>>> input = torch.Tensor(4,3,16,16)
>>> output = F.max_pool2d(input, kernel_size=2, stride=2)
>>> output.shape
torch.Size([4, 3, 8, 8])
>>>

平均池化就是將選擇的圖像區域中的平均值作爲該區域池化以後的值。

>>> import torch
>>> import torch.nn.functional as F
>>> input = torch.Tensor(4,3,16,16)
>>> output = F.avg_pool2d(input, kernel_size=2, stride=2)
>>> output.shape
torch.Size([4, 3, 8, 8])
>>>

2. 隨機池化

Stochastic pooling如下圖所示,特徵區域的大小越大,代表其被選擇的概率越高,比如左下角的本應該是選擇7,但是由於引入概率,5也有一定機率被選中。

下表是隨機池化在CIFAR-10上的表現,可以看出,使用隨機池化效果和採用dropout的結果接近,證明了其有一定防止過擬合的作用。

更詳細內容請看論文:《Stochastic Pooling for Regularization of Deep Convolutional Neural Networks》

3. 中值池化

與中值濾波特別相似,但是用的非常少,中值池化也具有學習邊緣和紋理結構的特性,抗噪聲能力比較強。

4. 組合池化

組合池化則是同時利用最大值池化與均值池化兩種的優勢而引申的一種池化策略。常見組合策略有兩種:Cat與Add。常常被當做分類任務的一個trick,其作用就是豐富特徵層,maxpool更關注重要的局部特徵,而average pooling更關注全局特徵。

def add_avgmax_pool2d(x, output_size=1):
    x_avg = F.adaptive_avg_pool2d(x, output_size)
    x_max = F.adaptive_max_pool2d(x, output_size)
    return 0.5 * (x_avg + x_max)

def cat_avgmax_pool2d(x, output_size=1):
    x_avg = F.adaptive_avg_pool2d(x, output_size)
    x_max = F.adaptive_max_pool2d(x, output_size)
    return torch.cat([x_avg, x_max], 1)

5. Spatial Pyramid Pooling

SPP是在SPPNet中提出的,SPPNet提出比較早,在RCNN之後提出的,用於解決重複卷積計算和固定輸出的兩個問題,具體方法如下圖所示:

在feature map上通過selective search獲得窗口,然後將這些區域輸入到CNN中,然後進行分類。

實際上SPP就是多個空間池化的組合,對不同輸出尺度採用不同的劃窗大小和步長以確保輸出尺度相同,同時能夠融合金字塔提取出的多種尺度特徵,能夠提取更豐富的語義信息。常用於多尺度訓練和目標檢測中的RPN網絡。

在YOLOv3中有一個網絡結構叫yolov3-spp.cfg, 這個網絡往往能達到比yolov3.cfg本身更高的準確率,具體cfg如下:

### SPP ###
[maxpool]
stride=1
size=5

[route]
layers=-2

[maxpool]
stride=1
size=9

[route]
layers=-4

[maxpool]
stride=1
size=13

[route]
layers=-1,-3,-5,-6

### End SPP ###

這裏的SPP相當於是原來的SPPNet的變體,通過使用多個kernel size的maxpool,最終將所有feature map進行concate,得到新的特徵組合。

再來看一下官方提供的yolov3和yolov3-spp在COCO數據集上的對比:

可以看到,在幾乎不增加FLOPS的情況下,YOLOv3-SPP要比YOLOv3-608mAP高接近3個百分點。

分析一下SPP有效的原因:

  1. 從感受野角度來講,之前計算感受野的時候可以明顯發現,maxpool的操作對感受野的影響非常大,其中主要取決於kernel size大小。在SPP中,使用了kernel size非常大的maxpool會極大提高模型的感受野,筆者沒有詳細計算過darknet53這個backbone的感受野,在COCO上有效很可能是因爲backbone的感受野還不夠大。
  2. 第二個角度是從Attention的角度考慮,這一點啓發自CSDN@小楞(鏈接在參考文獻中),他在文章中這樣講:

出現檢測效果提升的原因:通過spp模塊實現局部特徵和全局特徵(所以空間金字塔池化結構的最大的池化核要儘可能的接近等於需要池化的featherMap的大小)的featherMap級別的融合,豐富最終特徵圖的表達能力,從而提高MAP。

Attention機制很多都是爲了解決遠距離依賴問題,通過使用kernel size接近特徵圖的size可以以比較小的計算代價解決這個問題。另外就是如果使用了SPP模塊,就沒有必要在SPP後繼續使用其他空間注意力模塊比如SK block,因爲他們作用相似,可能會有一定冗餘。

ps: 這個想法還沒有進行試驗的驗證,有興趣的可以將YOLOv3-spp中的kernel size改爲19,然後在COCO數據集上測試,看是否能夠超越60.6。

6. Global Average/Max Pooling

Gloabel Average Pooling 是NIN裏邊的做法,一般使用torchvision提供的預訓練模型進行finetune的時候,通常使用Global Average Pooling,原因就是可以不考慮圖片的輸入尺寸,只與filter有關。

>>> import torch
>>> from torch.nn import AdaptiveAvgPool2d
>>> input = torch.zeros((4,12,18,18)) # batch size, fileter, h, w
>>> gap = AdaptiveAvgPool2d(1)
>>> output = gap(input)
>>> output.shape
torch.Size([4, 12, 1, 1])
>>> output.view(input.shape[0],-1).shape
torch.Size([4, 12])
>>>

7. NetVLAD池化

這部分來自:DeepLearning-500-questions#5

NetVLAD是論文《NetVLAD: CNN Architecture for Weakly Supervised Place Recognition》提出的一個局部特徵聚合的方法。

在傳統的網絡裏面,例如VGG啊,最後一層卷積層輸出的特徵都是類似於Batchsize x 3 x 3 x 512的這種東西,然後會經過FC聚合,或者進行一個Global Average Pooling(NIN裏的做法),或者怎麼樣,變成一個向量型的特徵,然後進行Softmax or 其他的Loss。

這種方法說簡單點也就是輸入一個圖片或者什麼的結構性數據,然後經過特徵提取得到一個長度固定的向量,之後可以用度量的方法去進行後續的操作,比如分類啊,檢索啊,相似度對比等等。

那麼NetVLAD考慮的主要是最後一層卷積層輸出的特徵這裏,我們不想直接進行欠採樣或者全局映射得到特徵,對於最後一層輸出的W x H x D,設計一個新的池化,去聚合一個“局部特徵“,這即是NetVLAD的作用。

NetVLAD的一個輸入是一個W x H x D的圖像特徵,例如VGG-Net最後的3 x 3 x 512這樣的矩陣,在網絡中還需加一個維度爲Batchsize。

NetVLAD還需要另輸入一個標量K即表示VLAD的聚類中心數量,它主要是來構成一個矩陣C,是通過原數據算出來的每一個W×HW \times H特徵的聚類中心,C的shape即C:K×DC: K \times D​,然後根據三個輸入,VLAD是計算下式的V:

V(j,k)=i=1Nak(xi)(xi(j)ck(j)) V(j, k) = \sum_{i=1}^{N}{a_k(x_i)(x_i(j) - c_k(j))}

其中j表示維度,從1到D,可以看到V的j是和輸入與c對應的,對每個類別k,都對所有的x進行了計算,如果xix_i屬於當前類別k,ak=1a_k=1,否則ak=0a_k=0,計算每一個x和它聚類中心的殘差,然後把殘差加起來,即是每個類別k的結果,最後分別L2正則後拉成一個長向量後再做L2正則,正則非常的重要,因爲這樣才能統一所有聚類算出來的值,而殘差和的目的主要是消減不同聚類上的分佈不均,兩者共同作用才能得到最後正常的輸出。

輸入與輸出如下圖所示:

image

中間得到的K個D維向量即是對D個x都進行了與聚類中心計算殘差和的過程,最終把K個D維向量合起來後進行即得到最終輸出的K×DK \times D​長度的一維向量。

而VLAD本身是不可微的,因爲上面的a要麼是0要麼是1,表示要麼當前描述x是當前聚類,要麼不是,是個離散的,NetVLAD爲了能夠在深度卷積網絡裏使用反向傳播進行訓練,對a進行了修正。

那麼問題就是如何重構一個a,**使其能夠評估當前的這個x和各個聚類的關聯程度?**用softmax來得到:

ak=eWkTxi+bkeWkTxi+bk a_k = \frac{e^{W_k^T x_i + b_k}}{e^{W_{k'}^T x_i + b_{k'}}}

將這個把上面的a替換後,即是NetVLAD的公式,可以進行反向傳播更新參數。

所以一共有三個可訓練參數,上式a中的W:K×DW: K \times D,上式a中的b:K×1b: K \times 1,聚類中心c:K×Dc: K \times D,而原始VLAD只有一個參數c。

最終池化得到的輸出是一個恆定的K x D的一維向量(經過了L2正則),如果帶Batchsize,輸出即爲Batchsize x (K x D)的二維矩陣。

NetVLAD作爲池化層嵌入CNN網絡即如下圖所示,

image

原論文中採用將傳統圖像檢索方法VLAD進行改進後應用在CNN的池化部分作爲一種另類的局部特徵池化,在場景檢索上取得了很好的效果。

後續相繼又提出了ActionVLAD、ghostVLAD等改進。

**評價:**這個NetVLAD Layer和SENet非常相似,可以看做是通道注意力機制的實現,不過具體實現方法有別於SENet。

8. 雙線性池化

Bilinear Pooling是在《Bilinear CNN Models for Fine-grained Visual Recognition》被提出的,主要用在細粒度分類網絡中。雙線性池化主要用於特徵融合,對於同一個樣本提取得到的特徵x和特徵y, 通過雙線性池化來融合兩個特徵(外積),進而提高模型分類的能力。

主要思想是對於兩個不同圖像特徵的處理方式上的不同。傳統的,對於圖像的不同特徵,我們常用的方法是進行串聯(連接),或者進行sum,或者max-pooling。論文的主要思想是,研究發現人類的大腦發現,人類的視覺處理主要有兩個pathway, the ventral stream是進行物體識別的,the dorsal stream 是爲了發現物體的位置。

論文基於這樣的思想,希望能夠將兩個不同特徵進行結合來共同發揮作用,提高細粒度圖像的分類效果。論文希望兩個特徵能分別表示圖像的位置和對圖形進行識別。論文提出了一種Bilinear Model。

如果特徵 x 和特徵y來自兩個特徵提取器,則被稱爲多模雙線性池化(MBP,Multimodal Bilinear Pooling)

如果特徵 x = 特徵 y,則被稱爲同源雙線性池化(HBP,Homogeneous Bilinear Pooling)或者二階池化(Second-order Pooling)。

pytorch實現:

X = torch.reshape(N, D, H * W)                        # Assume X has shape N*D*H*W
X = torch.bmm(X, torch.transpose(X, 1, 2)) / (H * W)  # Bilinear pooling
assert X.size() == (N, D, D)
X = torch.reshape(X, (N, D * D))
X = torch.sign(X) * torch.sqrt(torch.abs(X) + 1e-5)   # Signed-sqrt normalization
X = torch.nn.functional.normalize(X)                  # L2 normalization

之後又有很多人出於對雙線性池化存在的特徵維度過高等問題進行各種改進,具體可以看知乎文章:

https://zhuanlan.zhihu.com/p/62532887

9. UnPooling

是一種上採樣操作,具體操作如下:

流程描述:

1.在Pooling(一般是Max Pooling)時,保存最大值的位置。

2.中間經歷若干網絡層的運算。

3.上採樣階段,利用第1步保存的Max Location,重建下一層的feature map。

UnPooling不完全是Pooling的逆運算,Pooling之後的feature map,要經過若干運算,纔會進行UnPooling操作;對於非Max Location的地方以零填充。然而這樣並不能完全還原信息。

10. 參考

https://arxiv.org/pdf/1603.07285.pdf

https://arxiv.org/abs/1301.3557

https://zhuanlan.zhihu.com/p/77040467

https://arxiv.org/pdf/1611.05138.pdf

https://blog.csdn.net/qq_33270279/article/details/103898245

https://github.com/scutan90/DeepLearning-500-questions

https://zhuanlan.zhihu.com/p/62532887

https://blog.csdn.net/qq_32768091/article/details/84145088

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