1. 这不是“YOLO问题大全”,而是一份实战派工程师的排障手记
YOLO系列模型——从v3到v8、v10,再到当前工业界广泛部署的YOLOv5s/v8n/v10n轻量变体——早已不是实验室里的玩具。我在过去三年里,带团队落地了17个视觉检测项目:产线PCB焊点漏检、冷链车厢内生鲜腐坏识别、社区养老院跌倒行为监测、快递分拣面单OCR+目标定位联合推理……所有项目上线前都卡在同一个地方:训练看起来还行,部署一跑就崩;验证集mAP 0.82,现场视频流推理结果满屏飘红框;TensorRT加速后精度掉0.15,但延迟反而升高了8%。这些都不是理论问题,是凌晨三点盯着GPU显存曲线、反复比对labelImg标注边界、逐帧检查ONNX输出张量shape时,用指甲掐进掌心换来的经验。
“解决常见的YOLO问题”这个标题,听起来像教程合集,实则是一套以故障现象为入口、以硬件-数据-模型-部署四层耦合关系为解剖刀、以可复现的最小验证单元为手术台的排障方法论。它不讲YOLO的损失函数推导,不画网络结构图,不罗列“建议学习PyTorch基础”这种废话。它只回答三类人最急的问题:
- 标注员问:“我按规范画了矩形框,为什么训练loss一直不降?”
- 算法工程师问:“val mAP上得去,但测试视频里小目标全漏检,是anchor设置问题还是后处理阈值?”
- 部署工程师问:“转ONNX时报错‘Unsupported op: Resize’,是该换PyTorch版本,还是改模型结构?”
全文所有结论,均来自真实产线日志、TensorBoard曲线截图、Wireshark抓包分析(针对边缘设备推理服务通信异常)、以及我们自建的YOLO问题复现库中214个已归档case。下面进入正题——不是按“数据/模型/部署”分章节,而是按你第一次看到报错信息时,眼睛扫到的第一个关键词来组织逻辑。
2. 问题溯源:从终端第一行报错开始逆向拆解
YOLO相关问题的排查,必须放弃“先查文档再试”的学院派路径。真实场景中,错误信息往往被日志淹没,或因环境差异而失真。我建立了一套“三秒响应法则”:看到报错,先不动代码,打开终端执行三条命令,再决定下一步动作。这套流程已沉淀为团队内部SOP,覆盖92%的高频问题。
2.1 第一类报错:Python层直接崩溃(ImportError / ModuleNotFoundError)
典型表现:
ImportError: cannot import name 'non_max_suppression' from 'models.common' ModuleNotFoundError: No module named 'ultralytics'这不是YOLO的问题,是环境依赖的版本战争。Ultralytics官方repo在v8.0.192之后,将non_max_suppression从models.common移至ultralytics.utils.ops,但大量第三方仓库(如YOLOv8-DeepSORT、YOLOv8-pose)仍硬编码旧路径。更隐蔽的是PyTorch与CUDA的ABI兼容性问题:
torch==2.0.1+cu118与torchvision==0.15.2+cu118必须严格匹配,差一个小版本号,torch.cuda.is_available()就返回False;pip install ultralytics默认装最新版,但你的训练脚本可能基于v8.0.120开发,新版API已废弃model.train()中的cache参数。
实操方案(非重装!):
- 进入项目根目录,执行
pip show ultralytics torch torchvision,记录三者精确版本; - 查阅 ultralytics release history ,找到与你torch版本兼容的最后一个稳定版(例如torch 1.13.1 → ultralytics≤8.0.156);
- 执行
pip install ultralytics==8.0.156 --force-reinstall --no-deps,强制降级但不卸载torch; - 若仍报错,检查
sys.path:python -c "import sys; print('\n'.join(sys.path))",确认当前工作目录在path首位,避免系统级安装的ultralytics干扰。
提示:永远不要在conda环境中混用pip和conda安装同一包。我们曾因
conda install pytorch+pip install ultralytics导致libtorch_cpu.so符号冲突,GPU显存占用显示为0但实际未启用。
2.2 第二类报错:训练过程异常中断(CUDA out of memory / Nan loss)
这是新手最易栽跟头的区域。表面看是显存不足,实则90%源于数据预处理管道的隐式内存泄漏。YOLOv5/v8默认使用torch.utils.data.DataLoader,当num_workers>0且pin_memory=True时,每个worker进程会独立加载整个数据集到显存——注意,是每个worker都加载全部数据,而非分片加载。
验证方法:启动训练后,立即执行nvidia-smi,观察Memory-Usage是否随worker数量线性增长。若num_workers=4时显存占用比=0时高3.2GB,即证实此问题。
根治步骤:
- 禁用多进程预处理:将
train.py中DataLoader参数改为num_workers=0, pin_memory=False,首次训练必成功; - 定位内存杀手:在
datasets.py的__getitem__函数开头插入:import gc gc.collect() # 强制回收Python对象 torch.cuda.empty_cache() # 清空CUDA缓存 - 重构数据加载逻辑:将图像解码(cv2.imread)与增强(albumentations)分离。我们发现
albumentations.Compose在num_workers>0时会序列化整个transform对象,导致worker进程内存暴涨。解决方案是:- 在
__init__中预加载所有图像路径列表; __getitem__中仅执行cv2.imread(path)+cv2.cvtColor(CPU操作),增强操作移至collate_fn中,在主进程完成;
- 在
- Nan loss的终极检查项:检查标签文件中是否存在
width=0或height=0的bbox。YOLO系列计算GIoU时,若预测框宽高为0,会导致log(0)产生NaN,梯度反传后全网权重变为NaN。我们写了一个校验脚本:
一行命令扫出所有问题标签。awk '{if($4==0 || $5==0) print FILENAME,$0}' labels/*.txt
2.3 第三类报错:推理结果完全失效(全黑图/全白图/满屏乱框)
这类问题最折磨人,因为模型能跑通,loss正常下降,但输出毫无意义。根本原因在于输入数据格式与模型期望的错位,且YOLO系列对此极其敏感。
常见错位组合:
| 错误操作 | 实际影响 | 检测方法 |
|---|---|---|
图像归一化用/255.0但模型权重要求/255.0后还需-0.5/+0.5 | 输入分布偏移,特征图激活值饱和 | tensor.mean(), tensor.std()对比训练时统计值 |
| BGR读图(cv2)但模型训练用RGB(PIL) | 颜色通道颠倒,绿色植物被识别为红色火焰 | 可视化原始输入tensor,观察R/G/B通道数值分布 |
resize时用cv2.INTER_NEAREST插值小目标 | 小目标像素被抹平,CNN无法提取纹理 | 对比resize前后目标区域直方图 |
实操验证流程:
- 取一张训练集图片,保存为
test.jpg; - 在推理脚本中,
img = cv2.imread('test.jpg')后立即插入:print("Before normalize:", img.dtype, img.min(), img.max()) # 应为uint8, 0, 255 img = img.astype(np.float32) / 255.0 print("After /255:", img.dtype, img.min(), img.max()) # 应为float32, 0.0, 1.0 img = img.transpose(2,0,1) # HWC→CHW print("After transpose:", img.shape) # 应为(3,H,W) - 将此
img直接送入模型,打印model(img[None])输出的shape与数值范围。若输出全为0或极大值(>1e5),即确认预处理错误。
注意:YOLOv8默认输入尺寸为640×640,但
letterbox函数会保持长宽比缩放后补灰边。很多开发者直接cv2.resize(img, (640,640)),导致目标严重形变。必须使用ultralytics内置的LetterBox类,或手动实现:def letterbox(im, new_shape=(640, 640), color=(114, 114, 114)): shape = im.shape[:2] # current shape [height, width] r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw, dh = dw // 2, dh // 2 if shape[::-1] != new_unpad: im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = dh, new_shape[0] - dh left, right = dw, new_shape[1] - dw im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) return im
3. 数据层顽疾:标注质量、分布偏差与增强陷阱
YOLO模型的性能上限,80%由数据决定。我们做过对照实验:同一套YOLOv8n模型,用高质量标注训练mAP=0.78,用外包团队标注(存在15%的框偏移、5%的漏标)训练mAP=0.52。数据问题不解决,调参全是徒劳。
3.1 标注质量的量化评估方法
行业没有统一标准,但我们自建了三个硬性指标:
- IOU一致性:随机抽100张图,用同一标注工具让两名标注员独立标注,计算两组bbox的平均IOU。低于0.85需返工;
- 边界贴合度:对每个bbox,计算其与目标真实轮廓的Hausdorff距离。我们用OpenCV的
findContours提取GT轮廓,若距离>15像素(1080p下),判定为“框过大”; - 长宽比合规性:统计所有bbox的
w/h比值,绘制直方图。若出现双峰(如大量1:1和大量16:9),说明标注规范未统一(如车辆应标整车,但有人只标车头)。
修复工具链:
- 用
labelImg导出VOC格式XML; - 转为YOLO格式时,执行校验脚本:
# check_labels.py import xml.etree.ElementTree as ET for xml_file in xml_files: tree = ET.parse(xml_file) root = tree.getroot() for obj in root.findall('object'): bndbox = obj.find('bndbox') xmin = int(bndbox.find('xmin').text) xmax = int(bndbox.find('xmax').text) ymin = int(bndbox.find('ymin').text) ymax = int(bndbox.find('ymax').text) if xmax <= xmin or ymax <= ymin: print(f"Invalid box in {xml_file}: {xmin},{ymin},{xmax},{ymax}") - 对于“框过大”问题,我们开发了自动收缩算法:对每个bbox,用GrabCut算法提取前景,然后用
cv2.boundingRect重新拟合最小外接矩形,收缩率控制在15%以内。
3.2 训练集分布偏差的致命影响
YOLO对类别不平衡极度敏感。在我们的冷链项目中,新鲜三文鱼占比72%,腐坏三文鱼仅占3%。模型训练后,腐坏样本召回率仅11%。这不是Focal Loss能解决的,是数据采样逻辑缺陷。
解决方案不是简单过采样,而是分层重采样:
- 将腐坏样本按腐坏程度分为三级(轻微/中度/重度),每级单独计算采样权重;
- 构建
WeightedRandomSampler,权重公式为:weight = 1 / (class_count[class_id] * severity_factor)
其中severity_factor为人工设定(轻微=1.0,中度=1.5,重度=2.0); - 关键技巧:在
sampler中加入replacement=True,但限制同一epoch内同一腐坏样本最多出现3次,避免过拟合。
验证分布合理性:
训练前,用以下代码生成分布热力图:
import seaborn as sns import matplotlib.pyplot as plt # 统计每个类别的bbox数量、平均面积、长宽比 stats = {'class': [], 'count': [], 'area_mean': [], 'ar_mean': []} for cls_id in range(num_classes): boxes = all_boxes[all_boxes[:,0]==cls_id] stats['class'].append(cls_name[cls_id]) stats['count'].append(len(boxes)) stats['area_mean'].append(boxes[:,3:].prod(axis=1).mean()) stats['ar_mean'].append((boxes[:,3]/boxes[:,4]).mean()) sns.heatmap(pd.DataFrame(stats).set_index('class'), annot=True) plt.savefig('data_distribution.png')若某类count极低但area_mean极高,说明该类目标大但稀疏,需针对性增加小目标增强。
3.3 增强策略的“有效增强”与“无效增强”
YOLOv8默认启用Mosaic、MixUp、HSV增强,但我们在产线项目中发现:
- Mosaic在小目标检测中提升mAP 0.03,但导致推理速度下降12%(因需拼接4图);
- MixUp对遮挡场景有帮助,但若训练集已含大量遮挡样本,则MixUp反而降低精度;
- HSV增强中的
hgain=0.015对室内光照稳定场景无益,却使模型对白平衡变化更敏感。
我们的增强配置原则:
- 关闭所有全局增强(Mosaic/MixUp),改用
Albumentations的局部增强:RandomBrightnessContrast(p=0.3):仅调整亮度对比度,不改变颜色;MotionBlur(blur_limit=3, p=0.1):模拟摄像头运动模糊;CoarseDropout(max_holes=2, max_height=32, max_width=32, p=0.3):模拟传感器坏点;
- 为小目标专项增强:在
augment_hsv函数中,对w*h<128的目标,额外应用RandomScale(scale_limit=0.3, p=0.5),强制放大其纹理; - 验证增强有效性:训练时开启
--save-period 1,每epoch保存一次权重,用验证集测试mAP。若连续3个epoch mAP波动>0.02,立即停用当前增强组合。
4. 模型层深水区:Anchor设计、Head结构与Loss调试
YOLO的“黑盒感”主要来自其检测头(Detection Head)的设计。v5/v8虽宣称“anchor-free”,实则v8的Detect模块仍隐式依赖anchor尺寸。理解这一点,才能真正掌控模型行为。
4.1 Anchor尺寸的物理意义与重聚类方法
YOLOv5/v8的anchor本质是对训练集bbox尺寸的k-means聚类中心。官方提供的yolov5s.yaml中anchor为:[[10,13, 16,30, 33,23], [30,61, 62,45, 59,119], [116,90, 156,198, 373,326]]
这组数字并非魔法常数,而是COCO数据集上聚类的结果。当你用自己数据集时,必须重聚类。
重聚类实操(避坑版):
- 收集所有训练标签的
w,h(归一化到640×640尺寸); - 使用
scipy.cluster.vq.kmeans,但必须指定k=9(因YOLOv5/v8有3个检测头,每头3个anchor); - 关键陷阱:k-means对初始中心敏感。我们采用
k-means++初始化,并运行20次取最优解; - 输出anchor需按从小到大排序,否则模型会混乱。正确顺序:
# anchors按面积升序排列 areas = [w*h for w,h in anchors] sorted_anchors = [a for _,a in sorted(zip(areas, anchors))] - 将结果填入yaml的
anchors字段,必须保留3层结构:anchors: - [sorted_anchors[0], sorted_anchors[1], sorted_anchors[2]] # P3 - [sorted_anchors[3], sorted_anchors[4], sorted_anchors[5]] # P4 - [sorted_anchors[6], sorted_anchors[7], sorted_anchors[8]] # P5
提示:若你的数据集目标极小(如电路板焊点,尺寸<10px),聚类后可能出现
[2,3]这种anchor。此时需强制约束最小尺寸:在聚类前,将所有w,h截断到max(w,8), max(h,8),避免anchor过小导致训练不稳定。
4.2 Detection Head的梯度流向分析
YOLOv8的Detect模块包含三个核心张量:
pred_dist:分布预测(用于DFL Loss);pred_scores:置信度预测;pred_bboxes:边界框回归。
问题来了:当pred_bboxes输出全为0时,是backbone没学好特征,还是head的线性层权重初始化失败?
梯度追踪法:
- 在
detect.py的forward函数末尾插入:print("pred_bboxes grad norm:", torch.norm(pred_bboxes.grad)) print("pred_scores grad norm:", torch.norm(pred_scores.grad)) - 若
pred_bboxes.grad为0,但pred_scores.grad正常,则问题在pred_bboxes的计算路径; - 定位到
self.cv2卷积层(负责bbox回归),检查其权重:
正常值应为print("cv2 weight mean:", self.cv2.weight.mean().item()) print("cv2 weight std:", self.cv2.weight.std().item())mean≈0.0, std≈0.02。若std<0.001,说明权重坍缩,需重置初始化。
Head重置方案:
# 在model.__init__()中 for m in self.modules(): if isinstance(m, nn.Conv2d): if m is self.cv2: # bbox head nn.init.normal_(m.weight, mean=0.0, std=0.02) nn.init.constant_(m.bias, 0.0)4.3 Loss函数的定制化调试
YOLOv8默认使用BCEWithLogitsLoss(置信度)+DFLLoss(分布)+CIoULoss(框回归)。但在特定场景需调整:
- 密集小目标场景:CIoU对小目标不敏感,改用
EIoU(Explicit IoU),其惩罚项包含宽高差,对小目标定位更准; - 遮挡严重场景:
DFLLoss假设预测分布为均匀离散,但遮挡目标的分布是偏态的,此时改用QualityFocalLoss,将分类质量融入定位; - 实时性优先场景:
DFLLoss需计算16个分布点,耗时占比达35%,可简化为DIoULoss(Distance IoU),牺牲0.008 mAP换取12%速度提升。
Loss替换步骤:
- 复制
ultralytics/utils/loss.py,新建custom_loss.py; - 修改
ComputeLoss类的__call__函数:# 替换原CIoU计算 iou = bbox_iou(pred_bboxes.T, target_bboxes.T, xywh=False, CIoU=True) # 改为EIoU iou = bbox_iou(pred_bboxes.T, target_bboxes.T, xywh=False, EIoU=True) - 在训练命令中指定:
--loss custom_loss.ComputeLoss。
实测数据:在PCB焊点检测中,EIoU使小目标(<20px)定位误差从4.2px降至2.7px;在跌倒检测中,QualityFocalLoss将遮挡场景下的召回率从63%提升至79%。
5. 部署层生死线:ONNX转换、TensorRT优化与边缘设备适配
模型训练完成只是起点,90%的项目失败在部署环节。我们曾因一个torch.nn.Upsample操作,导致TensorRT 8.4无法解析ONNX,最终回退到TRT 7.2并重写上采样层。
5.1 ONNX转换的“安全模式”
YOLOv8官方export.py默认启用dynamic_axes,这对Web端推理友好,但对嵌入式设备是灾难。TRT解析动态轴需额外内存,且部分设备(如Jetson Nano)根本不支持。
安全转换命令:
yolo export model=yolov8n.pt format=onnx opset=12 dynamic=False simplify=True关键参数解析:
opset=12:TRT 8.x完全支持,避免opset=17中新增的NonMaxSuppression算子(TRT不支持);dynamic=False:禁用动态维度,所有shape固定为[1,3,640,640];simplify=True:调用onnxsim简化计算图,合并常量节点。
转换后必做三件事:
- 用
netron打开ONNX文件,确认输入节点名为images,输出节点名为output0(YOLOv8标准); - 检查是否有
Resize、ScatterND等TRT不支持算子:
若报错onnxruntime_test.exe --model yolov8n.onnx --provider CUDAExecutionProviderUnsupported op type: Resize,需修改模型源码,将F.interpolate替换为nn.Upsample并指定mode='nearest'; - 验证输出shape:用Python加载ONNX,输入全1张量,检查输出是否为
[1, 84, 8400](YOLOv8n)。
5.2 TensorRT引擎构建的避坑清单
TRT构建不是“一键生成”,而是精密的工程。我们总结出7个必查项:
- 精度模式选择:
FP16在Jetson AGX Orin上提速2.1倍,但若模型含大量BatchNorm,INT8量化后精度掉0.12,此时应选FP16+Calibration; - 最大batch size:TRT引擎固化batch size。若需动态batch,必须在构建时设
max_batch_size=16,但显存占用翻倍; - 工作空间大小:
builder.max_workspace_size = 1<<30(1GB)是底线,低于此值TRT会跳过复杂优化; - 层精度覆盖:对
Softmax层强制FP32,避免分类概率溢出; - 输出绑定:TRT输出为
[1,84,8400],需按[batch, class+4, num_boxes]解析,num_boxes=8400是YOLOv8n的固定值; - CUDA流同步:
context.execute_async_v2()后必须stream.synchronize(),否则CPU读取未完成的GPU内存; - 内存池管理:Jetson设备需启用
cudaMallocAsync,否则频繁分配释放显存导致OOM。
TRT构建脚本核心段:
config.set_flag(trt.BuilderFlag.FP16) config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1<<30) # 强制Softmax为FP32 for layer in network: if layer.type == trt.LayerType.SOFTMAX: layer.precision = trt.DataType.FLOAT layer.output_type[0] = trt.DataType.FLOAT # 构建引擎 engine = builder.build_serialized_network(network, config) with open("yolov8n.trt", "wb") as f: f.write(engine)5.3 边缘设备实测性能表
不同设备对YOLO的优化效果差异巨大,我们实测了6款主流设备(数据均为1080p输入,batch=1):
| 设备 | 芯片 | TRT版本 | YOLOv8n FPS | 内存占用 | 关键瓶颈 |
|---|---|---|---|---|---|
| Jetson Orin NX | GA10B | 8.5.2 | 124 | 1.8GB | GPU功耗墙(15W限频) |
| Jetson Xavier NX | GV100 | 8.0.1 | 68 | 2.1GB | PCIe带宽(x4 Gen3) |
| RK3588 | G52 MP6 | NPU SDK 2.0 | 89 | 1.2GB | NPU与CPU数据搬运 |
| Atlas 200I DK A2 | Ascend 310P | CANN 6.3 | 95 | 1.5GB | DVPP图像预处理延迟 |
| Intel NUC11 | Iris Xe | OpenVINO 2023.0 | 42 | 0.9GB | CPU核数不足(4核8线程) |
| Raspberry Pi 5 | VideoCore VII | TFLite 2.13 | 8.3 | 0.4GB | 内存带宽(LPDDR4X 4GB/s) |
Pi5的救星方案:
- 放弃YOLOv8,改用
YOLOv5n(更小的backbone); - 用
picamera2直接捕获YUV420格式,省去RGB转换; - 后处理用Cython重写NMS,速度提升3.2倍;
- 最终达到12FPS,满足基础需求。
6. 常见问题速查表与独家避坑技巧
以下是我们在17个项目中,高频出现且文档极少提及的12个问题,附带“一句话定位法”和“三步解决法”。
| 问题现象 | 一句话定位法 | 三步解决法 |
|---|---|---|
| 训练loss震荡剧烈(±0.3) | 检查train.py中cos_lr是否开启,且lrf=0.01 | 1. 关闭余弦退火,用linear_lr;2. 学习率设为0.01*batch_size/16;3. warmup_epoch设为5 |
| 验证集mAP高,但测试图全漏检 | 用cv2.imshow显示letterbox后的图,看目标是否被裁剪 | 1. 将test_size设为max(img_w, img_h)*1.2;2.letterbox中scaleup=False;3. 后处理conf_thres=0.001 |
| TRT推理结果bbox坐标错乱 | 打印输出tensor的shape,若为[1,8400,84]则顺序错误 | 1. ONNX中确保输出为[1,84,8400];2. TRT解析时用output = output.reshape(1,84,-1);3. 转置output = output.transpose(0,2,1) |
| 多尺度训练时小目标mAP不升反降 | 统计各尺度下小目标(<32px)的召回率 | 1. 关闭Mosaic;2. 小目标专用anchor(k-means时加权);3.hyp.yaml中scale=0.5(缩小图像) |
| labelImg标注后,训练报错“list index out of range” | 检查.txt文件末尾是否有空行 | 1.sed -i '/^$/d' *.txt;2.awk 'NF' *.txt > temp && mv temp *.txt;3. 用file命令确认文件编码为UTF-8 |
| 模型在A卡上训练快,N卡上慢2倍 | nvidia-smi看GPU利用率,若<30%则数据加载瓶颈 | 1.num_workers=0;2. 用torch.compile(model);3.pin_memory=True且persistent_workers=True |
| 导出ONNX后,OpenCV DNN模块报错“Unknown layer type” | 用netron看是否有NonMaxSuppression节点 | 1.yolo export ... nms=False;2. 后处理用cv2.dnn.NMSBoxes;3. 输出改为[1,8400,4+1+nc] |
| Jetson设备上,TRT推理10分钟后显存爆满 | nvidia-smi看Volatile GPU-Util是否持续100% | 1.sudo jetson_clocks解除功耗限制;2.nvpmodel -m 0切换性能模式;3. 代码中del outputs后gc.collect() |
| YOLOv8-pose关键点全部偏移 | 可视化kpt_score,若全<0.1则关键点分支未训练 | 1.pose.yaml中nc=1(单类);2.hyp.yaml中kpt_loss=1.0;3. 训练时--task pose |
| 使用半精度(FP16)训练,loss突变为inf | print(grad.norm())看梯度爆炸位置 | 1.torch.autocast(enabled=False)关闭半精度;2.clip_grad_norm_(model.parameters(), max_norm=10.0);3.optimizer.zero_grad(set_to_none=True) |
| 模型在服务器上OK,边缘设备上输出全0 | `readelf -d libmytrt.so | grep NEEDED`看依赖库版本 |
| TensorBoard中loss曲线平滑,但实际推理抖动大 | 抽取100帧连续视频,统计bbox中心点移动标准差 | 1.conf_thres=0.5;2.iou_thres=0.7;3. 添加卡尔曼滤波后处理(开源库filterpy) |
最后分享一个血泪技巧:
每次模型迭代前,务必执行git diff对比hyp.yaml、data.yaml、model.yaml三个文件。我们曾因hyp.yaml中mosaic=0.5被误改为1.0,导致新版本在测试集上mAP虚高0.07,上线后现场漏检率飙升。现在团队规定:任何超参数修改,必须提交PR并附before/after对比报告。
我在产线调试YOLO时养成一个习惯:在桌面贴一张便签,写三行字——
“输入是什么?输出应该是什么?现在输出是什么?”
不解决这三个问题,绝不碰代码。YOLO不是玄学,它是可测量、可拆解、可验证的工程系统。那些深夜报错的终端窗口,不是障碍,而是系统在向你发送最真实的反馈信号。