新闻详情

新闻详情

首页 / 资讯中心 / 详情

062、v8DetectionLoss 损失计算源码:pred与target 的矩阵维度对齐与 Loss 加和

发布时间:2026/9/1 5:44:36来源:尧图网络
062、v8DetectionLoss 损失计算源码:pred与target 的矩阵维度对齐与 Loss 加和
062、v8DetectionLoss 损失计算源码pred与target 的矩阵维度对齐与 Loss 加和上周调一个YOLOv8的自定义数据集发现loss死活不降打印出来一看分类loss直接是0。debug了一下午最后发现是pred和target的维度没对齐——分类分支的pred shape是[4, 8400, 80]target的class index却传成了[4, 8400, 1]广播的时候直接吞掉了所有信息。这种坑踩过一次就再也不会忘了。今天就把v8DetectionLoss里最绕的维度对齐逻辑掰开揉碎讲清楚。代码基于ultralytics v8.1.0核心文件在ultralytics/utils/loss.py。损失函数的入口v8DetectionLoss.init先看初始化这里埋了两个关键组件classv8DetectionLoss:def__init__(self,model):self.bcenn.BCEWithLogitsLoss(reductionnone)# 注意是none后面自己加权self.hypmodel.hyp# 超参包含box_loss_gain等self.stridemodel.stride# 下采样倍数[8,16,32]self.ncmodel.model[-1].nc# 类别数self.nomodel.model[-1].no# 每个anchor的输出维度 41ncself.assignerTaskAlignedAssigner()# 正样本分配器这里有个容易忽略的点BCEWithLogitsLoss用了reduction‘none’。别写成默认的’mean’否则后面正负样本加权的时候你会疯掉——每个像素的loss权重不一样必须保留逐元素结果。前向传播pred和target的维度战争def__call__(self,preds,batch):losstorch.zeros(3,deviceself.device)# [box, cls, dfl]featspreds[1]ifisinstance(preds,tuple)elsepreds# 兼容推理模式pred_distri,pred_scorestorch.split(feats,[4*16,self.nc],dim1)# 这里踩过坑等一下这里为什么是416因为YOLOv8把bbox回归换成了DFLDistribution Focal Loss每个坐标用16个bin表示。所以回归分支的输出维度是41664分类分支是nc。如果你改了reg_max这里要同步改。pred_scores的shape[batch, nc, h, w]但后面要变成[batch, num_anchors, nc]。怎么变看下面pred_scorespred_scores.permute(0,2,3,1).contiguous()# [b, h, w, nc]pred_scorespred_scores.view(batch,-1,self.nc)# [b, num_anchors, nc]这里permute之后一定要contiguous()否则view会报错。别问我怎么知道的debug两小时的血泪史。正样本分配TaskAlignedAssigner的维度魔法这是整个loss计算最绕的地方。TaskAlignedAssigner的输入是pred_scores: [b, num_anchors, nc]pred_bboxes: [b, num_anchors, 4] (注意这里是cxcywh格式)gt_labels: [b, max_gt, 1] (每个gt的类别索引)gt_bboxes: [b, max_gt, 4] (归一化的xywh)输出是target_bboxes: [b, num_anchors, 4] (正样本对应的gt bbox)target_scores: [b, num_anchors, nc] (one-hot形式正样本位置为1)fg_mask: [b, num_anchors] (正样本掩码)关键来了target_scores的shape是[b, num_anchors, nc]但gt_labels是[b, max_gt, 1]。怎么对齐# 在assigner内部先做维度扩展# gt_labels: [b, max_gt, 1] - [b, max_gt, nc] one-hot编码target_scorestorch.zeros_like(pred_scores)# [b, num_anchors, nc]# 对每个正样本anchor把对应gt类别的score设为1target_scores[fg_mask]F.one_hot(gt_labels[assigned_gt],self.nc).float()这里fg_mask是[b, num_anchors]的bool矩阵assigned_gt是每个正样本对应的gt索引。别写成target_scores F.one_hot(gt_labels)那样shape直接炸掉。Loss计算三个分支的维度对齐1. 分类Loss# target_scores: [b, num_anchors, nc]# pred_scores: [b, num_anchors, nc]# 直接逐元素计算但只对正样本加权loss_clsself.bce(pred_scores,target_scores)# [b, num_anchors, nc]loss_clsloss_cls*target_scores# 只保留正样本的loss负样本置0loss_clsloss_cls.sum()/(fg_mask.sum()1e-7)# 除以正样本数这里有个trick为什么用target_scores乘而不是fg_mask因为target_scores已经是one-hot了乘完之后负样本位置自动为0。但注意这样写有个隐患——如果某个正样本的类别预测完全错误它的loss会被保留但负样本的loss被完全忽略。YOLOv8的默认做法是只对正样本计算分类loss负样本不参与分类loss计算。2. 回归LossCIoU DFL回归分支的维度对齐更绕。pred_distri的shape是[b, 4*16, h, w]要变成[b, num_anchors, 4, 16]pred_distripred_distri.permute(0,2,3,1).contiguous()# [b, h, w, 64]pred_distripred_distri.view(batch,-1,4,16)# [b, num_anchors, 4, 16]然后通过积分得到预测的bbox坐标# 对16个bin做softmax然后加权求和pred_bboxesself.bbox_decode(pred_distri)# [b, num_anchors, 4] cxcywh格式target_bboxes来自assigner的输出shape是[b, num_anchors, 4]但只有正样本位置有值负样本位置是0。# 计算CIoU loss只对正样本ioubbox_iou(pred_bboxes[fg_mask],target_bboxes[fg_mask],CIoUTrue)loss_iou1.0-iou# [num_pos]loss_iouloss_iou.mean()这里注意bbox_iou的输入必须是两个shape相同的tensor不能直接传[b, num_anchors, 4]和[b, num_anchors, 4]因为负样本的bbox是0计算iou会得到0但loss会变成1导致负样本也参与回归。所以必须用fg_mask过滤。3. DFL LossDFL的输入是pred_distri和target的分布。target的分布怎么得到把gt bbox的坐标映射到16个bin上# target_bboxes是cxcywh格式需要转成ltrb格式target_ltrbbbox_cxcywh_to_ltrb(target_bboxes[fg_mask])# [num_pos, 4]# 每个坐标映射到[0, 15]的区间target_binstarget_ltrb*(self.reg_max-1)# 假设reg_max16# 取整得到左右边界target_lefttarget_bins.floor().long()target_righttarget_bins.ceil().long()然后计算交叉熵# pred_distri[fg_mask]: [num_pos, 4, 16]# 对每个坐标计算左边界和右边界的交叉熵loss_dflF.cross_entropy(pred_distri[fg_mask].view(-1,16),# [num_pos*4, 16]target_left.view(-1),# [num_pos*4]reductionnone)F.cross_entropy(pred_distri[fg_mask].view(-1,16),target_right.view(-1),reductionnone)loss_dflloss_dfl.mean()/2# 左右边界平均这里有个细节cross_entropy的输入是[num_pos4, 16]和[num_pos4]因为每个bbox有4个坐标每个坐标独立计算。别写成[num_pos, 4, 16]和[num_pos, 4]那样维度不匹配。Loss加和权重分配loss_boxloss_iou*self.hyp.box_loss_gain# 默认7.5loss_clsloss_cls*self.hyp.cls_loss_gain# 默认0.5loss_dflloss_dfl*self.hyp.dfl_loss_gain# 默认1.5total_lossloss_boxloss_clsloss_dfl这些超参在hyp.yaml里配置。注意box_loss_gain特别大因为CIoU loss本身数值很小0.1~0.9而分类loss是BCE数值可能到几十。如果不加权回归分支的梯度会被淹没。踩坑经验总结维度检查是第一生产力每次写loss之前先print所有tensor的shape。我习惯在__call__开头加一行print(pred_scores.shape, target_scores.shape)调试完再删掉。fg_mask的维度陷阱fg_mask是[b, num_anchors]的bool矩阵但target_scores是[b, num_anchors, nc]。用fg_mask索引时要确保维度匹配。比如target_scores[fg_mask]会得到[num_pos, nc]而target_bboxes[fg_mask]会得到[num_pos, 4]。别混用。BCEWithLogitsLoss的reduction一定要用’none’因为后面要手动加权。如果用’mean’正负样本的权重就混在一起了。DFL的reg_max一致性模型定义里的reg_max和loss里的reg_max必须一致。如果你改了模型结构记得同步改loss里的self.reg_max。梯度检查如果loss不降先检查梯度是否正常。在backward之前加一行loss.backward(retain_graphTrue)然后print每个参数的grad。如果某个分支的grad全是0说明那个分支的loss没参与计算。数值稳定性在除以fg_mask.sum()时加个1e-7防止batch里没有正样本导致除零。虽然YOLOv8的anchor分配策略保证至少有一个正样本但自定义数据集可能出幺蛾子。最后说一句别迷信默认超参。我调过的一个工业检测项目box_loss_gain从7.5改到15mAP直接涨了3个点。loss的权重分配最终还是要看你的数据分布。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

