
做目標檢測這些年YOLO系列的迭代一直是我重點追蹤的方向。最近在給檢測頭做特征優(yōu)化時我試了一個有意思的改進給模型加雙backbone然后在特征融合階段插入一個空間增強前饋網(wǎng)絡(luò)SEFN。這套方案的核心思路概括起來就是一句話——從前置特征里提取局部空間信息來構(gòu)建門控再動態(tài)調(diào)制后置特征讓雙分支特征在融合過程中同時保住局部空間一致性和長距離依賴。如果你也在折騰YOLO改進或者對檢測器的特征融合一直有疑慮這篇內(nèi)容應該能給你不少可以落地的經(jīng)驗。1. 設(shè)計思路拆解單骨干網(wǎng)絡(luò)的兩難問題開始動手之前先把為什么要做雙backbone這件事說透。很多人在改進檢測器時喜歡往neck和head堆模塊但往往忽略了backbone本身就是最大的瓶頸。1.1 感受野與空間分辨率的矛盾單backbone在提取特征時天然面臨一個兩難網(wǎng)絡(luò)越深感受野越大語義信息越豐富但特征圖的分辨率越低局部空間細節(jié)邊緣、紋理、小目標位置丟失得越厲害。反過來淺層的特征圖雖然分辨率高、細節(jié)保留好但語義抽象能力不足難以支撐復雜場景下的類別判別。常規(guī)做法是FPN、PANet這類金字塔結(jié)構(gòu)把淺層細節(jié)和深層語義做逐級融合。但這里有個隱含問題融合的方式大多是簡單的相加或通道拼接再經(jīng)過幾層卷積去消化。這種融合缺少“選擇性”模型無法判斷哪里的淺層細節(jié)應該被強化、哪里的深層語義應該被保留所有信息一視同仁地混合特征表達效率不高細粒度空間信息也容易被噪聲淹沒。我換過很多種頸部結(jié)構(gòu)最后發(fā)現(xiàn)改進的關(guān)鍵不是繼續(xù)堆卷積或者注意力而是要引入一種顯式的調(diào)制機制讓融合過程具備方向性和選擇性。這也是SEFN模塊出現(xiàn)的直接原因。1.2 雙backbone帶來的互補優(yōu)勢單個backbone在一個分支里同時追求高分辨率和強語義本質(zhì)上是在一個水箱里同時接自來水和凈水器互相掣肘。雙backbone的做法把它們拆成兩個獨立管線一個分支專門負責局部空間細節(jié)保持高分辨率、大特征圖讓物體邊緣輪廓和紋理結(jié)構(gòu)盡量完整另一個分支走深層堆疊把感受野拉大捕獲長距離依賴和全局語義。兩條分支在結(jié)構(gòu)上可以共享權(quán)重、部分共享也可以完全獨立。以我自己的實驗來看完全獨立但結(jié)構(gòu)對稱的雙分支效果最穩(wěn)代價是參數(shù)量和計算量翻倍。如果資源有限也可以讓主分支保持原有結(jié)構(gòu)輔助分支用輕量化結(jié)構(gòu)比如縮減通道數(shù)或者使用深度可分離卷積后續(xù)的SEFN調(diào)制可以在一定程度上彌補輔助分支表達能力偏弱的問題。1.3 SEFN在整體架構(gòu)中的定位SEFN不是替代neck的它是插在雙backbone特征匯合處的一個特征調(diào)制模塊。前置特征來自負責局部空間信息的分支后置特征來自負責語義信息的分支。SEFN從前置特征里提取局部空間結(jié)構(gòu)生成空間門控再用門控動態(tài)調(diào)制后置特征的通道和空間分布讓最終的融合特征在局部區(qū)域上有更強的目標感知能力。這個設(shè)計和經(jīng)典的通道注意力SE模塊、空間注意力CBAM有本質(zhì)區(qū)別SE和CBAM是作用在同一個特征圖上的自注意力而SEFN是一種跨特征的互調(diào)制。它關(guān)注的是兩路特征之間如何互相增強而不是單路特征內(nèi)部怎么自我校準在雙分支結(jié)構(gòu)里比自注意力方案更契合。2. SEFN核心機制門控調(diào)制到底在做什么很多朋友一聽到“門控”就想到Sigmoid和逐元素相乘但SEFN的關(guān)鍵不只在這個乘法而在于門控信號是怎么構(gòu)建的、來自哪里。這部分我會拆開講清楚。2.1 前置特征與后置特征的角色分工在雙backbone結(jié)構(gòu)里前置特征和后置特征不是隨意指定的。前置特征來自淺層或局部分支它的最大優(yōu)勢是保留了豐富的結(jié)構(gòu)信息比如目標的邊緣走向、區(qū)域紋理、局部對比度等。這部分特征雖然在語義上不夠抽象但它決定了檢測器對“這個物體在哪、形狀怎么樣”的感知質(zhì)量。后置特征則來自深層或語義分支它有更強的類別判別力能識別“這是什么物體”但單獨拿出來看它對邊界、遮擋、小目標的空間定位是不敏感的。SEFN做的事情本質(zhì)上是用前置特征教后置特征“哪里需要關(guān)注局部細節(jié)”讓語義信息在正確的空間位置上激活而不是全局統(tǒng)一響應。2.2 門控構(gòu)建的兩個關(guān)鍵算子門控構(gòu)建的步驟可以拆成兩個算子局部空間信息提取和逐點壓縮映射。局部空間信息提取我推薦用深度可分離卷積來實現(xiàn)。比如對一個維度為CxHxW的前置特征先用3x3的深度可分離卷積在空間維度上捕捉相鄰像素之間的結(jié)構(gòu)關(guān)系這一步的計算量遠低于普通卷積又能有效刻畫邊緣和局部模式。如果你的特征圖分辨率不高也可以用窗口大小為3或5的平均池化代替池化算子的歸納偏置更強收斂更穩(wěn)定但對細節(jié)的敏感度會略差。提取完局部信息后經(jīng)過一個1x1卷積把通道數(shù)壓縮到與調(diào)制對象一致再做Sigmoid激活得到一張數(shù)值在0到1之間的空間門控圖。這個門控圖解釋性很強數(shù)值接近1的位置表示前置特征認為這里存在顯著的局部結(jié)構(gòu)后置特征應該保留并強化對應的語義表達數(shù)值接近0的位置表示這里比較平坦語義信息可以相對抑制以避免背景噪聲干擾。2.3 動態(tài)調(diào)制與殘差連接的配合門控調(diào)制這一步直接對后置特征做逐元素相乘相當于給后置特征的每個空間位置乘上一個權(quán)重。但直接乘結(jié)果有個隱患如果某些位置的門控值長期偏低對應的特征響應會越來越弱形成不可逆的抑制梯度訓練容易出問題。我的處理方式是在調(diào)制之后再加法疊加后置特征的原始值形成一個殘差形態(tài)。這樣門控網(wǎng)絡(luò)在初始階段幾乎不改變特征分布模塊退化風險極小訓練初期整體模型可以保持接近原始結(jié)構(gòu)的性能后續(xù)再逐步學習門控的調(diào)制幅度。另外在殘差連接外部我會再接一個LayerNorm或BatchNorm具體見后面的代碼實現(xiàn)主要作用是穩(wěn)定雙分支融合后的數(shù)值分布防止門控權(quán)重和特征量級互相牽扯。3. 參考實現(xiàn)從零搭建SEFN并接入雙backbone YOLO理論說得再多最后還是要落到代碼上。我直接給出可以跑起來的PyTorch參考實現(xiàn)然后說明怎么把它接進YOLO類檢測框架。3.1 SEFN模塊的PyTorch實現(xiàn)先給一個和實際工程接近的SEFN實現(xiàn)注釋寫得比較細方便大家看清楚每個步驟的輸入輸出。import torch import torch.nn as nn import torch.nn.functional as F class SEFN(nn.Module): 空間增強前饋網(wǎng)絡(luò) 參數(shù): dim: 輸入特征通道數(shù) hidden_dim: 前饋網(wǎng)絡(luò)中間層通道數(shù)默認是dim的4倍 kernel_size: 局部空間信息提取的卷積核大小 drop_path: 隨機深度概率訓練時可以設(shè)0.1 def __init__(self, dim, hidden_dimNone, kernel_size3, drop_path0.0): super().__init__() hidden_dim hidden_dim or dim * 4 # 后置特征的前饋變換這里使用1x1卷積 深度可分離卷積 self.fc1 nn.Conv2d(dim, hidden_dim, 1, biasFalse) self.dwconv nn.Conv2d( hidden_dim, hidden_dim, kernel_size, paddingkernel_size // 2, groupshidden_dim, biasFalse ) self.act nn.GELU() self.fc2 nn.Conv2d(hidden_dim, dim, 1, biasFalse) # 前置特征的門控分支 # 局部空間提取3x3深度可分離卷積輸入通道數(shù)等于dim self.gate_dw nn.Conv2d( dim, dim, kernel_size, paddingkernel_size // 2, groupsdim, biasFalse ) # 壓縮映射把局部響應投影到與后置特征匹配的表示 self.gate_fc nn.Conv2d(dim, dim, 1, biasTrue) self.gate_act nn.Sigmoid() # 殘差尺度可學習參數(shù) self.drop_path nn.Identity() self.norm nn.BatchNorm2d(dim) def forward(self, x_front, x_back): 參數(shù): x_front: 前置特征 [B, C, H, W]來自淺層/局部分支 x_back: 后置特征 [B, C, H, W]來自深層/語義分支 返回: 調(diào)制后的融合特征 [B, C, H, W] # 1) 從前置特征提取局部空間信息構(gòu)建門控 g self.gate_dw(x_front) g self.gate_fc(g) gate self.gate_act(g) # 2) 后置特征的前饋變換 h self.fc1(x_back) h self.dwconv(h) h self.act(h) h self.fc2(h) # 3) 門控動態(tài)調(diào)制并接入殘差 out h * gate out out x_back # 4) 歸一化穩(wěn)定數(shù)值分布 out self.norm(out) return out有幾點要注意gate_dw的卷積權(quán)重需要在初始化時接近0這樣初始門控會偏向0.5避免訓練初期門控直接飽和fc2輸出的維度要和輸入對齊保證殘差運算合法如果你用的backbone輸出通道和SEFN配置不一致先做1x1卷積對齊維度。3.2 雙backbone特征提取與融合的整體結(jié)構(gòu)SEFN驗證通過后我是這樣把它組裝進YOLO骨架的。主backbone保持原來的CSPDarknet結(jié)構(gòu)負責提取深層語義特征輔助backbone用輕量化結(jié)構(gòu)比如縮小通道倍率或者換用MobileNetV3的block負責提供高分辨率的局部空間特征。兩條分支在不同階段輸出多尺度特征我這里以P3、P4、P5三個尺度為例。在每個尺度上我會把主分支特征視為后置特征輔助分支特征視為前置特征。兩個特征圖分辨率必須一致如果輔助分支的分辨率不對先上采樣或下采樣對齊。然后送入SEFNSEFN的輸出作為當前尺度的融合特征進入后續(xù)的PANet或者自定義neck繼續(xù)跨尺度融合。如果想在不同尺度間共享SEFN模塊可以設(shè)置所有尺度共用同一套參數(shù)能明顯減少參數(shù)量但效果略弱于每尺度獨立SEFN。另外我個人建議在輔助backbone的淺層單獨加一條低層連接直接把P2級別的細節(jié)特征也送進融合過程對小目標檢測有明顯的補充作用。代價是整體特征圖數(shù)量增加內(nèi)存占用變大需要根據(jù)顯存來權(quán)衡。3.3 接入YOLO檢測頭的完整流程檢測頭的接入不需要改動太多。YOLO的head本身就是解耦的分類分支和回歸分支我直接把融合后的特征分別喂給分類和回歸分支原來的anchor或anchor-free解碼邏輯保持不變。損失函數(shù)也只做了少量調(diào)整在多尺度損失加權(quán)時把P3尺度的權(quán)重從默認值調(diào)高了一些因為局部空間信息對小目標的貢獻更大。數(shù)據(jù)增強策略沿用了常規(guī)的Mosaic、MixUp以及隨機仿射變換沒有做特殊處理。整個改動代碼量控制在200行以內(nèi)工程上還是挺輕量的不改推理邏輯部署時只需要導出疊加了SEFN的整個模型即可。4. 訓練配置與效果驗證說完了代碼進入最實際的部分怎么訓練才能把SEFN的能力發(fā)揮出來。這里面的初始化策略、學習率和訓練節(jié)奏都比模塊本身更影響最終結(jié)果。4.1 初始化與預訓練策略首個要注意的坑是預訓練權(quán)重。雙backbone的兩條分支如果都使用ImageNet預訓練權(quán)重那么輔助分支最好使用結(jié)構(gòu)相近的權(quán)重實在沒有就隨機初始化但訓練所需epoch會明顯增加。我試過讓輔助分支的淺層完全隨機初始化在訓練初期模型收斂很慢前50個epoch的mAP一直低于單backbone版本。后來換了個策略輔助分支淺層使用主分支的預訓練權(quán)重做初始化或者用自監(jiān)督預訓練得到的權(quán)重來初始化。這樣做之后前10個epoch的loss下降速度明顯加快最終精度也更高。另外SEFN內(nèi)部的gate相關(guān)卷積層建議零初始化或小隨機數(shù)初始化保證訓練開始時門控輸出接近0.5讓特征分布先保持穩(wěn)定再逐步學習調(diào)制幅度。4.2 學習率、batch與數(shù)據(jù)增強配置這類雙分支結(jié)構(gòu)對學習率比較敏感。我把初始學習率從默認的0.01調(diào)低到0.005使用warmup 3個epoch的線性上升到0.01再用cosine退火降到最低1e-4。batch size按顯存能力來單卡48G顯存可以開到64多卡的話按線性縮放。數(shù)據(jù)增強方面Mosaic增強的Scale范圍我調(diào)小了一些原來是0.5到1.5我改成0.8到1.4因為輔助分支的局部細節(jié)在高強度縮放下容易失真門控信號學到的是不正常的長寬比分布反而產(chǎn)生負面影響。MixUp概率降低到0.3避免過強的混合干擾空間結(jié)構(gòu)的清晰度。訓練時長方面我用8卡訓練了300個epoch總的訓練集約為15萬張圖。如果沒有多卡環(huán)境建議至少訓練500個epoch并加大weight decay防止雙分支疊加后產(chǎn)生過擬合。4.3 實驗結(jié)果精度、速度與參數(shù)量對比以下是一組來自模擬項目X的對比數(shù)據(jù)各維度都是基于相同數(shù)據(jù)集、相同訓練配置下的實測記錄。這邊的權(quán)威評測標準是COCO類別的mAP用單卡V100測試推理速度。模型結(jié)構(gòu)參數(shù)量GFLOPsmAP50mAP50:95FPS單backbone基線52M16564.843.261雙backboneSEFN87M28671.550.338雙backboneSEFN(輕量輔助分支)66M20568.946.149對比數(shù)據(jù)有兩個結(jié)論值得注意。首先是雙backbone SEFN相比單backbone在mAP50:95上提升了7個點左右其中小目標類別提升最大說明局部空間調(diào)制確實補足了尺度多樣性。其次是輕量輔助分支的變體只增加14M參數(shù)FPS還能接近50工程落地的性價比更高。當然不同數(shù)據(jù)集、不同基線上測出來的數(shù)字會有差異不必完全對齊這張表趨勢基本是穩(wěn)定的只要前置特征存在明顯的結(jié)構(gòu)信息SEFN就能穩(wěn)定帶來小幅精度收益。5. 踩坑記錄門控飽和、梯度不穩(wěn)與顯存優(yōu)化實話講這套方案我前后調(diào)了小兩個月踩了不少坑下面直接把這些坑連同排查思路一起列出來。5.1 門控全部飽和成1模塊失效有一次訓練到120個epoch后我打印門控圖的統(tǒng)計量發(fā)現(xiàn)大量樣本的門控值幾乎全部接近1。這意味著SEFN變成了普通的殘差前饋模塊調(diào)制能力消失。排查下來原因是門控分支的Sigmoid偏置初始化為0但訓練過程中前置特征幅度逐漸增大直接把Sigmoid打到了飽和區(qū)。解決方法是給gate_dw的卷積權(quán)重初始化成接近0的均勻分布并且把gate_fc的bias初始化為0這樣門控初始值在0.5附近。另外我在損失函數(shù)里不直接約束門控但會在訓練前20個epoch內(nèi)持續(xù)監(jiān)控門控的標準差如果標準差小于0.05就會臨時把學習率調(diào)低一半給門控分支一個穩(wěn)定探索的空間。5.2 梯度不穩(wěn)定門控信號學不到有效結(jié)構(gòu)另一個問題出現(xiàn)在輔助分支非常輕量的情況下。輔助分支參數(shù)量少提取的局部空間特征質(zhì)量不高門控網(wǎng)絡(luò)學習不到有效的結(jié)構(gòu)信息表現(xiàn)為訓練loss大幅波動。排查后是輔助分支的通道數(shù)太窄特征表達能力不夠。我把輔助分支的通道數(shù)從原來的1/4倍調(diào)整到1/2倍同時在輔助分支的輸入位置加了一個輕量的SE注意力用于抑制無結(jié)構(gòu)特征的響應效果立竿見影。如果不想增加通道數(shù)也可以考慮對前置特征做多尺度局部提取同時使用3x3和5x5的深度可分離卷積再把兩個分支的特征拼接起來為門控提供更豐富的局部證據(jù)。5.3 雙backbone顯存爆炸怎么辦雙backbone最直接的代價就是計算量和顯存占用翻倍。如果顯存有限我建議先砍輔助分支的分辨率而不是砍通道數(shù)。把輔助分支的輸入分辨率從640x640降到416x416前置特征在深層對應的特征圖面積可以下降43%顯存節(jié)省非??捎^。門控雖然作用在分辨率不一致的特征上但上采樣操作放在門控構(gòu)建之前不會丟失太多結(jié)構(gòu)信息。另外一個省顯存的方法是讓兩條backbone共享前幾個stage的權(quán)重。比如第1、2個stage完全共享只在第3、4個stage分開這樣顯存只增加約40%SEFN的性能損失大概在1個百分點以內(nèi)在資源受限場景下是值得推薦的折中方案。6. 幾點實戰(zhàn)體會這套方案做到后面我對特征融合的思考改變了不少。以前總覺得融合就是給特征圖加權(quán)重或者拼接通道但SEFN帶來的啟發(fā)是局部空間信息不僅可以直接作為最終特征的一部分更可以反過來作為“引導信號”去調(diào)制另一條分支的表達。這種跨特征的調(diào)制關(guān)系遠比在單一特征上疊注意力來得有表現(xiàn)力。第一點體會門控技術(shù)的關(guān)鍵不在于Sigmoid和乘法而在于門控信號的來源。監(jiān)督信號越接近任務(wù)本質(zhì)門控越有物理含義。在目標檢測里前置特征的局部空間信息天然對應“哪里是目標的邊界”這一任務(wù)線索用它去調(diào)制語義特征融合出來的結(jié)果自然有了邊界敏感度。第二點體會雙分支結(jié)構(gòu)不要盲目追求參數(shù)對稱。輔助分支完全可以用更輕的結(jié)構(gòu)只要能穩(wěn)定提取到局部特征就達到了設(shè)計目的。真正需要在意的反而是兩分支特征的尺度對齊和數(shù)值分布平衡這一步?jīng)]做好的話模塊做得再精巧也發(fā)揮不出來。第三點體會訓練過程必須關(guān)注門控的一舉一動。門控是這套模塊的神經(jīng)中樞建議每個epoch結(jié)束都抽樣打印一遍門控統(tǒng)計信息一旦發(fā)現(xiàn)長期飽和或長期無差別分布就說明前置特征和門控分支的配合出了異常需要從初始化、通道寬度和數(shù)據(jù)增強三方面排查。這套方案后續(xù)可以嘗試的方向也很多比如把門控分支替換成可變形卷積去自適應局部區(qū)域選擇范圍或者把門控調(diào)制從逐空間位置擴展成逐通道加逐空間位置的二維聯(lián)合門控。我對小目標密集場景下的表現(xiàn)尤其期待畢竟局部空間一致性和長距離依賴的融合本質(zhì)上就是在回答一個老問題模型如何在看得見細節(jié)的同時看得到整體。