☰
GluonCV 损失函数模块 gluoncv.loss 详解:FocalLoss、SSDMultiBoxLoss 与检测/分割/跟踪损失实战
2026/10/15 12:19:51 网站建设 项目流程
  • 计算机视觉
  • 深度学习
  • 预训练

【免费下载链接】gluon-cv

Gluon CV Toolkit

项目地址:https://gitcode.com/gh_mirrors/gl/gluon-cv
点击查看免费下载

本文围绕 GluonCV 的官方 API 文档页docs/api/loss.rst展开,系统讲解gluoncv.loss模块中全部自定义损失函数的参数、计算公式与源码实现,并结合仓库中 SSD、YOLO、CenterNet、ICNet、SiamRPN 等真实训练脚本的调用方式,帮助读者在实际视觉任务训练时正确选择与配置损失函数。

模块总览:gluoncv.loss 是什么

docs/api/loss.rst是 GluonCV 的 loss API 参考页。该页通过 Sphinx 的automodule指令渲染 gluoncv/loss.py 整个模块的成员,并在页首用autosummary特别列出FocalLoss与SSDMultiBoxLoss两个核心损失。模块文档字符串说明了其设计定位:

Losses are subclasses ofgluon.loss.Losswhich is a HybridBlock actually.

即:这些损失函数本质上是继承自 MXNet GluonLoss/HybridBlock的可混合(hybridize)模块,可以像普通网络层一样参与net.hybridize(),这也是它们在多设备训练脚本中能以列表形式跨设备批量计算的原因。

当前仓库版本为0.11.0(见 gluoncv/init.py),模块中通过__all__导出以下损失:

损失类典型任务
FocalLoss类别不均衡的二分类/多标签分类
SSDMultiBoxLossSSD 目标检测
YOLOV3LossYOLOv3 目标检测
SoftmaxCrossEntropyLoss语义分割(带 ignore 标签)
SegmentationMultiLossesDAFUNet 等多头分割
MixSoftmaxCrossEntropyLoss带辅助输出/Mixup 的分割(FCN、PS 等)
ICNetLossICNet 三尺度级联分割
SoftmaxCrossEntropyOHEMLoss/MixSoftmaxCrossEntropyOHEMLoss带 OHEM 的分割
DistillationSoftmaxCrossEntropyLoss知识蒸馏训练
HeatmapFocalLossCenterNet 关键点/中心点热图
MaskedL1LossCenterNet 宽高/中心回归
SiamRPNLossSiamRPN 视觉跟踪

导入方式统一为import gluoncv as gcv后使用gcv.loss.<损失名>,或from gluoncv.loss import SiamRPNLoss。注意该模块依赖 MXNet 后端,而 PyTorch 生态下的对应损失(如HMFocalLoss)位于gluoncv/torch/子包中,不在本文范围。

FocalLoss:应对类别不均衡的分类损失

FocalLoss是文档页autosummary中首先列出的损失,实现位于 gluoncv/loss.py。其思想来自 RetinaNet 论文(Focal Loss for Dense Object Detection, arXiv:1708.02002):通过降低易分类负样本的权重,让网络聚焦于难样本。

参数说明

源码 docstring 定义的完整参数如下:

参数默认值说明
axis-1计算 softmax/熵时求和的轴,即类别维度
alpha0.25控制正负样本平衡的系数 α
gamma2控制难样本聚焦程度的指数 γ
sparse_labelTrue标签是否为整数索引(而非概率分布)
from_logitsFalse输入是否已经是 log 概率(通常来自 log_softmax)
batch_axis0表示 mini-batch 的轴
weightNone损失的全局标量权重
num_classNone类别数;当sparse_label=True时必填
eps1e-12防止对数数值问题的极小值
size_averageTrue是否对除batch_axis外的所有维求均值

构造时有一个硬性校验(见 gluoncv/loss.py):若sparse_label=True而未提供num_class >= 1的整数,会直接抛出ValueError。

实现细节

