天天看點

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

  作者丨皮特潘

編輯丨極市平台

導讀

所謂“插件”,就是要能錦上添花,又容易植入、落地,即真正的即插即用。本文盤點的“插件”能夠提升CNN平移、旋轉、scale等變性能力或多尺度特征提取,感受野等能力,在很多SOTA網絡中都會看到它們的影子。

前言

本文盤點一些CNN網絡中設計比較精巧而又實用的“插件”。所謂“插件”,就是不改變網絡主體結構, 可以很容易嵌入到主流網絡當中,提高網絡提取特征的能力,能夠做到plug-and-play。網絡也有很多類似盤點工作,都宣稱所謂的即插即用、無痛漲點。不過根據筆者經驗和收集,發現很多插件都是不實用、不通用、甚至不work的,于是有了這一篇。

首先,我的認識是:既然是“插件”,就要是錦上添花的,又容易植入,容易落地的,真正的即插即用。本文盤點的“插件”,在很多SOTA網絡中會看到它們的影子。是值得推廣的良心“插件”,真正能做到plug-and-play。總之一句話,就是能夠work的“插件”。很多“插件”都為提升CNN能力而推出的,例如平移、旋轉、scale等變性能力,多尺度特征提取能力,感受野等能力,感覺空間位置能力等等。

入圍名單:STN、ASPP、Non-local、SE、CBAM、DCNv1&v2、CoordConv、Ghost、BlurPool、RFB、ASFF

1 STN

出自論文:Spatial Transformer Networks

論文連結:https://arxiv.org/pdf/1506.02025.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

在OCR等任務中,你會經常看到它的身影。對于CNN網絡,我們希望其具有對物體的姿态、位置等有一定的不變性。即在測試集上可以适應一定的姿态、位置的變化。不變性或等變性可以有效提高模型泛化能力。雖然CNN使用sliding-window卷積操作,在一定程度上具有平移不變性。但很多研究發現,下采樣會破壞網絡的平移不變性。是以可以認為網絡的不變性能力非常弱,更不用說旋轉、尺度、光照等不變性。一般我們利用資料增強來實作網絡的“不變性”。

本文提出STN子產品,顯式将空間變換植入到網絡當中,進而提高網絡的旋轉、平移、尺度等不變性。可以了解為“對齊”操作。STN的結構如上圖所示,每一個STN子產品由Localisation net,Grid generator和Sampler三部分組成。Localisation net用于學習擷取空間變換的參數,就是上式中的六個參數。Grid generator用于坐标映射。Sampler用于像素的采集,是利用雙線性插值的方式進行。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

STN的意義是能夠把原始的圖像糾正成為網絡想要的理想圖像,并且該過程為無監督的方式進行,也就是變換參數是自發學習擷取的,不需要标注資訊。該子產品是一個獨立子產品,可以在CNN的任何位置插入。符合本次“插件”的盤點要求。

核心代碼:

class SpatialTransformer(nn.Module):
    def __init__(self, spatial_dims):
        super(SpatialTransformer, self).__init__()
        self._h, self._w = spatial_dims 
        self.fc1 = nn.Linear(32*4*4, 1024) # 可根據自己的網絡參數具體設定
        self.fc2 = nn.Linear(1024, 6)


    def forward(self, x): 
        batch_images = x #儲存一份原始資料
        x = x.view(-1, 32*4*4)
        # 利用FC結構學習到6個參數
        x = self.fc1(x)
        x = self.fc2(x) 
        x = x.view(-1, 2,3) # 2x3
        # 利用affine_grid生成采樣點
        affine_grid_points = F.affine_grid(x, torch.Size((x.size(0), self._in_ch, self._h, self._w)))
        # 将采樣點作用到原始資料上
        rois = F.grid_sample(batch_images, affine_grid_points)
        return rois, affine_grid_points      

2 ASPP

插件全稱:atrous spatial pyramid pooling

出自論文:DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Conv

論文連結:https://arxiv.org/pdf/1606.00915.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

