境搭建與訓(xùn)練全流程實(shí)戰(zhàn)指南)
1. 為什么選擇PaddleDetection而不是其他檢測框架如果你正在讀這篇文章大概率是準(zhǔn)備入坑目標(biāo)檢測或者手頭有個實(shí)際項(xiàng)目需要快速跑通一個檢測模型。我當(dāng)初選PaddleDetection的原因很直接開箱即用的模型庫足夠全中文文檔友好而且從訓(xùn)練到部署的鏈路是打通的。這一點(diǎn)對剛接觸檢測任務(wù)的人來說太重要了因?yàn)槟繕?biāo)檢測不像圖像分類從數(shù)據(jù)標(biāo)注、anchor設(shè)計(jì)、數(shù)據(jù)增強(qiáng)到后處理NMS每一步都有坑如果框架本身再不省心很容易在環(huán)境階段就勸退。PaddleDetection是飛槳生態(tài)下的目標(biāo)檢測開發(fā)套件覆蓋了PP-YOLOE、YOLOv3、Faster R-CNN、SOLOv2、Swin Transformer等主流檢測和實(shí)例分割模型同時(shí)提供了從數(shù)據(jù)準(zhǔn)備、模型訓(xùn)練、評估到導(dǎo)出部署的完整工具鏈。它解決的核心問題是讓開發(fā)者不用從零手寫網(wǎng)絡(luò)結(jié)構(gòu)和訓(xùn)練循環(huán)通過配置文件就能切換模型和數(shù)據(jù)集。適合的人群包括計(jì)算機(jī)視覺方向的學(xué)生、需要快速驗(yàn)證檢測方案的算法工程師、以及想把檢測模型落地到實(shí)際業(yè)務(wù)中的開發(fā)者。但我要先潑一盆冷水環(huán)境搭建這一步官方文檔給的是理想路徑實(shí)際執(zhí)行時(shí)會遇到各種版本沖突、依賴缺失、CUDA不匹配的問題。我前后在三臺不同配置的機(jī)器上裝過PaddleDetection每次都會碰到不一樣的情況。所以這篇文章不會只給你一條命令讓你復(fù)制粘貼而是把每一步背后的邏輯講清楚讓你遇到報(bào)錯時(shí)知道該往哪個方向排查。提示本文基于PaddleDetection 2.x版本系列撰寫不同小版本之間API和目錄結(jié)構(gòu)可能有差異建議先確認(rèn)你下載的版本號。2. 環(huán)境搭建前的硬件與軟件版本決策2.1 先搞清楚你的顯卡和CUDA版本這一步很多人會跳過直接pip install paddlepaddle就完事了結(jié)果跑訓(xùn)練時(shí)發(fā)現(xiàn)用的是CPU速度慢到懷疑人生。PaddleDetection的訓(xùn)練幾乎必須依賴GPU除非你只是跑一個推理demo看看效果。你需要先確認(rèn)三件事顯卡型號和顯存大小顯存直接決定了你能用多大的batch size和輸入分辨率。6GB顯存跑YOLOv3的416輸入基本是極限8GB以上會舒服很多。CUDA驅(qū)動版本在終端執(zhí)行nvidia-smi右上角會顯示當(dāng)前驅(qū)動支持的CUDA版本。注意這是驅(qū)動支持的最高版本不是你實(shí)際安裝的CUDA版本。cuDNN版本PaddlePaddle的GPU版本對cuDNN有最低版本要求一般CUDA 10.2對應(yīng)cuDNN 7.6以上CUDA 11.x對應(yīng)cuDNN 8.x以上。我踩過的一個坑是機(jī)器上裝了CUDA 11.6但PaddlePaddle當(dāng)時(shí)只支持到CUDA 11.2結(jié)果裝完GPU版paddle后paddle.utils.run_check()一直報(bào)找不到動態(tài)庫。后來降級CUDA才解決。所以不要盲目追求最新CUDA版本要看飛槳官方文檔里明確支持的版本列表。2.2 Python環(huán)境隔離別在base環(huán)境里亂裝我強(qiáng)烈建議用conda創(chuàng)建一個獨(dú)立環(huán)境不要直接在系統(tǒng)Python或base環(huán)境里裝。原因很簡單PaddleDetection依賴的opencv、numpy、pycocotools等庫版本要求比較嚴(yán)格和你系統(tǒng)里已有的其他項(xiàng)目很容易沖突。conda create -n paddledet python3.8 -y conda activate paddledet為什么選Python 3.8而不是3.9或3.10因?yàn)镻addleDetection 2.x在Python 3.8上的兼容性驗(yàn)證最充分很多預(yù)編譯的依賴包也是針對3.8構(gòu)建的。我試過Python 3.10pycocotools編譯時(shí)直接報(bào)錯折騰了半天還是退回3.8。2.3 PaddlePaddle安裝CPU版還是GPU版這是第一個關(guān)鍵決策點(diǎn)。如果你只是想在本地跑通推理流程、看看模型效果CPU版就夠了安裝簡單不會有CUDA兼容問題。但如果你要訓(xùn)練模型必須裝GPU版。GPU版安裝命令需要根據(jù)你的CUDA版本選擇# CUDA 10.2 python -m pip install paddlepaddle-gpu2.4.2 -i https://pypi.tuna.tsinghua.edu.cn/simple # CUDA 11.2 python -m pip install paddlepaddle-gpu2.4.2.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html安裝完成后一定要驗(yàn)證import paddle paddle.utils.run_check()如果輸出PaddlePaddle is installed successfully!并且顯示了GPU信息說明安裝正確。如果只顯示CPU信息那說明裝的是CPU版或者GPU版沒有正確識別到顯卡。注意run_check()如果報(bào)libcudnn.so找不到大概率是cuDNN路徑?jīng)]有加到LD_LIBRARY_PATH里。可以在~/.bashrc中追加export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATH然后source一下。3. PaddleDetection源碼獲取與依賴安裝的完整鏈路3.1 下載源碼的正確姿勢PaddleDetection的代碼托管在GitHub上國內(nèi)直接clone可能會很慢。我的做法是先用gitee鏡像或者直接下載release壓縮包。git clone https://github.com/PaddlePaddle/PaddleDetection.git cd PaddleDetection git checkout release/2.6 # 切換到穩(wěn)定分支為什么要checkout到release分支而不是直接用develop因?yàn)閐evelop分支的代碼可能包含未測試的新特性依賴文件也可能和文檔不一致。release分支是經(jīng)過驗(yàn)證的穩(wěn)定版本適合實(shí)際項(xiàng)目使用。下載完成后先看一眼目錄結(jié)構(gòu)PaddleDetection/ ├── configs/ # 模型配置文件 ├── ppdet/ # 核心代碼 ├── tools/ # 訓(xùn)練、評估、導(dǎo)出腳本 ├── deploy/ # 部署相關(guān) ├── dataset/ # 數(shù)據(jù)集存放目錄需自己創(chuàng)建 └── requirements.txt # 依賴列表3.2 安裝依賴時(shí)最容易卡住的三個地方直接執(zhí)行pip install -r requirements.txt看起來很簡單但實(shí)際執(zhí)行時(shí)大概率會卡在以下三個地方第一個是pycocotools。這個包在Windows上編譯需要Visual C Build Tools在Linux上需要cython和numpy先裝好。如果報(bào)錯可以先單獨(dú)裝pip install cython numpy pip install pycocotools第二個是opencv-python。PaddleDetection要求的是opencv-python但如果你系統(tǒng)里已經(jīng)裝了opencv-contrib-python或者opencv-python-headless會產(chǎn)生沖突。建議先卸載再裝pip uninstall opencv-python opencv-contrib-python opencv-python-headless -y pip install opencv-python4.5.5.64第三個是PaddleDetection自身的安裝。在PaddleDetection根目錄下執(zhí)行pip install -e .這個-e參數(shù)是可編輯安裝的意思它會把當(dāng)前目錄加入到Python的包搜索路徑中這樣你修改ppdet目錄下的代碼后不需要重新安裝就能生效。如果你不做這一步直接跑tools/train.py會報(bào)ModuleNotFoundError: No module named ppdet。3.3 驗(yàn)證安裝是否成功安裝完成后用官方提供的一個小測試來驗(yàn)證python tools/infer.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml \ -o weightshttps://paddledet.bj.bcebos.com/models/yolov3_mobilenet_v1_270e_coco.pdparams \ --infer_imgdemo/000000014439.jpg如果一切正常會在output目錄下生成一張帶檢測框的圖片。這一步能跑通說明環(huán)境基本沒問題了。我遇到過的情況是命令執(zhí)行后卡在下載權(quán)重那里不動。這是因?yàn)闄?quán)重文件托管在遠(yuǎn)程服務(wù)器上網(wǎng)絡(luò)不穩(wěn)定時(shí)會超時(shí)。解決辦法是手動下載權(quán)重文件放到本地然后用-o weights本地路徑指定。4. 跑通第一個訓(xùn)練任務(wù)從配置文件到模型收斂4.1 配置文件的結(jié)構(gòu)與繼承機(jī)制PaddleDetection的配置文件用的是YAML格式而且支持繼承。什么意思呢比如yolov3_mobilenet_v1_270e_coco.yml這個文件里可能只有幾行_BASE_: [ ../datasets/coco_detection.yml, ../runtime.yml, _base_/optimizer_270e.yml, _base_/yolov3_mobilenet_v1.yml, _base_/yolov3_reader.yml, ]它通過_BASE_字段繼承了數(shù)據(jù)集配置、優(yōu)化器配置、模型結(jié)構(gòu)配置和數(shù)據(jù)讀取配置。這種設(shè)計(jì)的好處是你只需要修改差異部分不用把整個配置復(fù)制一遍。但壞處是新手容易看暈不知道某個參數(shù)到底在哪個文件里定義的。我的建議是先用python tools/train.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml --eval跑一遍然后在訓(xùn)練日志里找到實(shí)際生效的完整配置把它打印出來對照著看。4.2 數(shù)據(jù)集準(zhǔn)備COCO格式還是VOC格式PaddleDetection支持COCO和VOC兩種數(shù)據(jù)格式。COCO格式更通用但標(biāo)注文件是一個大的jsonVOC格式是每張圖對應(yīng)一個xml更直觀。如果你用自己的數(shù)據(jù)我建議轉(zhuǎn)成COCO格式因?yàn)镻addleDetection里大部分預(yù)訓(xùn)練模型都是基于COCO訓(xùn)練的用COCO格式可以直接復(fù)用數(shù)據(jù)增強(qiáng)策略和評估指標(biāo)。數(shù)據(jù)集目錄結(jié)構(gòu)應(yīng)該是這樣的dataset/coco/ ├── annotations/ │ ├── instances_train2017.json │ └── instances_val2017.json ├── train2017/ │ ├── 000000000001.jpg │ └── ... └── val2017/ ├── 000000000002.jpg └── ...然后在配置文件中修改dataset_dir指向這個目錄。4.3 啟動訓(xùn)練與關(guān)鍵參數(shù)解讀python tools/train.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml \ --eval \ -o use_gputrue \ -o TrainReader.batch_size4 \ -o LearningRate.base_lr0.001這里幾個參數(shù)值得展開說batch_size顯存不夠時(shí)第一個要調(diào)的就是它。但注意batch_size減小后學(xué)習(xí)率也要相應(yīng)減小否則訓(xùn)練容易震蕩。經(jīng)驗(yàn)公式是new_lr base_lr * new_batch_size / base_batch_size。base_lrYOLOv3在COCO上的默認(rèn)學(xué)習(xí)率是0.001對應(yīng)batch_size8。如果你改成batch_size4學(xué)習(xí)率建議改成0.0005。--eval每訓(xùn)練一個epoch后在驗(yàn)證集上評估一次。這會增加訓(xùn)練時(shí)間但能讓你實(shí)時(shí)看到mAP的變化判斷模型是否在正常收斂。訓(xùn)練過程中重點(diǎn)關(guān)注loss的變化。正常情況下前幾個epoch loss會快速下降然后趨于平緩。如果loss一直不降或者變成nan大概率是學(xué)習(xí)率太大或者數(shù)據(jù)有問題。提示訓(xùn)練日志默認(rèn)保存在output目錄下可以用visualdl --logdir output啟動VisualDL可視化查看loss曲線和mAP曲線。5. 環(huán)境搭建中那些文檔不會告訴你的坑5.1 顯存明明夠卻報(bào)OOM這個坑我遇到過好幾次。明明nvidia-smi顯示顯存還剩很多但訓(xùn)練時(shí)就是報(bào)Out of Memory。原因通常是PaddlePaddle默認(rèn)會申請全部顯存而不是按需分配。解決辦法是在訓(xùn)練命令中加上-o use_gputrue -o GPUDeviceId0或者在代碼中設(shè)置import paddle paddle.set_device(gpu:0)另外如果你同時(shí)跑了多個訓(xùn)練任務(wù)每個任務(wù)都會嘗試占用顯存這時(shí)候需要指定不同的GPUDeviceId。5.2 數(shù)據(jù)增強(qiáng)導(dǎo)致的詭異報(bào)錯PaddleDetection默認(rèn)開啟了Mosaic、RandomExpand等數(shù)據(jù)增強(qiáng)。這些增強(qiáng)在COCO數(shù)據(jù)集上沒問題但如果你自己的數(shù)據(jù)集圖片尺寸特別小或者標(biāo)注框特別密集可能會觸發(fā)一些邊界條件的bug。我遇到過一次所有圖片的標(biāo)注框都被Mosaic拼接到了一起導(dǎo)致小目標(biāo)完全丟失訓(xùn)練出來的模型對小目標(biāo)檢測效果極差。后來在配置文件中把Mosaic的概率調(diào)低問題才解決。TrainReader: sample_transforms: - Decode: {} - Mosaic: prob: 0.5 # 從默認(rèn)的1.0降到0.55.3 評估指標(biāo)不漲反降的排查思路訓(xùn)練過程中如果發(fā)現(xiàn)mAP不升反降按以下順序排查檢查學(xué)習(xí)率是否過大loss曲線如果劇烈震蕩說明學(xué)習(xí)率太大。檢查數(shù)據(jù)標(biāo)注是否有問題用tools/infer.py在訓(xùn)練集上跑幾張圖看看模型預(yù)測的框和真實(shí)標(biāo)注是否對得上。檢查是否過擬合如果訓(xùn)練loss持續(xù)下降但驗(yàn)證mAP下降說明過擬合了需要增加數(shù)據(jù)增強(qiáng)或減小模型復(fù)雜度。檢查類別不平衡如果某些類別的樣本特別少模型會偏向預(yù)測多的類別。可以用--classwise參數(shù)查看每個類別的AP。5.4 導(dǎo)出推理模型時(shí)的版本兼容問題訓(xùn)練完成后用tools/export_model.py導(dǎo)出推理模型python tools/export_model.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml \ -o weightsoutput/yolov3_mobilenet_v1_270e_coco/best_model.pdparams \ --output_dirinference_model導(dǎo)出的模型包含model.pdmodel和model.pdiparams兩個文件。注意導(dǎo)出時(shí)的PaddlePaddle版本必須和推理時(shí)的版本一致否則會報(bào)模型格式不兼容。我試過用2.4訓(xùn)練、2.3推理直接報(bào)錯。所以生產(chǎn)環(huán)境中要鎖定版本。6. 從跑通到用好幾個提升效率的實(shí)操建議6.1 用AMP混合精度訓(xùn)練省顯存如果你的顯卡支持Tensor CoreRTX系列、V100等開啟AMP混合精度訓(xùn)練可以節(jié)省約30%的顯存同時(shí)訓(xùn)練速度提升20%左右。在配置文件中加一行use_amp: true或者在命令行中python tools/train.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml -o use_amptrue但要注意AMP在某些模型上可能會導(dǎo)致訓(xùn)練不穩(wěn)定如果發(fā)現(xiàn)loss異常先關(guān)掉AMP試試。6.2 預(yù)訓(xùn)練權(quán)重的正確加載方式PaddleDetection加載預(yù)訓(xùn)練權(quán)重有兩種方式pretrain_weights和weights。前者是在模型初始化時(shí)加載骨干網(wǎng)絡(luò)的權(quán)重后者是加載整個模型的權(quán)重繼續(xù)訓(xùn)練。# 加載骨干網(wǎng)絡(luò)預(yù)訓(xùn)練權(quán)重 pretrain_weights: https://paddledet.bj.bcebos.com/models/pretrained/MobileNetV1_pretrained.pdparams # 斷點(diǎn)續(xù)訓(xùn) weights: output/yolov3_mobilenet_v1_270e_coco/model_final斷點(diǎn)續(xù)訓(xùn)時(shí)優(yōu)化器狀態(tài)和學(xué)習(xí)率調(diào)度器狀態(tài)也會被恢復(fù)所以不用擔(dān)心學(xué)習(xí)率重置的問題。6.3 多卡訓(xùn)練的啟動方式如果你有多張顯卡可以用tools/train_multi_GPU.py啟動多卡訓(xùn)練python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py \ -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml多卡訓(xùn)練時(shí)總batch_size 單卡batch_size × 顯卡數(shù)學(xué)習(xí)率也要相應(yīng)放大。但注意學(xué)習(xí)率不是線性放大的一般按平方根縮放比較穩(wěn)妥。6.4 日志分析與訓(xùn)練監(jiān)控訓(xùn)練過程中我習(xí)慣用tail -f output/yolov3_mobilenet_v1_270e_coco/train.log實(shí)時(shí)看日志。重點(diǎn)關(guān)注幾個指標(biāo)指標(biāo)正常范圍異常表現(xiàn)可能原因loss持續(xù)下降后趨于平穩(wěn)震蕩或nan學(xué)習(xí)率過大mAP逐步上升不升或下降數(shù)據(jù)問題或過擬合學(xué)習(xí)率按調(diào)度器變化不變調(diào)度器配置錯誤顯存占用穩(wěn)定持續(xù)增長內(nèi)存泄漏如果發(fā)現(xiàn)顯存持續(xù)增長大概率是數(shù)據(jù)加載器里有變量沒有釋放可以嘗試減小num_workers或者檢查自定義的數(shù)據(jù)增強(qiáng)代碼。7. 一些個人體會PaddleDetection的環(huán)境搭建本身不難難的是遇到報(bào)錯時(shí)知道去哪里找答案。我的經(jīng)驗(yàn)是先看終端報(bào)錯的最后幾行那里通常有具體的錯誤類型和文件路徑然后去PaddleDetection的GitHub Issues里搜關(guān)鍵詞大部分坑別人已經(jīng)踩過了最后才是看官方文檔因?yàn)槲臋n往往只覆蓋理想情況。另外不要在一個環(huán)境里同時(shí)裝多個深度學(xué)習(xí)框架。我試過在同一個conda環(huán)境里裝PyTorch和PaddlePaddle結(jié)果兩者的CUDA庫版本沖突折騰了一整天才找到原因?,F(xiàn)在我的做法是每個框架一個獨(dú)立環(huán)境互不干擾。最后說一個細(xì)節(jié)PaddleDetection的配置文件里有很多_BASE_繼承修改參數(shù)時(shí)一定要確認(rèn)你改的是最終生效的那個文件。我見過有人改了_base_/yolov3_reader.yml里的batch_size但實(shí)際訓(xùn)練時(shí)用的是yolov3_mobilenet_v1_270e_coco.yml里覆蓋的值導(dǎo)致改了沒生效。排查方法是在訓(xùn)練日志開頭找到打印出來的完整配置以那個為準(zhǔn)。