hybrid_forward的前向逻辑(见 gluoncv/loss.py)可以逐行对应到公式:

  1. 若from_logits=False,先对pred做 sigmoid,得到每个类别的概率;
  2. 稀疏标签先经one_hot展开,稠密标签则用label > 0得到正类掩码;
  3. 计算pt = where(one_hot, pred, 1 - pred),即"预测值在真实类别方向的概率";
  4. 计算alpha_t = where(one_hot, alpha, 1 - alpha);
  5. 损失主体:loss = -alpha_t * (1 - pt)^gamma * log(min(pt + eps, 1));
  6. 经_apply_weighting应用全局权重与逐元素sample_weight后,沿batch_axis以外的维度求均值,输出形状为(batch_size,)。

使用示例:

import gluoncv as gcv import mxnet as mx import mxnet.ndarray as nd # pred: (N, C) 的 logits,label: (N,) 的整数索引 focal = gcv.loss.FocalLoss(num_class=10, alpha=0.25, gamma=2.0) pred = mx.gluon.nn.Conv2D(10, 1)(nd.random_normal((4, 3, 32, 32))) label = nd.array([0, 3, 7, 9]) loss = focal(pred, label)

SSDMultiBoxLoss:SSD 检测的损失与负样本挖掘

SSDMultiBoxLoss是文档页autosummary中列出的第二个损失,实现位于 gluoncv/loss.py。与FocalLoss不同,它继承自gluon.Block而非Loss,因为它的输入是列表(每个设备一个张量),需要跨设备归一化。

参数说明

参数默认值说明
negative_mining_ratio3负样本与正样本的数量比(负样本挖掘比例)
rho1.0L1-L2 平滑过渡阈值,即平滑参数
lambd1.0分类与回归损失的相对权重,总损失为L = loss_class + lambda * loss_loc
min_hard_negatives0每张图片至少保留的困难负样本数

docstring 中特别提示:由于需要跨设备同步计算 batch 级统计量,该损失比非同步版本开销略大,但作者发现这样对收敛后的模型精度更友好。

计算流程(对应源码)

结合 gluoncv/loss.py 的实现,前向分为几步:

  1. 统计全局正样本数:pos_samples = (cls_target > 0),把所有设备上的正样本数求和得到num_pos_all。若num_pos_all < 1且min_hard_negatives < 1,返回一组"零梯度占位损失"(cp * 0),保证反向传播图连通但不贡献梯度;
  2. 分类损失与负样本挖掘:先对cls_pred做log_softmax,用-nd.pick(pred, cls_target)取出真实类(含背景 0 类)的对数似然作为逐 anchor 交叉熵;随后用双次argsort的技巧计算每个位置在"按损失降序"中的排名,取rank < max(min_hard_negatives, 正样本数 * negative_mining_ratio)的位置作为困难负样本。只有"正样本 ∪ 困难负样本"参与损失,其余位置置零,最后除以max(1, num_pos_all)归一化;
  3. 边界回归损失(L1-L2 平滑):对正样本计算box_loss = |bp - bt|,再按|d| > rho ? |d| - rho/2 : (0.5/rho) * d²做 L1→L2 平滑,并只在正样本位置生效,同样除以全局正样本数;
  4. 合成总损失:sum_loss = cls_loss + lambd * box_loss。

返回值是三元组(sum_losses, cls_losses, box_losses),每个都是"每设备一个标量"的列表。

在训练脚本中的真实用法

scripts/detection/ssd/train_ssd.py 展示了标准用法:

mbox_loss = gcv.loss.SSDMultiBoxLoss() ... with autograd.record(): cls_preds = [] box_preds = [] for x in data: # 每个设备一张切片 cls_pred, box_pred, _ = net(x) cls_preds.append(cls_pred) box_preds.append(box_pred) sum_loss, cls_loss, box_loss = mbox_loss( cls_preds, box_preds, cls_targets, box_targets) autograd.backward(sum_loss) ... trainer.step(1) # 损失已按 num_pos 归一化,故 step 传 1 避免再除 batch

注意最后trainer.step(1)的注释:由于损失内部已按正样本数归一化,step时不再除以 batch size,这是多设备训练时的一个关键细节。

YOLOV3Loss:YOLOv3 的四项损失

