提到輕量級神經網絡,大家都會提到MobileNet V1 V2 和 ShuffleNet V1 V2,似乎較少看到大家提到PeleeNet,下面介紹一下檢測網絡Pelee
Pelee:移動端實時檢測骨幹網絡
在ImageNet數據集上,PeleeNet只有MobileNet模型的66%,並且比MobileNet精度更高。PeleeNet作爲backbone實現SSD能夠在VOC2007數據集上達到76.4%的mAP。文章總體上參考DenseNet的設計思路,提出了三個核心模塊進行改進,有一定參考價值。
PeleeNet實際上是DenseNet的變體,使用的依然是DenseNet的連接方法,核心的設計原則也和DenseNet相仿(特徵重用)。
PeleeNet作者還提出了以PeleeNet爲基礎的輕量級檢測網絡Pelee,與以VGG爲基礎網絡的SSD相比,爲了降低計算量,作者使用19x19大小特徵圖的層作爲SSD的第一層預測層。
預測層大小分別爲:19 x 19, 10 x 10, 5 x 5, 3 x 3, 1 x 1
MobileNet 同樣未使用38 x 38的特徵圖作爲預測層,但添加了2 x 2的預測層來保持6尺度SSD檢測。
在我的應用場景中,Pelee的表現遠超出了ShuffleNet V2-SSD的表現,也有可能是調參之前沒燒香,繼續調試中。
從coco的實驗看,其表現效果較好,比ssd-mobilenet網絡要好,本作者目前在使用TensorFlow object detect api中的ssd-mobilenetv2網絡做移動端目標檢測,發現其pb文件大小17.8m,單張圖片目標檢測前向傳播時間約爲0.32s好像。也在嘗試pelee網絡,目前在進行訓練。**用於預測的小型卷積核:**殘差預測塊讓我們應用 1×1 的卷積核來預測類別分數和邊界框設置成爲可能。實驗表明:使用 1×1 卷積核的模型的準確率和使用 3×3 的卷積核所達到的準確率幾乎相同。研究者在 iOS 上提供了 SSD 算法的實現。他們已經成功地將 SSD 移植到了 iOS 上,並且提供了優化的代碼實現。該系統在 iPhone 6s 上以 17.1 FPS 的速度運行,在 iPhone8 上以 23.6 FPS的速度運行。在 iPhone 6s(2015 年發佈的手機)上的速度要比在 Intel [email protected] CPU 上的官方算法實現還要快 2.6 倍。
表 1:不同的設計選擇的效果得到的性能
表 2:在 Stanford Dogs 數據集上的結果。MACs:乘法累加的次數,用於度量融合乘法和加法運算次數
表 3:在 ImageNet ILSVRC 2012 數據集上的結果
表 4:不同設計選擇上的性能結果
表 5:在 PASCAL VOC 2007 數據集上的結果。數據:07+12,VOC2007 和 VOC2012 聯合訓練;07+12+COCO,先在 COOC 數據集上訓練 35000 次,然後在 07+12 上繼續微調。
表6:實際設備上的速度
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-squrKfrk-1589591803962)(D:\CSDN\pic\車輛檢測畢設\1269652-20180724103432901-1285676795.png)]
表 7: COCO test-dev2015 數據集上的結果
這個輕便的PeLee一上線就備受矚目,是一種新型的輕型目標檢測算法我們來詳解一下
import os
import cv2
import numpy as np
import time
# from utils.nms_wrapper import nms
from configs.CC import Config
import argparse
from layers.functions import Detect, PriorBox
from peleenet import build_net
from data import BaseTransform, VOC_CLASSES
from utils.core import *
import torch
1:導入需要的庫
細說一下:
torch是pytorch的縮寫,是一種深度學習框架
理解pytorch的基礎主要從以下三個方面
Numpy風格的Tensor操作。pytorch中tensor提供的API參考了Numpy的設計,因此熟悉Numpy的用戶基本上可以無縫理解,並創建和操作tensor,同時torch中的數組和Numpy數組對象可以無縫的對接。
變量自動求導。在一序列計算過程形成的計算圖中,參與的變量可以方便的計算自己對目標函數的梯度。這樣就可以方便的實現神經網絡的後向傳播過程。
神經網絡層與損失函數優化等高層封裝。網絡層的封裝存在於torch.nn模塊,損失函數由torch.nn.functional模塊提供,優化函數由torch.optim模塊提供。
2:導入需要的數據
parser = argparse.ArgumentParser(description='Pelee Testing')
parser.add_argument('-c', '--config', default='./configs/Pelee_VOC.py')
parser.add_argument('-d', '--dataset', default='VOC',
help='VOC or COCO dataset')
parser.add_argument('-m', '--trained_model', default='./Pelee_VOC.pth',
type=str, help='Trained state_dict file path to open')
parser.add_argument('-t', '--thresh', default=0.3, type=float,
help='visidutation threshold')
parser.add_argument('--show', default=True,
help='Whether to display the images')
args = parser.parse_args()
cfg = Config.fromfile(args.config)
分別導入了:
config:PeLee模型參數及其配置
VOC數據集及其配置參數
閾值選取
和cfg參數(模型參數已下載好官網)
我們來看看coco數據集:
OCO數據集是一個大型的、豐富的物體檢測,分割和字幕數據集。這個數據集以scene understanding爲目標,主要從複雜的日常場景中截取,圖像中的目標通過精確的segmentation進行位置的標定。圖像包括91類目標,328,000影像和2,500,000個label。目前爲止有語義分割的最大數據集,提供的類別有80 類,有超過33 萬張圖片,其中20 萬張有標註,整個數據集中個體的數目超過150 萬個。
VOC數據集是目標檢測經常用的一個數據集,從05年到12年都會舉辦比賽(比賽有task: Classification 、Detection(將圖片中所有的目標用bounding box框出來) 、 Segmentation(將圖片中所有的目標分割出來)、Person Layout)
VOC2007:中包含9963張標註過的圖片, 由train/val/test三部分組成, 共標註出24,640個物體。 VOC2007的test數據label已經公佈, 之後的沒有公佈(只有圖片,沒有label)。
VOC2012:對於檢測任務,VOC2012的trainval/test包含08-11年的所有對應圖片。 trainval有11540張圖片共27450個物體。 對於分割任務, VOC2012的trainval包含07-11年的所有對應圖片, test只包含08-11。trainval有 2913張圖片共6929個物體。
2:構建檢測類
class Pelee_Det(object):
def __init__(self):
self.anchor_config = anchors(cfg.model)
self.priorbox = PriorBox(self.anchor_config)
self.net = build_net('test', cfg.model.input_size, cfg.model)
init_net(self.net, cfg, args.trained_model)
self.net.eval()
self.num_classes = cfg.model.num_classes
with torch.no_grad():
self.priors = self.priorbox.forward()
# self.net = self.net.cuda()
# self.priors = self.priors.cuda()
cudnn.benchmark = True
self._preprocess = BaseTransform(
cfg.model.input_size, cfg.model.rgb_means, (2, 0, 1))
self.detector = Detect(num_classes,
cfg.loss.bkg_label, self.anchor_config)
3:加載各類模型:比如核心檢測需要的cfg參數:
def anchors(config):
cfg = dict()
cfg['feature_maps'] = config.anchor_config.feature_maps
cfg['min_dim'] = config.input_size
cfg['steps'] = config.anchor_config.steps
cfg['min_sizes'], cfg['max_sizes'] = get_min_max_sizes(
config.anchor_config.min_ratio, config.anchor_config.max_ratio, config.input_size, len(cfg['feature_maps']))
cfg['aspect_ratios'] = config.anchor_config.aspect_ratios
cfg['variance'] = [0.1, 0.2]
cfg['clip'] = True
return cfg
4:初始化模型:
def init_net(net, cfg, resume_net):
if cfg.model.init_net and not resume_net:
net.init_model(cfg.model.pretained_model)
else:
print('Loading resume network...')
state_dict = torch.load(resume_net, map_location=torch.device('cpu'))
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
head = k[:7]
if head == 'module.':
name = k[7:]
else:
name = k
new_state_dict[name] = v
net.load_state_dict(new_state_dict, strict=False)
5:檢測算法
def detect(self, image):
loop_start = time.time()
w, h = image.shape[1], image.shape[0]
img = self._preprocess(image).unsqueeze(0)
# if cfg.test_cfg.cuda:
# img = img.cuda()
scale = torch.Tensor([w, h, w, h])
out = self.net(img)
boxes, scores = self.detector.forward(out, self.priors)
boxes = (boxes[0] * scale).cpu().numpy()
scores = scores[0].cpu().numpy()
allboxes = []
count = 0
for j in range(1, num_classes):
inds = np.where(scores[:, j] > cfg.test_cfg.score_threshold)[0]
if len(inds) == 0:
continue
c_bboxes = boxes[inds]
c_scores = scores[inds, j]
c_dets = np.hstack((c_bboxes, c_scores[:, np.newaxis])).astype(
np.float32, copy=False)
soft_nms = cfg.test_cfg.soft_nms
keep = nms(c_dets, cfg.test_cfg.iou, force_cpu=soft_nms)
keep = keep[:cfg.test_cfg.keep_per_class]
c_dets = c_dets[keep, :]
allboxes.extend([_.tolist() + [j] for _ in c_dets])
loop_time = time.time() - loop_start
allboxes = np.array(allboxes)
boxes = allboxes[:, :4]
scores = allboxes[:, 4]
cls_inds = allboxes[:, 5]
infos, im2show, num= draw_detection(image, boxes, scores,
cls_inds, -1, 0.15) # todo threshold need to be modify
return infos, im2show, num
6:繪製矩形框以及計算數量:
def draw_detection(im, bboxes, scores, cls_inds, fps, thr=0.2):
global num
imgcv = np.copy(im)
h, w, _ = imgcv.shape
infos = []
for i, box in enumerate(bboxes):
if scores[i] < thr:
continue
cls_indx = int(cls_inds[i])
# print(cls_indx)
if cls_indx == 7 or cls_indx == 6:
num += 1
box = [int(_) for _ in box]
thick = int((h + w) / 300)
cv2.rectangle(imgcv,
(box[0], box[1]), (box[2], box[3]),
colors[cls_indx], thick)
mess = '%s' % (labels[cls_indx])
infos.append(ch_labels[cls_indx]+' '+str(scores[i]))
cv2.putText(imgcv, mess, (box[0], box[1] - 7),
0, 1e-3 * h, colors[cls_indx], thick // 3)
if fps >= 0:
cv2.putText(imgcv, '%.2f' % fps + ' fps', (w - 160, h - 15),
0, 2e-3 * h, (255, 255, 255), thick // 2)
return infos, imgcv,num
運行主程序:
if __name__ == '__main__':
det = Pelee_Det()
im = cv2.imread('./test_imgs/demo.png') # todo threshold = 0.8
im = cv2.imread('./test_imgs/demo1.png') # todo threshold = 0
im = cv2.imread('./test_imgs/c.jpg') # todo threshold = 0.15
# im = cv2.imread('./test_imgs/a.png') # todo threshold = 0.4
# im = cv2.imread('./test_imgs/k.png') # todo threshold = 0.01
# im = cv2.imread('./test_imgs/h.jpg') # todo threshold = 0.07
# im = cv2.imread('./test_imgs/b.jpg') # todo threshold = 0.15
# im = cv2.imread('./test_imgs/f.jpg') # todo threshold = 0.15
_, im, num = det.detect(im)
print('there are', str(num), 'cars')
num = str(num) + 'cars'
cv2.putText(im, num, (50, 150), cv2.FONT_HERSHEY_COMPLEX, 2, (0, 255, 0), 4)
cv2.imshow(num, im)
cv2.waitKey(0)
cv2.destroyAllWindows()
下面再詳細介紹一下PeLee:
已有的在移動設備上執行的深度學習模型例如 MobileNet、 ShuffleNet 等都嚴重依賴於在深度上可分離的卷積運算,而缺乏有效的實現。在本文中,來自加拿大西安大略大學的研究者提出了稱爲 PeleeNet 的有效架構,它沒有使用傳統的卷積來實現。PeleeNet 實現了比目前最先進的 MobileNet 更高的圖像分類準確率,並降低了計算成本。研究者進一步開發了實時目標檢測系統 Pelee,以更低的成本超越了 YOLOv2 的目標檢測性能,並能流暢地在 iPhone6s、iPhone8 上運行。
在具有嚴格的內存和計算預算的條件下運行高質量的 CNN 模型變得越來越吸引人。近年來人們已經提出了很多創新的網絡,例如 MobileNets (Howard et al.(2017))、ShuffleNet (Zhang et al.(2017)),以及 ShuffleNet (Zhang et al.(2017))。然而,這些架構嚴重依賴於在深度上可分離的卷積運算 (Szegedy 等 (2015)),而這些卷積運算缺乏高效的實現。同時,將高效模型和快速目標檢測結合起來的研究也很少 (Huang 等 (2016b))。本研究嘗試探索可以用於圖像分類和目標檢測任務的高效 CNN 結構。本文的主要貢獻如下:
研究者提出了 DenseNet (Huang et al. (2016a)) 的一個變體,它被稱作 PeleeNet,專門用於移動設備。PeleeNet 遵循 DenseNet 的創新連接模式和一些關鍵設計原則。它也被設計來滿足嚴格的內存和計算預算。在 Stanford Dogs (Khosla et al. (2011)) 數據集上的實驗結果表明:PeleeNet 的準確率要比 DenseNet 的原始結構高 5.05%,比 MobileNet (Howard et al. (2017)) 高 6.53%。PeleeNet 在 ImageNet ILSVRC 2012 (Deng et al. (2009)) 上也有極具競爭力的結果。PeleeNet 的 top-1 準確率要比 MobileNet 高 0.6%。需要指出的是,PeleeNet 的模型大小是 MobileNet 的 66%。PeleeNet 的一些關鍵特點如下:
兩路稠密層:受 GoogLeNet (Szegedy et al. (2015)) 的兩路稠密層的激發,研究者使用了一個兩路密集層來得到不同尺度的感受野。其中一路使用一個 3×3 的較小卷積核,它能夠較好地捕捉小尺度的目標。另一路使用兩個 3×3 的卷積核來學習大尺度目標的視覺特徵。該結構如圖 1.a 所示:
圖 1: 兩路密集層和 stem 塊的結構
右邊(a)圖代表PeleeNet中設計的基本模塊,除了將原本的主幹分支的filter減半(主幹分支感受野爲3x3),還添加了一個新的分支,在新的分支中使用了兩個3x3的卷積,這個分支感受野爲5x5。這樣就提取得到的特徵就不只是單一尺度,能夠同時兼顧小目標和大目標。
(b)這個模塊可以在幾乎不增加計算量的情況下提升特徵的表達能力。
仔細看看上圖展示的結構,先使用strided 3x3卷積進行快速降維,然後用了兩分支的結構,一個分支用strided 3x3卷積, 另一個分支使用了一個maxpool。
這一部分和組合池化非常相似,stem block使用了strided 3x3卷積和最大值池化兩種的優勢引申而來的池化策略(組合池化使用的是最大值池化和均值池化),可以豐富特徵層。
瓶頸層通道的動態數量:另一個亮點就是瓶頸層通道數目會隨着輸入維度的變化而變化,以保證輸出通道的數目不會超過輸出通道。與原始的 DenseNet 結構相比,實驗表明這種方法在節省 28.5% 的計算資源的同時僅僅會對準確率有很小的影響。
沒有壓縮的轉換層:實驗表明,DenseNet 提出的壓縮因子會損壞特徵表達,PeleeNet 在轉換層中也維持了與輸入通道相同的輸出通道數目。
複合函數:爲了提升實際的速度,採用後激活的傳統智慧(Convolution - Batch Normalization (Ioffe & Szegedy (2015)) - Relu))作爲我們的複合函數,而不是 DenseNet 中所用的預激活。對於後激活而言,所有的批正則化層可以在推理階段與卷積層相結合,這可以很好地加快速度。爲了補償這種變化給準確率帶來的不良影響,研究者使用一個淺層的、較寬的網絡結構。在最後一個密集塊之後還增加了一個 1×1 的卷積層,以得到更強的表徵能力。
研究者優化了單樣本多邊框檢測器(Single Shot MultiBox Detector,SSD)的網絡結構,以加速並將其與 PeleeNet 相結合。該系統,也就是 Pelee,在 PASCAL VOC (Everingham et al. (2010)) 2007 數據集上達到了 76.4% 的準確率,在 COCO 數據集上達到了 22.4% 的準確率。在準確率、速度和模型大小方面,Pelee 系統都優於 YOLOv2 (Redmon & Farhadi (2016))。爲了平衡速度和準確率所做的增強設置如下:
特徵圖選擇:以不同於原始 SSD 的方式構建目標檢測網絡,原始 SSD 仔細地選擇了 5 個尺度的特徵圖 (19 x 19、10 x 10、5 x 5、3 x 3、1 x 1)。爲了減少計算成本,沒有使用 38×38 的特徵圖。
殘差預測塊:遵循 Lee 等人提出的設計思想(2017),即:使特徵沿着特徵提取網絡傳遞。對於每一個用於檢測的特徵圖,在實施預測之前構建了一個殘差 (He et al. (2016)) 塊(ResBlock)。ResBlock 的結構如圖 2 所示:
圖 2:殘差預測塊
用於預測的小型卷積核:殘差預測塊讓我們應用 1×1 的卷積核來預測類別分數和邊界框設置成爲可能。實驗表明:使用 1×1 卷積核的模型的準確率和使用 3×3 的卷積核所達到的準確率幾乎相同。然而,1x1 的核將計算成本減少了 21.5%。
研究者在 iOS 上提供了 SSD 算法的實現。他們已經成功地將 SSD 移植到了 iOS 上,並且提供了優化的代碼實現。該系統在 iPhone 6s 上以 17.1 FPS 的速度運行,在 iPhone8 上以 23.6 FPS 的速度運行。在 iPhone 6s(2015 年發佈的手機)上的速度要比在 Intel [email protected] CPU 上的官方算法實現還要快 2.6 倍。
核心思想
Two-Way Dense Layer
Pelee 使用兩路卷積層來得到不同尺寸的感受野。
圖 46
左邊先通過 1 x 1 卷積,後面接 3 x3 卷積,使其對小物體有更好表現。右邊先通過 1 x 1 卷積,兩個 3 x 3 卷積代替 5 x 5 卷積,有更小的計算量和參數個數,且對大物體有更好表現。
Stem Block
圖 47
網絡前幾層對於特徵的表達十分重要,Pelee 採用了類似於 DSOD 算法的 stem block 結構,經實驗可知該結構可以在不增加過多計算量情況下提高特徵表達能力。
動態調整 bottleneck layer 通道數
bottleneck layer 通道數目隨着輸入維度的變化而變化,以保證輸出通道的數目不會超過輸出通道。與原始的 DenseNet 結構相比,實驗表明這種方法在節省 28.5% 的計算資源的同時僅會對準確率有很小的影響。
過渡層不壓縮
實驗表明,DenseNet 提出的壓縮因子會損壞特徵表達,Pelee 在轉換層中也維持了與輸入通道相同的輸出通道數目。
Bn 層與卷積融合,起到加速的作用
捨棄了以前的 bn-relu-conv 採用了 conv-bn-relu 形式。Bn 層與卷積層融合加快推理速度(融合部分計算),同時後面接了 1 x 1 卷積提高表達能力。
作者將 SSD 與 Pelee 相結合,得到了優於 YOLOV2 的網絡結構。使用 Pelee 作爲基礎網絡對 SSD 改進:
\1. 捨棄 SSD 的 38 x 38 的 feature map。
\2. 採用了 Residual Prediction Block 結構,連接了不同的層。
\3. 用 1 x 1 卷積代替 SSD 裏的 3 x 3 卷積做最終物體的分類與迴歸。減少了 21.5%的計算量。
具體網絡結構:
圖 48:分類網絡結構圖
圖 49
首先圖片輸入後經過 Stem Block 結構,再通過 stage1、 stage2、stage3、stage4 及後續卷積操作得到相應 feature map,接 resblock 結構後進行分類及位置迴歸。
圖 50
如圖:
計算出該圖片中各個物體的得分(score):
score是一個座標框的各個類別的概率,取最大的那個(非背景類),大於某個闕值就是正樣本
例如第七行(對應着我們的label中的汽車)得分: 0.93653619 0.88733804 0.51076788 0.4675599 0.35944954 0.35482538
大於我們的閾值,所有判斷爲正!
計算每一個物體對應的車輛這一類別的得分:所以判斷出9輛車子:
算法效果
結合 SSD,在 VOC2007 數據集上達到了 76.4%的 mAP,在 COCO 數據集上達到了 22.4%的 mAP。在 iPhone 6s 上的運行速度是 17.1 FPS,在 iPhone 8 上的運行速度是 23.6 FPS。
圖 51
圖 52
最後:
PeLee作爲新型的目標檢測輕量級網絡。 他沒有使用SSD中3838的feature map做預測。對1919的feature map使用兩種不同大小的默認框,其餘4種分別對應1個默認框。這樣保持了最後還是輸出6種scale的feature map。
十分便捷與迅速!準確率也較高,實時情況下非常棒~。
碼字不易,看到這裏的朋友感興趣,可以一起玩玩,也邀請朋友們一起進羣學習深度學習(個人深度學習技術交流羣),羣裏大佬較多,感興趣的就聯繫我~可別忘記了點贊關注!!
上海第二工業大學 18智能A1 周小夏(CV調包俠)