1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        (附代碼)YOLOF:速度和效果均超過YOLOv4的檢測模型

        共 9889字,需瀏覽 20分鐘

         ·

        2021-09-05 10:19

        點擊左上方藍字關注我們



        一個專注于目標檢測與深度學習知識分享的公眾號

        編者薦語
        YOLOF 全稱是 You Only Look One-level Feature, 其通過詳細的實驗指出特征金字塔 FPN 模塊的成功在于其對目標優(yōu)化問題的分治解決方案,而不是我們常說的多尺度特征融合。
        作者 | 七月在線實驗室


        文章設計了一個簡潔優(yōu)雅的無需復雜 FPN 的網(wǎng)絡結構,僅僅依靠單尺度特征即可取得相匹配的效果,并且具備極快的推理速度,是一個不錯的算法。


        YOLOF 論文核心可以總結如下:


        1、設計了多組實驗,深入探討了 FPN 模塊成功的主要因素


        2、基于實驗結論,設計了無需 FPN 模塊,單尺度簡單高效的 Neck 模塊 Dilated Encoder


        3、基于 FPN 分治處理多尺度問題,配合 Neck 模塊提出 Uniform Matching 正負樣本匹配策略


        4、由于不存在復雜且耗內(nèi)存極多的 FPN 模塊,YOLOF 可以在保存高精度的前提下,推理速度快,消耗內(nèi)存也相對更小


        項目地址:github.com/open-mmlab/mmdetection,歡迎 star~


        1 FPN 模塊分析


        首先目標檢測算法可以簡單按照上述結構進行劃分,網(wǎng)絡部分主要分為 Backbone、Encoder 和 Decoder,或者按照我們前系列解讀文章劃分方法分為 Backbone、Neck 和 Head。對于單階段算法來說,常見的 Backbone 是 ResNet,Encoder 或者 Neck 是 FPN,而 Head 就是對應的輸出層結構。

        一般我們都認為 FPN 層作用非常大,不可或缺,其通過特征多尺度融合,可以有效解決尺度變換預測問題。
        而本文認為 FPN 至少有兩個主要作用:
        • 多尺度特征融合

        • 分治策略,可以將不同大小的物體分配到不同大小的的輸出層上,克服尺度預測問題

        作者試圖分析上述兩個作用中,最核心的部分,故選擇最常用的 RetinaNet 進行 FPN 模塊深入分析。

        對 FPN 模塊進一步抽象,如上圖所示,可以分成 4 種結構 MiMo、SiMo、MiSo 和 SiSo,其中 MiMo 即為標準的 FPN結構,輸入和輸出都包括多尺度特征圖。

        將 FPN 替換為上述 4 個模塊,然后基于 RetinaNet 重新訓練,計算 mAP 、 GFLOPs 和 FPS 指標

        • 從 mAP 角度分析,SiMo 結果和 MiMo 差距不大,說明 C5 (Backbone 輸出)包含了足夠的檢測不同尺度目標的上下文信息;而 MiSo 和 SiSo 則和 MiMo 差距較大,說明 FPN 分治優(yōu)化作用遠遠大于 多尺度特征融合

        • 從下表 GFLOPs 和 FPS 可以看出,MiMo 結構由于存在高分辨率特征圖 C3 會帶來較大的計算量,并且拖慢速度



        綜上所示,可以得到一些結論:
        • FPN 模塊的主要增益來自于其分治優(yōu)化手段,而不是多尺度特征融合

        • FPN 模塊中存在高分辨率特征融合過程,導致消耗內(nèi)存比較多,訓練和推理速度也比較慢,對部署不太優(yōu)化

        • 如果想在拋棄 FPN 模塊的前提下精度不丟失,那么主要問題是提供分治優(yōu)化替代手段



        2 YOLOF 原理簡析

        作者為了克服 FPN 存在的內(nèi)存占用多,速度慢問題,采用了 SiSo 結構,但是精度下降比較嚴重,從 35.9 變成了 24.6,故后續(xù)有針對性的改進,主要包括兩個部分:Dilated Encoder 和 Uniform Matching。

        2.1 Dilated Encoder

        雖然 FPN 的主要作用是分治優(yōu)化思想,但是多尺度融合也有一定作用,從上述實驗也可以看出。并且雖然 C5 提供了足夠的上下文,但是其感受野所對應的目標尺寸范圍是有限的,無法應對目標檢測場景中變化劇烈的目標尺寸。簡要理解如上圖所示,綠色點表示數(shù)據(jù)集中的多種目標尺寸,粉紅色區(qū)域代表特征圖能夠有效表達的目標尺寸范圍
        • 如果僅僅使用 C5 特征,會出現(xiàn)圖(a)所示的情況


        • 若使用空洞卷積操作來增大 C5 特征圖的感受野,則會出現(xiàn)圖(b)所示的情況,感受野變大,能夠有效地表達尺寸較大的目標,但是對小目標表達能力會變差


        • 如果采用不同空洞率的疊加,則可以有效避免上述問題





        為此,作者設計了 Dilated Encoder 結構,串聯(lián)多個不同空洞率的模塊以覆蓋不同大小物體,改善感受野單一問題,如下所示:


        對 C5 特征先進行壓縮通道,然后串聯(lián) 4 個不同空洞率的殘差模塊,從而得到不同感受野的特征圖。其實這種做法非常場景,在語義分割算法 ASPP 中和目標檢測算法 RFBNet 都采用了類似思想,只不過這兩個都是并聯(lián)結構,而本文是串聯(lián), RFBNet 結構如下所示:
        2.2 Uniform Matching
        前面說過 FPN 的核心功能是分治手段,但是我們知道雖然其輸出多個尺度特征圖,但是要想發(fā)揮分治功能則主要依靠 bbox 正負樣本分配策略,也就是說 FPN 和優(yōu)異的 bbox 正負樣本分配策略結合才能最大程度發(fā)揮功效,大部分最新的單階段目標檢測算法都在 bbox 分配策略上面做文章,可以借用 AutoAssign 論文中的圖說明:

        為了充分發(fā)揮 FPN 功效,一般會從 scale 和 spatial 兩個方面著手進行設計,scale 用于處理不同尺度大小的 gt bbox 應該屬于哪些輸出層負責,而 spatial 用于處理在某個輸出特征圖上哪些位置才是最合適的正樣本點。不同的 bbox 正負樣本分配策略對最終性能影響極大。

        一般來說,由于自然場景中,大小物體分布本身就不均勻,并且大物體在圖片中所占區(qū)域較大,如果不設計好,會導致大物體的正樣本數(shù)遠遠多于小物體,最終性能就會偏向大物體,導致整體性能較差。YOLOF 算法采用單尺度特征圖輸出,錨點的數(shù)量會大量的減少(比如從 100K 減少到 5K),導致了稀疏錨點,如果不進行重新設計,會加劇上述現(xiàn)象。為此作者提出了新的均勻匹配策略,核心思想就是不同大小物體都盡量有相同數(shù)目的正樣本。

        所提兩個模塊的作用如下所示:

        • Uniform Matching 作用非常大,說明該模塊其實發(fā)揮了 FPN 的分治作用


        • Dilated Encoder 配合 Uniform Matching 可以提供額外的變感受野功能,有助于多尺度物體預測



        需要特別注意:論文中所描述的 Uniform Matching 和代碼中實現(xiàn)的 Uniform Matching 有一定差距,在下一節(jié)源碼解讀時會詳細說明。

        3 YOLOF 源碼解析

        和前系列解讀一樣,依然按照 Backbone、Neck、Head、Bbox Coder、Bbox Assigner 和 Loss 順序解讀。

        3.1 BackboneBackbone 

        采用了 ResNet50,caffe 模式,和 FCOS 算法配置相同,只不過這里只需要輸出 C5 特征圖即可,不需要多尺度。

        pretrained='open-mmlab://detectron/resnet50_caffe', 
        backbone=dict(
        type='ResNet',
        depth=50,
        num_stages=4,
        out_indices=(3, ),
        frozen_stages=1,
        norm_cfg=dict(type='BN', requires_grad=False),
        norm_eval=True,
        style='caffe'),

        3.2 NeckNeck 
        模塊是本文新提出的 Dilated Encoder 模塊,包括一個通道壓縮模塊,然后串聯(lián) 4 個不同空洞率的殘差模塊,提供靈活變尺度的感受野。

        neck=dict( 
        type='DilatedEncoder',
        in_channels=2048,
        out_channels=512,
        block_mid_channels=128,
        num_residual_blocks=4),

        由于比較簡單,就不展開分析了。

        3.3 Head

        Head 模塊即上圖的 Decoder 結構,包括分類和回歸分支,借鑒 AutoAssign 算法,在回歸分支上并行引入一個 Objectness 分支,用于抑制背景區(qū)域的高響應,然后將其和分類分支相乘。故對外實際上兩個分支,Objectness是沒有監(jiān)督 label 的。其 Head forward 如下所示

        def forward_single(self, feature): 
        # 分類分支
        cls_score = self.cls_score(self.cls_subnet(feature))
        N, _, H, W = cls_score.shape
        cls_score = cls_score.view(N, -1, self.num_classes, H, W)

        # 回歸分支
        reg_feat = self.bbox_subnet(feature)
        bbox_reg = self.bbox_pred(reg_feat)
        objectness = self.object_pred(reg_feat)
        # implicit objectness
        objectness = objectness.view(N, -1, 1, H, W)

        normalized_cls_score = cls_score + objectness - torch.log(
        1. + torch.clamp(cls_score.exp(), max=INF) +
        torch.clamp(objectness.exp(), max=INF))
        normalized_cls_score = normalized_cls_score.view(N, -1, H, W)
        return normalized_cls_score, bbox_reg
        上述得到 normalized_cls_score 的計算過程看起來非常復雜,但是其實是為了能夠?qū)θ诤虾蟮?normalized_cls_score 采用 sigmoid 函數(shù)而已,其對應的公式是:

        也就是說 normalized_cls_score 是不含 sigmoid 的包括 cls_score 和 objectness 融合的值。可以通過如下簡單代碼驗證:

        import torch 

        if __name__ == '__main__':
        INF = 1e8
        N = 1
        num_classes = 2
        H = W = 3
        cls_score = torch.rand((N, 1, num_classes, H, W))
        objectness = torch.rand(N, 1, 1, H, W)

        normalized_cls_score = cls_score + objectness - torch.log(
        1. + torch.clamp(cls_score.exp(), max=INF) +
        torch.clamp(objectness.exp(), max=INF))

        cls_score_s = torch.sigmoid(cls_score) * torch.sigmoid(objectness)

        assert torch.allclose(cls_score_s, torch.sigmoid(normalized_cls_score))

        3.4 Bbox 
        CoderYOLOF 輸出格式采用 RetinaNet 算法中定義的 deltaXYWH ,即回歸分支輸出的 4 個值表示相對于 anchor 的偏移

        anchor_generator=dict( 
        type='AnchorGenerator',
        ratios=[1.0],
        scales=[1, 2, 4, 8, 16],
        strides=[32]),
        bbox_coder=dict(
        type='DeltaXYWHBBoxCoder',
        target_means=[.0, .0, .0, .0],
        target_stds=[1., 1., 1., 1.],
        add_ctr_clamp=True,
        ctr_clamp=32),

        只有一個輸出特征圖,每個位置鋪設了 5 個 anchor,寬高比是 1,設置了 5 種 scale。為了穩(wěn)定訓練過程,作者在 DeltaXYWHBBoxCoder 中引入了 add_ctr_clamp 參數(shù)即當中心坐標預測相比 anchor 偏離大于 32 個像素,則強制裁剪為 32,防止產(chǎn)生較大的梯度。


        3.5 Bbox Assigner

        這個部分是 YOLOF 的核心,需要重點分析。首先分析論文中描述,然后再基于代碼說明代碼和論文的差異。論文中描述的非常簡單,核心目的是保證不同尺度物體都盡可能有相同數(shù)目的正樣本

        • 遍歷每個 gt bbox,然后選擇 topk 個距離最近的 anchor 作為其匹配的正樣本

        • 由于存在極端比例物體和小物體,上述強制 topk 操作可能出現(xiàn) anchor 和 gt bbox 的不匹配現(xiàn)象,為了防止噪聲樣本影響,在所有正樣本點中,將 anchor 和 gt bbox 的 iou 低于 0.15 的正樣本(因為不管匹配情況,topk 都會選擇出指定數(shù)目的正樣本)強制認為是忽略樣本,在所有負樣本點中,將 anchor 和 gt bbox 的 iou 高于 0.75 的負樣本(可能該物體比較大,導致很多 anchor 都能夠和該 gt bbox 很好的匹配,這些樣本就不適合作為負樣本了)強制認為是忽略樣本

        實際上作者代碼的寫法如下所示

        • 遍歷每個 gt bbox,然后選擇 topk 個距離最近的 anchor 作為其匹配的正樣本

        • 遍歷每個 gt bbox,然后選擇 topk 個距離最近的預測框作為補充的匹配正樣本

        • 計算 gt bbox 和預測框的 iou,在所有負樣本點中,將 iou 高于 0.75 的負樣本強制認為是忽略樣本

        • 計算 gt bbox 和 anchor 的 iou,在所有正樣本點中,將 iou 低于 0.15 的正樣本強制認為是忽略樣本

        可以發(fā)現(xiàn)相比于論文描述,實際上代碼額外動態(tài)補充了一定量的正樣本,同時也額外考慮了一些忽略樣本。相比于純粹采用 anchor 和 gt bbox 進行匹配,額外引入預測框,可以動態(tài)調(diào)整正負樣本,理論上會更好。

        # 全部任務是負樣本 
        assigned_gt_inds = bbox_pred.new_full((num_bboxes, ),
        0,
        dtype=torch.long)

        # 計算兩兩直接的距離,包括 預測框和 gt bbox,以及 anchor 和 gt bbox
        cost_bbox = torch.cdist(
        bbox_xyxy_to_cxcywh(bbox_pred),
        bbox_xyxy_to_cxcywh(gt_bboxes),
        p=1)
        cost_bbox_anchors = torch.cdist(
        bbox_xyxy_to_cxcywh(anchor), bbox_xyxy_to_cxcywh(gt_bboxes), p=1)

        # 分別提取 topk 個樣本點作為正樣本,此時正樣本數(shù)會加倍
        index = torch.topk(
        C,
        k=self.match_times,
        dim=0,
        largest=False)[1]
        # self.match_times x n
        index1 = torch.topk(C1, k=self.match_times, dim=0, largest=False)[1]
        # (self.match_times*2) x n
        indexes = torch.cat((index, index1),
        dim=1).reshape(-1).to(bbox_pred.device)

        # 計算 iou 矩陣
        pred_overlaps = self.iou_calculator(bbox_pred, gt_bboxes)
        anchor_overlaps = self.iou_calculator(anchor, gt_bboxes)
        pred_max_overlaps, _ = pred_overlaps.max(dim=1)
        anchor_max_overlaps, _ = anchor_overlaps.max(dim=0)

        # 計算 gt bbox 和預測框的 iou,在所有負樣本點中,將 iou 高于 0.75 的負樣本強制認為是忽略樣本
        ignore_idx = pred_max_overlaps > self.neg_ignore_thr
        assigned_gt_inds[ignore_idx] = -1

        # 計算 gt bbox 和 anchor 的 iou,在所有正樣本點中,將 iou 低于 0.15 的正樣本強制認為是忽略樣本
        pos_gt_index = torch.arange(
        0, C1.size(1),
        device=bbox_pred.device).repeat(self.match_times * 2)
        pos_ious = anchor_overlaps[indexes, pos_gt_index]
        pos_ignore_idx = pos_ious < self.pos_ignore_thr
        pos_gt_index_with_ignore = pos_gt_index + 1
        pos_gt_index_with_ignore[pos_ignore_idx] = -1
        assigned_gt_inds[indexes] = pos_gt_index_with_ignore


        3.6 Loss

        在確定了每個特征點位置哪些是正樣本和負樣本后,就可以計算 loss 了,分類采用 focal loss,回歸采用 giou loss,都是常規(guī)操作。

        loss_cls=dict( 
        type='FocalLoss',
        use_sigmoid=True,
        gamma=2.0,
        alpha=0.25,
        loss_weight=1.0),
        loss_bbox=dict(type='GIoULoss', loss_weight=1.0))


        上述就是整個 YOLOF 核心實現(xiàn)過程。至于推理過程和 RetinaNet 算法完全相同。

        4 YOLOF 復現(xiàn)心得和體會

        如果不仔細思考,可能看不出上述代碼有啥問題,實際上在 Bbox Assigner 環(huán)節(jié)會存在重復索引分配問題,這個問題會帶來幾個影響。具體代碼是:


        # 對應 3.5 小節(jié)的源碼分析第 44 行 
        assigned_gt_inds[indexes] = pos_gt_index_with_ignore

        前面說過,YOLOF 會引入額外的預測框點作為補充正樣本,當 2 次 topk 選擇的位置相同時候就會出現(xiàn)意想不到問題。

        舉個簡單例子,當前圖片中僅僅有一個 gt bbox,且預測輸出特征圖大小是 10x10,設置 anchor 個數(shù)是 1,那么說明輸出特征圖上只有 10x10 個anchor,并且對應了 10x10 個預測框,topk 設置為 4

        • 計算該 gt bbox 和 100 個 anchor 的距離,然后選擇最近的前 4 個位置作為正樣本


        • 計算該 gt bbox 和 100 個預測框的距離,然后選擇最近的前 4 個位置作為正樣本,注意這里選擇的 4個位置很可能和前面選擇的 4 個位置有重復

        • 計算該 gt bbox 和預測框的 iou,在所有負樣本點中,將 iou 高于 0.75 的負樣本強制認為是忽略樣本

        • 計算該 gt bbox 和 anchor 的 iou,在所有正樣本點中,將 iou 低于 0.15 的正樣本強制認為是忽略樣本,注意和上一步的區(qū)別,由于 iou 計算的輸入是不一樣的,可能導致某個被重復計算的正樣本位置出現(xiàn) 2 種情況:1. 兩個步驟都認為是忽略樣本;2. 一個認為是忽略樣本,一個認為是正樣本,而一旦出現(xiàn)第二種情況則在 CUDA 并行計算中出現(xiàn)不確定輸出



        簡單來說:indexes 中可能存在重復值,并且重復值位置對應的 pos_gt_index_with_ignore 可能相同也可能不同,注意重復現(xiàn)象可能出現(xiàn)在兩個不同類別物體有重疊的情況下,那么上述賦值操作在 CUDA 中是不可預知的,可能會出現(xiàn)同一份數(shù)據(jù)跑兩次輸出結果不一樣。
        這個操作會給后面的回歸分支帶來歧義,因為回歸分支僅僅處理正樣本,那么會出現(xiàn)以下幾個情況:

        • 如果兩個重復索引處對應的 gt bbox 是同一個,那么相當于該 gt bbox 對應的正樣本 loss 權重加倍

        • 如果兩個重復索引處對應的 gt bbox 不是同一個,那么就會出現(xiàn)歧義,因為特征圖上同一個預測點,被同時分配給了兩個不同的 gt bbox



        總的來說,對于上述重復索引分配現(xiàn)象,會帶來幾個影響:

        • 讀者理解代碼運行流程會比較困惑

        • 同一個程序跑多次,可能輸出結果不一致

        • 訓練過程不穩(wěn)定

        • 當重復索引出現(xiàn)時候,回歸分支 loss 計算過程非常奇怪,難以理解

        • 低版本 CUDA 上會出現(xiàn)非法內(nèi)存越界錯誤, 實驗發(fā)現(xiàn) CUDA9.0 會出現(xiàn)非法內(nèi)存越界錯誤,但是 CUDA10.1 則正常,其余版本沒有進行測試




        關于第5點,原因暫時不清楚,但是現(xiàn)象是 pos_gt_index_with_ignore、indexes 和 assigned_gt_inds 都不存在越界情況,只不過 indexes 如果存在相同值,在賦值后會出現(xiàn) 4294967295(2^32 -1) 和 -4294967295 (-2^32 +1) 異常值,然后后續(xù)基于 assigned_gt_inds 取值后就出現(xiàn)出現(xiàn) RuntimeError: CUDA error: an illegal memory access was encountered. 經(jīng)過多次實驗發(fā)現(xiàn),錯誤是必現(xiàn)的。

        當時也試過其他幾個方案,例如 1. 將上述賦值操作放置到 cpu 上進行;2. 將賦值后異常值全部設置為忽略樣本,雖然可以避免報錯,但是實驗結果顯示會存在一定程度的掉點,所以最終沒有修改。這個問題我們也會持續(xù)關注,直到找到一個更加合適的方式以避免上述報錯問題。

        上述這個寫法,給代碼復現(xiàn)帶來了些問題,并且由于 YOLOF 學習率非常高 lr=0.12,訓練過程偶爾會出現(xiàn) Nan 現(xiàn)象,訓練不太穩(wěn)定,可能對參數(shù)設置例如 warmup 比較敏感。

        最后還是要感謝作者 Qiang Chen,在復現(xiàn)過程中解答了一些疑問。通過針對訓練不穩(wěn)定的問題,也指明了可能解決的方案。


        END



        雙一流大學研究生團隊創(chuàng)建,專注于目標檢測與深度學習,希望可以將分享變成一種習慣!

        點贊三連,支持一下吧↓

        瀏覽 72
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            狠狠干中文字幕 | 北条麻妃亚洲无码 | 美女一级黄色大片 | 97视频一区二区三区在线观看 | 色大香蕉| 特级大胆西西4444人体 | 免费高清在线观看黄网站 | 国产3p又大又爽又粗又硬免费 | 天天精品 | 国产精品成人A片在线果冻 |