YOLOV3Loss实现位于 gluoncv/loss.py,用于 YOLOv3 训练。它接收 10 个输入:

  • objness(B, N):预测的物体性(0,1);
  • box_centers(B, N, 2)、box_scales(B, N, 2):预测框中心与宽高;
  • cls_preds(B, N, num_class):类别预测;
  • 对应的目标张量objness_t、center_t、scale_t、class_t,其中objness_t中 0 表示负样本、1 表示正样本、-1 表示忽略;
  • weight_t(B, N, 2):中心与尺度目标的损失乘子;
  • class_mask(B, N, num_class):0/1 掩码,用于屏蔽被忽略的样本。

内部维护两个基础损失:SigmoidBinaryCrossEntropyLoss(from_sigmoid=False)与L1Loss。返回四项损失(均为总和):

  • obj_loss:物体性 logistic 损失,通过hard_objness_t = where(objness_t > 0, 1, objness_t)把正样本目标强制为 1,并用where(objness_t > 0, objness_t, objness_t >= 0)生成掩码使 -1 的忽略区不参与计算;
  • center_loss:中心点 logistic 回归损失,乘子为weight_t * objness_t;
  • scale_loss:尺度 L1 损失;
  • cls_loss:逐类别 logistic 损失,class_mask与objness_t相乘后屏蔽忽略区。

归一化因子denorm是目标张量除 batch 维外所有维度的乘积,中心与尺度损失额外乘 2(因为各有 2 个分量)。该损失在模型侧被封装:gluoncv/model_zoo/yolo/yolo3.py 中YOLOv3Net持有self._loss = YOLOV3Loss(),训练时直接由网络前向输出四项损失。

分割损失家族:以 SoftmaxCrossEntropyLoss 为基类

gluoncv.loss中语义分割相关损失构成一个继承家族,全部围绕"支持 ignore 标签的 Softmax 交叉熵"构建,这正是分割任务与分类任务的关键差异——预测图上存在无效像素(边界、遮挡),必须被排除在损失之外。

SoftmaxCrossEntropyLoss(带 ignore 标签)

实现位于 gluoncv/loss.py。相比 MXNet 原生的gluon.loss.SoftmaxCrossEntropyLoss,它增加:

参数默认值说明
ignore_label-1需要忽略的标签值
size_averageTrue是否按有效像素数重新归一化(normalization='valid')

前向调用 MXNet 的SoftmaxOutput算子,use_ignore=True并传入ignore_label;稀疏标签用pick取真实类概率,稠密标签则做-sum(log(softmaxout) * label);最后把ignore_label位置的损失显式置零,并沿batch_axis求均值。

SegmentationMultiLosses:多头直接相加

SegmentationMultiLosses(见 gluoncv/loss.py)继承上者,接收(pred1, pred2, pred3, label)四个输入,对三个预测头分别计算交叉熵后直接求和,即loss = loss1 + loss2 + loss3。它在 scripts/segmentation/train.py 中用于 DAFUNet('danet' in args.model)这类带多个预测分支的模型。

MixSoftmaxCrossEntropyLoss:辅助损失与 Mixup

MixSoftmaxCrossEntropyLoss(见 gluoncv/loss.py)是分割训练中最常用的损失,新增参数:

参数默认值说明
auxTrue是否启用辅助输出损失
mixupFalse是否启用 Mixup 损失
aux_weight0.2辅助损失的权重

其hybrid_forward按aux/mixup组合分派到四条路径:

  • 普通模式:基类交叉熵;
  • 辅助模式_aux_forward:主输出与辅助输出各算一次交叉熵,返回loss1 + aux_weight * loss2;
  • Mixup 模式_mixup_forward:输入(pred, label1, label2, lam),损失按lam * loss(label1) + (1 - lam) * loss(label2)加权,兼容稀疏/稠密标签;
  • 辅助 + Mixup 模式_aux_mixup_forward:两个输出头分别做 Mixup 加权,再按aux_weight合成。

scripts/segmentation/train.py 展示了按模型选择损失的完整逻辑:

# create criterion if 'icnet' in args.model: criterion = ICNetLoss(crop_size=args.crop_size) elif 'danet' in args.model or (args.model_zoo and 'danet' in args.model_zoo): criterion = SegmentationMultiLosses() else: criterion = MixSoftmaxCrossEntropyLoss(args.aux, aux_weight=args.aux_weight)

