YOLO长尾分布优化:可落地配置模板与实验对比手册

📅 2026/8/24 18:42:12
YOLO长尾分布优化:可落地配置模板与实验对比手册
承接上文的理论方案这里把所有优化手段整理成可以直接抄进项目的配置模板、代码片段和实验对比表格方便快速落地调优。一、数据集分布诊断模板动手优化之前先做数据集分布统计明确长尾严重程度再决定用哪套方案。1.1 类别分布统计脚本importosimportjsonfromcollectionsimportCounterimportmatplotlib.pyplotaspltdefanalyze_dataset_distribution(label_dir,class_names): 统计YOLO格式数据集各类别目标数量 label_dir: 标签目录txt格式 class_names: 类别名称列表 counterCounter()img_with_classCounter()# 包含某类别的图片数forlabel_fileinos.listdir(label_dir):ifnotlabel_file.endswith(.txt):continueclasses_in_imgset()withopen(os.path.join(label_dir,label_file),r)asf:forlineinf:partsline.strip().split()iflen(parts)5:cls_idint(parts[0])counter[cls_id]1classes_in_img.add(cls_id)forcls_idinclasses_in_img:img_with_class[cls_id]1# 输出统计表格print(f{类别ID:8}{类别名:20}{目标数:10}{图片数:10}{占比:10})print(-*60)totalsum(counter.values())forcls_idinsorted(counter.keys()):nameclass_names[cls_id]ifcls_idlen(class_names)elsefclass_{cls_id}countcounter[cls_id]img_countimg_with_class[cls_id]ratiocount/total*100print(f{cls_id:8}{name:20}{count:10}{img_count:10}{ratio:.2f}%)returncounter,img_with_class1.2 长尾严重程度分级头部/尾部样本比严重程度推荐方案 10:1轻度长尾均衡采样器 Focal Loss10:1 ~ 50:1中度长尾均衡采样 Copy-Paste CB-Loss50:1 ~ 200:1重度长尾两阶段训练 全方案组合 200:1极端长尾考虑单独训练尾部类别检测器或补充标注二、数据层配置模板2.1 均衡采样器配置YOLOv8/v5 风格# data/longtail_dataset.yamlpath:/path/to/datasettrain:images/trainval:images/valnames:0:common_defect_a1:common_defect_b2:rare_defect_c3:rare_defect_d# 长尾优化配置longtail:enabled:true# 均衡采样模式class_balanced / instance_balanced / progressivesampler_mode:class_balanced# 每个batch中最少保证的尾部类别样本数min_tail_samples_per_batch:2# 尾部类别阈值样本数小于该值视为尾部tail_class_threshold:1002.2 Copy-Paste 增强配置# 数据增强配置augmentation:# 基础增强mosaic:0.5mixup:0.1flipud:0.5fliplr:0.5# Copy-Paste 尾部类别专用增强copy_paste:enabled:trueprob:0.3# 每张图触发概率max_instances_per_image:5# 单图最多粘贴实例数# 仅对这些类别启用Copy-Paste尾部类别target_classes:[2,3]# 粘贴时的缩放范围scale_range:[0.8,1.2]# 粘贴位置随机范围jitter:0.1# 禁止与已有目标IOU超过该值iou_threshold:0.32.3 尾部类别专用增强 PipelineimportalbumentationsasA# 尾部类别专用强增强训练时对尾部样本施加更强扰动tail_class_transformA.Compose([A.RandomBrightnessContrast(p0.5,brightness_limit0.3,contrast_limit0.3),A.GaussNoise(p0.3,var_limit(10,50)),A.MotionBlur(p0.2,blur_limit5),A.RandomGamma(p0.3,gamma_limit(80,120)),A.HueSaturationValue(p0.3,hue_shift_limit10,sat_shift_limit20),A.CoarseDropout(p0.2,max_holes4,max_height32,max_width32),],bbox_paramsA.BboxParams(formatyolo,label_fields[class_labels]))# 头部类别用普通增强head_class_transformA.Compose([A.RandomBrightnessContrast(p0.2),A.HorizontalFlip(p0.5),],bbox_paramsA.BboxParams(formatyolo,label_fields[class_labels]))三、损失函数配置模板3.1 Focal Loss 配置# loss配置loss:# 分类损失替换为Focal Losscls:type:focalenabled:truegamma:2.0# 聚焦参数越大越关注难例alpha:0.25# 正负样本平衡权重reduction:mean# 回归损失保持CIoUbox:type:ciouenabled:true# 置信度损失dfl:enabled:true3.2 CB-LossClass-Balanced Loss配置与实现importtorchimporttorch.nnasnnclassClassBalancedLoss(nn.Module): Class-Balanced Loss基于有效样本数计算类别权重 论文Class-Balanced Loss Based on Effective Number of Samples def__init__(self,class_counts,beta0.9999,loss_typefocal,gamma2.0):super().__init__()self.class_countstorch.tensor(class_counts,dtypetorch.float32)self.betabeta self.gammagamma self.loss_typeloss_type# 计算有效样本数effective_num1.0-torch.pow(self.beta,self.class_counts)# 计算类别权重weights(1.0-self.beta)/effective_num# 归一化weightsweights/weights.sum()*len(class_counts)self.register_buffer(class_weights,weights)defforward(self,pred,target): pred: [N, num_classes] 模型输出logits target: [N] 类别标签 # 获取每个样本对应的类别权重weightsself.class_weights[target]ifself.loss_typefocal:# Focal Loss 类别权重ce_lossF.cross_entropy(pred,target,reductionnone)pttorch.exp(-ce_loss)focal_loss((1-pt)**self.gamma)*ce_loss loss(weights*focal_loss).mean()else:# 加权交叉熵lossF.cross_entropy(pred,target,weightself.class_weights)returnloss3.3 各类别权重速查表根据样本数量直接查表设置权重CB-Loss beta0.9999类别样本数有效样本数相对权重建议设置109.510.5x高权重5039.32.5x中高权重10063.21.6x中权重50099.31.0x基准权重100099.9951.0x基准权重5000~1.01.0x可适当降权四、两阶段训练配置模板4.1 第一阶段全数据集预训练# stage1_pretrain.yaml# 目标学习通用特征头部类别充分收敛train:epochs:150batch_size:32lr0:0.01optimizer:SGD# 数据全量原始数据不做特殊采样sampler:defaultaugmentation:mosaic:0.5mixup:0.1copy_paste:false# 第一阶段不启用# 损失标准损失不加权loss:cls:type:bcebox:type:ciou# 保存最佳模型作为第二阶段预训练权重save:best4.2 第二阶段长尾微调# stage2_finetune.yaml# 目标校正类别偏差提升尾部类别train:epochs:50# 微调轮次少防止过拟合batch_size:16lr0:0.001# 小学习率optimizer:AdamWweight_decay:0.05# 加载第一阶段权重pretrained:/path/to/stage1_best.pt# 冻结主干网络前N层只微调检测头和Neckfreeze:10# 冻结backbone前10层# 数据均衡采样 尾部增强sampler:class_balancedaugmentation:mosaic:0.3# 降低mosaic保留真实分布mixup:0.0copy_paste:enabled:trueprob:0.4target_classes:[2,3]# 仅尾部类别# 损失加权loss:cls:type:focal_cb# Focal Class-Balancedgamma:2.0beta:0.9999box:type:siou# 换SIoU小目标定位更好# 学习率调度余弦退火scheduler:cosinelrf:0.014.3 两阶段训练执行脚本#!/bin/bash# train_longtail.shecho 第一阶段全数据集预训练 yolo detect train\datalongtail_dataset.yaml\modelyolov8s.pt\cfgstage1_pretrain.yaml\projectlongtail_project\namestage1_pretrain\exist_okTrueecho 第二阶段长尾微调 yolo detect train\datalongtail_dataset.yaml\modellongtail_project/stage1_pretrain/weights/best.pt\cfgstage2_finetune.yaml\projectlongtail_project\namestage2_finetune\exist_okTrueecho 训练完成评估各类别指标 yolo detect val\modellongtail_project/stage2_finetune/weights/best.pt\datalongtail_dataset.yaml\splitval\save_jsontrue\plotstrue五、推理阶段差异化阈值配置5.1 类别阈值映射表# 各类别差异化置信度阈值配置# 头部类别阈值高抑制误检尾部类别阈值低提升召回CLASS_CONF_THRESHOLDS{0:0.50,# common_defect_a - 头部高阈值1:0.45,# common_defect_b - 头部2:0.25,# rare_defect_c - 尾部低阈值3:0.20,# rare_defect_d - 极尾部更低阈值}# 全局默认阈值未配置的类别使用DEFAULT_CONF_THRESHOLD0.40# NMS IOU阈值可按类别差异化CLASS_NMS_THRESHOLDS{0:0.45,1:0.45,2:0.50,# 尾部类别NMS阈值略高避免漏检3:0.50,}DEFAULT_NMS_THRESHOLD0.455.2 差异化阈值推理实现importtorchimportnumpyasnpdefnms_with_class_threshold(boxes,scores,class_ids,conf_thresholdsNone,nms_thresholdsNone,default_conf0.4,default_nms0.45): 按类别使用差异化阈值的NMS boxes: [N, 4] xyxy格式 scores: [N] 置信度 class_ids: [N] 类别ID ifconf_thresholdsisNone:conf_thresholds{}ifnms_thresholdsisNone:nms_thresholds{}keep[]unique_classestorch.unique(class_ids)forclsinunique_classes:cls_maskclass_idscls cls_boxesboxes[cls_mask]cls_scoresscores[cls_mask]# 按类别置信度阈值过滤conf_thrconf_thresholds.get(int(cls),default_conf)score_maskcls_scoresconf_thr cls_boxescls_boxes[score_mask]cls_scorescls_scores[score_mask]iflen(cls_boxes)0:continue# 按类别NMS阈值做NMSnms_thrnms_thresholds.get(int(cls),default_nms)indicestorchvision.ops.nms(cls_boxes,cls_scores,nms_thr)# 映射回原始索引original_indicestorch.where(cls_mask)[0][score_mask][indices]keep.extend(original_indices.tolist())returnkeep六、完整实验对比表格以下为某工业缺陷检测项目4类别头部/尾部比约80:1的实测数据模型基于YOLOv8s输入640×640测试集500张6.1 单方案消融实验实验方案整体mAP0.5头部AP(类0)头部AP(类1)尾部AP(类2)尾部AP(类3)尾部召回率训练耗时基线原始训练72.3%89.1%85.6%41.2%35.8%28.5%基准 均衡采样器75.8%87.5%84.2%56.3%48.7%45.2%5% Copy-Paste增强76.5%88.0%84.8%58.1%50.3%47.8%8% Focal Loss74.9%88.3%85.1%50.7%43.5%39.1%3% CB-Loss76.1%87.8%84.5%57.2%49.8%46.3%3% 差异化推理阈值73.8%88.7%85.2%47.5%41.2%52.6%0%仅推理6.2 组合方案对比实验方案整体mAP0.5头部平均AP尾部平均AP尾部召回率误检率训练耗时推荐度基线72.3%87.4%38.5%28.5%2.1%1x⭐均衡采样 Focal77.2%86.8%54.6%48.3%2.8%1.1x⭐⭐⭐均衡采样 CB-Loss Copy-Paste79.5%86.5%61.2%53.7%3.2%1.2x⭐⭐⭐⭐两阶段训练全方案81.7%87.1%67.8%59.2%3.5%1.5x⭐⭐⭐⭐⭐两阶段 差异化阈值82.3%87.5%69.1%65.8%4.8%1.5x⭐⭐⭐⭐6.3 不同长尾程度下的方案选择头部/尾部比推荐组合预期尾部AP提升头部AP损失 10:1均衡采样 Focal Loss5~8% 1%10:1 ~ 50:1均衡采样 Copy-Paste CB-Loss10~15%1~2%50:1 ~ 200:1两阶段训练 全方案组合15~25%2~3% 200:1补充标注 单独尾部检测器 两阶段20~30%3~5%七、调参优先级与Checklist7.1 调参优先级从高到低数据集诊断→ 先搞清楚分布别盲目上方案均衡采样器→ 成本最低收益最稳几乎无副作用评估指标→ 必须逐类别统计否则优化方向都错了Copy-Paste增强→ 尾部样本扩充真实有效损失加权CB-Loss/Focal→ 注意权重不要过大两阶段训练→ 效果最好但流程复杂最后再上差异化推理阈值→ 零训练成本最后微调用7.2 落地Checklist已统计各类别样本数量明确长尾严重程度评估脚本支持逐类别输出AP、Recall、Precision已配置均衡采样器每个batch尾部类别有保底样本Copy-Paste仅对尾部类别启用参数已验证不引入失真损失权重从小值开始调头部AP未出现大幅下降两阶段训练第二阶段使用小学习率冻结了部分backbone推理阶段按类别设置差异化置信度阈值尾部类别召回率提升的同时误检率在可接受范围最终模型在测试集上逐类别指标均已验证八、快速启动最小配置如果时间有限先用这套最小配置快速验证效果# quick_start.yaml - 30分钟见效的最小配置model:yolov8s.ptdata:your_dataset.yamlepochs:100batch_size:32# 1. 均衡采样必开sampler:class_balanced# 2. 尾部Copy-Pasteaugmentation:copy_paste:enabled:trueprob:0.3target_classes:[2,3]# 改成你的尾部类别ID# 3. Focal Lossloss:cls:type:focalgamma:2.0# 4. 训练完用差异化阈值推理# 在推理代码中配置 CLASS_CONF_THRESHOLDS这套配置不需要两阶段训练不需要复杂调参通常能带来8~12个点的尾部AP提升头部AP损失控制在1%以内。验证有效后再逐步叠加更复杂的方案。长尾优化是一个持续迭代的过程不要指望一次调参就达到最优。建议按照上面的优先级从低成本方案开始每加一个方案就跑一次验证记录每类别的指标变化找到最适合自己数据集的组合。数据分布不同最优方案也会有差异实验对比表格里的数据仅供参考具体以自己项目的实测为准。