本插件是帶有空洞卷積的空間金字塔池化子產品,主要是為了提高網絡的感受野,并引入多尺度資訊而提出的。我們知道,對于語義分割網絡,通常面臨是分辨率較大的圖檔,這就要求我們的網絡有足夠的感受野來覆寫到目标物體。對于CNN網絡基本是靠卷積層的堆疊加上下采樣操作來擷取感受野的。本文的該子產品可以在不改變特征圖大小的同時控制感受野,這有利于提取多尺度資訊。其中rate控制着感受野的大小,r越大感受野越大。

ASPP主要包含以下幾個部分:1. 一個全局平均池化層得到image-level特征,并進行1X1卷積,并雙線性插值到原始大小;2. 一個1X1卷積層,以及三個3X3的空洞卷積;3. 将5個不同尺度的特征在channel次元concat在一起,然後送入1X1的卷積進行融合輸出。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心代碼:

class ASPP(nn.Module):
    def __init__(self, in_channel=512, depth=256):
        super(ASPP,self).__init__()
        self.mean = nn.AdaptiveAvgPool2d((1, 1))
        self.conv = nn.Conv2d(in_channel, depth, 1, 1)
        self.atrous_block1 = nn.Conv2d(in_channel, depth, 1, 1)
        # 不同空洞率的卷積
        self.atrous_block6 = nn.Conv2d(in_channel, depth, 3, 1, padding=6, dilation=6)
        self.atrous_block12 = nn.Conv2d(in_channel, depth, 3, 1, padding=12, dilation=12)
        self.atrous_block18 = nn.Conv2d(in_channel, depth, 3, 1, padding=18, dilation=18)
        self.conv_1x1_output = nn.Conv2d(depth * 5, depth, 1, 1)


    def forward(self, x):
        size = x.shape[2:]
     # 池化分支
        image_features = self.mean(x)
        image_features = self.conv(image_features)
        image_features = F.upsample(image_features, size=size, mode='bilinear')
     # 不同空洞率的卷積
        atrous_block1 = self.atrous_block1(x)
        atrous_block6 = self.atrous_block6(x)
        atrous_block12 = self.atrous_block12(x)
        atrous_block18 = self.atrous_block18(x)
        # 彙合所有尺度的特征
     x = torch.cat([image_features, atrous_block1, atrous_block6,atrous_block12, atrous_block18], dim=1)
        # 利用1X1卷積融合特征輸出
        x = self.conv_1x1_output(x)
        return net      

3 Non-local

出自論文:Non-local Neural Networks

論文連結:https://arxiv.org/abs/1711.07971

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

Non-Local是一種attention機制,也是一個易于植入和內建的子產品。Local主要是針對感受野(receptive field)來說的,以CNN中的卷積操作和池化操作為例,它的感受野大小就是卷積核大小,而我們常用3X3的卷積層進行堆疊,它隻考慮局部區域,都是local的運算。不同的是,non-local操作感受野可以很大,可以是全局區域,而不是一個局部區域。捕獲長距離依賴(long-range dependencies),即如何建立圖像上兩個有一定距離的像素之間的聯系,是一種注意力機制。所謂注意力機制就是利用網絡生成saliency map,注意力對應的是顯著性區域,是需要網絡重點關注的區域。

  • 首先分别對輸入的特征圖進行 1X1的卷積來壓縮通道數,得到,特征。
  • 通過reshape操作,轉化三個特征的次元,然後對進行矩陣乘操作,得到類似協方差矩陣, 這一步為了計算出特征中的自相關性,即得到每幀中每個像素對其他所有幀所有像素的關系。
  • 然後對自相關特征進行 Softmax 操作,得到0~1的weights,這裡就是我們需要的 Self-attention系數。
  • 最後将 attention系數,對應乘回特征矩陣g上,與原輸入 feature map X 殘差相加輸出即可。

這裡我們結合一個簡單例子了解一下,假設g為(我們暫時不考慮batch和channel次元):

