SPP原理和代碼

空間金字塔池化(Spatial Pyramid Pooling, SPP)原理和代碼實現(Pytorch)

 

一、爲什麼需要SPP
首先需要知道爲什麼需要SPP。
我們都知道卷積神經網絡(CNN)由卷積層和全連接層組成,其中卷積層對於輸入數據的大小並沒有要求,唯一對數據大小有要求的則是第一個全連接層,因此基本上所有的CNN都要求輸入數據固定大小,例如著名的VGG模型則要求輸入數據大小是 (224*224) 。

固定輸入數據大小有兩個問題:
很多場景所得到數據並不是固定大小的,例如街景文字基本上其高寬比是不固定的,如下圖示紅色框出的文字。
在這裏插入圖片描述
2.可能你會說可以對圖片進行切割,但是切割的話很可能會丟失到重要信息。

綜上,SPP的提出就是爲了解決CNN輸入圖像大小必須固定的問題,從而可以使得輸入圖像高寬比和大小任意。
二、SPP原理

更加具體的原理可查看原論文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition
在這裏插入圖片描述

上圖是原文中給出的示意圖,需要從下往上看:

  • 首先是輸入層(input image),其大小可以是任意的

  • 進行卷積運算,到最後一個卷積層(圖中是conv5)輸出得到該層的特徵映射(feature maps),其大小也是任意的

  • 下面進入SPP層

    我們先看最左邊有16個藍色小格子的圖,它的意思是將從conv5得到的特徵映射分成16份,另外16X256中的256表示的是channel,即SPP對每一層都分成16份(不一定是等比分,原因看後面的內容就能理解了)。

    中間的4個綠色小格子和右邊1個紫色大格子也同理,即將特徵映射分別分成4X256和1X256份

那麼將特徵映射分成若干等分是做什麼用的呢? 我們看SPP的名字就是到了,是做池化操作,一般選擇MAX Pooling,即對每一份進行最大池化。

我們看上圖,通過SPP層,特徵映射被轉化成了16X256+4X256+1X256 = 21X256的矩陣,在送入全連接時可以擴展成一維矩陣,即1X10752,所以第一個全連接層的參數就可以設置成10752了,這樣也就解決了輸入數據大小任意的問題了。

注意上面劃分成多少份是可以自己是情況設置的,例如我們也可以設置成3X3等,但一般建議還是按照論文中說的的進行劃分。

三、SPP公式

理論應該理解了,那麼如何實現呢?下面將介紹論文中給出的計算公式,但是在這之前先要介紹兩種計算符號以及池化後矩陣大小的計算公式:
1. 預備知識
在這裏插入圖片描述
2.公式

假設

輸入數據大小是(c,hin,win),分別表示通道數,高度,寬度
池化數量:(n,n)
  •  

那麼則有

核(Kernel)大小: ⌈hinn,winn⌉=ceil(hinn,winn)
步長(Stride)大小: ⌊hinn,winn⌋=floor(hinn,winn)
  •  

我們可以驗證一下,假設輸入數據大小是(10,7,11), 池化數量(2,2):

那麼核大小爲(4,6), 步長大小爲(3,5), 得到池化後的矩陣大小的確是2∗2。

3.公式修訂

是的,論文中給出的公式的確有些疏漏,我們還是以舉例子的方式來說明

假設輸入數據大小和上面一樣是(10,7,11), 但是池化數量改爲(4,4):

此時核大小爲(2,3), 步長大小爲(1,2),得到池化後的矩陣大小的確是6∗5 ←[簡單的計算矩陣大小的方法:(7=2+15, 11=3+24)],而不是4∗4。

那麼問題出在哪呢?

我們忽略了padding的存在(我在原論文中沒有看到關於padding的計算公式,如果有的話。。。那就是我看走眼了,麻煩提示我一下在哪個位置寫過,謝謝)。

仔細看前面的計算公式我們很容易發現並沒有給出padding的公式,在經過N次使用SPP計算得到的結果與預期不一樣以及查找各種網上資料(儘管少得可憐)後,現將加入padding後的計算公式總結如下。
在這裏插入圖片描述
現在再來檢驗一下:
假設輸入數據大小和上面一樣是(10,7,11), 池化數量爲(4,4):

Kernel大小爲(2,3),Stride大小爲(2,3),所以Padding爲(1,1)。

利用矩陣大小計算公式:⌊h+2p−fs+1⌋*⌊w+2p−fs+1⌋得到池化後的矩陣大小爲:4∗4。

四、代碼實現

#coding=utf-8

import math
import torch
import torch.nn.functional as F

# 構建SPP層(空間金字塔池化層)
class SPPLayer(torch.nn.Module):

    def __init__(self, num_levels, pool_type='max_pool'):
        super(SPPLayer, self).__init__()

        self.num_levels = num_levels
        self.pool_type = pool_type

    def forward(self, x):
        num, c, h, w = x.size() # num:樣本數量 c:通道數 h:高 w:寬
        for i in range(self.num_levels):
            level = i+1
            kernel_size = (math.ceil(h / level), math.ceil(w / level))
            stride = (math.ceil(h / level), math.ceil(w / level))
            pooling = (math.floor((kernel_size[0]*level-h+1)/2), math.floor((kernel_size[1]*level-w+1)/2))

            # 選擇池化方式 
            if self.pool_type == 'max_pool':
                tensor = F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)
            else:
                tensor = F.avg_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1)

            # 展開、拼接
            if (i == 0):
                x_flatten = tensor.view(num, -1)
            else:
                x_flatten = torch.cat((x_flatten, tensor.view(num, -1)), 1)
        return x_flatten
  •  

原文地址:https://www.cnblogs.com/marsggbo/p/8572846.html
原文中的代碼:marsggbo/sppnet-pytorch

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