基於深度學習的目標檢測和分割

參考鏈接:https://www.jianshu.com/p/5056e6143ed5
目標檢測技術的演進:RCNN->SppNET->Fast-RCNN->Faster-RCNN
不同於分類問題,物體檢測可能會存在多個檢測目標,這不僅需要我們判別出各個物體的類別,而且還要準確定位出物體的位置。
在這裏插入圖片描述
首先講解幾個常用的概念:BboxIoU非極大值抑制
Bounding Box(bbox)
bbox是包含物體的最小矩形,該物體應在最小矩形內部,如上圖紅色框藍色框和綠色框。
物體檢測中關於物體位置的信息輸出是一組(x,y,w,h)數據,其中x,y代表着bbox的左上角(或者其他固定點,可自定義),對應的w,h表示bbox的寬和高.一組(x,y,w,h)可以唯一的確定一個定位框。
Intersection over Union(IoU)
對於兩個區域RRRR^{'},則兩個區域的重疊程度overlap計算如下:O(R,R)=RRRRO(R,R^{'})=\frac{R\cap R^{'}}{R\cup R^{'}}
非極大值抑制(Non-Maximum Suppression又稱NMS)
非極大值抑制(NMS)可以看做是局部最大值的搜索問題,把不是極大值的抑制掉,在物體檢測上,就是對一個目標有多個標定框,使用極大值抑制算法濾掉多餘的標定框。
在這裏插入圖片描述
主流的檢測框架:
主要分爲兩階段檢測器,單階段檢測器。
在這裏插入圖片描述

R-CNN(Regions with CNN features)

在這裏插入圖片描述
如上圖所示,R-CNN這個物體檢查系統可以大致分爲四步進行:

  • 獲取輸入圖像
  • 提取約2000個候選區域(Region Proposal)
  • 將候選區域分別輸入CNN網絡(這裏需要將候選圖片進行縮放)
  • 將CNN的輸出輸入到SVM中進行類別的判定

第一步:生成Region Proposal

高度非線性的深度網絡具有很強的建模能力,計算複雜度高僅生成少量Region Proposal;訓練需要大量標註數據有監督預訓練 +領域特定微調。比較常用的是selective search方法,具有如下特性:

  • 無監督:沒有訓練過程,不需要帶標註的數據
  • 數據驅動:根據圖像特徵生成候選窗
  • 基於圖像分割任務

有如下兩種方式:
對圖像進行分割,每個分割區域生成一個對應的外接矩形框
在這裏插入圖片描述
基於相似度進行層次化地區域合併
在這裏插入圖片描述

第二步:用CNN提取Region Proposal特徵

將不同大小的Region Proposal縮放到相同大小:227x227, 進行些許擴大以包含少量上下文信息。
縮放分爲兩大類:
1)各向同性縮放,長寬放縮相同的倍數

  • tightest square with context: 把region proposal的邊界進行擴展延伸成正方形,灰色部分用原始圖片中的相應像素填補,如下圖(B)所示
  • tightest square without context: 把region proposal的邊界進行擴展延伸成正方形,灰色部分不填補,如下圖 (C ) 所示

2)各向異性縮放, 長寬放縮的倍數不同
不管圖片是否扭曲,長寬縮放的比例可能不一樣,直接將長寬縮放到227*227,如下圖(D)所示
在這裏插入圖片描述
將所有窗口送入Backbone,如預訓練的 AlexNet,ResNet等提取特徵。一般與訓練的backbone需要進行微調finetune,
以最後一個全連接層FC的輸出作爲特徵表示。

第三步:對Region Proposal進行分類+邊框校準

分類算法:

  • SVM:對CNN輸出的特徵用SVM進行分類,針對每個類別單獨訓練。二分類問題,判斷是不是屬於這個類別,是就是positive,反之negative。
  • Softmax:和整個CNN一起端到端訓練,所有類別一起訓練,多類分類
    在這裏插入圖片描述