g = torch.tensor([[1, 2],
                           [3, 4]).view(-1, 1).float()      

為:

theta = torch.tensor([2, 4, 6, 8]).view(-1, 1)      

為:

phi = torch.tensor([7, 5, 3, 1]).view(1, -1)      

那麼,和矩陣相乘如下:

tensor([[14., 10.,  6.,  2.],
                [28., 20., 12.,  4.],
                [42., 30., 18.,  6.],
                [56., 40., 24.,  8.]])      

進過softmax(dim=-1)後如下,每一行代表着g裡面的元素的重要程度,每一行前面的值比較大,是以希望多“注意”到g前面的元素,也就是1比較重要一點。或者這樣了解:注意力矩陣代表着g中每個元素和其他元素的依賴程度。

tensor([[9.8168e-01, 1.7980e-02, 3.2932e-04, 6.0317e-06],
                [9.9966e-01, 3.3535e-04, 1.1250e-07, 3.7739e-11],
                [9.9999e-01, 6.1442e-06, 3.7751e-11, 2.3195e-16],
                [1.0000e+00, 1.1254e-07, 1.2664e-14, 1.4252e-21]])      

注意力作用上之後,整體值向原始g中的值都向1靠攏:

tensor([[1.0187, 1.0003],
                [1.0000, 1.0000]])      

核心代碼:

class NonLocal(nn.Module):
    def __init__(self, channel):
        super(NonLocalBlock, self).__init__()
        self.inter_channel = channel // 2
        self.conv_phi = nn.Conv2d(channel, self.inter_channel, 1, 1,0, False)
        self.conv_theta = nn.Conv2d(channel, self.inter_channel, 1, 1,0, False)
        self.conv_g = nn.Conv2d(channel, self.inter_channel, 1, 1, 0, False)
        self.softmax = nn.Softmax(dim=1)
        self.conv_mask = nn.Conv2d(self.inter_channel, channel, 1, 1, 0, False)


    def forward(self, x):
        # [N, C, H , W]
        b, c, h, w = x.size()
        # 擷取phi特征,次元為[N, C/2, H * W],注意是要保留batch和通道次元的,是在HW上進行的
        x_phi = self.conv_phi(x).view(b, c, -1)
        # 擷取theta特征,次元為[N, H * W, C/2]
        x_theta = self.conv_theta(x).view(b, c, -1).permute(0, 2, 1).contiguous()
        # 擷取g特征,次元為[N, H * W, C/2]
        x_g = self.conv_g(x).view(b, c, -1).permute(0, 2, 1).contiguous()
        # 對phi和theta進行矩陣乘,[N, H * W, H * W]
        mul_theta_phi = torch.matmul(x_theta, x_phi)
        # softmax拉到0~1之間
        mul_theta_phi = self.softmax(mul_theta_phi)
        # 與g特征進行矩陣乘運算,[N, H * W, C/2]
        mul_theta_phi_g = torch.matmul(mul_theta_phi, x_g)
        # [N, C/2, H, W]
        mul_theta_phi_g = mul_theta_phi_g.permute(0, 2, 1).contiguous().view(b, self.inter_channel, h, w)
        # 1X1卷積擴充通道數
        mask = self.conv_mask(mul_theta_phi_g)
        out = mask + x # 殘差連接配接
        return out      

4 SE

出自論文:Squeeze-and-Excitation Networks

論文連結:https://arxiv.org/pdf/1709.01507.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

本文是ImageNet最後一屆比賽的冠軍作品,你會在很多經典網絡結構中看到它的身影,例如Mobilenet v3。其實是一種通道注意力機制。由于特征壓縮和FC的存在,其捕獲的通道注意力特征是具有全局資訊的。本文提出了一種新的結構單元——“Squeeze-and Excitation(SE)”子產品,可以自适應的調整各通道的特征響應值,對通道間的内部依賴關系進行模組化。有以下幾個步驟:

  • Squeeze: 沿着空間次元進行特征壓縮,将每個二維的特征通道變成一個數,是具有全局的感受野。
  • Excitation: 每個特征通道生成一個權重,用來代表該特征通道的重要程度。
  • Reweight:将Excitation輸出的權重看做每個特征通道的重要性,通過相乘的方式作用于每一個通道上。

核心代碼:

class SE_Block(nn.Module):
    def __init__(self, ch_in, reduction=16):
        super(SE_Block, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局自适應池化
        self.fc = nn.Sequential(
            nn.Linear(ch_in, ch_in // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(ch_in // reduction, ch_in, bias=False),
            nn.Sigmoid()
        )


    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c) # squeeze操作
        y = self.fc(y).view(b, c, 1, 1) # FC擷取通道注意力權重,是具有全局資訊的
        return x * y.expand_as(x) # 注意力作用每一個通道上      

5 CBAM

出自論文:CBAM: Convolutional Block Attention Module

論文連結:https://openaccess.thecvf.com/content_ECCV_2018/papers/Sanghyun_Woo_Convolutional_Block_Attention_ECCV_2018_paper.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

SENet在feature map的通道上進行attention權重擷取,然後與原來的feature map相乘。這篇文章指出,該種attention方法法隻關注了通道層面上哪些層會具有更強的回報能力,但是在空間次元上并不能展現出attention。CBAM作為本文的亮點,将attention同時運用在channel和spatial兩個次元上, CBAM與SE Module一樣,可以嵌入在大部分的主流網絡中,在不顯著增加計算量和參數量的前提下能提升模型的特征提取能力。

通道注意力: 如上圖所示,輸入是一個 H×W×C 的特征F,我們先分别進兩個空間的全局平均池化和最大池化得到 兩個 1×1×C 的通道描述。再将它們分别送進一個兩層的神經網絡,第一層神經元個數為 C/r,激活函數為 Relu,第二層神經元個數為 C。注意,這個兩層的神經網絡是共享的。然後,再将得到的兩個特征相加後經過一個 Sigmoid 激活函數得到權重系數 Mc。最後,拿權重系數和 原來的特征 F 相乘即可得到縮放後的新特征。僞代碼:

def forward(self, x):
    # 利用FC擷取全局資訊,和Non-local的矩陣相乘本質上式一樣的
    avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
    max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
    out = avg_out + max_out
    return self.sigmoid(out)      

空間注意力: 與通道注意力相似,給定一個 H×W×C 的特征 F‘,我們先分别進行一個通道次元的平均池化和最大池化得到兩個 H×W×1 的通道描述,并将這兩個描述按照通道拼接在一起。然後,經過一個 7×7 的卷積層, 激活函數為 Sigmoid,得到權重系數 Ms。最後,拿權重系數和特征 F’ 相乘即可得到縮放後的新特征。僞代碼:

def forward(self, x):
    # 這裡利用池化擷取全局資訊
    avg_out = torch.mean(x, dim=1, keepdim=True)
    max_out, _ = torch.max(x, dim=1, keepdim=True)
    x = torch.cat([avg_out, max_out], dim=1)
    x = self.conv1(x)
    return self.sigmoid(x)      

6 DCN v1&v2

插件全稱:Deformable Convolutional

出自論文:

v1: [Deformable Convolutional Networks]

​​https://arxiv.org/pdf/1703.06211.pdf​​

v2: [Deformable ConvNets v2: More Deformable, Better Results]

​​https://arxiv.org/pdf/1811.11168.pdf​​

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

變形卷積可以看作變形+卷積兩個部分,是以可以當作插件使用。在各大主流檢測網絡中,變形卷積真是漲點神器,網上解讀也非常之多。和傳統的固定視窗的卷積相比,變形卷積可以有效地對幾何圖形,因為它的“局部感受野”是可學習的,面向全圖的。這篇論文同時提出了deformable ROI pooling,這兩個方法都是增加額外偏移量的空間采樣位置,不需要額外的監督,是自監督的過程。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

如上圖所示,a為不同的卷積,b為變形卷積,深色的點為卷積核實際采樣的位置,和“标準的”位置有一定的偏移。c和d為變形卷積的特殊形式,其中c為我們常見到的空洞卷積,d為具有學習旋轉特性的卷積,也具備提升感受野的能力。

變形卷積和STN過程非常類似,STN是利用網絡學習出空間變換的6個參數,對特征圖進行整體變換,旨在增加網絡對形變的提取能力。DCN是利用網絡學習數整圖offset,比STN的變形更“全面一點”。STN是仿射變換,DCN是任意變換。公式不貼了,可以直接看代碼實作過程。

變形卷積具有V1和V2兩個版本,其中V2是在V2的基礎上進行改進,除了采樣offset,還增加了采樣權重。V2認為3X3采樣點也應該具有不同的重要程度,是以該處理方法更具有靈活性和拟合能力。

核心代碼:

def forward(self, x):
    # 學習出offset,包括x和y兩個方向,注意是每一個channel中的每一個像素都有一個x和y的offset
    offset = self.p_conv(x)
    if self.v2: # V2的時候還會額外學習一個權重系數,經過sigmoid拉到0和1之間
        m = torch.sigmoid(self.m_conv(x))
    # 利用offset對x進行插值,擷取偏移後的x_offset
    x_offset = self.interpolate(x,offset)
    if self.v2: # V2的時候,将權重系數作用到特征圖上
        m = m.contiguous().permute(0, 2, 3, 1)
        m = m.unsqueeze(dim=1)
        m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)
        x_offset *= m
    out = self.conv(x_offset) # offset作用後,在進行标準的卷積過程
    return out      

7 CoordConv

出自論文:An intriguing failing of convolutional neural networks and the CoordConv solution

論文連結:https://arxiv.org/pdf/1807.03247.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

在Solo語義分割算法和Yolov5中你可以看到它的身影。本文從幾個小實驗為出發點,探究了卷積網絡在坐标變換上的能力。就是它無法将空間表示轉換成笛卡爾空間中的坐标。如下圖所示,我們向一個網絡中輸入(i, j)坐标,要求它輸出一個64×64的圖像,并在坐标處畫一個正方形或者一個像素,然而網絡在測試集上卻無法完成。雖然這項任務是我們人類認為極其簡單的工作。分析原因是卷積作為一種局部的、共享權重的過濾器應用到輸入上時,它是不知道每個過濾器在哪,無法捕捉位置資訊的。是以我們可以幫助卷積,讓它知道過濾器的位置。僅僅需要在輸入上添加兩個通道,一個是i坐标,另一個是j坐标。具體做法如上圖所示,送入濾波器之前增加兩個通道。這樣,網絡就具備了空間位置資訊的能力,是不是很神奇?你可以随機在分類、分割、檢測等任務中使用這種挂件。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...
【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

如上面第一組圖檔,傳統的CNN在根據坐标數值生成圖像的任務中,訓練集很好,測試集一團糟。第二組圖檔增加了 CoordConv 之後可以輕松完成該任務,可見其增加了CNN空間感覺的能力。

核心代碼:

ins_feat = x # 目前執行個體特征tensor
# 生成從-1到1的線性值
x_range = torch.linspace(-1, 1, ins_feat.shape[-1], device=ins_feat.device)
y_range = torch.linspace(-1, 1, ins_feat.shape[-2], device=ins_feat.device)
y, x = torch.meshgrid(y_range, x_range) # 生成二維坐标網格
y = y.expand([ins_feat.shape[0], 1, -1, -1]) # 擴充到和ins_feat相同次元
x = x.expand([ins_feat.shape[0], 1, -1, -1])
coord_feat = torch.cat([x, y], 1) # 位置特征
ins_feat = torch.cat([ins_feat, coord_feat], 1) # concatnate一起作為下一個卷積的輸入      

8 Ghost

插件全稱:Ghost module

出自論文:GhostNet: More Features from Cheap Operations

論文連結:https://arxiv.org/pdf/1911.11907.pdf

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

在ImageNet的分類任務上,GhostNet在相似計算量情況下Top-1正确率達75.7%,高于MobileNetV3的75.2%。其主要創新點就是提出了Ghost 子產品。在CNN模型中,特征圖是存在大量的備援,當然這也是非常重要和有必要的。如下圖所示,其中标“小扳手”的特征圖都存在備援的特征圖。那麼能否降低卷積的通道數,然後利用某種變換生成備援的特征圖?事實上這就是GhostNet的思路。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

而本文就從特征圖備援問題出發,提出一個僅通過少量計算(論文稱為cheap operations)就能生成大量特征圖的結構——Ghost Module。而cheap operations就是線性變換,論文中采用卷積操作實作。具體過程如下:

  • 使用比原始更少量卷積運算,比如正常用64個卷積核,這裡就用32個,減少一半的計算量。
  • 利用深度分離卷積,從上面生成的特征圖中變換出備援的特征。
  • 上面兩步擷取的特征圖concat起來輸出,送入後續的環節。

核心代碼:

class GhostModule(nn.Module):
    def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):
        super(GhostModule, self).__init__()
        self.oup = oup
        init_channels = math.ceil(oup / ratio)
        new_channels = init_channels*(ratio-1)


        self.primary_conv = nn.Sequential(
            nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),
            nn.BatchNorm2d(init_channels),
            nn.ReLU(inplace=True) if relu else nn.Sequential(), )
  # cheap操作,注意利用了分組卷積進行通道分離
        self.cheap_operation = nn.Sequential(
            nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
            nn.BatchNorm2d(new_channels),
            nn.ReLU(inplace=True) if relu else nn.Sequential(),)


    def forward(self, x):
        x1 = self.primary_conv(x)  #主要的卷積操作
        x2 = self.cheap_operation(x1) # cheap變換操作
        out = torch.cat([x1,x2], dim=1) # 二者cat到一起
        return out[:,:self.oup,:,:]      

