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

想直接看公式的可跳至第三節 3.公式修正python

1、爲何須要SPP

首先須要知道爲何會須要SPP。git

咱們都知道卷積神經網絡(CNN)由卷積層和全鏈接層組成,其中卷積層對於輸入數據的大小並無要求,惟一對數據大小有要求的則是第一個全鏈接層,所以基本上全部的CNN都要求輸入數據固定大小,例如著名的VGG模型則要求輸入數據大小是 (224*224)github

固定輸入數據大小有兩個問題:spring

1.不少場景所獲得數據並非固定大小的,例如街景文字基本上其高寬比是不固定的,以下圖示紅色框出的文字。網絡


2.可能你會說能夠對圖片進行切割,可是切割的話極可能會丟失到重要信息。框架

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

2、SPP原理

更加具體的原理可查閱原論文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition學習



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

  • 首先是輸入層(input image),其大小能夠是任意的
  • 進行卷積運算,到最後一個卷積層(圖中是\(conv_5\))輸出獲得該層的特徵映射(feature maps),其大小也是任意的
  • 下面進入SPP層
    • 咱們先看最左邊有16個藍色小格子的圖,它的意思是將從\(conv_5\)獲得的特徵映射分紅16份,另外16X256中的256表示的是channel,即SPP對每一層都分紅16份(不必定是等比分,緣由看後面的內容就能理解了)。
    • 中間的4個綠色小格子和右邊1個紫色大格子也同理,即將特徵映射分別分紅4X2561X256

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

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

注意上面劃分紅多少份是能夠本身是狀況設置的,例如咱們也能夠設置成3X3等,但通常建議仍是按照論文中說的的進行劃分。

3、SPP公式

理論應該理解了,那麼如何實現呢?下面將介紹論文中給出的計算公式,可是在這以前先要介紹兩種計算符號以及池化後矩陣大小的計算公式:

1.預先知識

取整符號:

  • ⌊⌋:向下取整符號 ⌊59/60⌋=0,有時也用 floor() 表示

  • ⌈⌉:向上取整符號 ⌈59/60⌉=1, 有時也用ceil() 表示

池化後矩陣大小計算公式:

  • 沒有步長(Stride):\((h+2p-f+1)*(w+2p-f+1)\)
  • 有步長(Stride):⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋

2.公式

假設

  • 輸入數據大小是\((c, h_{in}, w_{in})\),分別表示通道數,高度,寬度
  • 池化數量:\((n,n)\)

那麼則有

  • 核(Kernel)大小: \(⌈\frac{h_{in}}{n},\frac{w_{in}}{n}⌉=ceil(\frac{h_{in}}{n},\frac{w_{in}}{n})\)
  • 步長(Stride)大小: \(⌊\frac{h_{in}}{n},\frac{w_{in}}{n}⌋=floor(\frac{h_{in}}{n},\frac{w_{in}}{n})\)

咱們能夠驗證一下,假設輸入數據大小是\((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+1*5, 11=3+2*4)],而不是\(4*4\)

那麼問題出在哪呢?

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

仔細看前面的計算公式咱們很容易發現並無給出padding的公式,在通過N次使用SPP計算獲得的結果與預期不同以及查找各類網上資料(儘管少得可憐)後,現將加入padding後的計算公式總結以下。

\(K_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)
\(S_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)
\(p_h = ⌊\frac{k_h*n-h_{in}+1}{2}⌋=floor(\frac{k_h*n-h_{in}+1}{2})\)
\(h_{new} = 2*p_h +h_{in}\)


\(K_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)
\(S_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)
\(p_w = ⌊\frac{k_w*n-w_{in}+1}{2}⌋=floor(\frac{k_w*n-w_{in}+1}{2})\)
\(w_{new} = 2*p_w +w_{in}\)

  • \(k_h\): 表示核的高度
  • \(S_h\): 表示高度方向的步長
  • \(p_h\): 表示高度方向的填充數量,須要乘以2

注意核和步長的計算公式都使用的是ceil(),即向上取整,而padding使用的是floor(),即向下取整

如今再來檢驗一下:
假設輸入數據大小和上面同樣是\((10, 7, 11)\), 池化數量爲\((4,4)\)

Kernel大小爲\((2,3)\),Stride大小爲\((2,3)\),因此Padding爲\((1,1)\)

利用矩陣大小計算公式:⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋獲得池化後的矩陣大小爲:\(4*4\)

4、代碼實現(Python)

這裏我使用的是PyTorch深度學習框架,構建了一個SPP層,代碼以下:

#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

上述代碼參考: sppnet-pytorch

爲防止原做者將代碼刪除,我已經Fork了,也能夠經過以下地址訪問代碼:
marsggbo/sppnet-pytorch



MARSGGBO原創




2018-3-15

相關文章
相關標籤/搜索