邊框校準:
使用迴歸器精細修正候選框位置:對於每一個類,訓練一個線性迴歸模型去判定這個框是否框得完美。

  • 讓檢測框的位置更加準確,同時更加緊緻(包含更少的背景區域)
    在這裏插入圖片描述
  • 線性迴歸模型
    對於檢測框P(Px,Py,Pw,Ph)P(P_{x},P_{y},P_{w},P_{h})修正到預測框G^(G^x,G^y,G^w,G^h)\hat{G}(\hat{G}_{x},\hat{G}_{y},\hat{G}_{w},\hat{G}_{h}),首先平移中心點座標
    G^x=Pwdx(P)+PxG^y=Phdy(P)+Py\hat{G}_{x}=P_{w}d_{x}(P)+P_{x}\\ \hat{G}_{y}=P_{h}d_{y}(P)+P_{y}
    其中Pwdx(P)P_{w}d_{x}(P)Phdy(P)P_{h}d_{y}(P)是平移量,dx(P)d_{x}(P)dy(P)d_{y}(P)是迴歸的目標。
    對寬和高進行縮放:
    G^w=Pwexp(dw(P))G^h=Phexp(dh(P))\hat{G}_{w}=P_{w}\exp{(d_{w}(P))}\\ \hat{G}_{h}=P_{h}\exp{(d_{h}(P))}
    其中exp(dw(P))\exp{(d_{w}(P))}exp(dh(P))\exp{(d_{h}(P))}是伸縮因子,dw(P)d_{w}(P)dh(P)d_{h}(P)是迴歸的目標。

所以我們要學習的目標即爲:dx(P)d_{x}(P)dy(P)d_{y}(P)dw(P)d_{w}(P)dh(P)d_{h}(P),統一爲d(P)d_{*}(P),可寫爲:
d(P)=wTΦGAP(P)d_{*}(P)=w^{T}_{*}\Phi_{GAP}(P)
其中ΦGAP(P)\Phi_{GAP}(P)表示proposal PP經Backbone(AlexNet,VGGNet,ResNet,etc) Global Avg Pool之後的特徵向量。
dx(P)d_{x}(P)dy(P)d_{y}(P)dw(P)d_{w}(P)dh(P)d_{h}(P)對應的groud truth爲t={tx,ty,tw,th}t_{*}=\{t_{x},t_{y},t_{w},t_{h}\},那麼誤差函數寫爲:
w=argminw1N(twTΦGAP(P))2+λw2w_{*}=\arg\min_{w_{*}}\frac{1}{N}(t_{*}-w_{*}^{T}\Phi_{GAP}(P))^{2}+\lambda||w_{*}||^{2}
目標框groud truth爲G(Gx,Gy,Gw,Gh)G(G_{x},G_{y},G_{w},G_{h}),所以{tx,ty,tw,th}\{t_{x},t_{y},t_{w},t_{h}\}確定值爲:
tx=(GxPx)/Pwty=(GyPy)/Phtw=log(Gw/Pw)th=log(Gh/Ph)t_{x}=(G_{x}-P_{x})/P_{w}\\ t_{y}=(G_{y}-P_{y})/P_{h}\\ t_{w}=\log(G_{w}/P_{w})\\ t_{h}=\log(G_{h}/P_{h})
Note that 只有當Proposal樣本和Ground Truth比較接近時(這裏取IoU>0.6),才能將其作爲訓練樣本訓練我們的線性迴歸模型,否則會導致訓練的迴歸模型不work。(當Proposal跟G離得較遠,就是複雜的非線性問題了,此時用線性迴歸建模顯然不合理)

SPPNet (Spatial Pyramid Pooling)

R-CNN要求輸入圖像的尺寸相同,不同尺度和長寬比的區域被變換到相同大小。但是裁剪會使信息丟失(或引入過多背景),縮放會使物體變形:
在這裏插入圖片描述
卷積允許任意大小的圖像輸入網絡。原始圖像通過卷積層之後,Spatial Pyramid Pooling(SPP) layer負責將不同size的檢測框進行歸一化地pooling,每一個pooling的filter會根據輸入調整大小,而SPP的輸出尺度始終是固定的。
在這裏插入圖片描述
具體做法是,在conv5層得到的特徵圖是256個channel的,先把每個特徵圖分割成多個不同尺寸的網格,比如網格分別爲4×4、2×2、1×1,然後每個網格做max pooling,這樣256層特徵圖就形成了16×256,4×256,1×256維特徵