B站2023校招算法笔试卷B拆解:高频考点与实战策略 2026/9/1 21:40:57

B站2023校招算法笔试卷B拆解:高频考点与实战策略

去年秋招帮学弟做模拟面试的时候,他拿了一套哔哩哔哩2023校园招聘算法方向的笔试卷B出来,说连着做了两遍,选择题还好,一到编程题和问答题就心里没底。我翻了翻这套卷子,发现它其实很有代表性——它不只是B站的笔试&…

阅读更多 →
Plecs仿真四相交错Buck:C语言PI控制与DCM补偿实战 2026/9/1 21:40:57

Plecs仿真四相交错Buck:C语言PI控制与DCM补偿实战

这类低压大电流的电源仿真,最值得先看的不是拓扑有多复杂,而是控制算法能不能在仿真里稳定跑起来,以及从单相扩展到四相交错并联时,那些“坑”会不会被放大。很多人一上来就调环路参数,结果发现仿真要么不收敛&#xf…

阅读更多 →
Rust类型系统实战:通过国际象棋项目掌握所有权、生命周期与特质 2026/9/1 21:40:57

Rust类型系统实战:通过国际象棋项目掌握所有权、生命周期与特质

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
五大主流NAS系统深度横评:从群晖到飞牛,如何选择最适合你的网络存储方案? 2026/9/1 21:40:57

五大主流NAS系统深度横评:从群晖到飞牛,如何选择最适合你的网络存储方案?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Unity3D校招笔试题全拆解:从C#基础到引擎原理与性能优化 2026/9/1 21:40:57

Unity3D校招笔试题全拆解:从C#基础到引擎原理与性能优化

又到校招季,经常有人拿着笔试链接来问我“网易这套Unity3D题怎么准备”。说实话,2020届提前批这套题在当年算是游戏行业校招笔试里比较有代表性的,它考察的并不只是你背了多少API,而是你在真实项目里有没有踩过坑、有没有自己的工…

阅读更多 →
AI视频清晰化避坑指南:漫剧二次剪辑怎么保画质? 2026/9/1 21:37:57

AI视频清晰化避坑指南:漫剧二次剪辑怎么保画质?

正在制作AI漫剧或AI动画视频的小伙伴,给大家推荐这里:AIGC梦工厂(www.aigcc.vip)。Ai漫剧一站式成片。输入一句话进去就能一键成片;画布模式可以精修每一帧画面;还有500多种Ai图片玩法。有兴趣的可以看看。…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