從百度飛槳YOLOSeries庫看各個YOLO模型
近年來YOLO系列演算法遍地開花,前陣子YOLOv6和YOLOv7相繼被提出,一開源就有了極高的流量熱度。還在用v5呢結果v6出來了,趕緊換v6訓一波,結果v7又出來了不得不再換v7訓和測,相信不少人和我一樣,雖然結構差不多,但切換起來還是繁瑣,而且自己的資料集精度用哪個訓高還真不一定。當時就想把這3個程式碼整合在一個庫的想法,但是這三個程式碼基本就是yolov5程式碼,當年debug yolov5程式碼時候吃了不少苦頭,二次開發也非常的不友好。
前兩天看到了百度飛槳公眾號上的宣傳,推出了一個重構後的YOLO庫 YOLOSeries,說同時支援PP-YOLOE、YOLOX、YOLOv5、YOLOv6、YOLOv7,於是立馬去使用了下。程式碼連結是這個:
這個庫全稱是 PaddleDetection_YOLOSeries,顧名思義就是基於百度飛槳的PaddleDetection 復現的,這個庫看起來也是百度飛槳派人維護更新的。yoloseries程式碼其實就是PaddleDetection上加了v5 v6 v7模型,但是受限於v5 v6 v7的GPL協議而單獨另開一個維護,yoloseries作者也說會同步和PaddleDetectio保持更新。原版的PaddleDetection裡就有YOLOv3、YOLOv4、PP-YOLOE和YOLOX,程式碼連結是這個:
言歸正傳,yoloseries庫對yolov5、yolov6、yolov7的重構是我比較關心的,更不用說還有百度他們的yoloe (torch的yoloe好像沒看到有復現的) 和 convnext更高精度版本,看著首頁的一排排表格和下載連結就興奮。
1.各個YOLO基本結構
首先還是簡單回顧下幾個熱門YOLO的大致結構:從2020年開始
1.YOLOv5(2020):

YOLOv5主要提出更靈活和更輕量級的網路設計,還有Mosaic資料增強。模型精度最初不如YOLOv4,但是速度快的多,後來精度速度一直在迭代優化,逐漸成為業界頂流。關於取名之類的就不糾結了,作者幾乎全年甚至全天無休地維護也使得YOLOv5始終保持著極高的熱度,但是程式碼可讀性比較差。
2.YOLOX(2021.08):

YOLOX主要特點是提出了Anchor Free的YOLO檢測思路,使用SimOTA改進label assign,和Decoupled Head解耦頭。還有一些trick包括採用MixUp和Mosaic,最後15個epoch關閉Mosaic使用L1 loss等。
值得一提的是,PaddleDetection中的YOLOX我看了下,是我見過復現精度最高的版本,比曠視的原版也更高,其中大模型更高的多,YOLOX-x直接51.8。
3.YOLOE(2022.03):

YOLOE是PaddleDetection團隊自己研發出來的,backbone採用了新設計的CSP-RepVGG形式的變種ResNet,這個確實挺驚豔的,head也是解耦頭還有加了個ESE Attention,也是Anchor Free的,標籤分配採用了早期ATSS後期TAL(Task Alignment Learning)聯合的做法。PP-YOLOv2和PP-YOLO是百度之前的作品,一段時間出一個PP特色模型看得出來是很用心做的了。
4.YOLOv6(2022.06):
YOLOv6是美團做的,具體參照 美團技術團隊:YOLOv6:又快又準的目標檢測框架開源啦 。怎麼說呢,大致就是YOLOX+整體RepVGG,還有一些trick包括採用SIoU loss、relu加速、訓400epoch等。但我個人覺得創新點還不足以取名v6,也一直沒有放出m l x版本完整的一套模型,感覺就是3個小模型在坐吃山空。
5.YOLOv7(2022.07):

