目標檢測小網路
阿新 • • 發佈:2020-12-12
[TOC]
## 目標檢測小網路
## 一. Anchor-based
### 1.1 網路結構
當前一般採用
- backbone:shufflenetV2、mobilenetV2/3(V3比較難除錯)、Ghostnet、SandGlass
- FPN:PAN、BiFPN,這部分對檢測提升很大(實際使用得進行裁剪壓縮,原始版本不適合小模型),追求極致效能的網路可以去除(比如人臉檢測超小模型,這類目標特徵明顯且背景不是很複雜的情況)
- Head:RFB、SSH,如果不加FPN接面構,Head必須做一番手腳,如果存在FPN,簡單使用幾個卷積即可
- 輸出:(這部分一般包含在Head內部,最近遇到這個問題單獨提出來),可以使用cls+reg共享一個卷積,也可以單獨卷積(每層之間也可以共享或單獨),小模型一般每層不共享(共享效果差,有朋友已實測),cls和reg之間隨意選擇(共享最好做一些trick,最好增加FPN,不共享可以增加RFB等操作)
小模組一般採用:
- 模組卷積一般使用Depthwise結構,小卷積一般使用 $1\times1$ 卷積(很少使用 $3\times3$ 卷積)
- backbone的輸出通道盡可能的大(筆者嘗試 $chennels=[24, 48, 96]$ 的輸出效果很差),最基礎的保證backbone的輸出大於FPN的輸出
- 小模型一般在 $stride=[8,16,32,64]$ 進行取樣,$stride=4$ 沒見過有人在小模型上使用
- 關於BatchNormalize操作,小網路一般使用BN即可,大網路使用GN效果較好(也可以直接使用torch自帶的sync-BN)
loss的使用:
- 正負樣本採用 $3:1$ ,loss直接採用SSD,剛開始可以使用此方法,交叉熵的loss一般在1.0以下(具體多少適合你的網路看結果),如果在0.9附近無法下降,實測效果較差
- Focal-Loss、GHM、TopK,筆者測試在單目標檢測中使用較好,誤檢很少但難以訓練
- 目前使用GIOU、DIOU作為迴歸較多
- FocalLoss+GIOU實測效果較好
### 1.2 資料和anchor
- 使用mmdetection進行訓練,一定要注意目標的大小,匹配過小(未匹配等)會導致出現Nan出現(查詢問題比較困難,之前還以為LR等問題)
- 資料增強不是越多越好,比如人臉檢測不需要上下Flip、人臉檢測不需要遮擋問題(情況極少),資料增強過多會導致小模型不收斂,建議先進行基礎操作(color-transform + random-crop + resize),之後根據提升進行特定資料增強。
- 資料和網路儘量保持等比例,筆者資料 $1280\times720$ ,網路輸入 $256\times256$ ,padding之後再進行輸入明顯效果好
- 小目標過多,為了不丟棄資料,在增強策略先放大然後進行crop,或者直接crop目標區域(儘量不讓目標縮小再進網路),筆者測試效果較好
- anchor在每一層上一定大於stride(原始的RetinaNet中anchor比stride大4倍),小模型一般超過 $\times1.25$ 以上,大模型 $\times2$ 以上
- 之前有朋友建議兩層之間的anchor大小有交集(開源專案沒見過這種操作),實際測試提高了召回率同時也增加了誤檢,看個人取捨
- 關於感受野和anchor的關係,這部分前兩年說的較多,現在論文基本沒見過了
### 1.3 一系列問題
- loss出現Nan
- 首先排查資料問題,一般是未匹配(目標太小,超出邊界等)
- 儘量使用預訓練模型(如果沒有,先訓練小批量資料得到預訓練模型,之後再大資料上再訓練)
- 減少學習率和batchsize等操作
- 小目標檢測較差
- 檢視anchor最小檢測的尺寸(當正樣本IOU=0.5,意味著最小檢測尺寸是最小anchor的一般半,但實際上我們粗略認為最小目標等於最小anchor,因為考慮到anchor和目標匹配的問題(比如兩個anchor之間存在一個目標))
- 增大stride,上面我們說到小模型一般stride不小於8,過小的stride對網路來說負擔很大(只能增大網路獲得trade-off)
- 多尺度訓練,在實際的輸入上下浮動即可(筆者將當前概率設為0.6,上下兩個尺度設為0.2),實現方式兩種:mmdet裡面直接padding,yolov4直接resize操作,筆者僅使用了後者
- mosic資料增強
- 網路結構,比如SSH針對小人臉檢測較好
- anchor匹配的時候給予小目標更大權重(筆者猜測,未嘗試)
- 誤檢較多
- 語義無關的情況,可以扣mask打背景(比如杯子、蘋果等檢測),當然人臉檢測肯定不行
- 增加樣本,針對性的訓練
- 多尺度訓練,參見上文,實測有效
- 改變loss,針對困難樣本的訓練
### 1.4 具體案例
- MobilenetV3+SSD+FPN
- libface、utralface(實測對柔性檢測較差,對人臉檢測很好)
- Yolov3/4/5剪枝
- efficientnet縮小版
這裡以[目前最流行的人臉檢測器之一](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB)為例:
| Model | model file size(MB) |
| :----------------------------------------: | :-------------------: |
| libfacedetection v1(caffe) | 2.58 |
| libfacedetection v2(caffe) | 3.34 |
| Official Retinaface-Mobilenet-0.25 (Mxnet) | 1.68 |
| version-slim | **1.04** |
| version-RFB | **1.11** |
| Model | Easy Set | Medium Set | Hard Set |
| :-------------------------------: | :-------: | :--------: | :-------: |
| libfacedetection v1(caffe) | 0.65 | 0.5 | 0.233 |
| libfacedetection v2(caffe) | 0.714 | 0.585 | 0.306 |
| Retinaface-Mobilenet-0.25 (Mxnet) | 0.745 | 0.553 | 0.232 |
| version-slim | 0.77 | 0.671 | 0.395 |
| version-RFB | **0.787** | **0.698** | **0.438** |
從上面兩個官方給出的表格,速度快的同時精度也高
- backbone使用shufflenet改良版本,其中增加RFB用於擴大感受野
- 中間層使用SSD的multi-feature
- head使用層不共享且cls與reg之間也不共享的卷DW卷積
- feature在$stride=[8,16,32,64]$上進行檢測,anchor設定$[[10, 16, 24], [32, 48], [64, 96], [128, 192, 256]]$,由於人臉基本呈現方形,所以這裡anchor都是$1:1$
- loss直接使用SSD策略,交叉熵+smoothL1+正負樣本按比例挖掘
- 資料增強使用正常操作(color+random-crop等),未使用最新的mosic等
- train策略包含很多種,一般採用SGD+Warmup+Cosin
## 二. Anchor-free
### 2.1 具體案例
> 註釋: anchor-free之前流行的都是大網路,比如centerNet、FCOS、ATSS等,因為小網路很難訓練centerness(網路有人測試),基本沒見到小網路效果好的版本。自從[GFL-V1](https://zhuanlan.zhihu.com/p/147691786)論文出來以後,使得loss訓練較為容易,然後NanoDet問世了。
這裡直接以[目前最流程的anchor-free小網路](https://github.com/RangiLyu/nanodet)為例
| Model | Resolution | COCO mAP | Latency(ARM 4xCore) | FLOPS | Params | Model Size(ncnn bin) |
| :---------: | :--------: | :------: | :-----------------: | :---: | :----: | :------------------: |
| NanoDet-m | 320*320 | 20.6 | 10.23ms | 0.72B | 0.95M | 1.8mb |
| NanoDet-m | 416*416 | 21.7 | 16.44ms | 1.2B | 0.95M | 1.8mb |
| YoloV3-Tiny | 416*416 | 16.6 | 37.6ms | 5.62B | 8.86M | 33.7mb |
| YoloV4-Tiny | 416*416 | 21.7 | 32.81ms | 6.96B | 6.06M | 23.0mb |
- backbone使用shuffenetV2(原封不動)
- FPN使用PAN簡化版,直接雙線性插值之後add即可
- head層之間不共享,cls與reg之間進行共享卷積
- loss使用GFL-V1版本
- 資料增強常規操作(未使用random-crop,未使用mosaic)
- train直接使用SGD+Warmup+LinerStep
### 2.2 改進策略
- 資料增強
> 原版NanoDet僅使用簡單操作,針對自己資料集進行特定資料增強,實測效果很大
- 訓練多尺度
> 有效改善誤檢和漏檢(建議使用預訓練模型,直接使用容易崩)
- 最近[GFL-V2](https://zhuanlan.zhihu.com/p/313684358)出來了,嘗試將trick加入Nanodet之中
> 筆者稍微修改了一下trick,目的兩點:1)儘量不修改Nanodet結構。2)更容易訓練。3)效果儘可能好。
>
> 直接使用GFL-V2很難訓練,而且誤檢率較高,NanoDet作者給出的原因是共享的cls+reg對分佈不敏感,不適用共享的卷積效果可以提升。筆者猜測原因:1)共享卷積已經學到部分分佈資訊。2)小網路最後階段直接使用分佈去指導質量分數很難收斂。
>
> 未加入GFL-V2,實測效果未提升反而下降
``` python
# 層定義
self.reg_conf = nn.ModuleList([nn.Sequential(nn.Conv2d(4 * 8, 32, 1),
nn.LeakyReLU(negative_slope=0.1, inplace=True),
nn.Conv2d(32, 1, 1), nn.Sigmoid()) for _ in self.anchor_strides])
self.reg_cls_com = nn.ModuleList([nn.Conv2d(2, 1, 1) for _ in self.anchor_strides])
# 前向傳播
def forward_single(self, x, cls_convs, reg_convs, gfl_cls, gfl_reg, reg_conf, reg_cls_com):
cls_feat = x
reg_feat = x
for cls_conv in cls_convs:
cls_feat = cls_conv(cls_feat)
for reg_conv in reg_convs:
reg_feat = reg_conv(reg_feat)
if self.share_cls_reg:
feat = gfl_cls(cls_feat)
cls_score, bbox_pred = torch.split(feat, [self.cls_out_channels, 4 * (self.reg_max + 1)], dim=1)
else:
cls_score = gfl_cls(cls_feat)
bbox_pred = gfl_reg(reg_feat)
B,C,H,W = bbox_pred.shape
prob = F.softmax(bbox_pred.reshape(B, 4, self.reg_max+1, H*W), dim=2)
quality_score = reg_conf(prob.reshape(B, C, H, W))
cls_score = cls_score * quality_score
#cls_score = torch.cat([cls_score, quality_score],dim=1)
#cls_score = reg_cls_com(cls_score)
if torch.onnx.is_in_onnx_export():
cls_score = torch.sigmoid(cls_score).reshape(1, self.num_classes, -1).permute(0, 2, 1)
bbox_pred = bbox_pred.reshape(1, (self.reg_max+1)*4, -1).permute(0, 2, 1)
return cls_score, bbox_pred
```
- 改變網路結構
> - [ ] backbone優化
> - [ ] FPN優化(正在進行中)
> - [x] head優化(實測有效)
>
> 當然都是建立在儘量減少計算量的基礎上進行優化,做到tr