分类侧也复用了这个类:scripts/classification/imagenet/train_imagenet_nasnet.py 中 NASNet 训练用MixSoftmaxCrossEntropyLoss(aux_weight=0.4),开启sparse_label=False分支来配合稠密化(如 label smoothing)后的软标签。

ICNetLoss:三尺度级联加权

ICNetLoss(见 gluoncv/loss.py)专用于 ICNet 训练:

参数默认值说明
weights(0.4, 0.4, 1.0)级联标签引导的权重
height/width取crop_size各尺度预测上采样的目标尺寸
crop_size480默认裁剪尺寸

前向接收 5 个输入:三个尺度的预测pred1~pred3加label。每个预测先经BilinearResize2D统一到height × width,再分别算交叉熵,最终合成:

loss = loss1 + 0.4 * loss2 + 0.4 * loss3 + 1.0 * loss4

即在代码中写作loss[0] + weights[0] * loss[1] + weights[1] * loss[2] + weights[2] * loss[3]。

OHEM 变体:SoftmaxCrossEntropyOHEMLoss

SoftmaxCrossEntropyOHEMLoss(见 gluoncv/loss.py)把基类替换为 OHEM(Online Hard Example Mining)版本:底层调用F.contrib.SoftmaxOHEMOutput,固定thresh=0.6, min_keep=256,即只保留预测置信度低于 0.6 的最难像素(且至少保留 256 个),使网络在训练后期更关注难样本。MixSoftmaxCrossEntropyOHEMLoss(见 gluoncv/loss.py)在其上加了与前面同构的aux/aux_weight辅助输出支持。

DistillationSoftmaxCrossEntropyLoss:知识蒸馏训练

DistillationSoftmaxCrossEntropyLoss(见 gluoncv/loss.py)用于教师-学生蒸馏,参数:

参数默认值说明
temperature1软化教师预测的温度 T
hard_weight0.5one-hot 硬标签损失的权重
sparse_labelTrueone-hot 标签是否稀疏

内部持有两个原生SoftmaxCrossEntropyLoss(soft_loss用软标签、hard_loss用硬标签),前向hybrid_forward(F, output, label, soft_target)按权重分派:

  • hard_weight == 0:纯软标签损失T² * CE(output / T, soft_target)(平方温度因子用于补偿梯度缩放);
  • hard_weight == 1:纯硬标签损失CE(output, label);
  • 一般情形:(1 - hard_weight) * T² * CE(output/T, soft_target) + hard_weight * CE(output, label)。

scripts/classification/imagenet/train_imagenet.py 给出了真实调用:

sparse_label_loss = not (opt.label_smoothing or opt.mixup) if distillation: L = gcv.loss.DistillationSoftmaxCrossEntropyLoss(temperature=opt.temperature, hard_weight=opt.hard_weight, sparse_label=sparse_label_loss) else: L = gluon.loss.SoftmaxCrossEntropyLoss(sparse_label=sparse_label_loss)

可见一个值得注意的约定:当使用 label smoothing 或 mixup 时,标签已经是稠密概率,因此sparse_label置为False。同样的写法也出现在 scripts/classification/imagenet/train_horovod.py 和 AutoGluon 风格的分类估计器 gluoncv/auto/estimators/image_classification/image_classification.py。

HeatmapFocalLoss 与 MaskedL1Loss:CenterNet 的关键点损失

这两个损失服务于 CenterNet,在 scripts/detection/center_net/train_center_net.py 中成组出现:

heatmap_loss = gcv.loss.HeatmapFocalLoss(from_logits=True) wh_loss = gcv.loss.MaskedL1Loss(weight=args.wh_weight) center_reg_loss = gcv.loss.MaskedL1Loss(weight=args.center_reg_weight)

HeatmapFocalLoss(见 gluoncv/loss.py)是面向热图的 focal loss:

  • 正样本(label == 1):pos_loss = log(pred) * (1 - pred)^2;
  • 负样本(label < 1):neg_loss = log(1 - pred) * pred^2 * (1 - label)^4,其中(1 - label)^4让离关键点越远的负样本权重越低;
  • 最终按正样本数归一化:-(sum(pos_loss) + sum(neg_loss)) / max(num_pos, 1)。