YOLOv7是YOLOv4團隊做的,AlexeyAB和YOLOR/CSP的作者Wang Chien-Yao王建堯均為YOLO界的知名大佬,算是YOLO正統,並且已經列在了YOLO之父Joseph Redmon的darknet上 https:// github.com/pjreddie/dar knet 。
YOLOv7主要是結構上提出E-ELAN模組,大規模E-ELAN模組堆疊都保持鏈路計算量引數量穩定,還有YOLOR的隱式引數學習和aux輔助頭的設計。還放出了加P6的1280尺度的超大模型,可以看出目標不只是為了比較現有的YOLO,還為了和ConvNeXt Swin Transformer等模型硬碰硬,總之業內知名大佬出品,那肯定是用心設計過的。
2.各個YOLO程式碼實現和異同
yoloseries程式碼其實就是PaddleDetection上加了v5 v6 v7模型,PaddleDetection這個套件的檢測模型大體框架還是很清楚簡單的,yoloseries的實現也是直接繼承使用。配置檔案採用的yml形式是我比較喜歡的,因為以前用dict形式多層括號的時候就覺得繁瑣也出過錯,還有命名大小寫駝峰形式不會看的很累,巢狀形式的config我覺得比把所有的都塞一個yml裡方便清楚的多。
而模型結構的程式碼基本都在PaddleDetection/ppdet/modeling,新加yolo模型的話就是新增到這幾個檔案下:architectures、backbones 、neck、head、loss。YOLOE YOLOX的程式碼本來就都是Apache協議,百度和曠視寫的也比較清楚易懂了。而原版v5 v6 v7的程式碼都是GPL協議,其實都總歸是v5的程式碼,debug起來簡直就是噩夢了,儘管yolov5作者幾乎全天無休的維護,但是基本小修小改,程式碼幾乎不大改,可讀性依然很差,記得有個大佬說的很形象,yolov5程式碼就像是一輛搖搖晃晃東修西補的老爺車在高速公路上以120碼的速度狂飆。而YOLOSeries程式碼重構,我看了下還是做的很好的。
一起來看下YOLOSeries裡的v5 v6 v7相關的程式碼以及和yoloe yolox的異同點:
程式碼目錄主要在 https:// github.com/nemonameless /PaddleDetection_YOLOSeries/tree/develop/ppdet/modeling
1.architectures:
主要是整體結構排布,architectures下的py都是這個作用,每個網路都繼承自meta_arch.py裡的BaseArch,重寫get_loss()和get_pred()函式,分別對應訓練和預測階段。具體可以看 https:// github.com/nemonameless /PaddleDetection_YOLOSeries/tree/develop/ppdet/modeling/architectures
v5 v6 v7都採用的是ppdet/modeling/architectures/yolov5.py,應該是為了區分開yolov3和ppyolo的ppdet/modeling/architectures/yolo.py吧,內容基本一樣yolov5.py更簡潔點,yolo.py還有些跟蹤相關的。而yolox.py也另開一個,因為只有它是在網路中用 F.interpolate 來製造多尺度訓練的,所以yolox.py裡是新加了_preprocess和_get_size函式。

2.backbones:
是各個backbone的程式碼,其中yoloe和v5都是ppdet/modeling/backbones/csp_darknet.py,v6則是 efficientrep.py ,還有v7也寫在了csp_darknet.py裡,而yoloe則是 cspresnet.py 。主要看 ppdet/modeling/ backbones裡的程式碼,具體可以看
https:// github.com/nemonameless /PaddleDetection_YOLOSeries/tree/develop/ppdet/modeling/backbones

