現(xiàn)指南:單階段目標(biāo)檢測的起點)
如果你現(xiàn)在打開YOLOv8的源碼再回頭看YOLOv1會忍不住覺得它簡單得像玩具沒有anchor、沒有FPN、沒有CSPDarknet連BatchNorm都用得小心翼翼。但2016年這篇論文發(fā)表的時候目標(biāo)檢測圈正卡在“跑得動的不準(zhǔn)準(zhǔn)的跑不動”這個尷尬節(jié)點上。YOLOv1直接把檢測當(dāng)成回歸問題來做用一張448×448的圖像一次前向就同時輸出所有邊界框和類別概率在當(dāng)時把檢測速度拉到了45 FPS一舉改變了整個領(lǐng)域的技術(shù)路線。這篇文章不是論文翻譯而是我結(jié)合論文和實際復(fù)現(xiàn)踩坑的完整解讀。我會從它解決的問題出發(fā)把網(wǎng)格劃分、網(wǎng)絡(luò)結(jié)構(gòu)、損失函數(shù)、訓(xùn)練推理這些核心細(xì)節(jié)拆開講透最后再聊一聊自己動手實現(xiàn)時最容易翻車的幾個點。無論你是剛接觸目標(biāo)檢測想找入口還是已經(jīng)用過YOLOv5/YOLOv8想回頭補基礎(chǔ)這篇都能幫上忙。1. 從兩階段到單階段YOLOv1在2016年解決了什么1.1 當(dāng)時的主流檢測器慢在哪在YOLOv1之前目標(biāo)檢測的主流方案是R-CNN家族。R-CNN的思路是先通過Selective Search等算法在圖像上挖出大約2000個候選區(qū)域然后把這些候選區(qū)域逐個縮放、挨個送進CNN提取特征最后再用SVM和回歸器做分類、精修框。這個流程的精度確實高但慢也是真的——2000個候選區(qū)域要過2000次網(wǎng)絡(luò)一張圖在GPU上也要好幾秒CPU上更是能跑到幾十秒一張。后來的Fast R-CNN做了一個重要改進把整張圖先過一次CNN生成共享特征圖再用ROI Pooling從特征圖上摳出每個候選區(qū)域?qū)?yīng)的特征規(guī)避了“每個候選區(qū)域都過一遍網(wǎng)絡(luò)”的浪費。但問題也隨之而來候選區(qū)域的生成還是靠Selective Search這個CPU算法它本身是獨立于網(wǎng)絡(luò)的耗時依然不低整體離實時差得很遠(yuǎn)。Faster R-CNN后來用Region Proposal NetworkRPN把候選框生成也塞進了網(wǎng)絡(luò)速度有所提升但依然是“先提框、再分類”的兩階段結(jié)構(gòu)pipeline復(fù)雜部署和訓(xùn)練都不太省心。在這種背景下大家其實已經(jīng)意識到能不能繞開候選區(qū)域一步到位直接輸出所有目標(biāo)的位置和類別YOLOv1就是對這個問題的正面回答。1.2 YOLOv1的核心提法檢測就是回歸YOLOv1的全稱是You Only Look Once字面意思就是“你只看一次”。它把目標(biāo)檢測重新定義成一個端到端的回歸問題輸入一張圖像經(jīng)過一個單一神經(jīng)網(wǎng)絡(luò)直接輸出邊界框坐標(biāo)、框內(nèi)目標(biāo)的置信度以及目標(biāo)屬于各個類別的概率。這和兩階段檢測器的本質(zhì)區(qū)別在哪里兩階段是“先找可能含目標(biāo)的位置再判斷這些位置是什么”本質(zhì)上是兩步走YOLOv1則是把整張圖均勻劃分成網(wǎng)格讓每個網(wǎng)格直接負(fù)責(zé)“我這個區(qū)域里有沒有目標(biāo)、目標(biāo)在哪、目標(biāo)是什么”所有預(yù)測都在一次前向里完成。它沒有顯式的候選區(qū)域提取過程也沒有獨立的區(qū)域分類器整個系統(tǒng)從輸入到輸出是“one piece”。這種設(shè)計帶來的最大好處是速度和全局信息。速度上所有計算天然共享一張圖一次推理就能出全部結(jié)果架構(gòu)簡單到可以藏在實時的視頻流里。全局信息上YOLOv1在預(yù)測每個目標(biāo)時能看到整張圖的上下文不像滑窗或候選區(qū)域那樣容易只見局部所以背景誤檢率明顯更低。1.3 一個網(wǎng)格、一個邊界框、一組類別的極簡哲學(xué)YOLOv1的預(yù)測邏輯可以濃縮成一句話把圖像切成S×S個網(wǎng)格每個網(wǎng)格負(fù)責(zé)預(yù)測中心點落在該網(wǎng)格內(nèi)的目標(biāo)。論文里S取7所以輸入圖被分成7×7共49個網(wǎng)格。每個網(wǎng)格要做兩件事一是預(yù)測B個邊界框論文里B2每個框由位置和大小信息組成二是預(yù)測C個類別概率VOC數(shù)據(jù)集上C20。邊界框的置信度表示“這個框里有沒有目標(biāo)以及有目標(biāo)的話框得有多準(zhǔn)”類別概率則表示“如果這個網(wǎng)格里有目標(biāo)它屬于哪個類別”。最后把所有預(yù)測拼起來就得到了一個S×S×(B×5C)的張量也就是7×7×30。這個張量是YOLOv1所有魔法發(fā)生的地方下一節(jié)我就重點拆它。你也可以把這種思路理解成把整張圖像當(dāng)作一個巨大的標(biāo)簽網(wǎng)絡(luò)的任務(wù)就是把圖像“翻譯”成一張固定大小的預(yù)測表不需要再做任何后處理來生成候選框。2. 輸入輸出的幾何直覺7×7×30張量是怎么來的2.1 把圖片切成網(wǎng)格后每個格子要干什么我先用一個簡化例子說明。假設(shè)輸入圖是448×448網(wǎng)絡(luò)最后輸出的是7×7的特征圖那么原圖中每64×64個像素就對應(yīng)一個網(wǎng)格。如果某個目標(biāo)的中心點落在第(3,5)個網(wǎng)格內(nèi)那么這個網(wǎng)格就是所謂的“responsible”也就是由它來負(fù)責(zé)預(yù)測這個目標(biāo)。這里有個關(guān)鍵細(xì)節(jié)不是目標(biāo)所在的整個區(qū)域都由網(wǎng)格負(fù)責(zé)只看中心點落在哪個網(wǎng)格里。一個目標(biāo)即使跨了好幾個網(wǎng)格最終也只會分配給它中心點所在的那個網(wǎng)格。這個規(guī)則從訓(xùn)練標(biāo)注就開始用后面所有坐標(biāo)損失、類別損失都圍繞“目標(biāo)中心落在哪個網(wǎng)格”來劃分。每個網(wǎng)格預(yù)測B個邊界框論文B2但并不是兩個框都去擬合目標(biāo)。訓(xùn)練時我們會分別計算兩個預(yù)測框和真實目標(biāo)框的IoUIoU更高的那個框被選為“負(fù)責(zé)”預(yù)測該目標(biāo)的框另一個框如果和該目標(biāo)匹配就也算正樣本但整體上最終落地時每個目標(biāo)只有最優(yōu)的那個框真正參與定位回歸。這個設(shè)計我后面講損失函數(shù)時會再展開。2.2 30維輸出怎么拆55207×7×30張量里的30維由三個部分構(gòu)成每個格子預(yù)測B2個邊界框每個框有5個值x, y, w, h, confidence所以是2×510再加上C20個類別概率。于是總維度就是5×22030。這30個數(shù)依次的含義是前5個第一個邊界框的x、y、w、h、confidence接著5個第二個邊界框的x、y、w、h、confidence最后20個該網(wǎng)格內(nèi)目標(biāo)屬于20個類別的條件概率Pr(Class_i|Object)。注意類別概率是網(wǎng)格共享的不是每個框一套。也就是說YOLOv1里一個網(wǎng)格最多只能輸出一組類別概率這是它在設(shè)計上的重要約束。如果一個網(wǎng)格里同時有貓和狗的中心點YOLOv1只能二選一這是它后來被YOLOv2改進的動機之一。2.3 訓(xùn)練時的編碼與預(yù)測值的關(guān)系要理解x、y、w、h的含義必須搞清它們的歸一化方式。YOLOv1里x和y是目標(biāo)中心點相對當(dāng)前網(wǎng)格左上角的偏移用網(wǎng)格寬高歸一化所以通常在0到1之間。w和h是目標(biāo)寬度和高度相對整張圖像寬高的比例也在0到1之間。confidence是預(yù)測框與真實框的IoU乘以一個“是否有目標(biāo)”的指示值。為什么要這樣編碼因為網(wǎng)絡(luò)不可能直接回歸像素值幾百像素那樣數(shù)值尺度太大、不同尺寸的目標(biāo)差異也大網(wǎng)絡(luò)很難學(xué)到穩(wěn)定的映射。歸一化之后所有輸出都落在0到1這個量級配合最后的線性激活層網(wǎng)絡(luò)只需要擬合一個相對平滑的分布收斂難度會低很多。這里有個容易混淆的點YOLOv1的x和y是相對網(wǎng)格的偏移不是相對整張圖片的坐標(biāo)。也就是說網(wǎng)絡(luò)輸出的是“目標(biāo)中心離這個網(wǎng)格左上角多遠(yuǎn)”而不是“目標(biāo)中心在整張圖的絕對位置”。訓(xùn)練時我們要先把真實框的中心點換算到網(wǎng)格坐標(biāo)系里再計算損失這和很多現(xiàn)在用anchor的YOLO版本不一樣后面復(fù)現(xiàn)部分我專門講這個坑。3. 網(wǎng)絡(luò)結(jié)構(gòu)、預(yù)訓(xùn)練與448×448的取舍3.1 24層卷積2層全連接的結(jié)構(gòu)細(xì)節(jié)YOLOv1的主干網(wǎng)絡(luò)受GoogLeNet啟發(fā)但沒有直接用Inception模塊而是用1×1卷積做通道壓縮、緊跟3×3卷積做特征提取這樣能在控制計算量的同時加深網(wǎng)絡(luò)。整體結(jié)構(gòu)是24個卷積層后面接2個全連接層。具體展開的話前20個卷積層先用于ImageNet分類預(yù)訓(xùn)練這20層負(fù)責(zé)從原始像素中提取通用視覺特征在進入檢測任務(wù)時后面接上4個卷積層和2個全連接層。最后的全連接層輸出4096維再接一個全連接層把維度壓縮到7×7×30最后reshape成空間上的7×7×30張量。激活函數(shù)上YOLOv1除最后一層使用線性激活外中間層統(tǒng)一使用Leaky ReLUslope參數(shù)alpha0.1。為什么不用普通ReLU因為Leaky ReLU在負(fù)半軸保留了一個很小的梯度能減少神經(jīng)元“死亡”問題讓訓(xùn)練更穩(wěn)定這個小細(xì)節(jié)后來被無數(shù)檢測模型繼承。另外YOLOv1還推出了一個輕量版叫Fast YOLO卷積層從24層減到9層速度可以沖到150 FPS但精度降了不少mAP大約52.7。這說明當(dāng)時作者已經(jīng)意識到“速度-精度”是檢測模型繞不開的權(quán)衡而這個權(quán)衡一直到今天都是YOLO系列的主線。3.2 為什么輸入必須是448×448全連接層的代價YOLOv1的網(wǎng)絡(luò)最后是兩層全連接而全連接層的輸入尺寸是固定的這就導(dǎo)致模型輸入必須固定成448×448。你不能像后來的全卷積檢測器那樣任意尺寸輸入、任意尺寸輸出。448×448這個數(shù)字是從哪來的預(yù)訓(xùn)練時用的是ImageNet的標(biāo)準(zhǔn)224×224檢測階段為了提高定位精度把輸入翻倍成448×448相當(dāng)于在保留預(yù)訓(xùn)練權(quán)重的基礎(chǔ)上把特征圖分辨率提高了一倍。這樣可以保留更多小目標(biāo)的空間細(xì)節(jié)代價是計算量增加了約4倍但換來的是檢測能力的顯著提升。如果你現(xiàn)在自己動手復(fù)現(xiàn)YOLOv1建議老老實實把輸入resize到448×448。因為一旦改動輸入分辨率網(wǎng)絡(luò)輸出的7×7網(wǎng)格對應(yīng)的原圖感受野和空間粒度都會變訓(xùn)練超參數(shù)也需要從頭調(diào)整并不劃算。想要多尺度輸入那是YOLOv2引入anchor和全卷積之后的事情了。3.3 預(yù)訓(xùn)練分兩步ImageNet分類、檢測微調(diào)YOLOv1的訓(xùn)練策略非常經(jīng)典也是后來不少檢測模型的標(biāo)準(zhǔn)做法。第一步用前20層卷積加一個平均池化層和一個全連接層在ImageNet 1000類分類任務(wù)上預(yù)訓(xùn)練。這個階段大約跑了一周top-5準(zhǔn)確率能到88%證明這20層卷積確實學(xué)到了通用的視覺特征。第二步把分類用的全連接層換成檢測用的結(jié)構(gòu)也就是補上剩下的4個卷積層和2個全連接層然后切換到448×448輸入在VOC數(shù)據(jù)集上微調(diào)。這一步之所以有效是因為分類任務(wù)和檢測任務(wù)在低中層級特征上是共享的邊緣、紋理、形狀這些基礎(chǔ)特征不需要從頭學(xué)微調(diào)只需要讓高層的語義特征適應(yīng)“邊界框回歸多類別分類”這個新目標(biāo)。這種“分類預(yù)訓(xùn)練檢測微調(diào)”的思路本質(zhì)上是一種遷移學(xué)習(xí)。放到今天看似乎很常規(guī)但在2016年YOLOv1把這件事做成了一套明確可復(fù)制的流程對后來者影響很大。很多論文和開源庫都沿用了類似思路先用大規(guī)模分類數(shù)據(jù)初始化再微調(diào)到檢測任務(wù)上。4. 損失函數(shù)設(shè)計里的門道4.1 定位損失寬高開根號是為了小框YOLOv1的損失函數(shù)用的是一個統(tǒng)一形式的sum-squared error均方誤差分為坐標(biāo)損失、置信度損失和分類損失三塊。最初的坐標(biāo)損失長這樣對于“負(fù)責(zé)”預(yù)測目標(biāo)的網(wǎng)格i中的第j個框計算中心坐標(biāo)和寬高的平方誤差[ \lambda_{coord}\sum_{i0}^{S^2}\sum_{j0}^{B} 1_{ij}^{obj}\left[(x_i-\hat{x}i)^2(y_i-\hat{y}i)^2\right] ] [ \lambda{coord}\sum{i0}^{S^2}\sum_{j0}^{B} 1_{ij}^{obj}\left[(\sqrt{w_i}-\sqrt{\hat{w}_i})^2(\sqrt{h_i}-\sqrt{\hat{h}_i})^2\right] ]這里(1_{ij}^{obj})表示第i個網(wǎng)格的第j個預(yù)測框是否負(fù)責(zé)這個目標(biāo)。注意第二行對寬高先開根號再算平方誤差這是YOLOv1損失函數(shù)里最出名的一個細(xì)節(jié)。為什么要開根號因為大框和小框?qū)ν瑯哟笮〉钠钊萑潭仁遣煌?。舉個例子一個寬度為200像素的框偏差10像素相對誤差只有5%但一個寬度為20像素的框偏差10像素相對誤差就是50%。如果不處理大框的誤差天然會主導(dǎo)梯度小框的定位精度就很難學(xué)上去。開根號之后大框的數(shù)值變化被壓縮、小框的數(shù)值變化被放大相當(dāng)于變相給小框損失加了更大的權(quán)重讓網(wǎng)絡(luò)更重視小目標(biāo)的邊界框精度。這是很樸素但很有效的小目標(biāo)優(yōu)化思想。4.2 置信度損失有無目標(biāo)的權(quán)重差異置信度損失分兩塊有目標(biāo)和無目標(biāo)。[ \sum_{i0}^{S^2}\sum_{j0}^{B} 1_{ij}^{obj}(C_i-\hat{C}_i)^2 ] [\lambda_{noobj}\sum_{i0}^{S^2}\sum_{j0}^{B} 1_{ij}^{noobj}(C_i-\hat{C}_i)^2 ]有目標(biāo)的部分只對那些真實分配給目標(biāo)的預(yù)測框計算。無目標(biāo)的部分是所有沒有分配到目標(biāo)的預(yù)測框也就是絕大多數(shù)網(wǎng)格里的框。這里必須設(shè)置一個(\lambda_{noobj})論文取0.5。為什么不是1因為一張7×7圖像里真正包含目標(biāo)的網(wǎng)格通常只有幾個其余全是背景。如果背景框的置信度損失權(quán)重和前景一樣大網(wǎng)絡(luò)很容易走極端把所有confidence都預(yù)測成0因為“大部分框本來就沒有目標(biāo)”這樣總和誤差反而最小。0.5的權(quán)重是為了抑制這種背景主導(dǎo)但又不至于完全忽略背景讓網(wǎng)絡(luò)保留一定的判別能力。在訓(xùn)練之初你大概率會觀察到置信度預(yù)測值整體偏低這是正常的。因為絕大多數(shù)網(wǎng)格沒有目標(biāo)網(wǎng)絡(luò)在盡量壓低這些負(fù)樣本的confidence。關(guān)鍵是讓有目標(biāo)的網(wǎng)格里負(fù)責(zé)人的那個框能慢慢“頂出來”最終達(dá)到“有目標(biāo)時confidence接近IoU無目標(biāo)時confidence接近0”的狀態(tài)。4.3 分類損失與每個格子只能給一個類別的限制分類損失本身很簡單[ \sum_{i0}^{S^2} 1_i^{obj}\sum_{c\in classes}(p_i(c)-\hat{p}_i(c))^2 ]只對存在目標(biāo)中心點的網(wǎng)格i計算(1_i^{obj})表示第i個網(wǎng)格是否有目標(biāo)。注意YOLOv1沒有用交叉熵而是繼續(xù)用平方誤差這是為了和整個sum-squared error的框架保持一致優(yōu)化起來也簡單。但這個簡單設(shè)計背后有一個明顯短板每個網(wǎng)格只預(yù)測一組類別概率所以不管B2個框是不是對應(yīng)兩個不同的目標(biāo)最終類別預(yù)測只能是一個。如果兩個目標(biāo)中心點都落在同一個網(wǎng)格里哪怕一個在左上、一個在右下YOLOv1也只能輸出其中一類。這個“一格一類”的限制直接導(dǎo)致YOLOv1在密集小目標(biāo)場景下表現(xiàn)不佳。YOLOv2后來引入anchor并讓每個anchor獨立預(yù)測類別部分解決的正是這個問題。5. 訓(xùn)練、推理與評估從數(shù)據(jù)集到最終mAP5.1 準(zhǔn)備VOC格式數(shù)據(jù)標(biāo)注框怎么歸一化想在YOLOv1上訓(xùn)練自己的數(shù)據(jù)集第一步是把標(biāo)注轉(zhuǎn)成YOLO風(fēng)格。雖然YOLOv1有Darknet原生格式但你可以先用LabelImg或CVAT這類工具打標(biāo)然后統(tǒng)一轉(zhuǎn)成YOLO格式的txt文件。每張圖片對應(yīng)一個同名txt文件每一行代表一個目標(biāo)格式為class_id x_center y_center width height這里x_center、y_center是相對圖像寬高的中心點坐標(biāo)width、height也是相對圖像的寬高比例全部歸一化到0到1。LabelImg保存VOC格式XML時你可以直接用官方腳本轉(zhuǎn)成上述格式CVAT導(dǎo)出時也有現(xiàn)成的YOLO格式選項比較省事。數(shù)據(jù)做完之后只是完成了上游準(zhǔn)備。真正進入YOLOv1訓(xùn)練前還需要把標(biāo)注從“相對整張圖的中心坐標(biāo)”轉(zhuǎn)換成“相對網(wǎng)格左上角的偏移”這一步我會在復(fù)現(xiàn)坑里詳細(xì)講這里先提醒一句很多剛接觸的人會在這里算錯最后發(fā)現(xiàn)loss能降但框的位置完全不對。5.2 訓(xùn)練超參數(shù)與學(xué)習(xí)率調(diào)法論文里YOLOv1在PASCAL VOC 2007和2012的訓(xùn)練驗證集上訓(xùn)練大約135個epoch。batch size為64momentum為0.9weight decay為0.0005初始學(xué)習(xí)率調(diào)度比較特殊第一個epoch學(xué)習(xí)率從0.001緩慢升到0.01也就是現(xiàn)在常說的warmup。接下來75個epoch學(xué)習(xí)率固定在0.01。再之后30個epoch學(xué)習(xí)率降到0.001。最后30個epoch學(xué)習(xí)率降到0.0001。為什么要warmup因為訓(xùn)練剛開始時網(wǎng)絡(luò)權(quán)重來自預(yù)訓(xùn)練突然給一個很大的學(xué)習(xí)率可能破壞已經(jīng)學(xué)到的特征。用一個epoch慢慢把學(xué)習(xí)率推上去能讓模型平穩(wěn)過渡到檢測任務(wù)。這個細(xì)節(jié)我第一次訓(xùn)練時沒在意結(jié)果前幾個batch loss直接飛到數(shù)百換成warmup之后才穩(wěn)定下來。數(shù)據(jù)增強方面YOLOv1用了隨機縮放和平移最大偏移量約為原圖尺寸的20%同時會在HSV色彩空間隨機調(diào)整曝光和飽和度上限大約是1.5倍。另外在全連接層后加了dropout概率0.5用于減輕過擬合。這些手段放到今天看不算多但在當(dāng)時已經(jīng)能明顯提升泛化能力。5.3 推理時閾值過濾與NMSYOLOv1推理非常直觀。網(wǎng)絡(luò)輸出7×7×30后先把每個框的confidence和該網(wǎng)格的類別概率相乘得到每一個框針對每個類別的最終得分[ \text{score} P(\text{Class}_i|\text{Object}) \times \text{confidence} ]這個score表示“這個框里是某類目標(biāo)、且框得準(zhǔn)不準(zhǔn)”的綜合置信度。得到score后先按閾值過濾掉低分框論文在VOC上常用0.2左右剩下的框再按類別分別做NMS非極大值抑制。NMS的邏輯是同一類別的多個預(yù)測框如果重疊度很高就只保留得分最高的那個。具體做法是先按score排序取最高分的框刪除所有與它IoU超過某個閾值比如0.5的其他框然后重復(fù)這個過程。這個流程看似簡單但NMS的閾值很影響結(jié)果。閾值設(shè)得太高會殘留大量重復(fù)框設(shè)得太低又可能把挨得近的同類目標(biāo)誤刪。YOLOv1因為每個網(wǎng)格只預(yù)測一個類別同一個目標(biāo)通常只有一兩個框競爭NMS壓力不大所以閾值可以稍微寬松一些。5.4 用mAP給YOLOv1打分45 FPS和63.4 mAP在PASCAL VOC 2007測試集上YOLOv1取得了63.4的mAP在Titan X GPU上達(dá)到45 FPS。Fast YOLO則是52.7 mAP速度150 FPS。作為對比當(dāng)時的R-CNN達(dá)到66.0 mAP但速度只有0.5 FPSFast R-CNN的mAP是70.0速度依然遠(yuǎn)低于實時。從數(shù)字能看出YOLOv1在當(dāng)時并不是精度最高的它的賣點是“把速度拉到了可實用的級別”。63.4的mAP雖然在數(shù)字上落后Fast R-CNN約7個點但45 FPS意味著它可以實時處理視頻流這是質(zhì)變。很多應(yīng)用場景比如監(jiān)控、機器人、自動駕駛的初步感知之前根本不敢用CNN檢測器YOLOv1讓這些方向第一次有了嘗試的可能。mAP計算本身用的是VOC的AP平均方式對每個類別分別算Precision-Recall曲線下的面積再取平均。這個指標(biāo)對訓(xùn)練和部署都很重要建議在復(fù)現(xiàn)時自己實現(xiàn)一遍能幫你深刻理解置信度閾值怎么影響precision和recall的取舍。6. 自己動手復(fù)現(xiàn)YOLOv1原理之外的坑6.1 坐標(biāo)系編碼x,y的偏移量到底怎么算我復(fù)現(xiàn)YOLOv1時踩的第一個大坑就是坐標(biāo)編碼。網(wǎng)絡(luò)預(yù)測的x和y不是“相對整張圖的中心點”而是“相對當(dāng)前網(wǎng)格左上角的偏移”。但訓(xùn)練標(biāo)簽里目標(biāo)中心點坐標(biāo)通常是相對整張圖歸一化的。所以你得先算清楚假設(shè)目標(biāo)中心點歸一化坐標(biāo)是(tx, ty)當(dāng)前所在網(wǎng)格的列號是col、行號是row從0開始范圍0到6網(wǎng)絡(luò)預(yù)測的x、y目標(biāo)值應(yīng)該是[ x tx \times 7 - col ] [ y ty \times 7 - row ]也就是說如果目標(biāo)中心落在第3行第5列網(wǎng)格的正中間那么(ty, tx)大約對應(yīng)(3.5/7, 5.5/7)經(jīng)過轉(zhuǎn)換后y0.5, x0.5。網(wǎng)絡(luò)預(yù)測的x、y就應(yīng)該逼近0.5。推理時做反向操作把預(yù)測的x加上col再除以7就能還原成相對整張圖的中心點坐標(biāo)。很多初學(xué)代碼的人直接拿歸一化的中心點去算損失訓(xùn)練出來框全部偏移到網(wǎng)格左上角這就是坐標(biāo)編碼沒搞對。即使公式看似簡單一旦網(wǎng)格索引搞錯整個系統(tǒng)都會不可用。6.2 空白網(wǎng)格是置信度訓(xùn)練的主角另一個坑是置信度的初始化和正負(fù)樣本不均衡。由于7×7的網(wǎng)格里大部分區(qū)域是背景訓(xùn)練初期模型會瘋狂把所有confidence往0推。這時候損失函數(shù)里(\lambda_{noobj}0.5)就成了唯一屏障。實際操作中我建議訓(xùn)練剛開始時觀察無目標(biāo)網(wǎng)格的confidence均值如果下降太快且太快趨于0要考慮是否(\lambda_{noobj})設(shè)太大。有目標(biāo)網(wǎng)格中“負(fù)責(zé)框”的confidence訓(xùn)練早期可以允許它先升高到0.5左右再慢慢逼近IoU。如果你看到它長期在0.2以下大概率是學(xué)習(xí)率太低或者正樣本分配邏輯寫錯了。準(zhǔn)備訓(xùn)練數(shù)據(jù)時盡量保證每個batch里包含足夠多的正樣本網(wǎng)格否則網(wǎng)絡(luò)會被背景淹沒。YOLOv1沒有focal loss處理正負(fù)樣本不均衡全靠權(quán)重和采樣。你可以在實現(xiàn)時自己控制數(shù)據(jù)順序讓每批圖片里目標(biāo)不要過于稀疏這個技巧能明顯加速收斂。6.3 小目標(biāo)檢測差為什么是結(jié)構(gòu)性缺陷YOLOv1對密集小目標(biāo)的檢測效果差這不是訓(xùn)練不充分而是結(jié)構(gòu)層面的限制。主要原因有三個第一輸入448×448經(jīng)過多層卷積和下采樣后最后輸出的7×7特征圖上每個網(wǎng)格感受野對應(yīng)原圖64×64像素的區(qū)域。如果目標(biāo)比這個區(qū)域小很多能用于判別它的特征就很有限網(wǎng)絡(luò)很難精確定位。第二每個網(wǎng)格最多只能預(yù)測B2個框且只能有一組類別概率。目標(biāo)稍微密一點兩個目標(biāo)中心落在同一個網(wǎng)格系統(tǒng)就會直接超載。第三YOLOv1沒有anchor也沒有多尺度特征融合它對不同寬高比和尺度的目標(biāo)泛化能力弱全靠卷積層自動適應(yīng)。這和后來的YOLOv2用anchor、YOLOv3用FPN相比差距非常明顯。所以如果你要在無人機視角、小目標(biāo)密集場景里做檢測YOLOv1基本不太能用。它的歷史價值更多在于驗證了“單階段實時檢測可行”這個方向真正的工程實用性要到Y(jié)OLOv2和YOLOv3才逐漸成熟。6.4 從YOLOv1到Y(jié)OLOv2YOLOv1的邊界在哪復(fù)現(xiàn)過YOLOv1之后你會更明白YOLOv2的改進點為什么那么精準(zhǔn)。YOLOv2做了幾件關(guān)鍵的事移除了全連接層改成全卷積結(jié)構(gòu)輸入尺寸不再固定。引入anchor框機制每個網(wǎng)格預(yù)測更多候選框類別預(yù)測從“網(wǎng)格共享”變成“anchor獨立”解決了“一格一類”的限制。加入BatchNorm訓(xùn)練更穩(wěn)定收斂更快。多尺度訓(xùn)練讓網(wǎng)絡(luò)適應(yīng)不同輸入分辨率。把輸入分辨率提升到416×416并去掉了最后一個池化層讓特征圖分辨率從7×7變成13×13小目標(biāo)檢測能力明顯增強。這些改進每一項都精準(zhǔn)打在YOLOv1的痛點上。所以學(xué)習(xí)YOLOv1時不要只背結(jié)論最好把“YOLOv1為什么這樣設(shè)計”“它哪里不行”一起搞清楚這樣再看YOLOv2、YOLOv3的一系列演進會順理成章得多。7. YOLOv1真正留給后來者的遺產(chǎn)7.1 不是精度最高但開啟實時檢測YOLOv1今天看有很多不足mAP不如同期的兩階段方法小目標(biāo)檢測弱對密集場景也力不從心但它定義了“實時目標(biāo)檢測”這條技術(shù)路線。Fast R-CNN再準(zhǔn)跑不到實時許多實際應(yīng)用就落不了地YOLOv1用45 FPS第一次讓實時檢測成為可能讓行業(yè)開始認(rèn)真關(guān)注“如何在速度與精度之間做設(shè)計”而不僅僅是追求更高的mAP。這種“先定義問題再設(shè)計最簡方案”的思路對整個檢測領(lǐng)域的影響比那63.4 mAP大得多。直到現(xiàn)在YOLO系列的每個版本都還在沿用YOLOv1確立的單階段、網(wǎng)格化預(yù)測、統(tǒng)一損失函數(shù)的基本框架。無論你后續(xù)用哪個版本的YOLO回到Y(jié)OLOv1打地基都會讓你對它們的設(shè)計選擇有更深的體會。7.2 從YOLOv1入手理解YOLO系列的學(xué)習(xí)路徑如果你剛?cè)腴T目標(biāo)檢測我會很推薦用YOLOv1做第一個實踐項目原因很簡單它結(jié)構(gòu)直接沒有太多花哨模塊幾十行核心代碼就能跑通。你可以在它上面親手驗證損失函數(shù)、坐標(biāo)編碼、NMS這些基礎(chǔ)概念然后再逐步向YOLOv5、YOLOv8這些工程化更強的模型遷移。建議的學(xué)習(xí)順序是先把YOLOv1的論文原文讀一遍尤其聚焦網(wǎng)格劃分和損失函數(shù)部分然后照著開源PyTorch實現(xiàn)把數(shù)據(jù)流和loss函數(shù)一行行跑通最后換到自己的小數(shù)據(jù)集上訓(xùn)練對比檢測效果再去找YOLOv2和YOLOv3的改進論文逐項對比“哪里改了、為什么改”。一圈走下來你對目標(biāo)檢測的理解會比直接上手YOLOv8深得多。我自己當(dāng)年也是從YOLOv1開始入門的印象最深的是第一次用自己訓(xùn)練出來的模型檢測一張街景圖看到那些框在視頻流里穩(wěn)穩(wěn)跟住行人心里那種滿足感至今還記得。那之后我再換到Y(jié)OLOv3、YOLOv5感覺都不是從零學(xué)起而是在一個已有的框架里升級認(rèn)知成本低了很多。如果你也想把目標(biāo)檢測的地基打扎實YOLOv1是一個繞不開、也值得繞回去好好看一眼的起點。