練實(shí)戰(zhàn):Paddle版與YOLO版對比)
前陣子仔細(xì)讀完了RT-DETR論文順手把官方PaddleDetection版和ultralytics YOLO框架下的版本都完整訓(xùn)練了一遍。說實(shí)話這兩個(gè)路徑差別不小踩坑點(diǎn)也多但跑通之后對RT-DETR的網(wǎng)絡(luò)結(jié)構(gòu)和訓(xùn)練邏輯理解會深很多。這篇筆記就專門拆一下RT-DETR的核心思路以及官方版和YOLO版訓(xùn)練到底該怎么玩適合正在上手RT-DETR、或者想把Transformer檢測器拉到實(shí)時(shí)場景里的朋友。1. RT-DETR論文核心思想解讀1.1 從DETR到RT-DETR為什么要做實(shí)時(shí)Transformer檢測器DETR系列把目標(biāo)檢測變成了端到端的集合預(yù)測問題省掉了錨框和NMS這件事本身非常吸引人。但原始DETR有兩個(gè)致命痛點(diǎn)一是收斂速度極慢動輒幾百個(gè)epoch才能看到效果二是計(jì)算量大特征圖分辨率稍微拉高一點(diǎn)自注意力的復(fù)雜度就爆炸根本談不上實(shí)時(shí)。RT-DETR要解決的就是這兩點(diǎn)。它不再像DETR那樣直接在整個(gè)特征金字塔上做全局注意力而是用混合編碼器把多尺度特征高效融合再用IoU-aware query selection從編碼器特征里挑出一批高質(zhì)量的位置向量作為query最后送到Transformer decoder里做精細(xì)回歸。這樣既保留了端到端的簡潔性又把推理速度拉到和YOLO一個(gè)量級。論文里的數(shù)據(jù)很直觀RT-DETR-R50在COCO上達(dá)到53.1% mAP速度是108 FPS比同等精度的YOLOv8快不少。理解RT-DETR關(guān)鍵要抓住三個(gè)設(shè)計(jì)混合編碼器、IoU-aware query selection、去噪訓(xùn)練。后面逐條拆。1.2 混合編碼器內(nèi)尺度交互與跨尺度融合混合編碼器是整個(gè)網(wǎng)絡(luò)加速的核心。它分成兩步先在每個(gè)尺度內(nèi)部用自注意力捕捉全局上下文也就是所謂的內(nèi)尺度交互intra-scale interaction再用跨尺度融合模塊cross-scale fusion把不同分辨率的特征進(jìn)一步整合。初看你可能會覺得這不就是FPN那套嗎其實(shí)不一樣。FPN的融合主要靠簡單的加法或concatenate而RT-DETR的跨尺度融合用的是可變性注意力deformable attention讓不同尺度的特征之間能夠自適應(yīng)地對齊和聚合。整個(gè)過程有點(diǎn)像把YOLO的PANet結(jié)構(gòu)換成了Attention版本全局感受野保留住了但計(jì)算量卻沒有像標(biāo)準(zhǔn)自注意力那樣隨分辨率平方增長。實(shí)操中要注意混合編碼器的輸出是多個(gè)尺度的增強(qiáng)特征這些特征會同時(shí)用于query selection和classification。所以如果你改了輸入分辨率或者換backbone混合編碼器的參數(shù)和訓(xùn)練策略都要重新適應(yīng)。我自己測試下來如果直接用RT-DETR的默認(rèn)權(quán)重去跑一個(gè)分辨率差距很大的數(shù)據(jù)集效果會明顯下降因?yàn)榭绯叨茸⒁饬W(xué)到的對齊模式是針對原先的尺度分布的。1.3 IoU-aware query selection與去噪訓(xùn)練傳統(tǒng)DETR的object query是一組固定學(xué)習(xí)參數(shù)訓(xùn)練時(shí)每個(gè)query要自己學(xué)會去找目標(biāo)。RT-DETR換了個(gè)思路不再完全隨機(jī)初始化query而是從編碼器輸出特征里選top-K個(gè)點(diǎn)作為query并且使用IoU-aware分支來預(yù)測每個(gè)query和對應(yīng)目標(biāo)的IoU分?jǐn)?shù)。這樣做的好處是網(wǎng)絡(luò)一開始就知道該關(guān)注哪些位置收斂速度大幅提升而且最終輸出時(shí)還可以直接用IoU分?jǐn)?shù)來做排序替代NMS。去噪訓(xùn)練也是從DN-DETR借來的技巧。訓(xùn)練過程中會隨機(jī)破壞一部分GT框然后讓模型學(xué)習(xí)從被破壞的query里恢復(fù)出原框。這個(gè)機(jī)制的直觀理解是讓模型不只是靠匹配結(jié)果學(xué)還要學(xué)會“糾錯(cuò)”從而穩(wěn)定訓(xùn)練過程。實(shí)際訓(xùn)練里去噪部分對學(xué)習(xí)率、數(shù)據(jù)增強(qiáng)擾動都比較敏感尤其是如果你在自定義數(shù)據(jù)集上訓(xùn)練GT框很小或者物體密集去噪噪聲比例調(diào)太大會導(dǎo)致模型過度依賴GT反而影響泛化。2. 官方版訓(xùn)練PaddleDetection下的RT-DETR復(fù)現(xiàn)2.1 環(huán)境安裝PaddlePaddle的坑與解決官方RT-DETR是基于PaddlePaddle的PaddleDetection實(shí)現(xiàn)的所以訓(xùn)練第一步就是裝PaddlePaddle和PaddleDetection。我用的Python 3.9、CUDA 11.8環(huán)境安裝命令很簡單python -m pip install paddlepaddle-gpu2.5.2.post118 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html pip install paddlepaddle-gpu git clone https://github.com/PaddlePaddle/PaddleDetection.git cd PaddleDetection pip install -r requirements.txt python setup.py install這里的坑有幾個(gè)PaddlePaddle的GPU版本很容易裝成CPU版本需要確認(rèn)import paddle; paddle.utils.run_check()能通過。另外PaddleDetection和PaddlePaddle的版本有對應(yīng)關(guān)系用最新的PaddleDetection配合老PaddlePaddle會有算子兼容問題。我一開始裝了paddlepaddle 2.6但PaddleDetection還是舊的直接跑就會報(bào)rpn相關(guān)算子錯(cuò)誤后來統(tǒng)一到2.5.2才穩(wěn)定。如果只是做CPU環(huán)境測試可以直接pip install paddlepaddle但訓(xùn)練基本別想了RT-DETR哪怕是最小的模型也很吃算力。建議至少8G顯存才能把batch size開到2左右。2.2 數(shù)據(jù)準(zhǔn)備與配置文件修改官方訓(xùn)練默認(rèn)使用COCO數(shù)據(jù)集。如果你想用自己的數(shù)據(jù)集需要把標(biāo)注轉(zhuǎn)成COCO格式目錄結(jié)構(gòu)大致是這樣dataset/ custom/ annotations/ instances_train.json instances_val.json train/ (圖片) val/ (圖片)配置文件在configs/rtdetr/rtdetr_r50vd_6x_coco.yml。打開后要改幾個(gè)關(guān)鍵地方num_classes: 80 dataset: type: COCODataSet dataset_dir: dataset/custom class_names: [cat, dog, ...] train: data_source: ann_file: annotations/instances_train.json eval: data_source: ann_file: annotations/instances_val.jsonnum_classes是你的類數(shù)量注意COCO類別中如果是多類class_names要保持和標(biāo)注文件里類別順序一致。訓(xùn)練輪數(shù)和學(xué)習(xí)率策略在文件底部可以改epochs一般自定義數(shù)據(jù)集從1x12個(gè)epoch開始調(diào)但RT-DETR的調(diào)度器和數(shù)據(jù)增強(qiáng)量比較大我建議先用6x的訓(xùn)練策略跑一遍再根據(jù)驗(yàn)證集表現(xiàn)往下減。另外PaddleDetection的訓(xùn)練配置里有個(gè)use_gpu標(biāo)志多卡訓(xùn)練會在后面命令行里指定不需要提前改。warmup_iters、base_lr這些參數(shù)在_base_里如果要調(diào)整學(xué)習(xí)率需要同時(shí)改optimizer里面的內(nèi)容否則只改頂層base_lr不一定生效。2.3 官方訓(xùn)練與評估命令配置改好后訓(xùn)練非常直接python tools/train.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml --eval -o use_gpuTrue--eval表示每個(gè)epoch結(jié)束跑一次驗(yàn)證集方便觀察mAP曲線。-o可以用來覆蓋配置文件里的任意參數(shù)比如-o snapshot_epoch1每1個(gè)epoch保存一次權(quán)重防止訓(xùn)練中途斷電丟太多進(jìn)度。我有一臺單卡GPURT-DETR-R50配合batch size2COCO全量訓(xùn)練差不多要3天。自己小數(shù)據(jù)集配合預(yù)訓(xùn)練權(quán)重通常10個(gè)epoch左右就能看到收斂趨勢。如果顯存不夠可以在命令行加-o batch_size1但梯度不穩(wěn)定建議配合--fp16半精度訓(xùn)練python tools/train.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml --amp --evalPaddleDetection的AMP開啟之后顯存占用能降低將近一半但我測試過某些操作符在AMP下數(shù)值不穩(wěn)定比如混合編碼器里的deformable attention容易出現(xiàn)NaN loss。解決方法是把losses里某些loss保持float32不過新手的話還是先別開等完全能復(fù)現(xiàn)基準(zhǔn)再折騰。評估和導(dǎo)出的命令python tools/eval.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml -o weightsoutput/rtdetr_r50vd_6x_coco/model_final.pdparams python tools/export_model.py -c configs/rtdetr/rtdetr_r50vd_6x_coco.yml -o weightsoutput/rtdetr_r50vd_6x_coco/model_final.pdparams導(dǎo)出后會得到output_inference/目錄里面有模型和配置文件可以直接用PaddleInference推理也可以轉(zhuǎn)成ONNX再轉(zhuǎn)TensorRT部署。3. YOLO版訓(xùn)練用ultralytics訓(xùn)練RT-DETR3.1 為什么要在YOLO框架里訓(xùn)練RT-DETR官方版本訓(xùn)練效果自然是最準(zhǔn)的但PaddleDetection的生態(tài)相對封閉很多習(xí)慣用YOLO系列做項(xiàng)目的人更熟悉ultralytics的API。另外ultralytics團(tuán)隊(duì)早就把RT-DETR集成進(jìn)了YOLOv8的代碼庫可以直接用yolo train命令訓(xùn)練RTDETR模型數(shù)據(jù)標(biāo)注格式還是YOLO格式?jīng)]有任何額外轉(zhuǎn)換壓力。這個(gè)“YOLO版本訓(xùn)練”最大的優(yōu)勢是工程化程度高不需要看復(fù)雜的Paddle配置數(shù)據(jù)集就是一個(gè)images/和labels/文件夾標(biāo)注是txt文本訓(xùn)練過程中自動做緩存、數(shù)據(jù)增強(qiáng)、學(xué)習(xí)率調(diào)度、早停和best權(quán)重保留。適合快速迭代和部署驗(yàn)證。當(dāng)然代價(jià)是ultralytics實(shí)現(xiàn)和官方PaddleDetection實(shí)現(xiàn)有一些細(xì)節(jié)差異比如數(shù)據(jù)增強(qiáng)策略、loss組合系數(shù)、訓(xùn)練schedule不完全一致所以同樣訓(xùn)練輪數(shù)下精度表現(xiàn)會有幾個(gè)點(diǎn)的浮動。如果你最后要上生產(chǎn)我會建議兩個(gè)版本都訓(xùn)練一遍YOLO版跑通流程和做快速AB官方版作為精度上限的參考。3.2 數(shù)據(jù)格式轉(zhuǎn)換從COCO到Y(jié)OLO格式Y(jié)OLO訓(xùn)練需要的數(shù)據(jù)格式是每個(gè)txt文件對應(yīng)一張圖片每一行是一個(gè)目標(biāo)class x_center y_center width height這里的坐標(biāo)是歸一化到0~1之間的。如果你的原始數(shù)據(jù)是COCO格式寫個(gè)小腳本轉(zhuǎn)一下就行我直接用一段Python處理import os import json from PIL import Image def coco2yolo(coco_json, img_dir, out_labels): with open(coco_json, r) as f: data json.load(f) id_to_name {img[id]: img[file_name] for img in data[images]} cat_id_map {cat[id]: i for i, cat in enumerate(data[categories])} os.makedirs(out_labels, exist_okTrue) for ann in data[annotations]: img_id ann[image_id] file_name id_to_name[img_id] label_path os.path.join(out_labels, file_name.rsplit(., 1)[0] .txt) img_path os.path.join(img_dir, file_name) img_w, img_h Image.open(img_path).size x, y, w, h ann[bbox] x_center (x w / 2) / img_w y_center (y h / 2) / img_h w / img_w h / img_h cat_id cat_id_map[ann[category_id]] with open(label_path, a) as out: out.write(f{cat_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)注意COCO的bbox格式是[x, y, width, height]左上角原點(diǎn)轉(zhuǎn)換時(shí)一定記得歸一化。標(biāo)簽txt文件必須和圖片同名且放在labels/下。ultralytics訓(xùn)練時(shí)的數(shù)據(jù)集配置是# dataset.yaml path: D:/projects/rtdetr_yolo/dataset train: images/train val: images/val names: 0: cat 1: dognames里的類別索引必須和txt里的第一列嚴(yán)格對應(yīng)。類別數(shù)說是無所謂但超過50個(gè)類時(shí)names列表要完整否則訓(xùn)練報(bào)錯(cuò)。3.3 用yolo命令訓(xùn)練RT-DETRYOLO版本訓(xùn)練RT-DETR非常簡單先安裝ultralytics庫pip install ultralytics然后直接yolo train datadataset.yaml modelrtdetr-l.pt epochs100 imgsz640 batch8第一次運(yùn)行會從官方權(quán)重庫下載rtdetr-l.pt作為預(yù)訓(xùn)練權(quán)重。model參數(shù)也可以指定為rtdetr-l.yaml表示從零開始訓(xùn)練不加載預(yù)訓(xùn)練權(quán)重。實(shí)際中除非你的數(shù)據(jù)集和COCO差異極大否則強(qiáng)烈建議用預(yù)訓(xùn)練權(quán)重微調(diào)收斂速度和最終精度都要好很多。如果你習(xí)慣用Python腳本可以使用ultralytics的APIfrom ultralytics import RTDETR model RTDETR(rtdetr-l.pt) model.train(datadataset.yaml, epochs100, imgsz640, batch8, projectruns/rtdetr, nameexp1)這里有幾個(gè)參數(shù)很重要batch根據(jù)顯存調(diào)整8G顯存建議batch416G可以batch8。imgszRT-DETR原始輸入是640如果你的目標(biāo)較小可以調(diào)到800或960但顯存和訓(xùn)練時(shí)間會成倍增加。lr0初始學(xué)習(xí)率默認(rèn)是0.01自定義小數(shù)據(jù)集時(shí)經(jīng)常需要降低到0.002~0.005否則預(yù)熱階段就會有較大震蕩。在訓(xùn)練日志里可以看box_loss、cls_loss、dfl_loss和mAP50等指標(biāo)RT-DETR的loss里沒有DFL我記得日志里是GIoU和Class損失不過ultralytics做了統(tǒng)一接口輸出字段基本都是類似的。訓(xùn)練結(jié)束后模型會保存在runs/rtdetr/exp1/weights/best.pt直接用model.predict做推理或者用導(dǎo)出的ONNX做服務(wù)部署。3.4 兩種訓(xùn)練路徑的對比我用同一個(gè)自定義數(shù)據(jù)集分別跑了官方版和YOLO版數(shù)據(jù)集規(guī)模約8000張圖20個(gè)類訓(xùn)練100個(gè)epoch。我的感受是對比項(xiàng)官方PaddleDetection版ultralytics YOLO版環(huán)境配置復(fù)雜度較高依賴Paddle生態(tài)低pip安裝即可數(shù)據(jù)標(biāo)注格式COCO格式轉(zhuǎn)來轉(zhuǎn)去直接用YOLO txt格式訓(xùn)練速度同batch略快一點(diǎn)后端優(yōu)化好相差不大但顯存占用略高默認(rèn)精度略高推理時(shí)NMS策略更細(xì)致精調(diào)后也能接近默認(rèn)差1~2個(gè)點(diǎn)部署便利性導(dǎo)出PaddleInference/TensorRTONNX/TensorRT/NCNN都支持很順自定義數(shù)據(jù)友好度需要改yamljson只需要一個(gè)dataset.yaml如果你的項(xiàng)目原本就基于YOLO數(shù)據(jù)流建議直接用ultralytics版本快速驗(yàn)證效果如果追求精度上限或需要和Paddle產(chǎn)線集成再上官方版做一次基準(zhǔn)。4. 訓(xùn)練RT-DETR時(shí)的常見坑與疑難排查4.1 顯存溢出大分辨率、大batch怎么取舍RT-DETR的Transformer decoder部分顯存開銷比較大尤其在推理階段和YOLO這種純卷積網(wǎng)絡(luò)完全不同。我在8G顯存的GPU上imgsz640、batch8訓(xùn)練rtdetr-l直接OOM。解決辦法有三個(gè)方向降低batch size到2或4開啟梯度累積ultralytics直接設(shè)置batch4配合accumulate4效果相當(dāng)于batch16但訓(xùn)練時(shí)間會拉長使用ampTrue混合精度訓(xùn)練RT-DETR在AMP下基本能保持精度顯存可以降低約40%。如果你用的是官方PaddleDetection版感覺顯存不夠還可以考慮換小backbone比如rtdetr_r18vd_5x_coco.yml一樣能跑只是精度低一些。YOLO版則可以直接用rtdetr-l.pt切換到rtdetr-x.pt顯存需求會顯著增加不建議小卡用戶直接上x。4.2 損失函數(shù)調(diào)整GIoU、L1和分類損失的權(quán)重平衡RT-DETR的損失函數(shù)包含三部分分類損失的cls_loss、回歸的L1 loss和GIoU loss。官方權(quán)重默認(rèn)是cls_loss1、L1_loss5、GIoU_loss2。這個(gè)比例對大多數(shù)通用目標(biāo)檢測是合適的但有幾種例外情況需要手動調(diào)整小目標(biāo)很多L1 loss對小目標(biāo)的位置誤差比較敏感權(quán)重不變的話會過度關(guān)注小目標(biāo)的邊界導(dǎo)致大目標(biāo)定位偏差。我寫過官方配置文件把L1權(quán)重從5降到3GIoU權(quán)重升到2.5小目標(biāo)的Recall提升比較明顯。密集場景、物體互相遮擋GIoU權(quán)重可以提升到3以上讓模型更重視框之間的重疊關(guān)系。類別不均衡如果某些類樣本很少分類損失權(quán)重可以適當(dāng)調(diào)高但不要直接調(diào)節(jié)點(diǎn)權(quán)重優(yōu)先考慮用focal loss或者調(diào)整采樣策略。在ultralytics里改權(quán)重要小心它把RT-DETR的loss掛在model.loss里想改只能改源碼或者用回調(diào)函數(shù)。實(shí)際項(xiàng)目中我一般不動默認(rèn)權(quán)重先看日志里的各類loss走勢再決定要不要介入。4.3 訓(xùn)練不收斂學(xué)習(xí)率、數(shù)據(jù)增強(qiáng)與BN層的那些事我碰到最多的問題是無腦訓(xùn)練導(dǎo)致前50個(gè)epoch完全學(xué)不動或者loss直接發(fā)散。原因基本都是學(xué)習(xí)率或數(shù)據(jù)增強(qiáng)不匹配。官方版RT-DETR默認(rèn)是在COCO上預(yù)訓(xùn)練過的使用6xschedulebase_lr0.0001warmup 1000步。如果你換到自定義數(shù)據(jù)集數(shù)據(jù)量只有幾千張這個(gè)學(xué)習(xí)率會偏大導(dǎo)致一開始loss就飛。建議先用官方相同的學(xué)習(xí)率訓(xùn)練5個(gè)epoch看看如果loss上下跳動嚴(yán)重就調(diào)低到0.00002再試。YOLO版默認(rèn)初始學(xué)習(xí)率是0.01配合cos_lrTrue衰減。但RT-DETR的Transformer模塊對學(xué)習(xí)率比CNN敏感我試過幾次降到0.005收斂更穩(wěn)定。如果你用的是rtdetr-x這種大模型建議初始學(xué)習(xí)率直接乘以0.5。另外無論哪個(gè)版本都要確保輸入圖片做了歸一化到0~1的操作。RT-DETR的backbone對輸入均值方差有要求PaddleDetection內(nèi)部會做normalizeultralytics庫里也自動處理了。但如果你用ONNX導(dǎo)出后自己寫預(yù)處理代碼務(wù)必保持和訓(xùn)練一致很多部署生效不佳的案例都出在這個(gè)環(huán)節(jié)。4.4 推理部署從PyTorch到ONNX再到TensorRTYOLO版訓(xùn)練完部署相對簡單yolo export modelruns/rtdetr/exp1/weights/best.pt formatonnx simplifyTrue導(dǎo)出的ONNX可以直接用ONNXRuntime跑也可以轉(zhuǎn)成TensorRT engine加速。RT-DETR的ONNX導(dǎo)出整體比較順利只有一點(diǎn)要注意dynamic批量維度默認(rèn)關(guān)閉如果你的服務(wù)需要動態(tài)batch導(dǎo)出時(shí)加上dynamicTrue但TensorRT要先做優(yōu)化。官方版導(dǎo)出PaddleInference再轉(zhuǎn)ONNX也支持命令我前面提過。轉(zhuǎn)好ONNX后TensorRT用trtexec --onnxmodel.onnx --saveEnginemodel.engine --fp16編譯即可。實(shí)測RT-DETR轉(zhuǎn)TensorRT之后在3080上單張圖片推理時(shí)間能壓到5ms以內(nèi)比PyTorch原始模型快3倍以上和YOLOv8x的推理速度已經(jīng)很接近。部署時(shí)還有個(gè)細(xì)節(jié)RT-DETR默認(rèn)輸出是[num_queries, 6]其中6個(gè)值是[x1, y1, x2, y2, score, class]已經(jīng)不需要NMS。有些推理框架不了解這一點(diǎn)還硬是用NMS后處理反而把速度拖慢了。YOLO版本導(dǎo)出的模型會有(1, 300, 6)的輸出這里的300就是query數(shù)量直接取置信度大于閾值的框就行。最后再分享一個(gè)小技巧當(dāng)你想在自定義數(shù)據(jù)集上快速看效果時(shí)先用YOLO版訓(xùn)練100個(gè)epoch再把訓(xùn)練好的權(quán)重轉(zhuǎn)成ONNX在官方PaddleDetection的config里加載不了。反過來如果你想用官方版做最終模型可以把YOLO版訓(xùn)練好的模型權(quán)重作為預(yù)訓(xùn)練不行結(jié)構(gòu)有差異。最靠譜的方式是用官方COCO預(yù)訓(xùn)練權(quán)重在官方框架里按你的數(shù)據(jù)微調(diào)或者用YOLO版預(yù)訓(xùn)練權(quán)重在YOLO框架里微調(diào)兩者互不干擾。我個(gè)人現(xiàn)在習(xí)慣是先用YOLO版快速跑通流程和驗(yàn)證指標(biāo)確認(rèn)思路沒問題后再花時(shí)間用官方版做一次終版訓(xùn)練這樣時(shí)間和算力都能最大化利用。