backbone的組成還是很有規律的,都是1個stem + 4個stage,下采樣率分別是1/2,1/4, 1/8, 1/16, 1/32,最後一個stage還要加SPP模組才輸出。yolox和yolov5的backbone幾乎只有個別引數的差別,可能yolox本來就是用的yolov5 v6.0版本之前的backbone,所以用arch_settings給一個arch引數指定選用哪個。常規模型一般是640尺度的,return_idx都是後3位索引,表示輸出是後3個stage的特徵,步長分別是8, 16, 32,對應特徵圖就是80x80,40x40,20x20。而P6模型是1280尺度,就是1個stem+5個stage,輸出後4個stage的特徵圖,所以FPN和head也相應就是4層,步長分別是8,16,32,64, 對應特徵圖就是160x160,80x80,40x40,20x20。P6模型現在只有v5 v7提供了,訓一個P6模型是非常耗資源的。
2.1 stem的區別 ,stem主要是要進行1/2下采樣和通道數從3通道增大
(1) yolov5(包括它的P6)的做法是一個大卷積核為6的ConvBN
(2) yolox則是Focus結構=切片重組+一個3x3的ConvBN,切片重組是下采樣
(3) yoloe是3層3x3的ConvBN,程式碼裡稱為use_large_stem
(4) yolov6是一個RepConv(RepVGGBlock訓練時是兩個並聯的Conv BN部署時會融合成一個卷積)
(5) yolov7就複雜了,L和X版本是像yoloe那樣的3層3x3的ConvBN,tiny版本則是兩層3x3的ConvBN,P6的W6 E6 D6 E6E則是ReOrg+ConvBN,其實就是Focus結構=切片重組+一個3x3的ConvBN
2.2 stage的區別 ,每個stage基本都遵循著 下采樣+ConvBN堆疊的模組 的設計,最後一個stage一般再加一個SPP,這些stage輸出的特徵通常稱為c2 c3 c4 c5,而c1也就是stem,也可以記為2的幾次方就下采樣幾,c5就是下采樣1/32,特徵圖大小就是640/32=20
(1) yolov5(包括它的P6):一個3x3的ConvBN下采樣 + CSPLayer(C3),使用的是SPPF是一個單尺寸5核的SPP
(2) yolox:和yolov5一樣,也是一個3x3的ConvBN下采樣 + CSPLayer(C3),使用的SPP是(5, 9, 13)的核設定
(3) yoloe:是RepVGG+ResNet改造的,一個3x3的ConvBN下采樣 + 一堆BasicBlock + 可選的EffectiveSE注意力機制 + 一個3x3的ConvBN,SPP模組是寫在了neck裡
(4) yolov6:是和yolov5 yolox一樣,模組換成一個RepConv下采樣 + RepLayer,使用的SPP是SimSPPF是和yolov5一樣單尺寸5的核,但是使用relu啟用而不是silu
(5) yolov7還是最複雜也沒有章法:下采樣方法第一個stage和後3個略有不同,總共包括了Conv, DownC, MPConvLayer, MP, None等各種方法,按模型arch種類來選擇,在YOLOSeries程式碼裡寫的還是非常清楚的。 https:// github.com/nemonameless /PaddleDetection_YOLOSeries/blob/develop/ppdet/modeling/backbones/csp_darknet.py#L871 。至於堆疊的模組,是ELANLayer,在E6E裡是ELAN2Layer其實是兩個並聯ELANLayer。最後SPP是SPPCSPC(5, 9, 13)卷積核設定,tiny則是SPPELAN(5, 9, 13)卷積核設定。具體還是看程式碼吧。
3. neck:
neck的組成都是採用了PAN形式,自底向上+自頂上下兩個鏈路使得特徵融合更充分。主要看 ppdet/modeling/ necks裡的程式碼

(1) yolov5:是YOLOCSPPAN,兩個lateral_convs+fpn_blocks組合 + 兩個downsample_convs+pan_blocks組合,細看其實就是4個ConvBN+CSPLayer組合,P6模型則是3對+3對=6個組合
(2) yolox:是YOLOCSPPAN,和yolov5的neck一模一樣,曠視原版的寫法雖然清楚但不易複用為P6,只能另寫一個PAFPNP6之類的
(3) yoloe:是CustomCSPPAN,寫在了ppdet/modeling/necks/custom_pan.py裡,結構和以前ppyolo前兩版的PPYOLOPAN類似包括spp drop_block等trick,只是基礎結構使用了cspresnet的模組,ConvBNLayer+CSPStage的組合
(4) yolov6:是RepPAN,和yolox和v5一樣,只是基礎模組是RepLayer而不是CSPLayer,上取樣換成了Conv2DTranspose而不是nn.Upsample,是SimConv+RepLayer的組合
(5) yolov7:是ELANFPN,也是2對+2對=4個組合,BaseConv+ELANLayer,但是再各加一層卷積縮小下通道數,也還是複雜會分情況,L和X版本PAN的下采樣是MPConvLayer,在tiny裡則是普通ConvBN,最後一層卷積只有在L版本里是RepConv別的都是BaseConv。P6版本另寫了一個ELANFPNP6,3對+3對=6個組合,E6、D6和E6E版本PAN的下采樣是DownC,在W6裡則是普通ConvBN,另外E6E版本的是使用的ELAN2Layer,相當於並聯的兩個ELANLayer,最後一層卷積都是BaseConv。另外如果是P6版本還需要使用aux_head訓,就需要再返回更淺4層的fpn特徵,也就是ELANFPNP6 返回的是8層特徵圖,當然也可以不使用aux_head就返回常規的4層。具體可以看程式碼: https:// github.com/nemonameless /PaddleDetection_YOLOSeries/blob/develop/ppdet/modeling/necks/yolo_fpn.py#L1266
4. head:


head的組成也有很多區別。主要看 ppdet/modeling/ heads裡的程式碼:
(1) yolov5:和yolov3一樣簡潔直接就是一個anchor一層卷積直接輸出分類迴歸結果,通道數是3x(num_classes + 5),後續會拆出4維度的reg(xywh)+num_classes維度的分類資訊cls+1維度的obj,score是cls和obj想乘。
(2) yolox:是解耦的頭,1層公用卷積的stem_conv+2層卷積的conv_cls/conv_reg(含pred卷積),其中pred的卷積都是nn.Conv2D而不是BaseConv,而且conv_reg的pred是包含了obj一起的是5維度的,其實一起做或拆開做是一樣的,在曠視原版obj和reg的pred是拆分的。score是cls和obj想乘再開了個根號。
(3) yoloe:也是解耦的頭,但是分類迴歸沒有公用部分,一開始就分離cls和reg成為stem_cls和stem_reg,還加上了ESEAttn注意力,然後是nn.Conv2D的pred層卷積,還加上了一個projection conv。在預測的時候,neck的特徵先過一層adaptive_avg_pool2d,再短路連線之類的總之就像TOOD那樣。
(4) yolov6:和yolox一樣。。。
(5) yolov7:P5的幾個基礎版本像L X,還是和yolov3一樣簡潔直接就是一層卷積直接輸出分類迴歸結果,但是使用了YOLOR的ImplicitA和ImplicitM隱式引數機制,訓練的時候正常過,預測的時候會做一個引數融合。另外P6模型的時候就加了aux就得再加4個卷積層。通道數依然是3x(num_classes + 5),score依然是cls和obj想乘。
5. loss(label assign):
到了最激動人心的loss部分了,應該是各家YOLO閃光點的地方了。loss呼叫一般寫在 ppdet/modeling/ heads裡,主要看各個head裡的get_loss()函式,而定義一般寫在 ppdet/modeling/ losses裡:

(1) yolov5:anchor based,標籤分配是傳統的手動設計的anchor網格和gt匹配,cls obj都是BCEWithLogitsLoss,而reg採用了ciou loss。loss_weight是cls 0.5 obj 1.0 reg 0.05,obj還有一個balance[4.0, 1.0, 0.4]進一步乘上去加權obj。此外為了適配不同batch size的範圍,總loss是乘以了總batch size的,這樣就無論訓什麼batch size都保持lr設定不變,但還是總batch size大點訓精度會更高。
(2) yolox:anchor free,標籤分配採用simota能自動決定每個gt擁有多少正樣本和來自哪一層,cls和obj採用F.binary_cross_entropy,reg採用IouLoss,loss_weight除了IouLoss權重為5其餘均為1,而L1 loss是最後15epoch才加配合著關閉mosaic。
(3) yoloe:anchor free,標籤分配是聯合策略,前1/3時期ATSS+後1/3時期TAL,cls使用了VFL(varifocal_loss), reg使用了GIoULoss,還有df_loss,值得一提的是L1 loss雖然也計算了但是沒有加到總loss上,估計只是為了看收斂情況吧,因為確實L1 loss最能體現收斂。loss_weight是cls 1.0 iou 2.5 dfl 0.5,看樣子也是實驗過很多次調過了。
(4) yolov6:和yolox一樣。。。iou loss換成了ciou和siou。
(5) yolov7: 標籤分配方面,結合yolov5和Yolox,將simOTA中的第一步“使用中心先驗”替換成“yolov5中的策略”,至於Aux Head是為對應的主head加強每層特徵精修的。這個文章講的還不錯,yolov7正負樣本分配詳解 - 騷騷騷的文章 - 知乎 https:// zhuanlan.zhihu.com/p/54 3160484 。剩下的loss方面和yolov5一樣,loss_weight是cls 0.3 obj 0.7 reg 0.05,此外tiny版本和P6的幾個大模型loss_weight也不同。
6. data aug
這方面其實各個模型都可以借用過去試試。PaddleDetection的資料增強基本都用寫在 ppdet/data/transform/operators.py 和 ppdet/data/transform/batch_operators.py,直接搜OP名字就能看到定義。配置檔案都在各自的xxx_reader.yml裡

