ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于YOLO的疼痛检测实战:2200张医疗数据集训练与调优指南

基于YOLO的疼痛检测实战:2200张医疗数据集训练与调优指南 1. 疼痛检测数据集的项目背景与核心价值疼痛检测这个方向在医疗健康领域的计算机视觉圈子里这两年热度一直在涨。原因很直接疼痛是一种主观感受传统上靠患者自述或者护士用NRS、VAS这类量表打分但ICU里插管的病人说不出话认知障碍的老人表达不清婴幼儿更是没法配合。这时候如果有一套视觉系统能通过面部表情自动识别疼痛强度对临床监护和远程医疗的价值是实打实的。我拿到这个2200张YOLO格式的医疗健康数据集时第一反应是规模不算大但方向很精准。2200张图像在通用目标检测任务里属于小数据集但在疼痛检测这种垂直细分领域已经算是有一定可用性的起步资源了。关键在于它的标注质量、类别定义和场景覆盖这些决定了它能不能真正训出一个可用的模型。这个数据集适合谁用如果你是做医疗AI方向的研究生想快速验证一个疼痛检测的idea它省去了你从零标注的几周时间。如果你是做边缘部署的工程师想在人脸表情识别的基础上加一个疼痛维度它可以作为微调的起点。如果你只是刚接触YOLO目标检测想找一个有实际意义的项目练手疼痛检测比检测猫猫狗狗更有故事可讲。需要先明确一点疼痛检测本质上是面部表情识别的一个子任务但它比通用表情识别更难。因为疼痛表情和厌恶、恐惧、疲劳这些表情有大量重叠区域类间差异小而且疼痛强度是连续的离散化成几个等级本身就带有主观性。所以拿到数据集之后不要指望直接训一个YOLO就能达到临床级精度它的定位是基线模型和算法验证平台。2. 数据集结构与YOLO格式解析2.1 目录组织与标注文件格式一个标准的YOLO格式目标检测数据集目录结构通常是这样的pain_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages下面放的是jpg或png原图labels下面放的是同名的txt标注文件。注意图片和标注文件必须一一对应且同名只是扩展名不同。我见过太多人因为图片叫img_001.jpg而标注叫img_001.txt但放在不同层级导致训练时找不到标签loss直接不降。每个txt文件里的每一行代表一个目标框格式是class_id x_center y_center width height这五个值都是归一化到0到1之间的浮点数。x_center和y_center是框中心点相对于图像宽高的比例width和height是框宽高相对于图像宽高的比例。举个例子如果一张640x480的图里有一个框左上角在(100, 80)宽200高160那么x_center (100 200/2) / 640 200/640 0.3125y_center (80 160/2) / 480 160/480 0.3333width 200/640 0.3125height 160/480 0.3333对应的一行就是0 0.3125 0.3333 0.3125 0.3333注意YOLO格式的坐标原点在图像左上角y轴向下。如果你从COCO或VOC格式转换过来VOC的xyxy格式需要先转成xywh再归一化顺序不能错。2.2 类别定义与疼痛等级划分疼痛检测数据集的类别定义是整个项目最核心的设计决策。常见的做法有两种第一种是按疼痛强度分级比如0级无痛、1级轻度、2级中度、3级重度对应4个类别。这种方案的好处是输出直接对应临床量表坏处是等级边界模糊标注一致性难保证。第二种是按疼痛相关面部动作单元组合比如眉部收紧、眼睑闭合、鼻唇沟加深、嘴张开等每个动作单元作为一个类别。这种方案更客观但需要标注人员有FACS面部动作编码系统的训练背景。从2200张这个规模来看我倾向于认为它采用的是第一种方案因为动作单元标注的成本远高于等级标注。假设是4类那么data.yaml大概长这样path: ./pain_dataset train: images/train val: images/val test: images/test nc: 4 names: [no_pain, mild, moderate, severe]这里有个坑类别不平衡。真实场景里无痛表情的样本通常远多于重度疼痛表情如果no_pain占了60%以上训出来的模型会倾向于把所有脸都判成无痛。后面我会讲怎么处理。2.3 数据分布与场景覆盖分析2200张图按7:2:1划分训练集约1540张验证集440张测试集220张。这个量级下每个类别的训练样本可能只有几百张对于YOLO这种参数量较大的检测器来说过拟合风险很高。我建议拿到数据集后第一件事不是急着训模型而是先做一次数据分布统计。用几行Python就能搞定import os from collections import Counter label_dir pain_dataset/labels/train counter Counter() for f in os.listdir(label_dir): with open(os.path.join(label_dir, f)) as file: for line in file: cls int(line.split()[0]) counter[cls] 1 print(counter)如果发现某个类别样本数低于总样本的10%就需要考虑过采样、类别加权或者数据增强来补偿。另外还要看场景覆盖是实验室采集的标准正面人脸还是ICU里的侧脸、遮挡、光照不均这直接决定了你需不需要在训练时加大几何增强的力度。3. 从零搭建YOLO疼痛检测训练环境3.1 硬件选型与显存估算训练YOLO做疼痛检测硬件门槛其实不高但也不能太寒酸。我实测下来的经验是硬件配置可用模型batch size训练速度约GTX 1660 6GByolov8n82-3小时/100epochRTX 3060 12GByolov8s/m161.5小时/100epochRTX 4090 24GByolov8l/x3240分钟/100epochV100 32GByolov8x6430分钟/100epoch2200张图用yolov8n或yolov8s就够了没必要上大模型。疼痛检测的特征主要集中在眉、眼、鼻唇沟这几个区域属于中低层纹理特征小模型的感受野完全够用。上大模型反而容易过拟合因为数据量撑不住。显存估算的粗略公式显存占用 ≈ 模型参数量 × batch_size × 4字节 × 系数。实际用的时候yolov8n在640分辨率下batch16大约占4GByolov8s大约占6GByolov8m大约占10GB。如果你显存不够优先降batch而不是降分辨率因为疼痛检测对分辨率比较敏感224的输入会把鼻唇沟的细节糊掉。3.2 环境配置的坑与正确姿势环境配置这块我踩过的坑比训练本身还多。最稳妥的方案是用conda建独立环境然后按官方requirements装conda create -n pain_yolo python3.10 conda activate pain_yolo pip install ultralyticsultralytics这个包会把torch、torchvision、opencv这些依赖都带上。但要注意如果你机器上已经有CUDApip装的torch可能是CPU版本。验证方法import torch print(torch.cuda.is_available()) print(torch.version.cuda)如果输出False就去pytorch官网找对应CUDA版本的安装命令重装。我见过有人训了半天发现用的是CPUloss降得比蜗牛还慢。另一个常见问题是BN层崩溃。训练中如果loss突然变成nan大概率是某个batch里某个通道的方差为0导致BN除零。解决办法有两个一是把batch size调大让每个batch的统计量更稳定二是把模型里的BN换成GNGroupNorm但YOLO默认结构改起来麻烦。最实用的办法是降低初始学习率从0.01降到0.001同时加warmup。3.3 预训练权重的选择策略YOLO预训练模型下载这块我的建议是一定要用COCO预训练权重不要从随机初始化开始训。2200张图从零训收敛都困难更别说精度了。from ultralytics import YOLO model YOLO(yolov8s.pt) # 加载COCO预训练权重COCO里虽然没有疼痛类别但有人脸、人这些相关类别底层特征边缘、纹理、五官结构是通用的。微调时只需要让模型学会把“皱眉眯眼鼻唇沟”这个特征组合映射到疼痛类别上。如果你用的是yolov11或更新的版本权重文件命名规则类似去ultralytics的GitHub release页面找对应的.pt文件就行。注意别下成分类模型或分割模型要下检测模型的权重。4. 数据增强与类别不平衡处理实战4.1 针对疼痛检测的增强策略通用YOLO训练默认开启mosaic、mixup、随机缩放、随机翻转这些增强。但疼痛检测有它的特殊性不能无脑用默认配置。翻转要谨慎。水平翻转对大多数目标检测任务是安全的但疼痛表情有左右不对称性比如单侧嘴角上扬。如果你确定数据集里没有这种不对称标注水平翻转可以开。垂直翻转绝对不要开没有倒着的脸。mosaic增强要降概率。mosaic把4张图拼成1张对小数据集很有效能增加场景多样性。但疼痛检测的标注框集中在面部mosaic拼接后可能出现半张脸配半张脸的情况标注框被裁切。建议把mosaic概率从1.0降到0.5。色彩抖动要控制幅度。ICU场景下光照变化大适度的亮度、对比度抖动是合理的。但HSV的hue抖动不要太大因为肤色是疼痛检测的重要线索hue偏移过大会让肤色失真。我的配置参考# 在训练脚本里覆盖默认增强参数 model.train( datadata.yaml, epochs150, imgsz640, batch16, hsv_h0.01, # 默认0.015调小 hsv_s0.5, # 默认0.7略降 hsv_v0.4, # 默认0.4保持 degrees10, # 默认0加一点旋转 translate0.1, scale0.3, fliplr0.5, # 默认0.5保持 mosaic0.5, # 默认1.0调低 mixup0.1, # 默认0加一点 )4.2 类别不平衡的三种补偿方案假设统计后发现no_pain占55%severe只占8%直接训会导致模型对重度疼痛的召回率极低。三种方案按推荐度排序方案一类别加权损失。在YOLO的损失函数里给稀有类别更高的权重。ultralytics默认不支持直接传class weights但可以改loss计算部分或者用自定义训练循环。简单粗暴的办法是复制稀有类别的样本让每个类别样本数接近。方案二过采样强增强。把severe类别的图片单独拎出来在dataloader里提高采样概率同时对它们施加更强的增强更大的旋转角度、更强的色彩抖动。这样不增加磁盘占用但每个epoch里稀有类别被看到的次数更多。方案三focal loss。YOLOv8默认用的是BCE loss做分类可以换成focal loss让模型更关注难分类样本。但改loss需要动源码对新手不友好。我实际用下来方案二性价比最高。写一个自定义的Dataset类在__getitem__里根据类别做加权采样代码量不超过20行。4.3 标注质量检查与清洗2200张图的标注如果是人工标的一定有错标漏标。训练前花半小时做一次清洗比训完发现mAP上不去再回头查要划算得多。检查清单有没有宽高为0的框归一化后width或height为0训练时会报错。有没有坐标超出[0,1]范围的框说明标注时算错了。有没有同一张图里同一个目标被标了两次会导致重复检测。类别id有没有超出nc范围比如nc4但出现了class_id5。用这段代码快速扫一遍import os def check_labels(label_dir, nc): issues [] for f in os.listdir(label_dir): path os.path.join(label_dir, f) with open(path) as file: for i, line in enumerate(file): parts line.strip().split() if len(parts) ! 5: issues.append(f{f}:{i} 字段数不对) continue cls, x, y, w, h map(float, parts) if cls nc or cls 0: issues.append(f{f}:{i} 类别越界) if not (0 x 1 and 0 y 1): issues.append(f{f}:{i} 中心点越界) if w 0 or h 0 or w 1 or h 1: issues.append(f{f}:{i} 宽高异常) return issues print(check_labels(pain_dataset/labels/train, 4))5. 模型训练、调参与评估全流程5.1 训练参数设置与loss曲线解读启动训练的命令很简洁yolo detect train datadata.yaml modelyolov8s.pt epochs150 imgsz640 batch16 lr00.001 patience30几个关键参数的解释lr00.001初始学习率。微调任务不要用默认的0.01容易把预训练权重冲垮。patience3030个epoch验证集指标不提升就早停防止过拟合。epochs150小数据集训150轮足够了再多就是浪费电。训练开始后重点看三个lossbox_loss、cls_loss、dfl_loss。box_loss管定位cls_loss管分类dfl_loss是分布焦点损失管框的回归质量。正常情况三个loss都平稳下降验证集的mAP50稳步上升。如果box_loss降但cls_loss不降说明模型能定位到脸但分不清疼痛等级这时候要检查类别标注是否一致。如果训练loss降但验证loss升典型的过拟合加dropout或者减模型容量。5.2 混淆矩阵分析与错误模式识别训练完看混淆矩阵这是诊断模型问题最直接的工具。疼痛检测的混淆矩阵通常呈现这样的模式真实\预测no_painmildmoderatesevereno_pain高中低低mild中中中低moderate低中中中severe低低中高如果mild和moderate之间的混淆特别严重说明这两个等级的视觉差异太小标注本身可能就不一致。解决办法是把mild和moderate合并成一个“有痛”类别做二分类精度会立刻上去。注意YOLO输出的混淆矩阵有时候会出现“总合不唯一”的情况这是因为置信度阈值和IoU阈值的选择会影响匹配结果。看混淆矩阵时要固定conf0.25、iou0.45不要频繁改。5.3 评估指标的选择与临床意义mAP50和mAP50-95是通用指标但疼痛检测更关心的是召回率。临床上漏检一个重度疼痛的代价远大于误检一个轻度疼痛。所以在调参时如果mAP和recall冲突优先保recall。具体做法是降低推理时的置信度阈值。默认conf0.25可以降到0.15让更多疑似疼痛的框被保留。代价是误检增多但可以通过后续的人工复核或者时序平滑来过滤。另一个值得关注的指标是各类别的AP。如果severe的AP明显低于其他类别说明模型对重度疼痛的特征学习不足需要针对性补充样本或增强。6. 常见问题排查与避坑经验实录6.1 训练不收敛的排查路径训练不收敛的表现是loss震荡或者居高不下。按以下顺序排查学习率太大把lr0降到0.0001试试如果loss开始降了就是这个问题。标注格式错误用前面的检查脚本扫一遍确认没有越界或零宽高的框。数据路径错误确认data.yaml里的path和train/val路径正确图片和标签能对上。预训练权重没加载打印模型第一层权重看是不是随机初始化的。batch size太小小于8时BN统计量不稳定调到16以上。6.2 过拟合的识别与缓解2200张图训YOLO过拟合几乎是必然的。识别信号训练loss持续降但验证mAP在某个epoch后不再上升甚至下降。缓解手段按效果排序早停patience设小一点20-30。数据增强加大mosaic、mixup、旋转角度。减小模型从yolov8m换回yolov8n。加权重衰减weight_decay从0.0005加到0.001。冻结骨干前50个epoch只训检测头后面再解冻微调。6.3 推理部署时的性能优化训练完的模型要部署到实际场景推理速度很关键。如果目标是边缘设备比如RK3588或者Jetson需要做模型转换和量化。优化手段速度提升精度损失FP16半精度1.5-2倍几乎无损INT8量化3-4倍1-3% mAPTensorRT2-5倍几乎无损输入分辨率降到4162倍3-5% mAP我的建议是如果部署在服务器端用TensorRTFP16就够了。如果部署在边缘端先试FP16精度不够再考虑INT8。疼痛检测对精度要求高INT8量化要谨慎最好用真实数据做校准。6.4 常见问题速查表问题现象可能原因解决方法loss变nan学习率太大/BN崩溃降lr加warmupmAP不涨标注错误/类别不平衡清洗标注过采样显存溢出batch太大/分辨率太高降batch或imgsz推理速度慢模型太大/没量化换小模型TensorRT漏检严重conf阈值太高降到0.15-0.2误检严重负样本不足补充无痛样本7. 疼痛检测项目的扩展方向这个数据集训出基线模型之后有几个值得尝试的扩展方向。时序信息融合。疼痛是一个动态过程单帧图像的判别力有限。如果输入是视频可以用LSTM或者Transformer对连续帧的特征做时序建模捕捉表情变化的动态模式。YOLO负责逐帧检测后接一个时序分类头。多模态融合。疼痛检测不只靠视觉生理信号心率变异性、皮肤电导和语音呻吟声都是重要线索。可以把YOLO提取的面部特征和生理信号做特征级融合提升判别精度。开放词汇检测。如果想把模型扩展到检测其他医疗相关表情如谵妄、焦虑可以用开放词汇目标检测的思路用文本嵌入来引导检测。YOLO-World这类模型可以做零样本迁移。轻量化部署。把模型蒸馏到更小的网络或者用神经架构搜索找一个适合边缘设备的backbone让疼痛检测能跑在床旁监护仪上。我个人在实际操作中的体会是疼痛检测这个方向数据质量比模型结构重要得多。2200张图如果能保证标注一致性训出来的模型比一万张脏数据要强。另外不要迷信mAP临床场景下召回率和可解释性才是关键。最后分享一个小技巧训练时把验证集的可视化结果定期保存下来肉眼看看模型到底在关注面部的哪个区域比盯着loss曲线有用得多。
返回列表