一般來說檢測框很多都是重疊的,對檢測框進行卷積操作會帶來大量的重複操作,所有SPPNet對原始圖像進行卷積操作去除了各個區域的重複計算。此外,對於一個proposal,需要弄清楚SPP之後的每一個像素點對應的局部感受域的中心,如下給定一個例子:
在這裏插入圖片描述
通常情況下,設當前特徵圖下某位置爲xi+1x_{i+1},對應於上一個特徵圖的卷積核中心的位置爲xix_{i},則有對應關係:
xi=sixi+1+Fi12Pix_{i}=s_{i}*x_{i+1}+\left \lceil \frac{F_{i}-1}{2} \right \rceil-P_{i}
其中sis_{i}是stride,FiF_{i}是卷積核的尺寸,PiP_{i}是卷積核的padding。一般情況下,可以取Pi=Fi/2P_{i}=\left \lfloor F_{i}/{2} \right \rfloor,所以可以化簡爲:xi=sixi+1x_{i}=s_{i}*x_{i+1}
對公式進行級聯可以得到:x0=i=0LsixL+1x_{0}=\prod _{i=0}^{L}s_{i*}x_{L+1}

Fast R-CNN

在這裏插入圖片描述
加入了的ROI(Region Of Interest) Pooling層,對每個region都提取一個固定維度的特徵表示。相當於特殊的SPP層,RoI層是使用單個尺度的SPP層(不用多個尺度的原因是多個尺度準確率提升不高,但是計算量開銷顯著)。
在這裏插入圖片描述
RoI Pooling原理
RoI層將每一個候選區域都分爲提前定義的H×WH\times W塊。對每個小塊做max-pooling,此時每一個將候選區的局部特徵映射轉變爲大小統一的數據,送入下一層。
在這裏插入圖片描述
梯度反向傳播:
xix_{i}爲輸入層結點,yiy_{i}爲輸出層的節點.
Lxi={0,if δ(i,j)=FalseLyj,if δ(i,j)=True\frac{\partial L }{\partial x_{i}}=\left\{\begin{matrix} 0, if\ \delta(i,j)=False \\ \frac{\partial L }{\partial y_{j}},if\ \delta(i,j)=True \end{matrix}\right.
中判決函數δ(i,j)\delta(i,j)表示ii節點是否被jj節點選爲最大值輸出。不被選中有兩種可能:xix_{i}不在yjy_{j}範圍內,或者xix_{i}不是最大值.
一個輸入節點可能和多個輸出節點相連。設xix_{i}爲輸入層的節點,yrjy_{rj}爲第rr個候選區域的第jj個輸出節點。
Lxi=r,jδ(i,r,j)Lyrj\frac{\partial L }{\partial x_{i}}=\sum_{r,j}\delta(i,r,j)\frac{\partial L }{\partial y_{rj}}
多任務
另外,之前RCNN的處理流程是先提proposal,然後CNN提取特徵,之後用SVM分類器,最後再做bbox regression,而在Fast-RCNN中,作者巧妙的把bbox regression放進了神經網絡內部,與region分類和併成爲了一個multi-task模型,實際實驗也證明,這兩個任務能夠共享卷積特徵。
邊框校準誤差:smooth L1 Loss smoothL1(x)={0.5xx,x<1x0.5,otherwisesmooth_{L_{1}}(x)=\left\{\begin{matrix} 0.5x^{x},|x|<1\\ |x|-0.5,otherwise \end{matrix}\right.

Mask R-CNN

論文地址:https://arxiv.org/pdf/1703.06870.pdf
實例分割(instance segmentation):對於檢測到的每個物體(實例),精確地標記出其每個像素
在這裏插入圖片描述

RoIAlign

在Faster R-CNN中增加實例分割模塊:RoIPoolRoIAlign
ROIAlign:https://www.cnblogs.com/wangyong/p/8523814.html
在這裏插入圖片描述
在這裏插入圖片描述
對一張800×800800\times 800原圖,經過VGG16的處理後,一共stride=32,圖片縮小爲25×2525\times 25。設定原圖中有一665×665665\times 665的proposal,映射到特徵圖中的大小:665/32=20.78,即20.78×20.78

  • 對於RoIPool:在計算的時候會進行取整操作,於是,進行所謂的第一次量化,即映射的特徵圖大小爲20×20。設歸一化的尺寸爲7×7,則每一個小區域的尺寸爲:20/7=2.86,即2.86×2.86。此時,進行第二次量化,故小區域大小變成2×2。每個2×2的小區域裏,取出其中最大的像素值,作爲這一個區域的‘代表’,這樣,49個小區域就輸出49個像素值,組成2.97×2.97大小的feature map
    總結: 經過兩次量化,即將浮點數取整,原本在特徵圖上映射的20×20大小的region proposal,偏差成大小爲14×14的,這樣的像素偏差勢必會對後層的迴歸定位產生影響。所以,產生了更精細的替代方案,RoiAlign。
  • 對於RoIAlign:沒有像RoiPooling那樣就行取整操作,保留浮點數特徵圖大小20.78×20.78,之後劃分每個小區域:20.78/7=2.97,即2.97×2.97。假定採樣點數爲4,即對於每個2.97×2.97的小區域,平分4份,每一份取其中心點位置,而中心點位置的像素,採用雙線性插值法進行計算,這樣,就會得到四個點的像素值,如下圖
    在這裏插入圖片描述
    上圖中,四個紅色叉叉‘×’的像素值是通過雙線性插值算法計算得到的。最後,取四個像素值中最大值作爲這個小區域(即:2.97×2.97大小的區域)的像素值,如此類推,同樣是49個小區域得到49個像素值,組成7×7大小的feature map

Faster R-CNN上增加了Instance Segmentation Head:
在這裏插入圖片描述

FCN

首先簡單介紹一下全卷積 (FCN,fully-connected networks) ,FCN將傳統CNN後面的全連接層替換爲卷積,這樣就可以獲得2維的feature map,後接softmax獲得每一個像素點的分類信息,從而解決分割問題。
論文:https://arxiv.org/pdf/1411.4038.pdf
衆所周知,每一次卷積都是對圖像的一次縮小,每一次縮小帶來的是分辨率越低,圖像越模糊,而在第一部分我們知道FCN是通過像素點進行圖像分割,那FCN是怎麼解決的這一個問題?答案是上採樣,比如我們在3次卷積後,圖像分別縮小了2 4 8倍,因此在最後的輸出層,我們需要進行8倍的上採樣,從而得到原來的圖像大小.而上採樣本身就是一個反捲積實現的。
在這裏插入圖片描述從論文中得到的結果來看,從32倍,16倍,8倍到最終結果,結果越來越精細:
在這裏插入圖片描述

Instance Segmentation Head

在這裏插入圖片描述
具體的,Head Architecture如下所示:
在這裏插入圖片描述
圖中,箭頭表明卷積反捲積FC層(根據context可以推斷,conv保護spatial信息,deconv升採樣,FC作用於一維向量)。所有的conv是3×3,除了輸出conv是1×1,deconvs是2×2(stride=2)。Left:‘res5’表明ResNet的第5個階段;Right:‘×4’表明4個連續的convs。

Pytorch實現

目標檢測和分割可使用mmdetection庫來實現,代碼參考(CUHK,MM Lab):https://github.com/open-mmlab/mmdetection

1.安裝

需要首先創建anaconda的虛擬環境,在虛擬環境中進行mmdection的安裝:
(1)創建虛擬環境並激活:

conda create -n open-mmlab python=3.7 -y
conda activate open-mmlab

(2)安裝pytorch,torchvision以及依賴的mmcv庫,版本可以隨機更改:

pip install torch==1.1.0
pip install torchvision==0.3.0
pip install mmcv

(3)定位到mmdection文件夾,運行如下命令編譯安裝:

python setup.py develop

2.更換backbone爲自己的net

open-mmlab/mmdetection/tree/master/mmdet/models/backbones裏放入backbone文件,這裏以ResNetSE爲例,創建resnet_se.py文件:

import logging

import torch.nn as nn
from mmcv.cnn import constant_init, kaiming_init
from mmcv.runner import load_checkpoint
from torch.nn.modules.batchnorm import _BatchNorm

from ..registry import BACKBONES
from ..utils import build_conv_layer, build_norm_layer


class SELayer(nn.Module):
    def __init__(self, channel, reduction = 16):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc       = nn.Sequential(
                        nn.Linear(channel, channel // reduction),
                        nn.ReLU(inplace = True),
                        nn.Linear(channel // reduction, channel),
                        nn.Sigmoid()
                )
        print('add one SELayer!')

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self,
                 inplanes,
                 planes,
                 stride=1,
                 dilation=1,
                 downsample=None,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN')):
        super(Bottleneck, self).__init__()

        self.inplanes = inplanes
        self.planes = planes
        self.stride = stride
        self.dilation = dilation
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg

        self.conv1_stride = 1
        self.conv2_stride = stride

        self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1)
        self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2)
        self.norm3_name, norm3 = build_norm_layer(
            norm_cfg, planes * self.expansion, postfix=3)

        self.conv1 = build_conv_layer(
            conv_cfg,
            inplanes,
            planes,
            kernel_size=1,
            stride=self.conv1_stride,
            bias=False)
        self.add_module(self.norm1_name, norm1)

        self.conv2 = build_conv_layer(
            conv_cfg,
            planes,
            planes,
            kernel_size=3,
            stride=self.conv2_stride,
            padding=dilation,
            dilation=dilation,
            bias=False)
        self.add_module(self.norm2_name, norm2)

        self.conv3 = build_conv_layer(
            conv_cfg,
            planes,
            planes * self.expansion,
            kernel_size=1,
            bias=False)
        self.add_module(self.norm3_name, norm3)

        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.se = SELayer(planes * self.expansion)

    @property
    def norm1(self):
        return getattr(self, self.norm1_name)

    @property
    def norm2(self):
        return getattr(self, self.norm2_name)

    @property
    def norm3(self):
        return getattr(self, self.norm3_name)

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.norm1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.norm2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.norm3(out)

        out = self.se(out)

        if self.downsample is not None:
            identity = self.downsample(x)
        out += identity
        out = self.relu(out)
        return out


def make_res_layer(block,
                   inplanes,
                   planes,
                   blocks,
                   stride=1,
                   dilation=1,
                   conv_cfg=None,
                   norm_cfg=dict(type='BN')):
    downsample = None
    if stride != 1 or inplanes != planes * block.expansion:
        downsample = nn.Sequential(
            build_conv_layer(
                conv_cfg,
                inplanes,
                planes * block.expansion,
                kernel_size=1,
                stride=stride,
                bias=False),
            build_norm_layer(norm_cfg, planes * block.expansion)[1],
        )

    layers = []
    layers.append(
        block(
            inplanes=inplanes,
            planes=planes,
            stride=stride,
            dilation=dilation,
            downsample=downsample,
            conv_cfg=conv_cfg,
            norm_cfg=norm_cfg))
    inplanes = planes * block.expansion
    for i in range(1, blocks):
        layers.append(
            block(
                inplanes=inplanes,
                planes=planes,
                stride=1,
                dilation=dilation,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg))

    return nn.Sequential(*layers)


@BACKBONES.register_module
class ResNetSE(nn.Module):

    arch_settings = {
        50: (Bottleneck, (3, 4, 6, 3)),
        101: (Bottleneck, (3, 4, 23, 3)),
        152: (Bottleneck, (3, 8, 36, 3))
    }

    def __init__(self,
                 depth,
                 in_channels=3,
                 num_stages=4,
                 strides=(1, 2, 2, 2),
                 dilations=(1, 1, 1, 1),
                 out_indices=(0, 1, 2, 3),
                 frozen_stages=-1,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN', requires_grad=True),
                 norm_eval=True,
                 zero_init_residual=True):
        super(ResNetSE, self).__init__()
        self.depth = depth
        self.strides = strides
        self.dilations = dilations
        self.out_indices = out_indices
        self.frozen_stages = frozen_stages
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.norm_eval = norm_eval
        self.zero_init_residual = zero_init_residual
        self.block, stage_blocks = self.arch_settings[depth]
        self.stage_blocks = stage_blocks[:num_stages]
        self.inplanes = 64

        self._make_stem_layer(in_channels)

        self.res_layers = []
        for i, num_blocks in enumerate(self.stage_blocks):
            stride = strides[i]
            dilation = dilations[i]
            planes = 64 * 2**i
            res_layer = make_res_layer(
                self.block,
                self.inplanes,
                planes,
                num_blocks,
                stride=stride,
                dilation=dilation,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg)
            self.inplanes = planes * self.block.expansion
            layer_name = 'layer{}'.format(i + 1)
            self.add_module(layer_name, res_layer)
            self.res_layers.append(layer_name)

        self._freeze_stages()

        self.feat_dim = self.block.expansion * 64 * 2**(
            len(self.stage_blocks) - 1)

    @property
    def norm1(self):
        return getattr(self, self.norm1_name)

    def _make_stem_layer(self, in_channels):
        self.conv1 = build_conv_layer(
            self.conv_cfg,
            in_channels,
            64,
            kernel_size=7,
            stride=2,
            padding=3,
            bias=False)
        self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1)
        self.add_module(self.norm1_name, norm1)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

    def _freeze_stages(self):
        if self.frozen_stages >= 0:
            self.norm1.eval()
            for m in [self.conv1, self.norm1]:
                for param in m.parameters():
                    param.requires_grad = False

        for i in range(1, self.frozen_stages + 1):
            m = getattr(self, 'layer{}'.format(i))
            m.eval()
            for param in m.parameters():
                param.requires_grad = False

    def init_weights(self, pretrained=None):
       if isinstance(pretrained, str):
            checkpoint = torch.load(pretrained)
            param_dict = {}
            for k, v in zip(self.state_dict().keys(), checkpoint['state_dict'].keys()):
                param_dict[k] = checkpoint['state_dict'][v]
            self.load_state_dict(param_dict)
        elif pretrained is None:
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    kaiming_init(m)
                elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
                    constant_init(m, 1)

            if self.zero_init_residual:
                for m in self.modules():
                    if isinstance(m, Bottleneck):
                        constant_init(m.norm3, 0)
        else:
            raise TypeError('pretrained must be a str or None')

    def forward(self, x):
        x = self.conv1(x)
        x = self.norm1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        outs = []
        for i, layer_name in enumerate(self.res_layers):
            res_layer = getattr(self, layer_name)
            x = res_layer(x)
            if i in self.out_indices:
                outs.append(x)
        return tuple(outs)

    def train(self, mode=True):
        super(ResNetSE, self).train(mode)
        self._freeze_stages()
        if mode and self.norm_eval:
            for m in self.modules():
                # trick: eval have effect on BatchNorm only
                if isinstance(m, _BatchNorm):
                    m.eval()

Note that backbone文件會與pytorch的model文件略有不同,因爲目標檢測和實例分割還需要在數據集上finetune等等,結合mmdetection庫,修改model文件的細節如下所示:

  • 所有創建conv的操作都由nn.Conv2d變爲build_conv_layer,並且第一個參數是conv_cfg
  • 所有創建batch_norm的操作都由nn.BatchNorm2d變爲build_norm_layer,並且都要使用self.add_module(self.norm_name, norm)@property來進行索引。
  • 加入參數凍結函數:_freeze_stages

3.修改配置文件

open-mmlab/mmdetection/tree/master/mmdet/models/backbones裏放入配置文件,以mask_rcnn_r50_fpn_1x.py爲模板,注意修改如下部分的內容:

model = dict(
    type='MaskRCNN',
    pretrained='torchvision://resnet50',  # 預訓練的模型文件
    backbone=dict(
        type='ResNetSE',   # 模型名字,下面的參數與model文件參數一致
        depth=50,  # 
        num_stages=4,
        out_indices=(0, 1, 2, 3),
        frozen_stages=1),  # frozen_stages表示凍結的階段編號
    neck=dict(
        type='FPN',
        in_channels=[256, 512, 1024, 2048],  # 這裏需要修改對應out_indices每個階段輸出的channel
        out_channels=256,
        num_outs=5),
    ...

訓練細節部分:
(1)對於8GPUS* 2imgs=16imgs/batch設置,初始學習率爲 0.02。若每個batch處理的img數量不同,則需要調整,比如2GPUS*2imgs=4imgs/batch,則初始學習率爲0.005.
(2)finetune一共有2種epoch數量設置,12epochs和24epochs。兩種方法需要調整lr_config中的step參數,分別爲[8,11][16,22]

optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
# learning policy
lr_config = dict(
    policy='step',
    warmup='linear',
    warmup_iters=500,
    warmup_ratio=1.0 / 3,
    step=[8, 11])
...
total_epochs = 12
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章