(1) yolov5 v6 v7:reader基本一致,Mosaic裡是用的random_perspective仿射變換,還有非常耗時的圖片hsv變換。v7的Mosaic裡還加了mixup和paste_in。
(2) yolox:mosaic是用的random_affine仿射變換,使用mixup當copy paste用,還有一個另類的hsv變換。最後15個epoch會關閉Mosaic使用L1 loss,因為做了mosaic會把原圖的gt相對變小改變了分佈,最後關閉mosaic相當於加大了變相加大了訓練的gt分佈,這個肯定是有增益的。
(3) yoloe:只有最基本的,除了RandomDistort、RandomExpand、RandomCrop,ppyolo前兩版還有mixup。
需要說明的是,資料增強的操作會影響訓練速度。其中hsv和mosaic(包括mixup copy paste等)都是非常耗時的OP,yolov5 採用了cache機制加速,後面v6 v7直接照用。mosaic是公認漲點很多的操作,從這點看yoloe很吃虧,後續看yoloe作者他們會不會加上吧。
7.其他
模型庫完整性上看,v5 v7 yoloe yolox都是s m l x齊全的,v6只有3個小模型沒有m l x版本,P6-1280尺度模型只是v5 v7有提供。
還有其他一些trick方面的:
(1)訓練epoch數:v6是訓了400epoch,v5 v6 v7 yoloe都是300epoch,yoloe後來也訓了個400epoch是比v6更高的,聽說yoloe yolox還會開放obj365預訓練權重,這可是個超級外掛。
(2)多尺度訓練方面:yoloe yolox是多尺度,v5 v6 v7是單尺度640,但是這個多尺度好像只能漲一點點精度不像兩階段那種漲很多,P6的1280尺度只有v5 v7有,訓一個可費時間了。
(3)優化器方面,全都是SGD(Momentum),但yolox v5 v6 v7還帶了nesterov,v5 v6 v7還有momentum=0.937和分組優化器不同lr等trick,估計也能漲0.幾吧,只能說v5種樹,v6 v7乘涼。
(4)NMS:關於NMS方面看了這兩個回答後才恍然大悟,【 如何評價美團提出的yolo v6? - 知乎 https://www. zhihu.com/question/5392 05443/answer/2557990982 】【如何評價Alexey Bochkovskiy團隊提出的YoloV7? - 知乎 https://www. zhihu.com/question/5419 85721/answer/2562541931 】。原來v5 v6 v7的同一套nms還有這麼多貓膩,這樣部署的時候和eval嚴格意義上就不一樣了,權重還是同一個權重,但是部署onnx後再去eval就無法得到直接eval那麼高精度,所以v5 v6 v7的確是eval出了一個虛高的精度。而yolox和yoloe這方面就做的一致,這樣子看eval的精度表格上yolox和yoloe其實是吃虧的,但是部署onnx方面不至於有掉精度的落差。
(5)啟用函式和速度的trick:v5 v7 yoloe yolox都是整體全部使用了silu啟用,因為在trt部署的時候無論是torch還是paddle都會用x*sigmoid(x)融合無去代替silu,這樣會快很多很多。而v6在大部分卷積裡使用的是relu,也有silu,看了這篇 【如何評價美團提出的yolo v6? - 張志的回答 - 知乎 https://www. zhihu.com/question/5392 05443/answer/2543602804 】後才明白,理論上來講只需要把任何yolo中的所有啟用函式換成relu,速度就可以提高20%~30%的效能。
(6)量化:v6 v7 yoloe都是使用了RepVGG,但是量化效能不佳是RepVGG的固有問題,看到PaddleSlim團隊對v6 v7 yoloe的處理也有些措施和成效,有空去試用下看看。 https:// github.com/PaddlePaddle /PaddleSlim/tree/develop/example/auto_compression
3.總結
現在YOLO各個模型庫各自為營,維護力度不一,使用者只能使用某一套,如想使用多個模型在使用時需要切換程式碼庫和調整用法。而yoloseries這個庫設計的初衷和實施方面都還是很好的,yoloseries作者也說會和PaddleDetection定期同步更新,加了個飛槳的使用者群也一直有百度的工程師回覆初學者問題。
我試驗了一下YOLOSeries的每個YOLO模型都能訓練測試ok,用起來還行吧,程式碼風格也是比較易懂了,至少是比v5系列的程式碼可讀性高很多了,二次開發也挺方便。YOLO還在繼續發展中,不知道後面還有什麼yolo模型,總之還是繼續保持學習態度吧,結合程式碼和論文一起看模型,一起來say yolo again吧!
引用連結:
YOLOSeries :
- React 原理系列 —— Hook 是這樣工作的
- A100 買不到了,只有小顯示卡怎麼訓大模型
- MedISeg:面向醫學影象語義分割的技巧、挑戰和未來的方向
- CoRL 2022 | SurroundDepth: 自監督環視深度估計
- 【機器學習】邏輯迴歸(非常詳細)
- dnn實踐-特徵處理
- Google資料安全自動化建設之路(白皮書)
- 用typescript型別來實現快排
- 基於自建 VTree 的全鏈路埋點方案
- 除了鮑威爾講話,全球央行年會還揭露了什麼?
- coost v3.0.0 (微型boost庫)釋出
- 仔細研究 Go(golang) 型別系統
- 乾貨 | 嵌入式資料分析最佳實踐
- 關於高頻量化交易的程式碼專案
- 徹底解決 qiankun 找不到入口的問題
- VIM 外掛推薦
- 全網最通透:MySQL 的 redo log 保證資料不丟的原理
- 蟻群演算法的簡要分析
- 7月美聯儲會議紀要
- 容器平臺架構之道