<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于CenterFace的模型優(yōu)化記錄

          共 6672字,需瀏覽 14分鐘

           ·

          2021-01-27 21:10

          【GiantPandaCV導(dǎo)語(yǔ)】CenterFace移動(dòng)端模型優(yōu)化實(shí)驗(yàn)記錄

          一、序

          CenterFace是基于CenterNet的一種AnchorFree人臉檢測(cè)模型。在widerface上性能雖然沒有超過SOTA(Retinaface),但是勝于推理速度較快(不需要NMS),模型結(jié)構(gòu)簡(jiǎn)單,便于移植部署。

          二、背景

          模型主要是使用在移動(dòng)端App中,需要滿足:

          1. 上傳圖片后返回帶有人臉檢測(cè)框的結(jié)果圖片。
          2. 打開攝像頭實(shí)時(shí)進(jìn)行檢測(cè)并拍照,返回檢測(cè)后的圖片。
          3. 需要在室內(nèi)、室外、白天,晚上場(chǎng)景下均可使用,受眾群體密集度比較高,需要支持戴口罩檢測(cè),Recall和precision均要求比較高(大于90%)。
          4. 需要支持大小人臉檢測(cè)。樣例如下,難點(diǎn)在于支持小人臉檢測(cè)以及小模型優(yōu)化。
          ? ?
          ? ?

          圖1-場(chǎng)景例子

          三、CenterFace

          本節(jié)先簡(jiǎn)單介紹一下CenterFace模型

          • 模型結(jié)構(gòu)

            CenterFace模型構(gòu)造比較簡(jiǎn)單,基礎(chǔ)backbone+FPN+head即完成網(wǎng)絡(luò)構(gòu)建。1. backbone模型上采用了mobilenetv2作為backbone,mobilenetv3因?yàn)槎喾种б约癝E模塊,手機(jī)端移植不是很友好,實(shí)機(jī)測(cè)試,iphonex上mbv3相比mbv2要慢1ms左右,如果是低端機(jī),這個(gè)差距會(huì)更大。2. FPN采用的是傳統(tǒng)的top-down結(jié)構(gòu),沒有使用PAN。3. Head采用4個(gè)conv+bn結(jié)構(gòu)分別輸出locationmapscalemap,offsetmappointsmap。整體結(jié)構(gòu)圖如下:


            圖2-模型結(jié)構(gòu)

          • 損失函數(shù)

            CenterFace的損失函數(shù)和CenterNet一樣,由FocalLoss形式Location分類損失和L1回歸損失構(gòu)成

          1. Location分類損失:

          2. L1回歸損失:

            • offset
            • points
            • scale
          3. 最終損失:

          對(duì)于location損失,只有每個(gè)bbox的中心點(diǎn)為正樣本,其余點(diǎn)均為負(fù)樣本,公式。對(duì)于offset損失,由于featuremap進(jìn)行下采樣的時(shí)候,計(jì)算中心點(diǎn)會(huì)由于取整產(chǎn)生偏移,需要用l1損失計(jì)算這個(gè)偏差。對(duì)于scale損失,這個(gè)是對(duì)bbox的w和h進(jìn)行回歸,取log便于計(jì)算。對(duì)于points損失,計(jì)算的是人臉5個(gè)關(guān)鍵點(diǎn)到中心點(diǎn)之間的距離的損失,做了normalize處理。最后的損失,就是各個(gè)損失的加權(quán)之和

          • 標(biāo)簽生成

            1. locationmap, centerface中最重要的target就是bbox中心點(diǎn)gaussianmap生成,代碼,效果圖如下:

              def?_gaussian_radiusv1(self,?height,?width,?min_overlap=0.7):
              ????"""from?cornernet"""
              ????a1?=?1
              ????b1?=?(height?+?width)
              ????c1?=?width?*?height?*?(1?-?min_overlap)?/?(1?+?min_overlap)
              ????sq1?=?torch.sqrt(b1?**?2?-?4?*?a1?*?c1)
              ????r1?=?(b1?+?sq1)?/?2

              ????a2?=?4
              ????b2?=?2?*?(height?+?width)
              ????c2?=?(1?-?min_overlap)?*?width?*?height
              ????sq2?=?torch.sqrt(b2?**?2?-?4?*?a2?*?c2)
              ????r2?=?(b2?+?sq2)?/?2

              ????a3?=?4?*?min_overlap
              ????b3?=?-2?*?min_overlap?*?(height?+?width)
              ????c3?=?(min_overlap?-?1)?*?width?*?height
              ????sq3?=?torch.sqrt(b3?**?2?-?4?*?a3?*?c3)
              ????r3?=?(b3?+?sq3)?/?2
              ????return?min(r1,?r2,?r3)

              def?_gaussian2D(self,?shape,?sigma=1):
              ????m,?n?=?[(ss?-?1.)?/?2.?for?ss?in?shape]
              ????#?y,?x?=?np.ogrid[-m:m+1,-n:n+1]
              ????y?=?torch.arange(-m,?m+1,?dtype=torch.int).view(-1,?1)
              ????x?=?torch.arange(-n,?n+1,?dtype=torch.int).view(1,?-1)

              ????h?=?torch.exp(-(x?*?x?+?y?*?y)?/?(2?*?sigma?*?sigma))
              ????h[h?1e-5?*?h.max()]?=?0
              ????return?h

              def?_draw_umich_gaussian(self,?heatmap,?center,?radius,?k=1):
              ????diameter?=?2?*?radius?+?1
              ????#?get?the?gaussian?heatmap
              ????gaussian?=?self._gaussian2D((diameter,?diameter),?sigma=diameter?/?6)
              ????x,?y?=?int(center[0]),?int(center[1])
              ????height,?width?=?heatmap.shape[0:2]

              ????left,?right?=?min(x,?radius),?min(width?-?x,?radius?+?1)
              ????top,?bottom?=?min(y,?radius),?min(height?-?y,?radius?+?1)

              ????masked_heatmap?=?heatmap[y?-?top:y?+?bottom,?x?-?left:x?+?right]
              ????masked_gaussian?=?gaussian[radius?-?top:radius?+
              ????????????????????????????bottom,?radius?-?left:radius?+?right]
              ????if?min(masked_gaussian.shape)?>?0?and?min(masked_heatmap.shape)?>?0:??#?TODO?debug
              ????????torch.max(masked_heatmap,?masked_gaussian?*?k,?out=masked_heatmap)

              ????return?heatmap

              圖3-heatmap

            2. offsetmap, 每個(gè)bbox中心點(diǎn)上x方向和y方向的偏移結(jié)果,輸出是一個(gè)(BX2XHXW)的map。

            3. scalemap, 每個(gè)bbox中心點(diǎn)上w和h的log結(jié)果,輸出是一個(gè)(BX2XHXW)的map。

            4. pointsmap, 每個(gè)bbox的中心點(diǎn)到5個(gè)關(guān)鍵點(diǎn)x和y方向的距離,輸出是一個(gè)(BX10XHXW)的map。

          • 模型推理

            CenterFace沒有使用NMS作為后處理,而是采用的maxpooling作為代替,代碼如下:

            loc?=?loc.unsqueeze(0)????#?heatmap
            hm_max?=?nn.MaxPool2d(kernel_size=3,?stride=1,?padding=1)(loc)
            loc[loc?!=?hm_max]?=?0

          四、優(yōu)化路線

          1. 數(shù)據(jù):由于場(chǎng)景任務(wù)中存在部分帶有口罩的情況,所以采集了2000張百度的帶有口罩的數(shù)據(jù),混合widerface的train和val的數(shù)據(jù)來進(jìn)行訓(xùn)練,測(cè)試數(shù)據(jù)使用業(yè)務(wù)提供的數(shù)據(jù),保持一致。

          2. 優(yōu)化:流程圖如下:


            圖4-模型優(yōu)化流程

          • baseline模型為mobilenetv2+fpn模型,測(cè)試數(shù)據(jù)上ap為95%,初始圖片訓(xùn)練大小為800x800,測(cè)試大小為416x416,F(xiàn)LOPs為1G。

          • 通常來講,train的size大于測(cè)試的時(shí)候,效果表現(xiàn)不好。所以調(diào)整圖片的trainsize,分別為800,640,512,416,最終在416x416測(cè)試的情況下,訓(xùn)練size為416的時(shí)候效果最好。模型固定為416訓(xùn)練,416測(cè)試。

          • centerface的FPN的最后一層直接進(jìn)行輸出,這里把多層layer進(jìn)行了concat,ap提升了一個(gè)點(diǎn),但是FLOPs增加,收益不大。

          • 由于centerface沒有anchorbbox,bbox回歸和中心點(diǎn)損失沒有實(shí)質(zhì)的關(guān)聯(lián),所以bbox的表現(xiàn)不是很好,添加了一個(gè)scale_dis分支,輸出(BX4XHXW)的一個(gè)featuremap,分別表示的是中心點(diǎn)到上下左右的距離,計(jì)算IOU損失,效果提升不明顯,還帶來了4個(gè)通道的featuremap的冗余計(jì)算,直接棄用了(有興趣的同學(xué)可以參考FCOS的說明)。

          • 由于FPN最后的輸出,經(jīng)過一個(gè)卷積后,要輸出多個(gè)head,會(huì)帶來多個(gè)卷積計(jì)算,所以考慮優(yōu)化為一個(gè)卷積輸出,輸出的層為(BX(1+2+2+10)xHXW),計(jì)算的時(shí)候分別對(duì)應(yīng)channel計(jì)算損失,減少了10M的FLOPs,推理速度有所提升,同時(shí)提升了將近1個(gè)點(diǎn)的ap。

            ?
            ?

            圖5-修改head結(jié)構(gòu)

          • 由于業(yè)務(wù)只要求輸出框,對(duì)于關(guān)鍵點(diǎn)沒有需求,所以構(gòu)建結(jié)構(gòu)圖的時(shí)候,把關(guān)鍵點(diǎn)的channel砍掉,可以減少20M的FLOPs,精度保持不變。

            ?
            ?

            圖6-推理減少channel

          • 使用mobilenetv2x0.25+fpn作為backbone,使用上面的操作,F(xiàn)LOPs降低為0.2G,ap基本保持不變。

          • 修改訓(xùn)練size為400,推理size也為400,修改FPN的channel從24降低到16,F(xiàn)LOPs降低到了0.131G,精度保持95.2%相比baseline有輕微的提升。

          • 最后對(duì)模型進(jìn)行剪枝,采用SlimmingBN方法,對(duì)mobilenetv2中的InvertResdiual模塊中的升維層進(jìn)行剪枝。流程如下:

            • 訓(xùn)練的時(shí)候,對(duì)bn的gamma進(jìn)行l(wèi)1正則,設(shè)置為1e-4代碼如下:

              def?updataBN(self):
              ????"""add?a?l1?panety?for?bn?channel"""
              ????for?m?in?self.model.modules():
              ????????if?isinstance(m,?nn.BatchNorm2d):
              ????????????m.weight.grad.data.add_(self.s?*?torch.sign(m.weight.data))
            • 剪枝的時(shí)候,根據(jù)bn總數(shù)設(shè)置閾值,對(duì)weights從小到大進(jìn)行排序,按比例定位閾值,根據(jù)閾值對(duì)bn的weights置0,重新計(jì)算測(cè)試集ap,測(cè)試發(fā)現(xiàn)卡0.3的時(shí)候,精度保持不變,0.4的時(shí)候精度下降1個(gè)點(diǎn)的ap,需要進(jìn)行finetune。

            • 保存模型,根據(jù)剪枝出來的Config重構(gòu)模型結(jié)構(gòu),復(fù)制保留下來的權(quán)重到對(duì)應(yīng)修改的層上,再次進(jìn)行推理,ap保持不變即可,需要finetune的話,load權(quán)重后進(jìn)行finetune,這里finetune個(gè)人建議是pretrain,finetune的話,可能會(huì)由于剪枝導(dǎo)致過檢和漏檢的情況在訓(xùn)練結(jié)束后還存在。

              for?[m0,?m1]?in?zip(model.fpn.backbone.modules(),?newmodel.fpn.backbone.modules()):
              ????#?only?prune?the?invertedresidual?conv?and?bn
              ????if?isinstance(m0,?InvertedResidual):
              ????????if?len(m0.conv)?>?5:
              ????????????for?i?in?range(len(m0.conv)):
              ????????????????if?i?==?1:
              ?????????????????if?isinstance(m0.conv[i],?nn.BatchNorm2d):

              ????????????????????????idx1?=?np.squeeze(np.argwhere(np.asarray(mask.cpu().numpy())))
              ????????????????????????#?first?batchnormalize?(hidden)
              ????????????????????????m1.conv[i].weight.data?=?m0.conv[i].weight.data[idx1].clone()
              ????????????????????????m1.conv[i].bias.data?=?m0.conv[i].bias.data[idx1].clone()
              ????????????????????????m1.conv[i].running_mean?=?m0.conv[i].running_mean[idx1].clone()
              ????????????????????????m1.conv[i].running_var?=?m0.conv[i].running_var[idx1].clone()
              ????????????????????????#?first?conv?(hidden,?inp,?1,?1)
              ????????????????????????if?isinstance(m0.conv[i-1],?nn.Conv2d):
              ????????????????????????????w?=?m0.conv[i-1].weight.data[idx1,?:,?:,?:].clone()
              ?????????????????????????m1.conv[i-1].weight.data?=?w.clone()

              ????????????????????????#?second?conv??(hidden,?1,?1,?1)
              ????????????????????????if?isinstance(m0.conv[i+2],?nn.Conv2d):
              ????????????????????????????w?=?m0.conv[i+2].weight.data[idx1,?:,?:,?:].clone()
              ?????????????????????????m1.conv[i+2].weight.data?=?w.clone()

              ????????????????????????#?second?bn?(hidden)
              ????????????????????????if?isinstance(m0.conv[i+3],?nn.BatchNorm2d):
              ????????????????????????????m1.conv[i+3].weight.data?=?m0.conv[i+3].weight.data[idx1].clone()
              ????????????????????????????m1.conv[i+3].bias.data?=?m0.conv[i+3].bias.data[idx1].clone()
              ????????????????????????????m1.conv[i+3].running_mean?=?m0.conv[i+3].running_mean[idx1].clone()
              ?????????????????????????m1.conv[i+3].running_var?=?m0.conv[i+3].running_var[idx1].clone()

              ????????????????????????#?third?conv?(oup,?hidden,?1,?1)
              ????????????????????????if?isinstance(m0.conv[i+5],?nn.Conv2d):
              ????????????????????????????w?=?m0.conv[i+5].weight.data[:,?idx1,?:,?:].clone()
              ?????????????????????????m1.conv[i+5].weight.data?=?w.clone()

              ????????????????????????#?third?bn?(oup)
              ????????????????????????if?isinstance(m0.conv[i+6],?nn.BatchNorm2d):
              ????????????????????????????m1.conv[i+6].weight.data?=?m0.conv[i+6].weight.data.clone()
              ????????????????????????????m1.conv[i+6].bias.data?=?m0.conv[i+6].bias.data.clone()
              ????????????????????????????m1.conv[i+6].running_mean?=?m0.conv[i+6].running_mean.clone()
              ?????????????????????????m1.conv[i+6].running_var?=?m0.conv[i+6].running_var.clone()

              ????????????????????????layer_id_in_cfg?+=?1
              ????????????????????????if?layer_id_in_cfg??????????????????????????mask?=?cfg_mask[layer_id_in_cfg]

            • 最后輸出的模型為0.116G的FLOPs,相比baseline降低了10倍FLOPs,參數(shù)量400k左右,測(cè)試集上ap95.2%相比baseline高了0.2%個(gè)點(diǎn)。iphonex上測(cè)試10ms左右,低端機(jī)可以滿足10FPS以上的輸出,精度,速度均滿足要求。

          五、簡(jiǎn)單的思考

          1. 為什么gaussian map是一個(gè)3X3的map,而不是全局的map或者說是一個(gè)點(diǎn)?
            • 可能是因?yàn)閏ornernet是計(jì)算角點(diǎn)的,因?yàn)榇嬖谄钏孕枰粋€(gè)小點(diǎn)的map來做約束,centernet直接繼承了這個(gè)idea,論文中也是直接引用沒有過多的思考。
            • 從損失構(gòu)建上來看,如果增大gaussianmap,對(duì)應(yīng)的增加負(fù)樣本,會(huì)影響中心點(diǎn)的約束。相當(dāng)于中心點(diǎn)是points anchor,其他的都是非anchor。
            • 從gaussianmap生成來看,對(duì)應(yīng)的3x3可以cover出現(xiàn)offset過大的情況,可以約束范圍,畢竟沒有bboxanchor。
          2. 如果把bbox全部填滿?
            • bbox填滿,那么就是每個(gè)點(diǎn)都是正樣本,框中不存在負(fù)樣本,這樣中心點(diǎn)的價(jià)值就不存在了,論文的延伸也就是FCOS。
          3. loss之間的聯(lián)系?
            • 由于anchorbase的方法是存在海量的positive和negative anchors,回歸的好壞,一是會(huì)受到anchor的影響,二是會(huì)受到classification的影響,所以生成的bbox比較穩(wěn)定。centernet的各個(gè)loss實(shí)際上是獨(dú)立的,只是建立在中心點(diǎn)的基礎(chǔ)上,所以導(dǎo)致框會(huì)不穩(wěn)定,尤其是在實(shí)時(shí)中抖動(dòng)比較大,OneNet就這個(gè)問題給出了解決方案。

          預(yù)測(cè)結(jié)果

          400x400 移動(dòng)端上進(jìn)行測(cè)試的結(jié)果如下


          圖7-移動(dòng)端檢測(cè)結(jié)果

          結(jié)束語(yǔ)

          本人才疏學(xué)淺,以上都是自己在做項(xiàng)目中的一些方法和實(shí)驗(yàn),以及一些粗淺的思考,并不一定完全正確,只是個(gè)人的理解,歡迎大家指正,留言評(píng)論。

          參考文獻(xiàn)

          • CenterFace(https://arxiv.org/pdf/1911.03599.pdf)
          • CenteNet(https://arxiv.org/abs/1904.07850)
          • CornerNet(https://arxiv.org/abs/1808.01244)
          • SlimmingBN(https://openaccess.thecvf.com/content_ICCV_2017/papers/Liu_Learning_Efficient_Convolutional_ICCV_2017_paper.pdf)
          • FCOS(https://arxiv.org/abs/1904.01355)
          • OneNet(https://arxiv.org/abs/2012.05780)

          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨(dú)家的深度學(xué)習(xí)分享,堅(jiān)持原創(chuàng),每天分享我們學(xué)習(xí)到的新鮮知識(shí)。( ? ?ω?? )?

          有對(duì)文章相關(guān)的問題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號(hào)的作者發(fā)布一些Github工程的更新,我們成立了一個(gè)QQ群,二維碼如下,感興趣可以加入。

          公眾號(hào)QQ交流群


          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  豆花视频在线播放 | 日韩一级a免费在线视频 | 亚洲AV无码专区一级婬片毛片 | 国产成人三级在线 | 婷婷在线综合激情 |