改进粒子群算法求解建筑光储系统规划运行综合优化
2026/10/3 9:28:04
================================================================================ YOLOv5训练源码精讲:从 train.py 到 best.pt 的完整优化链路 ================================================================================ 【写在前面】 你有没有过这种感觉:照着网上教程跑了训练,但指标一掉就不知道怎么办—— 只能"加epoch、改lr、换增强"三连,结果改了一圈说不清哪个有效。 这篇文章不讲"玄学调参"。我会用一个具体例子(用YOLOv5训练一个安全帽检测模型) 贯穿全文,让你知道训练出问题该去哪找原因、该优先改什么、怎么证明改动真的有效。 看完之后你应该能够:独立诊断训练问题、能系统性做对照实验、能向面试官 解释"为什么这样设计"。 你将获得三样东西: 1)一张训练主链路图(知道问题该去哪里查) 2)一份高杠杆改点清单(知道优先改什么) 3)一套单变量实验顺序(知道怎么证明改动有效) ================================================================================ 一、为什么你调参无效?——用一个具体例子说清楚 ================================================================================ 【场景带入:你要训一个安全帽检测模型】 假设你现在要训一个安全帽检测器,数据集有1000张标注图片。 你按教程跑了训练,但发现mAP只有0.4,远低于预期。 你的第一反应是什么? A. 加 epochs(从300改成600) B. 调高学习率 C. 换数据增强策略 D. 不知道,先试试再说 如果你选D,恭喜你,你和大多数人一样——缺少"链路视角"。 【链路视角是什么?】 想象一下你家门口的快递柜。快递从入库到被你取出,经历了: 投递员放件 → 系统记录 → 柜子存储 → 你扫码取件 如果快递丢了,你知道去哪个环节找:投递出了问题找快递员,系统出了问题找客服。 但如果你对整个流程没概念,你就会在四个环节之间来回折腾,还找不到原因。 训练YOLO也一样。Loss不降,可能是: - 学习率设错了(对应"快递员环节") - 数据标注有问题(对应"柜子存储环节") - 增强策略不合适(对应"投递方式环节") 没有链路视角,就会盲目试参。有链路视角,就能精准定位。 【训练流程的十个节点】 用安全帽检测的例子,YOLOv5训练会经历这10个环节: 节点1(参数解析):你输入了 --batch 16 --img 640 --epochs 300 节点2(模型构建):YOLOv5根据yaml文件构建了一个检测器 节点3(权重加载):你加载了COCO预训练权重 节点4(冻结策略):你设置了 --freeze 0-10 冻结backbone前10层 节点5(优化器):你用了SGD,学习率0.01 节点6(数据加载):Dataset在读你拍的1000张安全帽照片 节点7(训练循环):每个epoch在算loss、反向传播、更新参数 节点8(验证):每隔几个epoch跑一次验证,看mAP涨没涨 节点9(保存模型):mAP新高了,保存为best.pt 节点10(断点续训):训练到一半断了,从last.pt恢复 如果安全帽漏检严重,问题可能在节点6(数据集),也可能在节点7(损失权重), 但如果你没这个地图,你就会瞎改batch size和epochs——改完了发现没效果。 ================================================================================ 二、参数来源:三个层次的优先级问题(附安全帽案例) ================================================================================ 【三个层次的参数来源】 你在终端输入的每一行命令,参数可能来自三个地方: 第一层:命令行参数 python train.py --batch 16 --img 640 --epochs 300 第二层:hyp.yaml配置文件 里面写了 mosaic=0.5、lr0=0.01、box=0.05 等 第三层:opt.yaml(断点续训时) 之前训练保存的快照,恢复训练时会覆盖命令行参数 【安全帽项目的坑:resume后batch没变】 你用1000张图训安全帽模型,跑了200个epoch断电了。 你想继续训练,同时把batch从16改成32: python train.py --resume runs/exp5/weights/last.pt --batch 32 结果:batch还是16,不是32。 为什么?因为resume时,opt.yaml里保存的batch=16会覆盖你命令行的--batch 32。 正确做法: - 做法1:完全新开训练 --batch 32(断点续训的目的本就是恢复同一实验) - 做法2:手动改opt.yaml里的batch值,再resume 这个坑会导致你"明明改了参数但不生效",很多人绕了好久才发现是这个问题。 【实战建议】 训练开始时,在日志里找opt打印,确认这几个值对不对: batch、imgsz、lr0、weight_decay、epochs 养成这个习惯,能避开70%的"参数不生效"问题。 ================================================================================ 三、预训练权重加载:交集机制的通俗理解 ================================================================================ 【为什么叫"交集"加载?】 你训练安全帽检测,用的是COCO预训练权重。 COCO有80类,安全帽不在COCO里。那预训练权重有用吗? 有用,但有限。 预训练权重加载的本质是"取交集": 假设COCO权重是一个装有100万个参数的盒子, 你的安全帽模型需要50万个参数。 加载时,会一个个检查:盒子里这个参数,你模型里有没有?形状一样不一样? - 名字对上了 + 形状一样 → 加载进来 - 名字对不上 → 跳过(你模型没这层) - 名字对上了 + 形状不一样 → 跳过(你改了这层的结构) 最终加载进来的,就是交集。 【安全帽项目的例子】 你的安全帽检测模型把检测头从COCO的80类改成了1类(只有"安全帽")。 结果: - backbone(特征提取部分):几乎100%加载,因为结构和COCO一样 - 检测头(最后的分类层):加载率很低,因为类别数完全不同 这说明:改检测头类别的改动,对预训练权重的影响很小。 改backbone结构的改动(比如换通道数),才会严重影响加载率。 【匹配率低于80%意味着什么?】 训练日志会打印:Transfering params [xxx/xxx] (xx%) 如果匹配率低于80%,说明你的模型结构和预训练权重差得太多, 预训练收益不明显,基本上等同于从零训练。 ================================================================================ 四、冻结训练:什么时候该冻结?用一个故事说清楚 ================================================================================ 【为什么要冻结?一个故事】 假设你是一个经验丰富的厨师,被派去教一个新厨师做川菜。 你多年的手艺(知道怎么切丝、怎么掌握火候、怎么调味)就像预训练权重—— 这些基本功对新任务(做安全帽检测)仍然有用。 新厨师的"灾难性遗忘": 如果一上来就让他完全自由发挥,他可能会用他原有的"炒菜直觉"来做川菜, 反而把你教给他的刀工和火候控制给忘了——这就是"灾难性遗忘"。 冻结策略 = 让他先只学新菜的部分,基本功先不动。 【三种冻结策略】 策略1:全冻结(只训head) 适用:你只有200张安全帽图,数据少得可怜 效果:收敛快,但head能力受限,安全帽检测精度可能到不了最优 策略2:部分冻结(冻backbone前几层) 适用:你有800张安全帽图,领域有一定差异(室内安全帽 vs 室外安全帽) 效果:大多数场景下的默认选择 策略3:分阶段解冻 适用:你有1500张图,想最大化预训练收益 效果:分三阶段——先只训head、再解冻后几层、最后全量微调 【安全帽项目:什么时候该解冻?】 经验法则:观察head loss(分类loss)是否进入平台期 具体表现: - 连续5个epoch,cls_loss变化小于1% - 但训练集loss还有下降空间 - 同时满足上面两点 → 可以尝试解冻 解冻时记得:学习率降到原来的1/10,同时打开warmup,避免参数大幅震荡。 【冻结和BatchNorm的关系】 BatchNorm层会维护均值和方差的统计量。 冻结后,如果不把BN切换到eval模式: - BN还在用当前batch的数据更新统计量 - 但梯度已经不更新了 - 结果:统计量和实际参数对不上,推理时精度下降 YOLOv5的冻结操作会自动把对应BN层切成eval模式,不用你手动处理。 ================================================================================ 五、输入尺寸:640和416这些数字是怎么来的 ================================================================================ 【为什么必须是32的倍数】 YOLOv5的backbone做了5次下采样,每次尺寸减半: 640 → 320 → 160 → 80 → 40 → 20 每次下采样,YOLO都能检测不同大小的目标: - 80×80特征图:每个格子看8×8像素区域 → 负责小目标(安全帽离镜头远时) - 40×40特征图:每个格子看16×16像素区域 → 负责中目标 - 20×20特征图:每个格子看32×32像素区域 → 负责大目标(安全帽离镜头近时) 【安全帽项目的实际影响】 如果输入图片是 640×500: - 640/32=20,没问题 - 500/32=15.625,不是整数! 代码会怎么处理? - 方法1:自动padding,500→512(两边填充灰边),但这会改变目标的形状 - 方法2:直接报错 如果目标被padding导致形变(比如圆形的安全帽变成椭圆),检测精度肯定下降。 【实战建议】 训练前:把所有图片resize到统一尺寸,640×640或416×416都行,但必须32倍数。 推理时:保持和训练时完全一样的预处理方式。 ================================================================================ 六、优化器和学习率:训练能不能稳住的关键 ================================================================================ 【为什么默认用SGD而不是Adam】 做个比喻: - SGD = 认准一条路就一直走下去( momentum累积方向),虽然慢但稳 - Adam = 每步都会调整方向,更快但可能走偏 安全帽检测这种任务,需要强泛化能力——你训完的模型要能检测各种场景下的安全帽, 而不是只在某一两个场景下效果好。SGD更适合这个目标。 Adam/AdamW什么时候用? - batch很小(8-16)的时候 - 训练周期很短(几十个epoch)的时候 - 数据量很少的时候 【weight decay和安全帽项目的关系】 weight decay可以理解为一个"惩罚项",让模型不要过于依赖某一个特征。 有个坑:effective_wd = wd × (effective_batch / nbs) 什么意思? 假设你之前用batch=16训练,现在改成batch=64(扩大4倍), 如果其他参数不变,实际上你的正则强度也扩大了4倍。 这可能导致模型欠拟合——不是学习率的问题,是weight decay的问题。 【warmup:为什么训练初期要用小学习率】 刚开始训练时,模型参数是随机的,梯度方向很不稳定。 如果一上来就用大学习率,参数更新幅度太大,可能直接跳到一个很差的位置。 warmup = 用一个很小的学习率慢慢试探,让模型先找到正确的梯度方向。 【EMA:让验证指标更稳定的技巧】 EMA = 维护一套"影子权重",每次更新只吸收当前权重0.01%的变化。 好处:训练末期的参数在最优解附近震荡,EMA帮你把这些震荡平滑掉。 在YOLOv5中,你拿到的best.pt是EMA权重,不是最后一个epoch的权重。 ================================================================================ 七、数据管线:70%的问题出在这里 ================================================================================ 【Dataset在干什么】 用安全帽检测的例子,Dataset做四件事: 1. 找图片:扫描images/目录,找到所有.jpg文件 2. 找标签:找同名.txt文件,解析成[class_id, cx, cy, w, h]格式 3. 读图片:用cv2读取图片,做resize和归一化 4. 做增强:Mosaic、MixUp、翻转、HSV调整 【文件命名最容易踩的坑】 安全帽数据集有4种常见问题: 坑1:文件名有空格 "safety helmet .jpg"(helmet和.jpg之间有空格) 路径解析时会认为文件名是" safety helmet ",找不到对应的.txt 坑2:文件名有隐藏字符 从Windows复制过来的文件名可能带了不可见字符 坑3:大小写不一致 images/下是 SafetyHelmet.jpg,labels/下是 safetyhelmet.txt Windows不区分大小写能找到,Linux服务器就找不到了 坑4:扩展名不统一 图片是 .JPG(大写),标签是 .txt(小写) 建议:训练前写个脚本统一检查一遍。 【cache机制的双刃剑】 第一次跑训练,YOLOv5会解析所有标签并缓存到.cache文件。 第二次启动时直接读cache,跳过重新解析,能加速Dataset构建。 但如果你手动改了标签,cache不会自动更新! 程序会继续读旧的cache,你改了标注但指标没变化。 解决方法:每次改完标签,删掉对应的.cache文件。 【collate_fn为什么要加image index】 假设batch=4,就是4张图片一起前向传播。 输出是一个合并的大tensor,但loss计算时必须知道: "这条GT标签是第0张图的"还是"第1张图的"? image index就在每条GT前插入一个数字,表示属于哪张图。 没有这个,GT和预测会错位,loss计算完全错误。 ================================================================================ 八、数据增强:不是越多越好 ================================================================================ 【安全帽项目的增强原则】 增强的目的是:让训练数据模拟真实场景的多样性。 真实安全帽数据集的局限: - 样本有限(拍了1000张) - 场景单一(白天拍的,夜晚场景少) - 目标形态单一(大部分是正面照) 增强就是用低成本的图像变换,模拟高成本的真实场景多样性。 【为什么不能叠加太多】 假设你把mosaic设成1.0(每张图都是4张合成的),同时开了mixup。 结果:每张训练图都是"四张图混合的超级合成图" 问题:模型长期在"过度人工"的数据上训练, 学到的东西不够贴近真实单一目标,泛化到现场时效果反而下降。 【增强的执行顺序】 以默认配置为例(mosaic开启): 第一步:Mosaic(4图拼接) 4张安全帽图拼成1张,模拟多目标场景 第二步:MixUp(两图混合) 把当前图和另一张图加权混合(像素级blend) 第三步:几何变换 随机旋转(-5°到+5°)、平移、缩放 关键:GT框坐标必须同步变换,否则标签和图对不上 第四步:颜色域增强 HSV调整(模拟不同光照:白天/阴天/室内/室外) 第五步:翻转 水平翻转,概率0.5 注意:不做垂直翻转,因为真实拍摄不会上下颠倒 【安全帽项目增强实验顺序】 每次只开一个增强,观察3-5个epoch的趋势: 第一步:关掉mosaic和mixup,只用HSV+翻转 → 建立基线 第二步:开mosaic,看指标涨了多少 第三步:开了mosaic之后,如果类别之间容易混淆,开mixup 第四步:调整HSV范围(如果白天夜晚场景都有) 第五步:调整翻转概率(如果安全帽有方向性,不能随意翻转) ================================================================================ 九、损失函数:三个loss各自在干什么 ================================================================================ 【box_loss:框的位置准不准】 box_loss负责衡量"预测框和真实框的差距"。 YOLOv5用CIoU,它同时考虑: - 重叠面积(两个框叠了多少) - 中心点距离(框和框的中心离得远不远) - 宽高比(框的形状像不像) 类比:安全帽检测中,box_loss低意味着: "预测框的位置和大小都和真实的安全帽框很接近" 【obj_loss:这个地方有没有目标】 obj_loss衡量"模型有没有发现这里有安全帽"。 正样本:GT框中心点落在哪个格子,哪个格子的anchor就是正样本 负样本:背景格子(没有安全帽) 忽略样本:GT框附近但不是正样本的格子 【cls_loss:目标是什么类别】 cls_loss衡量"如果有安全帽,它是哪一类"。 安全帽检测如果是二类(安全帽/无安全帽), cls_loss就会很低,如果模型能正确区分这两类。 【多尺度头损失平衡】 YOLOv5有3个检测头,分别负责不同大小的安全帽: P3头(80×80):负责小的、远处的人 P4头(40×40):负责中等距离的 P5头(20×20):负责近处的大安全帽 默认权重是[4.0, 1.0, 0.4],小目标权重最高。 如果你发现远处的小安全帽总漏检: 把P3权重从4.0调到6.0或8.0,看有没有改善。 【Focal Loss解决的是什么问题】 背景格子(没有安全帽的区域)数量远远多于有安全帽的格子。 大量背景是"简单样本",模型很容易判断"这里没东西"。 Focal Loss的思路: - 简单样本产生的损失小 → 调低它的权重 - 难样本产生的损失大 → 继续重点优化 ================================================================================ 十、best.pt是怎么确定的 ================================================================================ 【fitness不是只看mAP】 best.pt的保存依据是fitness(适应度),不是单一的mAP。 典型公式:fitness = 0.1 × P + 0.9 × mAP@0.5:0.95 为什么要加权?因为不同场景看重的东西不一样。 【安全帽项目的fitness调整】 场景1:安全生产监控 漏检(假阴性)危险,有人没戴安全帽你没检测出来 → 提高Recall的权重,宁可多报警也不要漏检 场景2:安全帽数量统计 误检(假阳性)导致计数错误 → 提高Precision的权重,宁可少计也不要多数 场景3:通用检测 没有明显偏好 → 用默认权重就行 【为什么最高Recall的epoch可能不是best】 假设某epoch: - Recall = 0.95(几乎所有安全帽都找到了) - 但Precision = 0.3(一大堆误检,把非安全帽也标成安全帽了) fitness = 0.1 × 0.3 + 0.9 × 0.5 = 0.48,可能比不过 Recall = 0.8、Precision = 0.7 的 epoch(fitness = 0.71) 所以best.pt不是"找得最全的",是"综合表现最好的"。 ================================================================================ 十一、排错清单:安全帽项目常见问题 ================================================================================ 【问题1:改了参数但指标没变化】 排查步骤: 1. 看训练日志开头打印的opt,确认参数真的生效了 2. 如果用了resume,检查opt.yaml是否覆盖了你的修改 3. 如果改了hyp.yaml,加个特殊注释(比如 # DEBUG_001)确认文件被读取了 4. 清理__pycache__,避免跑的是旧代码 【问题2:改了标签但指标不变】 排查步骤: 1. 检查.cache文件有没有删 2. 可视化几张图,确认标签框真的画在安全帽上了 3. 检查images/和labels/文件名是否完全对应 【问题3:自己写的推理和官方推理结果差很多】 排查步骤: 1. 预处理是否一致:颜色通道(RGB还是BGR)、归一化(/255)、resize方式(letterbox) 2. 后处理是否一致:conf_thres和iou_thres是否一样 3. 模型是否eval模式,有没有漏.float() 4. 输出张量reshape顺序是否正确 【问题4:loss发散(第一个epoch就爆炸)】 排查步骤: 1. 梯度爆炸 → 加梯度裁剪,或降低学习率 2. 学习率过高 → 从0.01降到0.001试试 3. 增强过强 → 关掉mosaic和mixup,只用HSV+翻转 4. weight decay没配合batch调整 → batch扩大4倍,wd也要降低 ================================================================================ 十二、实验顺序:一次只改一个 ================================================================================ 这是最重要的一节。很多人的问题是"同时改了很多东西,不知道哪个有效"。 【第一阶段:数据正确性验证】 在优化之前,必须确认数据没问题。 操作: - 可视化10-20张图,看GT框有没有画在正确位置 - 检查图片能不能正常读取 - 确认类别标签和图片内容一致 数据有问题,优化一切白搭。 【第二阶段:建立可信基线】 用最基础的配置跑完一个完整训练周期: - mosaic=0, mixup=0 - 只有HSV调整和水平翻转 - 记录best.fitness和对应epoch 这个基线的意义是"基准",不是"最优"。 增强越少,训练指标可能越好看,但泛化能力不一定最好。 【第三阶段:逐个开启增强】 每隔3-5个epoch开启一个新的增强,记录变化: - baseline → +mosaic → +mixup → +其他增强 知道每个增强单独带来的收益是正还是负。 【第四阶段:损失权重调优】 观察哪个尺度(小/中/大目标)的AP最低。 小目标AP低 → 把P3权重从4.0调到6.0或8.0 【第五阶段:IoU变体替换】 如果基础优化都做完了还有提升空间: - CIoU → DIoU → SIoU → EIoU - 每次替换跑20-30个epoch,观察长期趋势 【第六阶段:fitness权重校准】 根据业务目标调整fitness计算公式。 如果更看重Recall → 提高Recall的权重。 ================================================================================ 十三、面试回答模板 ================================================================================ 【问题:YOLOv5训练流程是怎样的?】 回答: "YOLOv5训练分为10个节点:参数解析、模型构建、预训练权重加载、冻结层处理、优化器初始化、Dataset构建、epoch循环(包含前向传播、损失计算、反向传播、EMA更新)、验证、模型保存、断点续训。 任何训练异常都可以先定位到具体节点。比如loss发散,可能是lr过高(节点5)或增强过强(节点6);改了标签但指标不变,几乎一定是Dataset或cache的问题(节点6)。" 【问题:预训练权重加载的交集机制是什么?】 回答: "加载时遍历checkpoint中的每个参数,检查两个条件:键名在当前模型是否存在,shape是否一致。只有两个都满足才加载。 比如我改检测头类别数从80改成1,backbone参数几乎100%加载,因为结构没变。但检测头加载率很低,因为shape完全不同。 这种设计允许在预训练权重基础上做结构改动,不需要每次从零训练。" 【问题:如何诊断训练loss发散?】 回答: "按顺序排查四个方面: 第一,梯度爆炸。加梯度范数打印,grad_norm超100就说明爆炸,处理方法是降lr或开梯度裁剪。 第二,lr过高。第一个epoch的loss就非常高(比如>10),通常就是lr过高。 第三,增强过强。关掉mosaic和mixup,看loss曲线是否恢复正常。 第四,batch和weight decay配合。effective_wd = wd × (effective_batch/nbs),batch扩大后要同比降低wd。 核心思路:一次只改一个因素,观察是不是该因素导致的问题。" 【问题:best.pt是怎么确定的?】 回答: "best.pt按fitness综合评分决定,不是单个指标。比如fitness = 0.1×P + 0.9×mAP@0.5:0.95。 所以最高Recall的epoch可能不是best——Recall高但Precision很低,fitness综合起来反而不高。 另外,YOLOv5保存时用的是EMA权重,不是训练末期的即时权重。EMA通过时间平均抵消高频波动,通常更接近最优解。" ================================================================================ 十四、最终总结:三个能力让你区别于其他调参者 ================================================================================ 第一个能力:链路视角 能说出参数从哪来到哪去,能定位任何训练异常到具体模块。 第二个能力:系统优化 有计划地建立基线、逐项实验、记录对照,用数据证明"这个改动有效"。 第三个能力:工程闭环 训练和推理全链路保持一致,能设计符合业务目标的fitness规则。 当你同时具备这三个能力,你就已经不是"会跑YOLO"了, 而是"会优化YOLO"。 ================================================================================