檢測實戰(zhàn):從原理到自制數(shù)據(jù)集訓(xùn)練全流程)
SSDSingle Shot MultiBox Detector目標(biāo)檢測領(lǐng)域里繞不過去的一個經(jīng)典模型。它把目標(biāo)定位和分類統(tǒng)一在一個前向過程中一次推理直接輸出所有候選框和類別概率不需要兩階段方法那樣的先提候選區(qū)域再逐區(qū)域分類的繁瑣流程。我在實際項目里用SSD做過工業(yè)零件檢測、桌面場景識別、還有一些邊緣設(shè)備上的輕量級檢測整體感受是原理清晰、訓(xùn)練可控、對環(huán)境要求也不苛刻非常適合作為你上手訓(xùn)練自己的目標(biāo)檢測數(shù)據(jù)集的第一套完整方案。這篇文章我會從模型設(shè)計思路講起再一步步帶你走完環(huán)境搭建、自制數(shù)據(jù)集、訓(xùn)練調(diào)參、推理評估、踩坑排錯的完整流程。適合有Python基礎(chǔ)、了解基本卷積網(wǎng)絡(luò)概念、但還沒真正在自己的數(shù)據(jù)上跑通過完整目標(biāo)檢測流程的學(xué)習(xí)者。你不需要頂級GPU也不需要海量數(shù)據(jù)只要按著步驟來就能在自制數(shù)據(jù)集上完成SSD的端到端訓(xùn)練和檢測。1. SSD的設(shè)計思路與核心機制拆解1.1 單階段檢測到底是什么要理解SSD得先分清目標(biāo)檢測的兩條技術(shù)路線。兩階段檢測的典型代表是Faster R-CNN系列思路是先粗篩再細(xì)查第一步用區(qū)域建議網(wǎng)絡(luò)RPN在整張圖上找出幾千個可能存在目標(biāo)的區(qū)域第二步再對每個區(qū)域做分類和邊界框回歸。好處是精度高壞處是慢兩個階段各跑一遍很難做到實時。SSD走的是另一條路一步到位。它把整張圖分成多個網(wǎng)格每個網(wǎng)格點上預(yù)先放置一組不同尺寸和長寬比的默認(rèn)框default box網(wǎng)絡(luò)同時預(yù)測每個默認(rèn)框里有沒有目標(biāo)、是什么類別、以及框的位置該往哪個方向調(diào)整多少。所有預(yù)測在一次前向過程中完成所以叫Single Shot。我經(jīng)常用一個類比來解釋這件事兩階段檢測像你先用望遠(yuǎn)鏡掃一遍整個草原發(fā)現(xiàn)可疑目標(biāo)然后換單筒放大鏡一個個仔細(xì)確認(rèn)單階段檢測則是你直接站在瞭望塔上一次性用不同倍數(shù)的鏡頭同時掃過全場景誰是什么、在哪里一次看清。SSD屬于后者速度優(yōu)勢非常明顯。這里還有一個MultiBox的概念。它指的不是預(yù)測一個框而是預(yù)測一大批框。訓(xùn)練階段會把默認(rèn)框和真實標(biāo)注框做匹配匹配好的默認(rèn)框負(fù)責(zé)學(xué)習(xí)我該往哪里偏移、這個位置是什么目標(biāo)沒匹配上的默認(rèn)框則負(fù)責(zé)學(xué)習(xí)這里沒有目標(biāo)。網(wǎng)絡(luò)本質(zhì)上是在做多任務(wù)學(xué)習(xí)一邊學(xué)分類一邊學(xué)框回歸。1.2 多尺度特征圖預(yù)測SSD的看家本領(lǐng)SSD最核心的設(shè)計創(chuàng)新是它不在單一層特征圖上做預(yù)測而是在多個不同分辨率的特征圖并行做預(yù)測。這個設(shè)計直接解決了一個老大難問題——小目標(biāo)檢測。早期的檢測模型習(xí)慣只在網(wǎng)絡(luò)最后一層特征圖上接檢測頭但最后一層特征圖經(jīng)過多次池化和卷積下采樣分辨率很低一個小目標(biāo)可能只占據(jù)幾個像素信息已經(jīng)嚴(yán)重丟失檢測效果自然差。SSD的做法是既然不同層特征圖保留了不同粒度的信息那就在特征金字塔的多個層級上各接一組檢測器。淺層特征圖分辨率高、感受野小適合檢測小目標(biāo)深層特征圖分辨率低、感受野大適合檢測大目標(biāo)。你可以把它理解成不同高度的觀察者在看同一個場景站在二樓只能看到局部細(xì)節(jié)站在二十樓能看到全景輪廓SSD是讓兩者同時工作再匯總結(jié)果。這就是它名字里MultiBox之外的另一個關(guān)鍵——多尺度預(yù)測。具體到300x300輸入SSD會在6層特征圖上做預(yù)測38x38、19x19、10x10、5x5、3x3、1x1。每層給不同尺度的默認(rèn)框尺度按線性遞增公式分配s_k s_min (s_max - s_min) * (k - 1) / (m - 1)對一個m6層的網(wǎng)絡(luò)結(jié)構(gòu)s_min取0.2、s_max取0.9算出來6層的相對尺度約為0.2、0.34、0.48、0.62、0.76、0.9對應(yīng)300輸入就是約60、102、144、186、228、270像素。再配合多種長寬比比如1:1、1:2、2:1、1:3、3:1一個位置最多可以匹配6個不同形狀的默認(rèn)框。整個SSD300一張圖會產(chǎn)生8732個候選框SSD512則是24564個。這個設(shè)計帶來一個非常實際的收益檢測各種尺寸的目標(biāo)時模型天然具備了每個尺度都有各自專門的預(yù)測頭的歸納偏置訓(xùn)練起來比單純依賴最后一層特征圖容易收斂得多。1.3 訓(xùn)練階段不容忽視的配套機制多尺度預(yù)測是骨架但真正讓SSD訓(xùn)練穩(wěn)定的是配套的匹配策略和損失設(shè)計。先看先驗框匹配。訓(xùn)練時每個真實標(biāo)注框需要和默認(rèn)框建立對應(yīng)關(guān)系。匹配策略分兩步第一步每個真實框優(yōu)先匹配IoU交并比最高的那個默認(rèn)框保證每一個真實目標(biāo)都至少有一個正樣本負(fù)責(zé)學(xué)習(xí)第二步對剩下的默認(rèn)框只要和任意真實框的IoU大于0.5也算作正樣本。這個0.5的閾值不是拍腦袋定的它保證了正樣本在確實和某個目標(biāo)重疊得不錯的前提下盡量多緩解了正負(fù)樣本比例天然懸殊的問題。再看負(fù)樣本處理。一張圖里大多數(shù)默認(rèn)框都是背景如果全部參與損失計算負(fù)樣本數(shù)量可能是正樣本的幾十倍模型會被背景類淹沒。SSD的解決辦法是難例挖掘hard negative mining按分類置信度損失從大到小排序挑選損失最高的那些負(fù)樣本參與訓(xùn)練把正負(fù)樣本比例控制在1:3左右。換句話說模型會把精力集中在那些讓網(wǎng)絡(luò)誤以為有目標(biāo)但實際沒有的難例上而不是簡單背景框上。最后是數(shù)據(jù)增強。SSD訓(xùn)練時的數(shù)據(jù)增強是一套組合拳隨機裁剪、隨機縮放、顏色抖動、水平翻轉(zhuǎn)。其中隨機裁剪特別重要它模擬了目標(biāo)在視野中只出現(xiàn)局部的場景相當(dāng)于無語義地制造了大量目標(biāo)尺度變化和遮擋的困難樣本。這也是SSD在VOC這類數(shù)據(jù)集上能和小目標(biāo)斗爭的關(guān)鍵手段之一。我現(xiàn)在自己訓(xùn)練模型時也保留了這個習(xí)慣寧可訓(xùn)練慢一點也要讓模型見過足夠多的殘缺目標(biāo)。2. 訓(xùn)練環(huán)境搭建從零把項目跑起來2.1 框架選型別在舊時代踩坑SSD最早是在Caffe框架下實現(xiàn)的網(wǎng)上大量老教程也都是Caffe版本。我不推薦你走那條路因為Caffe的環(huán)境配置至今仍是個深坑依賴庫多、編譯鏈路長、CUDA版本敏感很多老代碼在新驅(qū)動上直接編譯不過去。我見過不少同學(xué)把時間全耗在配置環(huán)境上模型一個epoch都沒跑完。更推薦的是基于PyTorch的開源復(fù)現(xiàn)版本。PyTorch現(xiàn)在對訓(xùn)練、調(diào)試、可視化、模型導(dǎo)出的支持都更順手處理圖像數(shù)據(jù)的接口也更符合直覺。雖然Python代碼跑起來的原始性能可能不如C后端硬核但對大多數(shù)個人項目和中小型業(yè)務(wù)場景來說開發(fā)效率和迭代速度遠(yuǎn)比那一點性能差異重要。你可以去代碼托管平臺搜SSD_PyTorch之類的倉庫選一個star多、維護活躍的版本作為起點。依賴安裝很直接核心就是PyTorch加上一些輔助庫。我的建議是# 根據(jù)自身顯卡驅(qū)動選擇合適的CUDA與PyTorch版本 pip install torch torchvision pip install opencv-python numpy tqdm pip install pycocotools # 如果評估需要COCO API如果你用的是Linux環(huán)境建議直接用conda創(chuàng)建獨立環(huán)境避免和系統(tǒng)Python環(huán)境打架。硬件方面我用一張12GB顯存的消費級顯卡訓(xùn)過300分辨率的SSDbatch size開到16或者32都沒問題。8GB顯存也能跑只要把batch size降到8左右。完全沒有GPU的話小數(shù)據(jù)集也能用CPU硬扛但訓(xùn)練時間會長到讓人懷疑人生。2.2 項目目錄與數(shù)據(jù)目錄規(guī)劃我習(xí)慣動手之前先把目錄結(jié)構(gòu)規(guī)劃好否則訓(xùn)練中途會發(fā)現(xiàn)亂七八糟的文件散落一地。下面是一套我驗證過很多次的布局ssd_project/ ├── train.py ├── test.py ├── evaluate.py ├── data/ │ ├── datasets/ │ │ └── VOCdevkit/ │ │ └── MyDataset/ │ │ ├── Annotations/ # XML標(biāo)注文件 │ │ ├── JPEGImages/ # 原始圖片 │ │ └── ImageSets/ │ │ └── Main/ # train.txt, val.txt, test.txt │ └── config.py ├── checkpoints/ │ └── ssd300_epoch120.pth └── logs/這里項目名我用某自制數(shù)據(jù)集代指實際數(shù)據(jù)。為什么要用VOC格式而不是別的格式因為它結(jié)構(gòu)直觀、文件組織清晰標(biāo)注和圖片分開管理索引文件就是一行一個文件名非常容易理解。而且大多數(shù)SSD開源實現(xiàn)的DataLoader就是按VOC格式寫的你只需要把自己的數(shù)據(jù)整理成這個結(jié)構(gòu)代碼基本不用大改。一個常被忽略的細(xì)節(jié)是圖片名和XML文件名必須嚴(yán)格一一對應(yīng)命名里不能有空格、中文、特殊符號。我建議統(tǒng)一用6位數(shù)字編號比如000001.jpg對應(yīng)000001.xml。文件名不規(guī)則會導(dǎo)致數(shù)據(jù)加載莫名其妙報錯而且定位問題時要花大量時間。2.3 預(yù)訓(xùn)練權(quán)重選擇SSD的骨干網(wǎng)絡(luò)默認(rèn)是VGG16的前幾層。為什么不自己從頭訓(xùn)練整個VGG因為目標(biāo)檢測的骨干網(wǎng)絡(luò)需要提取通用的視覺特征邊緣、紋理、形狀這些特征在大規(guī)模分類數(shù)據(jù)集上已經(jīng)學(xué)得很好了。用預(yù)訓(xùn)練權(quán)重做初始化再在自己的目標(biāo)檢測數(shù)據(jù)上微調(diào)收斂速度會快非常多最終精度也更高。從零訓(xùn)練不是不行但除非你有幾十萬張級別的數(shù)據(jù)否則通常效果不佳。一些PyTorch復(fù)現(xiàn)版本會把VGG16預(yù)訓(xùn)練權(quán)重轉(zhuǎn)換后塞進SSD結(jié)構(gòu)里你只需要下載轉(zhuǎn)換好的權(quán)重文件放到checkpoints目錄即可。這里有一個實測過的注意事項不同復(fù)現(xiàn)版本對權(quán)重的組織方式略有差異下載時注意和你的代碼版本對應(yīng)起來否則加載權(quán)重時會出現(xiàn)鍵名不匹配的報錯。萬一報錯也沒關(guān)系檢查一下狀態(tài)字典的鍵名做一個簡單的重映射就行后面我會在排錯章節(jié)講具體做法。3. 制作自己的數(shù)據(jù)集比訓(xùn)練更花時間的部分3.1 數(shù)據(jù)收集原則少而精不如多而雜你在訓(xùn)練上花一天往往能在數(shù)據(jù)上花一周。數(shù)據(jù)質(zhì)量直接決定模型上限網(wǎng)絡(luò)結(jié)構(gòu)只是逼近這個上限的手段。先給個數(shù)量參考如果是單類別目標(biāo)檢測我建議每類至少準(zhǔn)備300到500張原始圖片多一些更好。我這里說的不是那種同一場景連拍10張的水?dāng)?shù)據(jù)而是要有足夠的場景多樣性。拿我自己做過的三類果蔬檢測舉例蘋果、香蕉、橙子各400張左右但我會刻意覆蓋不同光照室內(nèi)燈光、自然光、不同角度俯拍、側(cè)拍、斜拍、不同遮擋部分被其他物體遮住、堆疊擺放、不同距離近景特寫、中景、遠(yuǎn)景甚至故意放進一些模糊圖片做難例。模型只有在訓(xùn)練時見過足夠多難看的姿勢推理時面對真實場景才不會露怯。有一個原則特別值得強調(diào)寧可圖片總數(shù)少一點也要保證所有圖里有目標(biāo)、目標(biāo)標(biāo)注無誤。網(wǎng)上有些人為了趕進度大量下載圖片后隨便框一下就開訓(xùn)結(jié)果模型學(xué)到的全是錯誤的定位模式效果還不如小數(shù)據(jù)集精心標(biāo)注。數(shù)據(jù)質(zhì)量永遠(yuǎn)是第一位的。3.2 使用開源標(biāo)注工具標(biāo)注并整理標(biāo)注工具我推薦開源免費、界面簡單的矩形框標(biāo)注工具操作邏輯是打開一張圖鼠標(biāo)拖拽畫框填寫類別名稱保存后就生成VOC格式的XML文件。這些工具一般會幫你自動生成XML裝在本地即可使用不需要聯(lián)網(wǎng)配置服務(wù)。標(biāo)注時有兩個很容易被忽視的細(xì)節(jié)。第一標(biāo)注框要盡量貼合目標(biāo)邊緣不要為了省事留一圈空氣邊。第二框得太緊也不行因為框回歸的偏移量本身就包含了一定的誤差空間標(biāo)得太緊反而讓模型學(xué)不到合理的邊界。我自己通常的做法是讓框的邊緣比目標(biāo)實際輪廓稍微收縮一到兩個像素這樣模型預(yù)測的框普遍更緊湊。遮擋目標(biāo)的標(biāo)注建議遵循一個原則只要目標(biāo)還有明確的可見主體就標(biāo)注它的可見部分不要試圖去標(biāo)一個腦補出來的完整物體。這個細(xì)節(jié)對模型非常關(guān)鍵它教會模型物體會被遮擋但可檢測性依然存在。標(biāo)注完后一定要做二次校驗。最有效的辦法是找一個沒參與標(biāo)注的同學(xué)隨機抽20%的圖重新過一遍看看框的位置和類別命名是否一致。我發(fā)現(xiàn)多人標(biāo)注項目里類別命名的混亂同一個物體一會兒叫apple一會兒叫Apple是導(dǎo)致訓(xùn)練loss異常的主要元兇之一。一個統(tǒng)一的命名規(guī)范能省無數(shù)麻煩。3.3 劃分訓(xùn)練集與測試集數(shù)據(jù)集做好之后第一步是把圖片和XML組織進JPEGImages與Annotations目錄然后劃分樣本索引。我建議按8:1:1劃分訓(xùn)練、驗證、測試集并且在程序里固定隨機種子保證每次劃分結(jié)果一致。下面給一個簡單的劃分腳本你可以根據(jù)自己的情況修改import os import random from pathlib import Path random.seed(42) image_dir Path(JPEGImages) names [p.stem for p in image_dir.glob(*.jpg)] random.shuffle(names) n len(names) train_names names[:int(n * 0.8)] val_names names[int(n * 0.8):int(n * 0.9)] test_names names[int(n * 0.9):] def write_txt(path, names): with open(path, w) as f: for name in names: f.write(name \n) main_dir Path(ImageSets/Main) main_dir.mkdir(parentsTrue, exist_okTrue) write_txt(main_dir / train.txt, train_names) write_txt(main_dir / val.txt, val_names) write_txt(main_dir / test.txt, test_names) write_txt(main_dir / trainval.txt, train_names val_names)這里必須提醒一個數(shù)據(jù)泄漏問題如果同一個物體的多張連拍圖被同時分進訓(xùn)練集和測試集你的測試分?jǐn)?shù)會虛高因為模型等于已經(jīng)見過測試目標(biāo)了。我處理這類問題的方法是先按場景分組在場景級別而不是圖片級別做劃分。比如同一次拍攝會話產(chǎn)生的圖片歸到同一個場景組然后以場景組為單位切分?jǐn)?shù)據(jù)集。這樣評估結(jié)果才可信。3.4 轉(zhuǎn)成目標(biāo)格式的注意事項如果你拿到的數(shù)據(jù)不是VOC格式而是像CSV、JSON或者某種私有標(biāo)注格式需要寫一個轉(zhuǎn)換腳本。轉(zhuǎn)換的核心步驟無非是把標(biāo)注信息讀出來重新組織成VOC XML再把圖片統(tǒng)一命名和格式。寫轉(zhuǎn)換腳本時有一個細(xì)節(jié)值得注意類別名稱建議統(tǒng)一為小寫英文。雖然VOC格式本身對大小寫不敏感但很多數(shù)據(jù)處理代碼、數(shù)據(jù)增強模塊、評估腳本都默認(rèn)按名稱匹配如果名稱大小寫不一致可能在類別統(tǒng)計時出現(xiàn)兩個不同的分類導(dǎo)致評估時對不上號。我也遇到過因為類別名帶空格導(dǎo)致數(shù)據(jù)加載失敗的案例所以在轉(zhuǎn)換時做一遍清理去空格、統(tǒng)一小寫、替換特殊符號是一個低成本高收益的動作。另外轉(zhuǎn)換完一定要抽查。隨機打印幾張圖像的標(biāo)注框信息再配合圖像可視化畫出框來確認(rèn)坐標(biāo)沒有錯位、類別名稱沒有混亂。坐標(biāo)錯位最常見的原因是原數(shù)據(jù)里坐標(biāo)是相對坐標(biāo)比如0到1的小數(shù)而轉(zhuǎn)換時沒有按圖片寬高還原成絕對像素這個問題排查起來很費時間最好在源頭就驗證清楚。4. 開始訓(xùn)練配置、命令與節(jié)奏控制4.1 修改模型與訓(xùn)練配置每個參數(shù)都有講究拿到開源代碼后第一個要改的就是類別數(shù)。VOC原本是20類但你的數(shù)據(jù)集可能只有3類。SSD在計算分類損失時類別數(shù)等于實際類別數(shù)加1額外一個背景類所以num_classes需要改成實際類別數(shù)加1。如果你檢測三類果蔬這個值就是4。訓(xùn)練尺寸的選擇同樣重要。SSD300表示輸入300x300SSD512表示輸入512x512。分辨率更高小目標(biāo)檢測能力更強但顯存占用和訓(xùn)練時間會明顯增加。我的建議是先在300分辨率上把流程跑通確認(rèn)數(shù)據(jù)沒問題后再考慮要不要換成512。我自己在做小目標(biāo)密集場景時才用512普通場景300完全夠用。batch size和初始學(xué)習(xí)率是一對好兄弟。參考常見的訓(xùn)練策略batch size調(diào)小時初始學(xué)習(xí)率也該等比降低。我推薦一個保守的起點batch size 16初始學(xué)習(xí)率0.001如果你只能開batch size 8就把學(xué)習(xí)率降到0.0005左右。優(yōu)化器用SGD加動量momentum0.9配合權(quán)重衰減5e-4這是SSD訓(xùn)練很經(jīng)典的一套配置。不要一上來就盲目上AdamSSD這類帶大量錨框的模型對學(xué)習(xí)率比較敏感SGD的穩(wěn)定性更適合。還有一個容易被新手忽略的參數(shù)學(xué)習(xí)率調(diào)度策略??俥poch設(shè)多少warmup要開多久學(xué)習(xí)率在哪個階段線性衰減這些直接影響收斂質(zhì)量。通常我會先跑一個100到200個epoch的大循環(huán)學(xué)習(xí)率在前幾個epoch做warmup從很小的值升到目標(biāo)值然后在訓(xùn)練后期幾個階段各衰減一次。如果你用的復(fù)現(xiàn)代碼默認(rèn)只寫了VOC的120輪調(diào)度那么對自制小數(shù)據(jù)集來說epoch要適當(dāng)延長模型才能充分收斂。4.2 從Debug到正式訓(xùn)練的完整流程我強烈建議你正式開訓(xùn)之前先做一個冒煙測試從每類數(shù)據(jù)中抽10張左右組成一個迷你訓(xùn)練集跑一個epoch。這個動作看起來簡單卻能幫你避開大量無效等待。冒煙測試通過的標(biāo)準(zhǔn)很簡單程序不崩、loss穩(wěn)定下降、每個batch都能正常反傳。如果冒煙測試中l(wèi)oss卡住不動先別急著調(diào)學(xué)習(xí)率檢查數(shù)據(jù)標(biāo)簽是不是有問題例如某些XML里類別名對不上配置列表、某些圖里標(biāo)注框是空的。一次batch里如果大部分目標(biāo)都有標(biāo)注問題loss會異常波動甚至不降。把數(shù)據(jù)問題排查干凈模型訓(xùn)練才談得上收斂。冒煙測試通過后就可以開始正式訓(xùn)練了。命令行跑起來時記得把日志輸出到文件方便回溯。通常在訓(xùn)練過程中我會同時開一個tensorboard類工具實時看四個曲線總loss、分類loss、定位loss、學(xué)習(xí)率變化。這四個曲線是判斷訓(xùn)練是否健康的核心儀表盤。訓(xùn)練中還有一個容易被低估的細(xì)節(jié)定期保存checkpoint。很多代碼默認(rèn)只保存最后一個epoch的模型但訓(xùn)練后期如果出現(xiàn)震蕩或過擬合你會希望回到一個相對穩(wěn)定的中間狀態(tài)。我更推薦每5到20個epoch保存一次同時始終保留驗證集mAP最高的那次權(quán)重作為best模型。我的習(xí)慣是checkpoint按epoch存best按指標(biāo)存這樣既不會丟失訓(xùn)練進度又總能拿最優(yōu)模型出來推理。4.3 訓(xùn)練中的性能觀察與調(diào)參建議訓(xùn)練過程中你觀察到的曲線應(yīng)該有這樣的變化前20個epoch里總loss快速下降尤其是分類loss中間階段loss下降變緩定位loss開始起作用后期loss緩慢震蕩下降進入細(xì)調(diào)階段。如果某個階段出現(xiàn)了loss完全不變或者驗證集mAP驟降就要警覺了。我用一個實際案例說明之前某次訓(xùn)練做的是三類果蔬檢測1200張訓(xùn)練圖跑SSD300VGG骨干單卡12GB顯存一共訓(xùn)練200個epoch。前10個epoch熱身后驗證集上mAP已經(jīng)能到0.7左右之后在0.75到0.8之間緩慢爬升最終穩(wěn)定在0.83附近。整個訓(xùn)練過程大約幾個到十幾個小時完全在可接受范圍。如果訓(xùn)練結(jié)果顯示mAP只有0.3甚至更低大概率不是網(wǎng)絡(luò)結(jié)構(gòu)問題而是以下某個環(huán)節(jié)出了問題標(biāo)注錯位、類別不均衡、訓(xùn)練集劃分包含重復(fù)圖、學(xué)習(xí)率過大導(dǎo)致震蕩。我建議按順序排查先可視化一批訓(xùn)練樣本檢查標(biāo)注框是否正確對齊到目標(biāo)再統(tǒng)計每個類別的訓(xùn)練樣本數(shù)量看是否存在嚴(yán)重的類別不平衡最后再回頭看學(xué)習(xí)率是否合適。關(guān)于loss出現(xiàn)nan最常見的原因是學(xué)習(xí)率太大導(dǎo)致梯度爆炸數(shù)據(jù)處理時出現(xiàn)除零或者是輸入圖像中出現(xiàn)異常像素值。我的排查順序是先降低學(xué)習(xí)率十倍看看nan是否消失如果還不行再檢查數(shù)據(jù)增強模塊里會不會產(chǎn)生非法邊界框。訴諸于開梯度裁剪或者調(diào)小batch是最后的手段。很多同學(xué)喜歡一上來就追求花哨的新模型但我想說先把SSD用透真的足夠了。SSD的訓(xùn)練流程覆蓋了數(shù)據(jù)加載、錨框匹配、損失計算、NMS去重、評估指標(biāo)等全部核心知識點如果你在一個模型上把這些環(huán)節(jié)都理解透了再遷移到任何其他檢測模型都不費勁。5. 檢測推理、評估與落地擴展5.1 推理管線與核心代碼訓(xùn)練完模型后離檢測自己的數(shù)據(jù)集只差最后一步推理。推理流程和訓(xùn)練時不一樣不需要匹配真實框也不需要難例挖掘核心只有預(yù)處理、前向、解碼、NMS四步。預(yù)處理的關(guān)鍵是把輸入圖片縮放到訓(xùn)練時的尺寸并做同樣的歸一化。很多推理報錯都源于這里你訓(xùn)練時用了一張300x300的圖推理時卻塞進去一張原始高清大圖模型當(dāng)然輸出不對。SSD的輸入是固定尺寸的這也是它和全卷積網(wǎng)絡(luò)的一個區(qū)別。下面是一段基于PyTorch的推理核心代碼可以直接參考import torch import torch.nn.functional as F import cv2 import numpy as np def detect_one_image(model, img_path, class_names, conf_thresh0.5, nms_thresh0.45): model.eval() img cv2.imread(img_path) # 縮放并歸一化 img_resized cv2.resize(img, (300, 300)).astype(np.float32) # 按訓(xùn)練配置做減均值、乘縮放 img_tensor torch.from_numpy(img_resized).permute(2, 0, 1).unsqueeze(0) img_tensor (img_tensor / 255.0) with torch.no_grad(): # 前向返回分類置信度和預(yù)測框偏移 confidences, locations model(img_tensor) # 解碼根據(jù)默認(rèn)框和偏移計算真實坐標(biāo) boxes decode(locations, default_boxes) # 由具體實現(xiàn)提供 # 置信度過濾 keep confidences.squeeze(0).max(dim1).values conf_thresh boxes boxes.squeeze(0)[keep] scores confidences.squeeze(0)[keep] # NMS去重 final_boxes, final_scores, final_labels nms(boxes, scores, nms_thresh) return final_boxes, final_scores, final_labels這里的decode函數(shù)做的是把網(wǎng)絡(luò)預(yù)測的偏移量加回默認(rèn)框上。SSD在訓(xùn)練時不是直接回歸框的絕對坐標(biāo)而是回歸相對于默認(rèn)框的編碼偏移量所以推理時也必須用同樣的編碼方式解碼。這一步很容易出錯如果你的推理框位置完全對不上目標(biāo)先懷疑解碼邏輯有沒有和數(shù)據(jù)預(yù)處理對齊。NMS非極大值抑制的目標(biāo)是去掉重復(fù)檢測同一目標(biāo)的框。它的邏輯很直白先把所有候選框按置信度從高到低排序然后取置信度最高的框把所有和它IoU大于閾值的框都刪掉再取下一個最高分的框重復(fù)直到處理完。nms_thresh一般設(shè)0.45太高會導(dǎo)致多個框重疊在一起太低會把同一目標(biāo)的不同部位拆成多個目標(biāo)??梢暬评頃rconf_thresh可以設(shè)0.5甚至更高減少誤檢框的干擾。5.2 mAP評估與結(jié)果可視化跑完測試集后你需要一個客觀的量化指標(biāo)來衡量模型好壞。標(biāo)準(zhǔn)做法是計算mAPmean Average Precision。具體來說對每個類別繪制精度-召回率曲線曲線下的面積就是該類別的AP所有類別AP取平均就是mAP。mAP這個數(shù)字很有用但它會掩蓋一些信息。一個常見陷阱是如果某個類別的訓(xùn)練樣本特別少它的AP會非常不穩(wěn)定而mAP把這個不穩(wěn)定平均掉了。所以我評估時不僅看mAP總數(shù)值還會逐個類別看AP并打印每個類別的召回率。如果發(fā)現(xiàn)某個類檢測得特別差多半是它的樣本太少或者場景和訓(xùn)練數(shù)據(jù)差異過大這時候優(yōu)先補數(shù)據(jù)而不是調(diào)模型。除了數(shù)字指標(biāo)可視化評估也必不可少。把測試圖片跑一遍檢測把預(yù)測框畫在原圖上并保存成圖片然后人工掃一遍這些結(jié)果圖。這一步能讓你發(fā)現(xiàn)數(shù)字指標(biāo)發(fā)現(xiàn)不了的問題模型是不是總在某個固定位置誤檢是不是對某一類特定顏色特別敏感可視化結(jié)果是和業(yè)務(wù)方溝通時最有說服力的材料也是判斷模型是否真的可用的最后一關(guān)。5.3 擴展模型導(dǎo)出與部署思路如果你只想在自己的電腦上跑推理直接加載pth文件就行。但如果你要把它集成到服務(wù)或者另一個程序里可以導(dǎo)出成標(biāo)準(zhǔn)格式。PyTorch模型導(dǎo)出最常用的方式是ONNX導(dǎo)出后用ONNX Runtime推理可以明顯提升部署時的加載速度和推理性能。導(dǎo)出前有幾點要注意模型結(jié)構(gòu)固定成訓(xùn)練時的狀態(tài)輸入尺寸也固定下來導(dǎo)出后一定要在ONNX Runtime里跑一張測試圖和PyTorch的推理結(jié)果做數(shù)值對比偏差應(yīng)該在極小范圍。如果差異大基本可以確定是預(yù)處理環(huán)節(jié)在兩邊寫得不一致。部署到CPU時還要注意有些在GPU上天然支持的操作在CPU上會退化為較慢的實現(xiàn)所以導(dǎo)出后實測CPU推理時間是很必要的。業(yè)務(wù)場景如果對實時性要求很高還可以考慮把SSD模型轉(zhuǎn)成更輕量的版本但這已經(jīng)屬于優(yōu)化范疇不在跑通流程的范圍內(nèi)。先把標(biāo)準(zhǔn)流程跑通再考慮優(yōu)化是我反復(fù)強調(diào)的做事節(jié)奏。6. 常見問題、避坑實錄與個人心得6.1 高頻錯誤排查速查表下面這張表是我在多次指導(dǎo)和自用過程中總結(jié)出來的高頻問題清單。你訓(xùn)練時遇到報錯和異常先來這里對照一下。問題現(xiàn)象最常見原因解決建議loss一直不降標(biāo)注錯位 / 類別錯亂 / 學(xué)習(xí)率太小可視化訓(xùn)練樣本、檢查標(biāo)簽映射、適當(dāng)調(diào)大學(xué)習(xí)率loss變成nan學(xué)習(xí)率太大 / 數(shù)據(jù)增強產(chǎn)生非法框 / 梯度爆炸降低學(xué)習(xí)率10倍、檢查增強代碼、加梯度裁剪顯存不足OOMbatch太大 / 輸入分辨率太高減小batch、降低分辨率、使用梯度累積檢測不到任何目標(biāo)conf_thresh太高 / 模型欠擬合 / 解碼邏輯錯誤調(diào)低閾值、延長訓(xùn)練、重點檢查decode函數(shù)小目標(biāo)漏檢嚴(yán)重300分辨率信息丟失 / 小尺寸默認(rèn)框偏少換512輸入、檢查小尺度特征圖預(yù)測頭預(yù)測框位置偏移解碼偏移量計算錯誤 / 預(yù)處理不一致核對編碼解碼公式、統(tǒng)一輸入歸一化方式評估m(xù)AP虛高數(shù)據(jù)泄漏 / 測試集包含訓(xùn)練圖像按場景分組劃分?jǐn)?shù)據(jù)集重新生成索引加載權(quán)重報錯預(yù)訓(xùn)練權(quán)重結(jié)構(gòu)不匹配比對狀態(tài)字典鍵名做鍵名重映射補充一點訓(xùn)練環(huán)境報錯時不要一上來就懷疑代碼庫。先看自己有沒有改壞配置再看數(shù)據(jù)路徑是否正確最后才考慮代碼本身的bug。大部分人的問題都出在前兩類。6.2 幾個很少有人提醒的隱性坑有個坑是我自己踩過多次的訓(xùn)練集和驗證集的數(shù)據(jù)增強不一致。很多代碼默認(rèn)訓(xùn)練集做全套數(shù)據(jù)增強驗證集和測試集只做簡單歸一化。這本身沒錯但如果你為了省事在劃分?jǐn)?shù)據(jù)集時把驗證集圖片也扔進訓(xùn)練數(shù)據(jù)增強管道里驗證指標(biāo)就會異常波動而且你很難意識到是這里出了問題。我建議每一個需要改數(shù)據(jù)處理的環(huán)節(jié)都單獨寫清楚注釋不弄混訓(xùn)練和評估兩套流程。另一個隱性坑是訓(xùn)練集里有些圖的標(biāo)注框完全在圖像外。出現(xiàn)這種情況多半是轉(zhuǎn)換腳本處理坐標(biāo)時出了問題或者圖片被裁剪縮放后標(biāo)注沒跟著變。SSD的代碼一般會跳過或者報錯但如果你用的是那種隱式忽略的版本模型就會在不知不覺中學(xué)到錯誤信息。我發(fā)現(xiàn)這個問題的方法是統(tǒng)計每個圖片的標(biāo)注框位置看看有沒有框的中心點坐標(biāo)異常超出圖像尺寸的情況一旦發(fā)現(xiàn)就回到轉(zhuǎn)換腳本修正。還有一個值得養(yǎng)成的好習(xí)慣訓(xùn)練過程中每過一定輪次就抽樣可視化一次驗證集的檢測結(jié)果保存成帶有epoch編號的圖片。這樣你能直接看到模型從亂框到有模有樣的變化過程也能及時發(fā)現(xiàn)模型開始過擬合的征兆比如訓(xùn)練loss下降但驗證集AP開始下降。我把這個習(xí)慣保留到了現(xiàn)在每次訓(xùn)練都會定期翻開那些可視化圖片直觀感受模型學(xué)到了什么這比單純盯loss曲線更能建立對模型行為的直覺。最后說一個我從實踐里得到的總經(jīng)驗?zāi)繕?biāo)檢測項目里最珍貴的東西是數(shù)據(jù)質(zhì)量和數(shù)據(jù)處理的一致性而不是模型結(jié)構(gòu)有多酷炫。SSD給了你一套非常成熟可靠的框架你把數(shù)據(jù)管好、把訓(xùn)練流程走穩(wěn)效果就能超過很多數(shù)據(jù)一團糟但模型很新的項目。先用SSD跑通自己的完整流程等你對錨框、匹配、NMS、mAP這些概念都有了真實手感再去看任何更復(fù)雜的模型都會輕松很多。