注意其from_logits=True的用法:CenterNet 网络输出的热图已经过 sigmoid,因此传入的是概率而非 logits。

MaskedL1Loss(见 gluoncv/loss.py)计算带掩码的均值绝对误差:

L = sum(|label * mask - pred * mask|) / max(sum(mask), 1)

pred、label、mask任意形状但元素数须一致,最终按 mask 中非零元素数归一化,支持逐元素sample_weight。它在 CenterNet 中分别承担宽高回归(wh_weight)与中心点回归(center_reg_weight)的损失。仓库的 Auto 模块也复用了同样的组合,见 gluoncv/auto/estimators/center_net/center_net.py。

SiamRPNLoss:视觉跟踪的加权 L1 + 交叉熵

SiamRPNLoss(见 gluoncv/loss.py)是 SiamRPN 跟踪算法的专用损失,结构上有几个值得注意的固定常量:锚点网格self.h = self.w = 17(17×17 个锚点)、位置通道数loc_c = 10(5 个锚框 × 2)、分类通道数cls_c = 5。构造参数只有一个batch_size(默认 128,每设备训练 batch size),用于归一化。

前向hybrid_forward(F, cls_pred, loc_pred, label_cls, pos_index, neg_index, label_loc, label_loc_weight)返回(cls_loss, loc_loss):

  • 位置损失weight_l1_loss:把预测 reshape 为(B, 4, -1, 17, 17),对 4 个回归维求和后逐位置乘label_loc_weight,按 batch 取均值;
  • 分类损失cross_entropy_loss:把类别预测重组为(N, 2)的二分类张量,分别对正样本索引pos_index与负样本索引neg_index用gather_nd取样计算 softmax 交叉熵,再以 0.5/0.5 加权合成;若某类索引为空则该项为 0。

scripts/tracking/siamrpn/train.py 中可以直接看到用法:from gluoncv.loss import SiamRPNLoss,并在训练前criterion = SiamRPNLoss(opt.batch_size)(见该文件 L22、L158)。

小结:按任务选择损失

回到docs/api/loss.rst本身,它作为 API 参考页的职责是把gluoncv.loss模块的成员索引给读者;本文则结合 gluoncv/loss.py 源码给出了完整的落地视图。可以按任务建立如下映射:

任务首选损失仓库内参考调用
SSD 检测SSDMultiBoxLossscripts/detection/ssd/train_ssd.py
YOLOv3 检测YOLOV3Loss(封装在网络内)gluoncv/model_zoo/yolo/yolo3.py
CenterNet 检测HeatmapFocalLoss+MaskedL1Lossscripts/detection/center_net/train_center_net.py
语义分割(带 aux)MixSoftmaxCrossEntropyLossscripts/segmentation/train.py
ICNet 分割ICNetLoss同上(L194)
类别不均衡分类FocalLossgluoncv/loss.py
知识蒸馏DistillationSoftmaxCrossEntropyLossscripts/classification/imagenet/train_imagenet.py
SiamRPN 跟踪SiamRPNLossscripts/tracking/siamrpn/train.py

使用这些损失时的两个通用注意点:其一,所有损失都继承自 Gluon 的Loss/Block,因此可以与hybridize协同,但SSDMultiBoxLoss需要跨设备列表输入,单设备调试时也可传单张量(内部_as_list会自动包装);其二,多损失组合(如aux_weight、lambd、weights)决定了各分支梯度强度,调参时应以训练日志中各分项损失(如CrossEntropy、SmoothL1等 metric)为观测抓手。

  • 计算机视觉
  • 深度学习
  • 预训练

【免费下载链接】gluon-cv

Gluon CV Toolkit

项目地址:https://gitcode.com/gh_mirrors/gl/gluon-cv
点击查看免费下载

相关推荐

上一篇:OpenTalking社区与贡献指南:如何参与这个工业级开源AI数字人项目
下一篇:i-have-adhd 跨平台安装指南:为 15 种 AI 编程 Agent 启用 ADHD 友好输出

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询