9 BlurPool

出自論文:Making Convolutional Networks Shift-Invariant Again

論文連結:https://arxiv.org/abs/1904.11486

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

我們都知道,基于滑動視窗的卷積操作是具有平移不變性的,是以也預設為CNN網絡具有平移不變性或等變性,事實上真的如此嗎?實踐發現,CNN網絡真的非常敏感,隻要輸入圖檔稍微改一個像素,或者平移一個像素,CNN的輸出就會發生巨大的變化,甚至預測錯誤。這可是非常不具有魯棒性的。一般情況下我們利用資料增強擷取所謂的不變性。本文研究發現,不變性的退化根本原因就在于下采樣,無論是Max Pool還是Average Pool,抑或是stride>1的卷積操作,隻要是涉及步長大于1的下采樣,均會導緻平移不變性的丢失。具體示例如下圖所示,僅僅平移一個像素,Max pool的結果就差距很大。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

為了保持平移不變性,可以在下采樣之前進行低通濾波。傳統的max pool可以分解為兩部分,分别是stride = 1的max + 下采樣 。是以作者提出的MaxBlurPool = max + blur + 下采樣來替代原始的max pool。實驗發現,該操作雖然不能徹底解決平移不變性的丢失,但是可以很大程度上緩解。

核心代碼:

class BlurPool(nn.Module):
    def __init__(self, channels, pad_type='reflect', filt_size=4, stride=2, pad_off=0):
        super(BlurPool, self).__init__()
        self.filt_size = filt_size
        self.pad_off = pad_off
        self.pad_sizes = [int(1.*(filt_size-1)/2), int(np.ceil(1.*(filt_size-1)/2)), int(1.*(filt_size-1)/2), int(np.ceil(1.*(filt_size-1)/2))]
        self.pad_sizes = [pad_size+pad_off for pad_size in self.pad_sizes]
        self.stride = stride
        self.off = int((self.stride-1)/2.)
        self.channels = channels
    # 定義一系列的高斯核
        if(self.filt_size==1):
            a = np.array([1.,])
        elif(self.filt_size==2):
            a = np.array([1., 1.])
        elif(self.filt_size==3):
            a = np.array([1., 2., 1.])
        elif(self.filt_size==4):    
            a = np.array([1., 3., 3., 1.])
        elif(self.filt_size==5):    
            a = np.array([1., 4., 6., 4., 1.])
        elif(self.filt_size==6):    
            a = np.array([1., 5., 10., 10., 5., 1.])
        elif(self.filt_size==7):    
            a = np.array([1., 6., 15., 20., 15., 6., 1.])


        filt = torch.Tensor(a[:,None]*a[None,:])
        filt = filt/torch.sum(filt) # 歸一化操作,保證特征經過blur後資訊總量不變
        # 非grad操作的參數利用buffer存儲
        self.register_buffer('filt', filt[None,None,:,:].repeat((self.channels,1,1,1)))
        self.pad = get_pad_layer(pad_type)(self.pad_sizes)


    def forward(self, inp):
        if(self.filt_size==1):
            if(self.pad_off==0):
                return inp[:,:,::self.stride,::self.stride]    
            else:
                return self.pad(inp)[:,:,::self.stride,::self.stride]
        else:
            # 利用固定參數的conv2d+stride實作blurpool
            return F.conv2d(self.pad(inp), self.filt, stride=self.stride, groups=inp.shape[1])      

