練自定義數(shù)據(jù)集:從圖片到可用權(quán)重的完整實(shí)踐)
簡介面向目標(biāo)檢測初學(xué)者的YOLOv8自定義數(shù)據(jù)集訓(xùn)練完整筆記內(nèi)容覆蓋環(huán)境安裝、圖片與標(biāo)簽文件整理、數(shù)據(jù)集切分與格式轉(zhuǎn)換、模型訓(xùn)練及斷點(diǎn)續(xù)訓(xùn)等關(guān)鍵環(huán)節(jié)。整套資源僅包含一份Worddocx格式文檔大小約2.18MB以步驟化敘述和命令示例組織便于對照實(shí)操當(dāng)前已有448人學(xué)習(xí)。文檔詳細(xì)說明安裝依賴包和ultralytics庫的指令演示將標(biāo)注好的圖片與XML文件分別放入images與xml目錄、運(yùn)行腳本劃分訓(xùn)練集和驗(yàn)證集、再轉(zhuǎn)換為YOLO訓(xùn)練所需格式的做法同時(shí)逐一解釋task、mode、model、data、epochs、batch等訓(xùn)練參數(shù)的含義并介紹預(yù)訓(xùn)練模型下載與訓(xùn)練命令寫法。此外針對訓(xùn)練中可能出現(xiàn)的Arial字體下載失敗問題給出了下載并放置到項(xiàng)目根目錄的解決辦法也講解了修改default.yaml或使用resume參數(shù)實(shí)現(xiàn)斷點(diǎn)續(xù)訓(xùn)的方式能夠幫助讀者避開常見坑點(diǎn)按流程完成自定義數(shù)據(jù)集的YOLOv8訓(xùn)練。1. YOLOv8訓(xùn)練自定義數(shù)據(jù)集把“別人家的模型”變成自己的檢測器做目標(biāo)檢測的人多半經(jīng)歷過這種尷尬對著COCO上表現(xiàn)亮眼的YOLOv8權(quán)重跑demo一切正常一旦換成自己拍的安全帽、工件或者遙感圖精度直接垮掉。原因不復(fù)雜——預(yù)訓(xùn)練權(quán)重沒見過你的數(shù)據(jù)分布遷移學(xué)習(xí)也得按規(guī)矩喂數(shù)據(jù)、調(diào)參數(shù)、看損失曲線才能收斂出能用的模型。這份《YOLOv8訓(xùn)練自定義數(shù)據(jù)集》筆記就是把從原始圖片到可用權(quán)重的全過程拆開環(huán)境怎么搭、目錄怎么擺、標(biāo)注怎么轉(zhuǎn)、超參怎么改、報(bào)錯(cuò)怎么查適合手里有數(shù)據(jù)集但沒完整跑通過自訓(xùn)練流程的從業(yè)者也適合剛接觸YOLOv8想復(fù)現(xiàn)一套標(biāo)準(zhǔn)pipeline的新手。下面按我實(shí)際跑項(xiàng)目的順序來寫每一節(jié)都是踩過坑之后的固定動作。2. 環(huán)境與工程骨架先把訓(xùn)練環(huán)境裝成“不折騰”的樣子2.1 版本選型為什么我鎖在YOLOv8.1.0附近YOLOv8的ultralytics倉庫更新很頻繁幾個(gè)月不用可能API就變了。早年跑通一套流程過陣子換新版本model.train的參數(shù)表和回調(diào)接口可能已經(jīng)改了名網(wǎng)上教程對不上號。所以我一般建議鎖一個(gè)相對穩(wěn)定的版本而不是追最新。常見做法是pip install ultralytics8.1.0 pip list | grep torch邏輯說明ultralytics這個(gè)包自帶YOLOv8的模型定義、訓(xùn)練器和數(shù)據(jù)集工具版本鎖住之后復(fù)現(xiàn)成本低。torch版本建議2.0以上因?yàn)閅OLOv8的某些增強(qiáng)操作依賴新版CUDA算子老torch可能跑不起來。參數(shù)說明如果你用的是GTX 1660 Ti這類6GB顯存的顯卡torch別裝太高版本CUDA 11.8對應(yīng)的torch 2.0.x就夠用。顯存不夠時(shí)后面訓(xùn)練只能開小batch這個(gè)在第4章會細(xì)說。2.2 目錄約定一張圖理清數(shù)據(jù)、權(quán)重和輸出有了訓(xùn)練環(huán)境下一步是搭目錄。YOLOv8對數(shù)據(jù)集路徑要求不算嚴(yán)但目錄結(jié)構(gòu)清晰能讓你少踩很多“找不到文件”的坑。我習(xí)慣這樣建yolov8-custom ├── data │ ├── images │ │ ├── train │ │ └── val │ └── labels │ ├── train │ └── val ├── weights ├── runs └── config.yaml邏輯說明data/images/train放訓(xùn)練圖片data/images/val放驗(yàn)證圖片labels下是對應(yīng)的標(biāo)注txt文件。weights放預(yù)訓(xùn)練權(quán)重runs放每次訓(xùn)練的輸出目錄config.yaml是數(shù)據(jù)集配置。參數(shù)說明圖片和標(biāo)注的txt必須同名比如img001.jpg對應(yīng)img001.txt否則訓(xùn)練時(shí)YOLOv8會直接跳過這張圖。這是新手最容易翻車的地方文件名對不上訓(xùn)練半天損失不降一看日志全是“WARNING: 0 labels found”。2.3 config.yaml寫法類別數(shù)錯(cuò)了訓(xùn)練就是玄學(xué)數(shù)據(jù)集配置是訓(xùn)練入口內(nèi)容不多但每個(gè)字段都關(guān)鍵。按YOLOv8的約定path: ./data train: images/train val: images/val names: 0: helmet 1: person邏輯說明path是數(shù)據(jù)集根目錄的路徑可以用相對路徑但建議在config.yaml被調(diào)用的那個(gè)目錄下啟動訓(xùn)練避免相對路徑找不到。train和val填圖片目錄的相對路徑Y(jié)OLOv8會自動拼接。names是類別名與索引的映射順序必須和標(biāo)注txt里的類別ID一一對應(yīng)。參數(shù)說明類別ID從0開始不是1。如果標(biāo)注文件里寫了1而你的names里0對應(yīng)的才是helmet那模型訓(xùn)練時(shí)會把helmet當(dāng)背景損失曲線看著正常實(shí)際學(xué)了個(gè)寂寞。3. 數(shù)據(jù)準(zhǔn)備把VOC轉(zhuǎn)成YOLO格式轉(zhuǎn)換腳本與四個(gè)邊界坑3.1 為什么非轉(zhuǎn)不可YOLO要的是歸一化坐標(biāo)YOLOv8要求的標(biāo)注是txt格式每行一個(gè)目標(biāo)格式是class x_center y_center width height四個(gè)坐標(biāo)值都是相對于圖片寬高的歸一化結(jié)果。而我們拿到手的標(biāo)注數(shù)據(jù)最常碰到的兩種格式是VOC的XML和LabelImg導(dǎo)出的XML里面存的是xmin ymin xmax ymax絕對像素坐標(biāo)。直接用絕對坐標(biāo)喂給YOLOv8訓(xùn)練會出問題因?yàn)榭s放和增強(qiáng)時(shí)坐標(biāo)體系對不上。轉(zhuǎn)換方式不復(fù)雜核心就一步import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: class_map {helmet: 0, person: 1} xml_dir xmls out_dir labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), os.path.join(out_dir, xml_file.replace(.xml, .txt)), class_map)邏輯說明遍歷XML目錄把每個(gè)XML里的object信息解析出來計(jì)算出歸一化的中心點(diǎn)和寬高寫成一行一目標(biāo)的txt。class_map控制類別到ID的映射和config.yaml里的names必須一致。參數(shù)說明坐標(biāo)輸出保留了6位小數(shù)夠用。歸一化計(jì)算時(shí)除以的是圖片原始寬高不是縮放后的。如果你的數(shù)據(jù)集是直接用ROI截出來的圖注意核對XML里size字段和實(shí)際圖片尺寸是否一致不一致時(shí)轉(zhuǎn)換出的坐標(biāo)全是錯(cuò)的。3.2 邊界坑一類別不在映射表里轉(zhuǎn)換時(shí)如果XML里有names之外的類別比如背景、未知腳本默認(rèn)continue跳過。但訓(xùn)練時(shí)這類目標(biāo)就完全不會被學(xué)習(xí)。我的習(xí)慣是把這些類別單獨(dú)導(dǎo)出一個(gè)ignore列表方便回頭統(tǒng)計(jì)而不是直接丟。3.3 邊界坑二零面積框和超界框標(biāo)注軟件有時(shí)會標(biāo)出xmin等于xmax的框歸一化后w就是0訓(xùn)練時(shí)YOLOv8的loss計(jì)算會崩。常見解決是在轉(zhuǎn)換腳本里加一個(gè)過濾if w 0 or h 0: continue3.4 邊界坑三中文路徑和中文類名Windows下如果數(shù)據(jù)集路徑帶中文ultralytics內(nèi)部讀圖或?qū)懭罩緯r(shí)容易報(bào)編碼錯(cuò)誤。類名更直接names里如果用中文訓(xùn)練日志和輸出權(quán)重都能正常出但做部署推理時(shí)標(biāo)簽回顯會亂碼。建議所有類名只用英文字母和下劃線路徑保持ASCII。3.5 邊界坑四數(shù)據(jù)劃分別在腳本里隨機(jī)做常見做法是先把所有圖片放一個(gè)文件夾腳本按比例隨機(jī)劃分到train和val。這個(gè)思路沒錯(cuò)但要注意隨機(jī)種子問題。我見過有人跑兩次轉(zhuǎn)換同一張圖一次在train一次在val導(dǎo)致模型過擬合評估失真。正確做法是轉(zhuǎn)換前先把圖片清單寫好按清單復(fù)制圖片和對應(yīng)的txt不做多次隨機(jī)。4. 訓(xùn)練參數(shù)與啟動從命令行到損失曲線一次說清4.1 訓(xùn)練入口為什么我推薦命令行而不是Python腳本YOLOv8支持兩種訓(xùn)練方式y(tǒng)olo train命令行和from ultralytics import YOLO的Python腳本。兩種本質(zhì)跑的是同一套東西但命令行更適合做實(shí)驗(yàn)記錄參數(shù)直接打在shell里每改一次就是一個(gè)可追溯的版本。我的固定寫法是yolo train modelyolov8s.pt dataconfig.yaml epochs100 imgsz640 batch8 device0 projectruns/helmet_2024 nameexp1邏輯說明model填預(yù)訓(xùn)練權(quán)重的路徑data是數(shù)據(jù)集配置epochs是訓(xùn)練輪數(shù)imgsz是訓(xùn)練輸入尺寸batch是每個(gè)batch的圖片數(shù)device表示用哪張GPU。這行命令跑完模型權(quán)重、損失曲線、驗(yàn)證指標(biāo)全在runs/helmet_2024/exp1里。參數(shù)說明imgsz默認(rèn)640如果你的目標(biāo)是小物體比如遠(yuǎn)處的安全帽可以嘗試896或1280但顯存占用會明顯上漲。batch在6GB顯存的卡上最多開到8到16根據(jù)顯存動態(tài)調(diào)爆顯存就先減半。4.2 batch大小與顯存的博弈batch不是越大越好小顯存硬開大batch會導(dǎo)致OOM訓(xùn)練中斷。更隱蔽的問題是過小的batch加默認(rèn)的增強(qiáng)策略可能出現(xiàn)類別不均衡放大某些類別收斂慢。我一般這么測yolo train modelyolov8s.pt dataconfig.yaml epochs3 imgsz640 batch16 device0邏輯說明先用3個(gè)epoch跑通流程觀察顯存占用接近上限還是有余量再決定正式訓(xùn)練時(shí)batch的檔位。參數(shù)說明OOM時(shí)報(bào)錯(cuò)信息通常是CUDA out of memory看到這個(gè)直接調(diào)小batch。如果用了batch-1YOLOv8會自動根據(jù)顯存算最大batch但實(shí)測這個(gè)自動值偏激進(jìn)我會手動減2再跑。4.3 損失曲線怎么看何時(shí)加epoch何時(shí)該停訓(xùn)練結(jié)束后乍一看results.png幾條下降曲線訓(xùn)練集和驗(yàn)證集分開長。新手容易犯的錯(cuò)誤是只看train的loss忽略val的指標(biāo)。實(shí)際判斷標(biāo)準(zhǔn)是import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/helmet_2024/exp1/results.csv) plt.plot(df[epoch], df[train/box_loss], labeltrain box) plt.plot(df[epoch], df[val/box_loss], labelval box) plt.legend() plt.show()邏輯說明results.csv記錄每一項(xiàng)指標(biāo)包括三類loss、精度、召回率、mAP50和mAP50-95。訓(xùn)練過程中如果val/box_loss連續(xù)20個(gè)epoch不降或反升說明過擬合早停更合理不用硬跑100輪。參數(shù)說明如果前10個(gè)epoch訓(xùn)練loss就降到很低但val的mAP50不到0.3大概率是數(shù)據(jù)集類別不均衡或標(biāo)注質(zhì)量有問題先回去查數(shù)據(jù)不是調(diào)參能解決的。5. 避坑常見問題排查五條血淚經(jīng)驗(yàn)5.1 訓(xùn)練日志一堆WARNINGloss不降現(xiàn)象訓(xùn)練時(shí)日志滾動輸出WARNING: 0 labels found in ...loss數(shù)值變化不大。原因圖片文件名和標(biāo)注txt文件名不一致最常見的是圖片是.jpgtxt是.txt但大小寫不匹配或者標(biāo)注目錄下存在沒有對應(yīng)txt的圖。解決寫個(gè)腳本核對images/train和labels/train下的同名文件數(shù)量不一致的分出來處理。常見做法是for f in data/images/train/*.jpg; do base$(basename $f .jpg) if [ ! -f data/labels/train/$base.txt ]; then echo missing label: $f fi done5.2 顯存爆了但減小batch還是報(bào)錯(cuò)現(xiàn)象CUDA out of memory反復(fù)出現(xiàn)batch從16降到8仍然報(bào)錯(cuò)。原因除了batchimgsz和workers也會影響顯存。輸入圖越大特征圖越占顯存workers開太高會導(dǎo)致CPU/GPU數(shù)據(jù)搬運(yùn)排隊(duì)顯存空不出來。解決先降imgsz到416跑通后再逐步提回640。workers適中調(diào)整比如workers4。5.3 訓(xùn)練正常但驗(yàn)證結(jié)果全黑現(xiàn)象訓(xùn)練跑完驗(yàn)證集上mAP為0val_batch_labels.jpg里沒有框。原因類別映射和標(biāo)注類別ID對不上或者驗(yàn)證集中的圖本身就沒有目標(biāo)。解決檢查config.yaml的names數(shù)量和順序確認(rèn)轉(zhuǎn)換腳本里class_map完全一致。用打開一張驗(yàn)證圖和對應(yīng)的txt目測坐標(biāo)是否在圖片范圍內(nèi)。5.4 預(yù)訓(xùn)練權(quán)重加載報(bào)錯(cuò)現(xiàn)象Error loading pretrained weight但權(quán)重文件明明存在。原因下載的權(quán)重文件不完整或者權(quán)重版本和ultralytics版本不匹配。解決刪除權(quán)重文件重新下或換用YOLOv8官方提供的yolov8s.pt不要用第三方轉(zhuǎn)換的中間格式。5.5 數(shù)據(jù)集大但訓(xùn)練非常慢現(xiàn)象GPU利用率只有30%左右訓(xùn)練一個(gè)epoch耗時(shí)過長。原因數(shù)據(jù)加載瓶頸workers太低圖片存儲在高延遲設(shè)備上或者圖像解碼消耗CPU。解決把圖片壓成較小尺寸長邊不超過1280再訓(xùn)練減少解碼負(fù)擔(dān)確認(rèn)workers至少不低于4如果內(nèi)存足夠可以先將小數(shù)據(jù)集整體緩存到內(nèi)存中。但更推薦的方案是保持原圖分辨率先用一半的epoch觀察收斂趨勢再決定是否增補(bǔ)數(shù)據(jù)。6. 進(jìn)階技巧用驗(yàn)證集輸出反向查漏訓(xùn)練收斂之后第一件事不是直接部署,而是把驗(yàn)證集的預(yù)測結(jié)果導(dǎo)出來用框疊在原圖上肉眼檢查一遍。這一步能發(fā)現(xiàn)指標(biāo)上看不出來的問題比如小目標(biāo)漏檢、同類目標(biāo)緊貼時(shí)被合并框。我的習(xí)慣是跑一次model.val再把驗(yàn)證輸出目錄里的val_batch_pred.jpg逐張過一遍重點(diǎn)看置信度低于0.5的漏檢框。具體做法是from ultralytics import YOLO model YOLO(runs/helmet_2024/exp1/weights/best.pt) results model.val(dataconfig.yaml, conf0.25, iou0.6, save_jsonTrue) results.save_dir邏輯說明conf控制置信度閾值iou控制NMS的IoU閾值save_jsonTrue會導(dǎo)出COCO格式的預(yù)測結(jié)果方便后續(xù)做誤差分析。results.save_dir是可視化輸出的目錄所有預(yù)測圖都在里面。參數(shù)說明conf0.25是驗(yàn)證時(shí)的常用默認(rèn)值但實(shí)際業(yè)務(wù)場景如果要求低漏檢率可以降到0.1再看這時(shí)候能找出哪些目標(biāo)確實(shí)沒被訓(xùn)練好而不是被置信度閾值過濾掉。驗(yàn)證通過后下一步才是導(dǎo)出部署格式。如果你要部署到RK3588這類邊緣設(shè)備那邊接收的格式和PC端推理不一樣建議在導(dǎo)出ONNX之前先用yolo export modelbest.pt formatonnx imgsz640走一遍確認(rèn)輸出層的shape符合預(yù)期。在這之前先回到訓(xùn)練配置里核對類別名和預(yù)處理尺寸保證導(dǎo)出時(shí)用的參數(shù)和訓(xùn)練時(shí)完全一致。從那以后我每次訓(xùn)練完都強(qiáng)制走一遍“驗(yàn)證集視覺檢查 低置信度漏檢分析”再決定要不要調(diào)數(shù)據(jù)或加訓(xùn)練輪數(shù)。這套習(xí)慣幫我篩出過不少指標(biāo)漂亮但實(shí)際不能用的模型。希望這份筆記能幫你在YOLOv8自訓(xùn)練這條路上少翻幾次車。本文還有配套的精品資源點(diǎn)擊獲取