情緒識(shí)別實(shí)戰(zhàn):從數(shù)據(jù)標(biāo)注到實(shí)時(shí)部署全流程解析)
簡(jiǎn)介一套完整的基于YOLO的犬類(lèi)情緒識(shí)別項(xiàng)目包主要面向深度學(xué)習(xí)初學(xué)者和圖像分類(lèi)實(shí)戰(zhàn)開(kāi)發(fā)者尤其適合作為畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)或期末大作業(yè)的參考項(xiàng)目。項(xiàng)目覆蓋從數(shù)據(jù)準(zhǔn)備、模型訓(xùn)練到測(cè)試評(píng)估與結(jié)果可視化的全流程能幫助使用者理解目標(biāo)檢測(cè)與動(dòng)物情緒分類(lèi)的落地方式適用于教學(xué)演示、項(xiàng)目匯報(bào)和算法對(duì)比實(shí)驗(yàn)。壓縮包共72個(gè)文件約60.25MB其中以jpg/jpeg/png圖像樣本和結(jié)果圖為多數(shù)配合py訓(xùn)練與測(cè)試腳本、md說(shuō)明文檔、依賴(lài)文件及gif演示結(jié)果目錄中另有模型目錄、輸出結(jié)果目錄和評(píng)估圖表包含P曲線(xiàn)、R曲線(xiàn)、PR曲線(xiàn)、F1曲線(xiàn)、混淆矩陣等便于直接查看模型效果。已有56人瀏覽學(xué)習(xí)。通過(guò)源碼、樣例圖片、訓(xùn)練后的模型與說(shuō)明文檔使用者可復(fù)現(xiàn)犬類(lèi)情緒識(shí)別實(shí)驗(yàn)也可替換數(shù)據(jù)集進(jìn)行遷移訓(xùn)練節(jié)省從零搭建代碼與調(diào)參的時(shí)間是兼具教學(xué)參考和實(shí)際應(yīng)用價(jià)值的完整資料。1. 犬類(lèi)情緒識(shí)別一個(gè) YOLO 就能帶走的課程設(shè)計(jì)項(xiàng)目如果你正在為畢業(yè)設(shè)計(jì)或期末大作業(yè)發(fā)愁想找一個(gè)“深度學(xué)習(xí) 圖像識(shí)別”方向、能落地演示又能寫(xiě)進(jìn)簡(jiǎn)歷的項(xiàng)目基于 YOLO 的犬類(lèi)情緒識(shí)別是個(gè)很務(wù)實(shí)的選擇。它能對(duì)狗的臉部表情和姿態(tài)做實(shí)時(shí)分類(lèi)區(qū)分開(kāi)心、焦慮、憤怒、悲傷、平靜這幾類(lèi)典型情緒狀態(tài)覆蓋從數(shù)據(jù)標(biāo)注、模型訓(xùn)練到攝像頭推理的完整流程。這套資源最大的價(jià)值不是“識(shí)別有多準(zhǔn)”而是把深度學(xué)習(xí)中目標(biāo)檢測(cè)落地的完整鏈路都串起來(lái)了數(shù)據(jù)怎么清洗、VOC 標(biāo)注怎么轉(zhuǎn) YOLO 格式、訓(xùn)練參數(shù)怎么調(diào)、攝像頭實(shí)時(shí)推理怎么接、模型怎么導(dǎo)出部署。對(duì)新手來(lái)說(shuō)它是一份能跟到最后的完整代碼包對(duì)已經(jīng)有基礎(chǔ)的人來(lái)說(shuō)它也能當(dāng)成一個(gè)快速出結(jié)果的基線(xiàn)工程省掉從零搭環(huán)境的時(shí)間。我拆這個(gè)項(xiàng)目時(shí)最直觀的感受是真正的功夫不在訓(xùn)練那一步而在數(shù)據(jù)整理和參數(shù)調(diào)試上。下面按我自己的復(fù)現(xiàn)路徑往下走每個(gè)環(huán)節(jié)都給到可直接抄的代碼和參數(shù)。2. 數(shù)據(jù)先行整理、標(biāo)注轉(zhuǎn)換與五分類(lèi)標(biāo)簽的落地方法2.1 數(shù)據(jù)從哪來(lái)構(gòu)建與清洗犬類(lèi)情緒識(shí)別本質(zhì)上是一個(gè)目標(biāo)檢測(cè)任務(wù)輸入是包含犬只的圖片輸出是檢測(cè)框加上情緒類(lèi)別。情緒識(shí)別比普通品種識(shí)別更難因?yàn)橥?lèi)情緒在不同犬種上的表現(xiàn)差異很大哈士奇和柯基的“開(kāi)心臉”完全是兩個(gè)畫(huà)風(fēng)。因此數(shù)據(jù)集的構(gòu)成直接影響模型能不能收斂。資源里自帶的原始圖片多為網(wǎng)圖、公開(kāi)寵物數(shù)據(jù)集和少量自拍素材的混合體第一件要做的事是清洗。常見(jiàn)做法是把這三類(lèi)圖片按“犬只朝向正面或側(cè)面 45 度以?xún)?nèi)、臉部占比大于 1/4、單張圖只有一只狗、無(wú)大面積遮擋”的標(biāo)準(zhǔn)篩一遍。我一般會(huì)用一個(gè)簡(jiǎn)單的腳本統(tǒng)計(jì)每張圖的尺寸和通道把損壞的、灰度異常的、分辨率低于 300px 的圖直接剔除。import os from PIL import Image src_dir raw_images valid_dir cleaned_images os.makedirs(valid_dir, exist_okTrue) min_side 300 removed [] for name in os.listdir(src_dir): path os.path.join(src_dir, name) try: img Image.open(path) img.verify() # 檢查文件是否損壞 w, h img.size if w min_side or h min_side: removed.append((name, too small)) continue if img.mode L: removed.append((name, grayscale)) continue img Image.open(path).convert(RGB) img.save(os.path.join(valid_dir, name)) except Exception as e: removed.append((name, str(e))) print(fremoved {len(removed)} files: {removed[:10]})img.verify()只做完整性校驗(yàn)不開(kāi)圖速度很快img.mode L是灰度圖過(guò)濾因?yàn)橛?xùn)練時(shí)顏色信息對(duì)情緒識(shí)別有貢獻(xiàn)灰度圖會(huì)引入分布偏移。這里把篩掉的圖片名打印出來(lái)便于回頭補(bǔ)數(shù)據(jù)時(shí)排查。清洗后大約能留下 70%80% 的圖片。這一步不能省臟數(shù)據(jù)進(jìn)訓(xùn)練集后面所有指標(biāo)都會(huì)變得奇怪。2.2 標(biāo)注格式轉(zhuǎn)換VOC 轉(zhuǎn) YOLO清洗完成后要處理的是標(biāo)注格式。資源包里原始標(biāo)注是 XMLVOC 格式而 YOLO 訓(xùn)練需要的是每個(gè)圖片對(duì)應(yīng)一個(gè) txt每行寫(xiě)“類(lèi)別_id x_center y_center width height”坐標(biāo)全部歸一化到 0 到 1 之間。# convert_voc_to_yolo.py import xml.etree.ElementTree as ET import os # 類(lèi)別順序必須與 data.yaml 中完全一致 CLASSES [angry, anxious, calm, happy, sad] def voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip().lower() if cls_name not in CLASSES: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h cls_id CLASSES.index(cls_name) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) xml_dir annotations/voc txt_dir annotations/yolo os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue stem os.path.splitext(xml_name)[0] voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(txt_dir, stem .txt))寬度和高度必須從size標(biāo)簽里取不能自己用 PIL 再算一次。有些手機(jī)照片帶 EXIF 旋轉(zhuǎn)信息直接讀圖得到的寬高會(huì)被方向信息干擾標(biāo)注會(huì)整體偏移。歸一化坐標(biāo)讓不同分辨率的圖片在訓(xùn)練時(shí)能統(tǒng)一到同一個(gè)尺度這也是 YOLO 系列一直用這種方式的原因。轉(zhuǎn)換完成后抽查幾份 txt如果出現(xiàn) x_center 大于 1 或小于 0 的情況說(shuō)明標(biāo)注框越界需要回到原 XML 修正。2.3 數(shù)據(jù)劃分與類(lèi)別均衡分類(lèi)項(xiàng)目的數(shù)據(jù)劃分不能只用隨機(jī)切分尤其是情緒識(shí)別這類(lèi)天然不均衡的數(shù)據(jù)集。狗的表情里“開(kāi)心”和“平靜”的樣本遠(yuǎn)多于“焦慮”和“憤怒”如果按原始比例切分驗(yàn)證集里憤怒樣本可能只有個(gè)位數(shù)模型全猜平靜也能拿到高準(zhǔn)確率這會(huì)讓指標(biāo)失真。import os import random from collections import Counter from shutil import copy2 image_dir cleaned_images label_dir annotations/yolo train_dir dataset/images/train val_dir dataset/images/val train_label_dir dataset/labels/train val_label_dir dataset/labels/val for d in [train_dir, val_dir, train_label_dir, val_label_dir]: os.makedirs(d, exist_okTrue) label_files [f for f in os.listdir(label_dir) if f.endswith(.txt)] random.seed(42) # 統(tǒng)計(jì)每個(gè)類(lèi)別的樣本框數(shù)量按框數(shù)而不是圖片數(shù)分配 cls_counter Counter() for lf in label_files: with open(os.path.join(label_dir, lf), r, encodingutf-8) as f: for line in f: cls_id int(line.split()[0]) cls_counter[cls_id] 1 print(class distribution:, dict(cls_counter)) train_ratio 0.85 train_files [] val_files [] # 分層采樣保證每個(gè)類(lèi)別在驗(yàn)證集中都有足夠樣本 for cls_id in range(len(CLASSES)): cls_files [ lf for lf in label_files if any(int(line.split()[0]) cls_id for line in open(os.path.join(label_dir, lf), r, encodingutf-8)) ] random.shuffle(cls_files) split_idx int(len(cls_files) * train_ratio) train_files.extend(cls_files[:split_idx]) val_files.extend(cls_files[split_idx:]) for f in train_files: copy2(os.path.join(image_dir, f.replace(.txt, .jpg)), train_dir) copy2(os.path.join(label_dir, f), train_label_dir) for f in val_files: copy2(os.path.join(image_dir, f.replace(.txt, .jpg)), val_dir) copy2(os.path.join(label_dir, f), val_label_dir)這里用了分層采樣先把包含某個(gè)類(lèi)別的圖片單獨(dú)拎出來(lái)再按比例抽取確保“憤怒”這類(lèi)長(zhǎng)尾類(lèi)別不會(huì)在驗(yàn)證集里消失。訓(xùn)練集比例取 0.85如果數(shù)據(jù)總量少于 1000 張可以提高到 0.9。這里有一個(gè)常見(jiàn)的翻車(chē)點(diǎn)按圖片數(shù)切分看似沒(méi)問(wèn)題但一張圖里可能同時(shí)有兩只狗、兩個(gè)情緒框按圖片數(shù)切分會(huì)讓驗(yàn)證集和訓(xùn)練集出現(xiàn)內(nèi)容重疊所以我按標(biāo)簽文件數(shù)來(lái)切并且只關(guān)心標(biāo)簽里出現(xiàn)過(guò)的類(lèi)別。劃分完成后數(shù)據(jù)集目錄結(jié)構(gòu)就是標(biāo)準(zhǔn)的 YOLO 布局dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/最后寫(xiě)一個(gè)data.yaml這是訓(xùn)練時(shí)唯一要指定的數(shù)據(jù)集配置文件類(lèi)別列表順序必須與上一步的 CLASSES 完全一致。path: dataset train: images/train val: images/val names: 0: angry 1: anxious 2: calm 3: happy 4: sad3. 訓(xùn)練自己的識(shí)別模型環(huán)境、參數(shù)配置與損失函數(shù)解讀3.1 環(huán)境搭建與預(yù)訓(xùn)練權(quán)重訓(xùn)練環(huán)境是第一個(gè)攔路虎網(wǎng)上搜“YOLO 環(huán)境配置”能看到的坑基本都集中在 CUDA 和 torch 版本不匹配上。資源包里的代碼基于 YOLOv8ultralytics 框架Python 版本 3.8 到 3.10 都能跑我建議用 3.10。# 創(chuàng)建虛擬環(huán)境避免污染系統(tǒng) Python conda create -n yolo python3.10 -y conda activate yolo # 安裝 PyTorch先確認(rèn)本機(jī) CUDA 版本再選擇對(duì)應(yīng)命令 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安裝 ultralytics pip install ultralytics8.0.100這里不要直接pip install ultralytics了事鎖版本很有必要。YOLOv8 的 API 在 8.0.x 之后有過(guò)一些調(diào)整資源代碼是在這個(gè)版本下調(diào)試過(guò)的升到 8.2 以上有些接口簽名會(huì)變新手照抄代碼可能跑不通。安裝完成后用yolo predict sourcehttps://ultralytics.com/images/bus.jpg驗(yàn)證環(huán)境能出結(jié)果說(shuō)明環(huán)境正常。預(yù)訓(xùn)練權(quán)重建議用yolov8s.pt而不是yolov8n.pt。雖然 nano 模型更小、訓(xùn)練更快但情緒特征嘴角弧度、耳朵位置非常細(xì)微nano 的淺層特征表達(dá)力不夠最終 mAP 會(huì)比 s 低 5 到 8 個(gè)點(diǎn)。如果顯卡顯存低于 6G再退回到y(tǒng)olov8n.pt。3.2 訓(xùn)練命令與超參數(shù)設(shè)置訓(xùn)練命令看起來(lái)簡(jiǎn)單真正決定結(jié)果的是參數(shù)。下面是我在這套資源上調(diào)通的一組基線(xiàn)參數(shù)。yolo train \ modelyolov8s.pt \ datadog_emotion.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience30 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ mosaic1.0 \ fliplr0.5 \ scale0.3 \ projectruns/detect \ namedog_emotion_train逐個(gè)說(shuō)參數(shù)含義。epochs100對(duì)于這個(gè)規(guī)模的數(shù)據(jù)集夠用太多會(huì)過(guò)擬合太少不收斂imgsz640是訓(xùn)練輸入分辨率如果顯卡允許改成 768 對(duì)情緒識(shí)別有明顯幫助因?yàn)槟槻筷P(guān)鍵特征在低分辨率下容易糊掉batch16由顯存決定顯存 8G 用 1612G 以上可以到 32patience30表示連續(xù) 30 個(gè) epoch 驗(yàn)證集指標(biāo)沒(méi)有提升就提前停止這是防止時(shí)間浪費(fèi)的保險(xiǎn)絲optimizerAdamW比默認(rèn)的 SGD 收斂更快尤其適合遷移學(xué)習(xí)場(chǎng)景。mosaic1.0是馬賽克增強(qiáng)把四張圖拼成一張訓(xùn)練對(duì)小目標(biāo)檢測(cè)非常有效但如果訓(xùn)練集里單張圖只有一只狗mosaic 過(guò)強(qiáng)會(huì)在早期造成“半個(gè)頭”的幻覺(jué)訓(xùn)練初期 loss 可能不降反升。真遇到這種情況把mosaic0.5跑幾個(gè) epoch后期再調(diào)到 1.0 也行。訓(xùn)練過(guò)程中重點(diǎn)看兩行輸出box_loss和cls_loss。box_loss 是檢測(cè)框回歸誤差cls_loss 是分類(lèi)誤差。情緒識(shí)別的難點(diǎn)在 cls_loss 上如果訓(xùn)練了十幾輪 cls_loss 還在 1.5 以上先想到的是數(shù)據(jù)問(wèn)題而不是模型問(wèn)題。3.3 損失函數(shù)與訓(xùn)練曲線(xiàn)怎么看YOLOv8 的損失由三部分構(gòu)成分類(lèi)損失BCE 變體、框回歸損失CIoU、分布式焦點(diǎn)損失DFL。DFL 是 YOLOv8 相對(duì) v5 的新增項(xiàng)它把邊界框的四個(gè)邊當(dāng)成分布來(lái)回歸而不是直接預(yù)測(cè)數(shù)值這提升了小目標(biāo)的定位精度代價(jià)是訓(xùn)練時(shí)對(duì)學(xué)習(xí)率更敏感。我習(xí)慣每 10 個(gè) epoch 停下來(lái)看一下results.png里的曲線(xiàn)。有幾個(gè)異常信號(hào)需要記住train/box_loss快速下降但val/box_loss震蕩上升這是過(guò)擬合的早期信號(hào)立即加大weight_decay或降低epochscls_loss在訓(xùn)練初期就在 0.3 以下說(shuō)明類(lèi)別分布嚴(yán)重不均衡模型在走捷徑優(yōu)先學(xué)容易的類(lèi)別訓(xùn)練開(kāi)始就出現(xiàn)nanloss幾乎可以肯定是學(xué)習(xí)率過(guò)大或某張圖的標(biāo)注框坐標(biāo)異常比如寬高為 0去檢查數(shù)據(jù)集比調(diào)參更有用。訓(xùn)練結(jié)束后在runs/detect/dog_emotion_train/目錄下會(huì)生成best.pt和last.pt后續(xù)推理用best.pt。這個(gè)目錄還包含confusion_matrix.png它比 loss 曲線(xiàn)更能說(shuō)明問(wèn)題——如果對(duì)角線(xiàn)很亮但某個(gè)類(lèi)別的列全黑那就是典型的長(zhǎng)尾漏檢。4. 推理與部署從單張圖片到實(shí)時(shí)攝像頭識(shí)別4.1 單圖推理腳本訓(xùn)練完模型后的第一件事是用它跑幾張訓(xùn)練集之外的圖片驗(yàn)證基本效果。下面這段腳本可以直接復(fù)制使用。# inference_image.py from ultralytics import YOLO model YOLO(runs/detect/dog_emotion_train/weights/best.pt) results model.predict( sourcetest_images, conf0.35, iou0.45, imgsz640, saveTrue, save_txtTrue, device0 ) for r in results: boxes r.boxes if boxes is not None: for box in boxes: cls_id int(box.cls[0].item()) conf float(box.conf[0].item()) print(fclass{model.names[cls_id]} confidence{conf:.3f})conf0.35是置信度閾值低于這個(gè)值的檢測(cè)框會(huì)被丟棄。情緒識(shí)別的置信度普遍比普通目標(biāo)檢測(cè)低因?yàn)楣纺樈嵌茸兓笤O(shè) 0.35 到 0.4 是一個(gè)平衡點(diǎn)。iou0.45是 NMS 交并比閾值值越小抑制越強(qiáng)同一只狗被框兩次的場(chǎng)景可以調(diào)到 0.5。打印類(lèi)別名和置信度有助于快速定位哪些圖片誤檢嚴(yán)重。4.2 攝像頭實(shí)時(shí)檢測(cè)課程設(shè)計(jì)答辯現(xiàn)場(chǎng)用攝像頭實(shí)時(shí)演示效果遠(yuǎn)比靜態(tài)圖片震撼。這里給一個(gè)接 OpenCV 的實(shí)時(shí)推理版本。# inference_webcam.py import cv2 from ultralytics import YOLO model YOLO(runs/detect/dog_emotion_train/weights/best.pt) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame cap.read() if not ret: break results model.predict(frame, imgsz640, conf0.4, device0, verboseFalse) annotated_frame results[0].plot() fps cap.get(cv2.CAP_PROP_FPS) cv2.putText(annotated_frame, fFPS: {fps:.0f}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Dog Emotion Recognition, annotated_frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()實(shí)時(shí)推理時(shí)我把imgsz從 640 降到 416這不是為了精度而是為了幀率。攝像頭場(chǎng)景下運(yùn)動(dòng)模糊已經(jīng)讓細(xì)小情緒特征不可靠640 的輸入會(huì)拖慢推理速度反而導(dǎo)致畫(huà)面卡頓。這里有個(gè)隱藏問(wèn)題results[0].plot()會(huì)在原圖上繪制檢測(cè)框和標(biāo)簽但如果檢測(cè)到多個(gè)框且類(lèi)別置信度不高畫(huà)面上的字會(huì)非常多答辯時(shí)反而顯得 demo 雜亂可以只在畫(huà)面上畫(huà)置信度高于 0.5 的框。4.3 模型導(dǎo)出與邊緣部署訓(xùn)練好的 PyTorch 權(quán)重不能直接跑在手機(jī)上或者 Jetson 這類(lèi)邊緣設(shè)備上需要導(dǎo)出成 ONNX 或 TensorRT 格式。yolo export modelruns/detect/dog_emotion_train/weights/best.pt formatonnx imgsz640 opset12 yolo export modelruns/detect/dog_emotion_train/weights/best.pt formatengine device0 halfTrueONNX 導(dǎo)出的關(guān)鍵是opset版本設(shè)成 12 兼容性最好新版 PyTorch 默認(rèn)的 opset 17 在部分老設(shè)備推理引擎上會(huì)報(bào)不支持的算子。TensorRT 導(dǎo)出前先確認(rèn)本機(jī) CUDA 版本和 TensorRT 版本匹配否則引擎構(gòu)建到一半會(huì)報(bào)顯存問(wèn)題。halfTrue開(kāi)啟 FP16 精度推理速度提升明顯但精度有輕微損失情緒識(shí)別這個(gè)任務(wù)影響不大。如果在樹(shù)莓派或者 RK3588 這類(lèi)設(shè)備上部署ONNX 格式是更穩(wěn)妥的選擇配合 RKNN-Toolkit 轉(zhuǎn)換工具可以做硬件加速。這方面的行話(huà)叫“邊緣部署監(jiān)控誤檢率高”核心原因是邊緣設(shè)備上為了性能往往會(huì)降低輸入分辨率而情緒特征恰恰依賴(lài)細(xì)節(jié)所以部署時(shí)的輸入分辨率不要小于 512。5. 常見(jiàn)問(wèn)題與避坑訓(xùn)練不收斂、BN 崩潰與誤檢排查5.1 訓(xùn)練階段BN 崩潰、過(guò)擬合與損失異?,F(xiàn)象一訓(xùn)練到第 20 個(gè) epoch 左右loss 突然變成nan控制臺(tái)開(kāi)始刷紅色警告模型權(quán)重?zé)o法保存。這大概率是訓(xùn)練中 BN 崩潰BatchNorm blowup。產(chǎn)生原因通常是學(xué)習(xí)率設(shè)置過(guò)高加上早期批次數(shù)據(jù)里某些標(biāo)注框異常導(dǎo)致某一層的統(tǒng)計(jì)量爆炸。解決方法是先把lr0降到 0.0005同時(shí)把batch設(shè)成 8 或 16 的倍數(shù)確保每個(gè)批次里的樣本數(shù)足夠讓 BN 統(tǒng)計(jì)量穩(wěn)定。另一個(gè)常見(jiàn)做法是在yolo train命令里加cacheTrue把數(shù)據(jù)緩存到內(nèi)存減少讀取抖動(dòng)帶來(lái)的輸入異?!,F(xiàn)象二訓(xùn)練集精度升到 95% 以上驗(yàn)證集 mAP 卻只有 50%而且還在往下掉。這是典型的過(guò)擬合在小數(shù)據(jù)集上非常普遍尤其是清洗后圖片數(shù)少于 800 張時(shí)。對(duì)策不止一個(gè)我建議按順序操作先加weight_decay0.0005再把mosaic關(guān)掉或降到 0.3然后嘗試用yolov8s-cls.pt的分類(lèi)預(yù)訓(xùn)練權(quán)重做遷移初始化。注意這不是玄學(xué)是超參數(shù)和數(shù)據(jù)規(guī)模不匹配的必然結(jié)果?,F(xiàn)象三val/cls_loss曲線(xiàn)一路平坦怎么調(diào)參都下不去。先檢查你的data.yaml里類(lèi)別順序是否和訓(xùn)練標(biāo)簽的 class_id 對(duì)應(yīng)。我見(jiàn)過(guò)不止一次因?yàn)?CLASSES 列表順序變了導(dǎo)致模型把“開(kāi)心”學(xué)成了“焦慮”的慘案。模型沒(méi)毛病是標(biāo)簽語(yǔ)義對(duì)不上。5.2 推理階段誤檢、漏檢與混淆矩陣異?,F(xiàn)象四視頻里對(duì)著墻壁或地板也會(huì)框出一個(gè)小方框置信度還不低打上了某個(gè)情緒類(lèi)別。原因是背景過(guò)擬合。如果訓(xùn)練集里大量圖片的背景都是室內(nèi)地板或狗窩模型會(huì)把紋理特征當(dāng)成判斷依據(jù)。解決方式是做數(shù)據(jù)增強(qiáng)加hsv_h0.5 hsv_s0.5 hsv_v0.5隨機(jī)改變色調(diào)飽和度降低顏色紋理對(duì)分類(lèi)的依賴(lài)再用translate0.1讓目標(biāo)在畫(huà)面里移動(dòng)破壞固定的位置關(guān)聯(lián)。推理時(shí)把conf調(diào)高到 0.5 也能過(guò)濾一部分背景誤檢?,F(xiàn)象五confusion_matrix.png里所有類(lèi)別的橫排總和明顯大于縱排總和甚至出現(xiàn)奇怪的對(duì)角線(xiàn)。搜過(guò)“YOLO 混淆矩陣總合不唯一”的應(yīng)該都知道目標(biāo)檢測(cè)的混淆矩陣計(jì)算邏輯與圖像分類(lèi)不同它按預(yù)測(cè)框是否與真實(shí)框匹配來(lái)計(jì)數(shù)一個(gè)真實(shí)框可能被多個(gè)預(yù)測(cè)框命中一個(gè)預(yù)測(cè)框也可能對(duì)應(yīng)多個(gè)類(lèi)別得分??椿煜仃嚂r(shí)不要追求行和列相等重點(diǎn)看每個(gè)類(lèi)別是否在對(duì)角線(xiàn)上有足夠高的值。某一行除對(duì)角格外還有高響應(yīng)說(shuō)明模型在類(lèi)別間混淆優(yōu)先補(bǔ)區(qū)分這兩個(gè)類(lèi)別的數(shù)據(jù)比調(diào)參更有效。6. 進(jìn)階注意力機(jī)制改進(jìn)與 TensorBoard 驗(yàn)證習(xí)慣6.1 給 YOLOv8 加一個(gè)輕量注意力模塊如果完成基礎(chǔ)版本之后還有余力給模型加一個(gè) SESqueeze-and-Excitation注意力模塊是性?xún)r(jià)比很高的改進(jìn)方向。犬類(lèi)情緒識(shí)別依賴(lài)的耳朵位置、嘴角弧度這些細(xì)小特征注意力機(jī)制能讓特征提取網(wǎng)絡(luò)更關(guān)注這些區(qū)域同時(shí)計(jì)算開(kāi)銷(xiāo)非常小。# common.py 中添加的 SE 模塊 import torch import torch.nn as nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super(SEBlock, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channels, channels // reduction, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels, biasFalse), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x)這個(gè)模塊通過(guò)全局平均池化把每個(gè)通道壓縮成一個(gè)值再用兩層全連接學(xué)習(xí)通道間的關(guān)系最后用 Sigmoid 產(chǎn)生 0 到 1 的通道權(quán)重乘回原特征圖。reduction16控制中間層壓縮比值越大參數(shù)越少但信息損失也越多。修改完模型結(jié)構(gòu)后訓(xùn)練命令不變只是在yaml配置里替換對(duì)應(yīng)層的模塊名。6.2 用 TensorBoard 對(duì)比改進(jìn)前后的訓(xùn)練曲線(xiàn)改進(jìn)是否有效不能靠感覺(jué)。ultralytics 框架自帶 TensorBoard 支持但需要先安裝依賴(lài)再啟用。pip install tensorboard tensorboard --logdir runs/detect打開(kāi)http://localhost:6006就能同時(shí)看到基線(xiàn)模型和注意力改進(jìn)模型的 loss 曲線(xiàn)。我通常會(huì)對(duì)比兩組指標(biāo)val/cls_loss和mAP0.5:0.95。如果加了注意力模塊后cls_loss更低但box_loss略高這是正常的注意力機(jī)制本質(zhì)上是犧牲一部分定位精度換取分類(lèi)精度在情緒識(shí)別這個(gè)任務(wù)上是劃算的。對(duì)比訓(xùn)練時(shí)還有個(gè)習(xí)慣值得養(yǎng)成用固定的random_seed42讓兩次訓(xùn)練的數(shù)據(jù)增強(qiáng)順序一致。如果不固定種子兩次訓(xùn)練的提升可能是隨機(jī)性造成的那就失去了對(duì)比的意義。這個(gè)資源我前后拆了三遍第一遍踩在數(shù)據(jù)清洗上第二遍死在 BN 崩潰第三遍才把訓(xùn)練跑順。從那以后我每次做目標(biāo)檢測(cè)項(xiàng)目都會(huì)強(qiáng)制自己先寫(xiě)一個(gè)數(shù)據(jù)分布統(tǒng)計(jì)腳本再進(jìn)訓(xùn)練環(huán)節(jié)。模型可以迭代數(shù)據(jù)問(wèn)題不能靠模型硬扛。完整的標(biāo)簽轉(zhuǎn)換腳本、分層采樣代碼和訓(xùn)練配置都在資源包里希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取