10 RFB

插件全稱:Receptive Field Block

出自論文:Receptive Field Block Net for Accurate and Fast Object Detection

論文連結:https://arxiv.org/abs/1711.07767

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

論文發現目标區域要盡量靠近感受野中心,這會有助于提升模型對小尺度空間位移的魯棒性。是以受人類視覺RF結構的啟發,本文提出了感受野子產品(RFB),加強了CNN模型學到的深層特征的能力,使檢測模型更加準确。RFB可以作為一種通用子產品嵌入到絕大多數網路當中。下圖可以看出其和inception、ASPP、DCN的差別,可以看作是inception+ASPP的結合。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

具體實作如下圖,其實和ASPP類似,不過是使用了不同大小的卷積核作為空洞卷積的前置操作。

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心代碼:

class RFB(nn.Module):
    def __init__(self, in_planes, out_planes, stride=1, scale = 0.1, visual = 1):
        super(RFB, self).__init__()
        self.scale = scale
        self.out_channels = out_planes
        inter_planes = in_planes // 8
        # 分支0:1X1卷積+3X3卷積
        self.branch0 = nn.Sequential(conv_bn_relu(in_planes, 2*inter_planes, 1, stride),
                conv_bn_relu(2*inter_planes, 2*inter_planes, 3, 1, visual, visual, False))
        # 分支1:1X1卷積+3X3卷積+空洞卷積
        self.branch1 = nn.Sequential(conv_bn_relu(in_planes, inter_planes, 1, 1),
                conv_bn_relu(inter_planes, 2*inter_planes, (3,3), stride, (1,1)),
                conv_bn_relu(2*inter_planes, 2*inter_planes, 3, 1, visual+1,visual+1,False))
        # 分支2:1X1卷積+3X3卷積*3代替5X5卷積+空洞卷積
        self.branch2 = nn.Sequential(conv_bn_relu(in_planes, inter_planes, 1, 1),
                conv_bn_relu(inter_planes, (inter_planes//2)*3, 3, 1, 1),
                conv_bn_relu((inter_planes//2)*3, 2*inter_planes, 3, stride, 1),
                conv_bn_relu(2*inter_planes, 2*inter_planes, 3, 1, 2*visual+1, 2*visual+1,False)  )
        self.ConvLinear = conv_bn_relu(6*inter_planes, out_planes, 1, 1, False)
        self.shortcut = conv_bn_relu(in_planes, out_planes, 1, stride, relu=False)
        self.relu = nn.ReLU(inplace=False)
    def forward(self,x):
        x0 = self.branch0(x)
        x1 = self.branch1(x)
        x2 = self.branch2(x)
    # 尺度融合
        out = torch.cat((x0,x1,x2),1)
        # 1X1卷積
        out = self.ConvLinear(out)
        short = self.shortcut(x)
        out = out*self.scale + short
        out = self.relu(out)
        return out      

11 ASFF

插件全稱:Adaptively Spatial Feature Fusion

出自論文:Adaptively Spatial Feature Fusion Learning Spatial Fusion for Single-Shot Object Detection

論文連結:https://arxiv.org/abs/1911.09516v1

【深度學習】真正的即插即用!盤點11種CNN網絡設計中精巧通用的“小”插件...

核心解析:

為了更加充分的利用高層語義特征和底層細粒度特征,很多網絡都會采用FPN的方式輸出多層特征,但是它們都多用concat或者element-wise這種融合方式,本論文認為這樣不能充分利用不同尺度的特征,是以提出了Adaptively Spatial Feature Fusion,即自适應特征融合方式。FPN輸出的特征圖經過下面兩部分的處理:

Feature Resizing:特征圖的尺度不同無法進行element-wise融合,是以需要進行resize。對于上采樣:首先利用1X1卷積進行通道壓縮,然後利用插值的方法上采樣特征圖。對于1/2的下采樣:利用stride=2的3X3卷積同時進行通道壓縮和特征圖縮小。對于1/4的下采樣:在stride=2的3X3的卷積之前插入tride=2的maxpooling。

Adaptive Fusion:特征圖自适應融合,公式如下

其中x n→l表示在(i,j)位置的特征向量,來自n特征圖,經過上述resize到l尺度。Alpha。Beta,gamma為空間注意力權重,經過softmax處理,如下:

代碼解析:

class ASFF(nn.Module):
    def __init__(self, level, rfb=False):
        super(ASFF, self).__init__()
        self.level = level
        # 輸入的三個特征層的channels, 根據實際修改
        self.dim = [512, 256, 256]
        self.inter_dim = self.dim[self.level]
        # 每個層級三者輸出通道數需要一緻
        if level==0:
            self.stride_level_1 = conv_bn_relu(self.dim[1], self.inter_dim, 3, 2)
            self.stride_level_2 = conv_bn_relu(self.dim[2], self.inter_dim, 3, 2)
            self.expand = conv_bn_relu(self.inter_dim, 1024, 3, 1)
        elif level==1:
            self.compress_level_0 = conv_bn_relu(self.dim[0], self.inter_dim, 1, 1)
            self.stride_level_2 = conv_bn_relu(self.dim[2], self.inter_dim, 3, 2)
            self.expand = conv_bn_relu(self.inter_dim, 512, 3, 1)
        elif level==2:
            self.compress_level_0 = conv_bn_relu(self.dim[0], self.inter_dim, 1, 1)
            if self.dim[1] != self.dim[2]:
                self.compress_level_1 = conv_bn_relu(self.dim[1], self.inter_dim, 1, 1)
            self.expand = add_conv(self.inter_dim, 256, 3, 1)
        compress_c = 8 if rfb else 16  
        self.weight_level_0 = conv_bn_relu(self.inter_dim, compress_c, 1, 1)
        self.weight_level_1 = conv_bn_relu(self.inter_dim, compress_c, 1, 1)
        self.weight_level_2 = conv_bn_relu(self.inter_dim, compress_c, 1, 1)


        self.weight_levels = nn.Conv2d(compress_c*3, 3, 1, 1, 0)


  # 尺度大小 level_0 < level_1 < level_2
    def forward(self, x_level_0, x_level_1, x_level_2):
        # Feature Resizing過程
        if self.level==0:
            level_0_resized = x_level_0
            level_1_resized = self.stride_level_1(x_level_1)
            level_2_downsampled_inter =F.max_pool2d(x_level_2, 3, stride=2, padding=1)
            level_2_resized = self.stride_level_2(level_2_downsampled_inter)
        elif self.level==1:
            level_0_compressed = self.compress_level_0(x_level_0)
            level_0_resized =F.interpolate(level_0_compressed, 2, mode='nearest')
            level_1_resized =x_level_1
            level_2_resized =self.stride_level_2(x_level_2)
        elif self.level==2:
            level_0_compressed = self.compress_level_0(x_level_0)
            level_0_resized =F.interpolate(level_0_compressed, 4, mode='nearest')
            if self.dim[1] != self.dim[2]:
                level_1_compressed = self.compress_level_1(x_level_1)
                level_1_resized = F.interpolate(level_1_compressed, 2, mode='nearest')
            else:
                level_1_resized =F.interpolate(x_level_1, 2, mode='nearest')
            level_2_resized =x_level_2
    # 融合權重也是來自于網絡學習
        level_0_weight_v = self.weight_level_0(level_0_resized)
        level_1_weight_v = self.weight_level_1(level_1_resized)
        level_2_weight_v = self.weight_level_2(level_2_resized)
        levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v,
                                     level_2_weight_v),1)
        levels_weight = self.weight_levels(levels_weight_v)
        levels_weight = F.softmax(levels_weight, dim=1)   # alpha産生
    # 自适應融合
        fused_out_reduced = level_0_resized * levels_weight[:,0:1,:,:]+\
                            level_1_resized * levels_weight[:,1:2,:,:]+\
                            level_2_resized * levels_weight[:,2:,:,:]


        out = self.expand(fused_out_reduced)
        return out      

結語

本文盤點了近年來比較精巧而又實用的CNN插件,希望大家活學活用,用在自己的實際項目中。

◎作者檔案

繼續閱讀