MXNet Gluon 损失函数库(mxnet.gluon.loss)完整使用指南
【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet
导读
Gluon 在mxnet.gluon.loss模块中提供了 16 个预定义损失函数,覆盖回归、分类、大间隔排序、分布逼近与序列对齐等主流深度学习任务。本文以 docs/python_docs/python/api/gluon/loss/index.rst 为索引骨架,深入 python/mxnet/gluon/loss.py 的完整实现,逐一解析每个损失函数的数学定义、构造参数、输入输出约定与数值稳定性处理,并结合 tests/python/unittest/test_loss.py 的测试用例给出可复现的验证方式。读完本文,你将掌握 Gluon 损失函数的通用机制(权重、批轴、样本加权)以及针对具体任务的选择与配置方法。
gluon.loss 模块概览
mxnet.gluon.loss是 Gluon 高层 API 的核心组成之一,在 Gluon API 总索引 docs/python_docs/python/api/gluon/index.rst 中与gluon.nn、gluon.rnn、gluon.data等并列。该模块的全部类均继承自HybridBlock,因此既可以在命令式(NDArray)模式下直接调用,也可以通过hybridize()编译成符号图执行,天然兼容 Gluon 的混合编程范式。
模块导出清单(见 loss.py 的__all__):
- 基类:
Loss - 回归类:
L1Loss、L2Loss、HuberLoss、PoissonNLLLoss - 分类类:
SoftmaxCrossEntropyLoss(别名SoftmaxCELoss)、SigmoidBinaryCrossEntropyLoss(别名SigmoidBCELoss)、LogisticLoss - 大间隔/度量学习类:
HingeLoss、SquaredHingeLoss、TripletLoss、CosineEmbeddingLoss - 分布与序列类:
KLDivLoss、CTCLoss
别名(Alias)机制在源码中通过直接赋值实现,例如SigmoidBCELoss = SigmoidBinaryCrossEntropyLoss(loss.py)与SoftmaxCELoss = SoftmaxCrossEntropyLoss(loss.py),两个名字完全等价,可任意混用。
基类 Loss 与通用机制
所有损失函数都继承自Loss(HybridBlock)(loss.py),构造函数统一接收两个关键参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
weight | 因子类而异(多数为None,L2Loss为1.) | 损失整体的全局标量缩放系数 |
batch_axis | 0 | 表示 mini-batch 维度的轴,其余维度会被平均掉 |
基类通过__repr__提供形如L2Loss(batch_axis=0, w=1.0)的可读字符串,便于调试时打印损失对象。
通用输入约定
除TripletLoss(三个张量输入)和CosineEmbeddingLoss(两个向量加一个标签)外,大多数损失函数统一接受:
- pred:预测张量,形状任意;
- label:真值张量,与
pred元素数相同,内部会先经_reshape_like对齐形状(loss.py); - sample_weight(可选):逐元素权重张量,必须可广播到
pred的形状。例如pred形状为(64, 10)时,想按样本加权应传入形状(64, 1)的张量。
所有损失输出的形状均为(batch_size,),即非批轴维度全部被平均/求和掉,方便后续直接与优化器配合或继续累加。
加权机制的实现
私有辅助函数_apply_weighting(loss.py)统一完成两步加权:
- 若提供
sample_weight,先做逐元素(广播)相乘; - 若提供
weight,断言其为数值类型后整体乘以标量。
该函数还会根据当前是否处于 NumPy 兼容模式(is_np_array())自动选择broadcast_mul或np.multiply等不同算子实现,保证在新旧两套 ndarray API 下行为一致。
回归损失:L1Loss、L2Loss、HuberLoss、PoissonNLLLoss
L2Loss(均方误差)
数学定义(loss.py):
L = 1/2 * Σ |label_i - pred_i|²注意源码中传入_apply_weighting的标量是self._weight / 2,即默认weight=1.时恰好实现标准的 MSE 定义。L2Loss的默认weight为1.,而其他损失多为None。由于平方项的存在,它对离群点(outlier)敏感,适合误差呈高斯分布的回归场景。
测试用例 test_loss.py 直接验证了数值:对output=[1,2,3,4]、label=[1,3,5,7],默认L2Loss求和为 7.0,weight=0.25时为 1.75,传入逐样本权重[0.5,1,0.5,1]时为 6.0。
L1Loss(平均绝对误差)
数学定义:
L = Σ |label_i - pred_i|对离群点更鲁棒,常用于对异常值不敏感的回归任务。其实现与 L2 完全对称,只是把平方替换为绝对值(loss.py)。测试验证:相同输入下默认L1Loss求和为 6.0,weight=0.5时为 3.0。
HuberLoss(平滑 L1,别名 SmoothedL1)
数学定义(loss.py):
L = Σ { 1/(2ρ) * (label_i - pred_i)² 若 |label_i - pred_i| < ρ |label_i - pred_i| - ρ/2 否则 }rho(默认1):L1 与 L2 的分界阈值。
实现先用F.where按绝对误差是否大于rho选择分段表达式,误差小时呈现 L2 的平滑特性、误差大时退化为 L1 的线性增长,兼顾了平滑性与离群鲁棒性,是目标检测中回归分支的常见选择。
PoissonNLLLoss(泊松负对数似然)
适用于计数型目标(服从泊松分布)的回归任务,数学定义(loss.py):
L = pred - target * log(pred) + log(target!)参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
from_logits | True | 为 True 时假设pred已是 log 值,计算exp(pred) - target * pred;为 False 时计算pred - target * log(pred + epsilon) |
compute_full | False | 是否加入对阶乘项log(target!)的 Stirling 近似target*log(target) - target + 0.5*log(2π*target)(仅对target > 1生效) |
epsilon | 1e-08 | 防止log(0)的数值保护项 |
该损失输出是标量平均(形状(1,1)),与其他返回(batch_size,)的损失不同。测试 test_loss.py 分别对from_logits=True、False及compute_full=True三种模式与 NumPy 手写公式做了逐一比对。
分类损失:SoftmaxCrossEntropyLoss、SigmoidBinaryCrossEntropyLoss、LogisticLoss
SoftmaxCrossEntropyLoss(软最大交叉熵)
这是多分类任务使用最频繁的损失,支持稠密与稀疏两种标签形式(loss.py):
- 当
sparse_label=True(默认):label为整数类别索引,其形状为pred去掉axis维后的形状。例如pred形状(1,2,3,4)、axis=2时,label形状应为(1,2,4),取值在[0, 3)内;损失为L = -Σ log p_{i,label_i}。 - 当
sparse_label=False:label为概率分布(one-hot 或软标签),形状与pred相同;损失为L = -Σ Σ label_j * log p_{ij}。
参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
axis | -1 | 计算 softmax 与熵所沿的类别轴 |
sparse_label | True | 标签是否为整数索引而非概率分布 |
from_logits | False | 输入是否为 log 概率(通常来自log_softmax)。若为 False,内部先做log_softmax,数值上更稳定 |
实现细节上,非 logits 模式下先用F.log_softmax(pred, axis),稀疏标签时用F.pick按索引取出对应类别的 log 概率并取负,避免了显式计算 softmax 再取 log 带来的中间溢出。
测试 test_loss.py 验证了对output=[[0,2],[1,4]]、label=[0,1]的输出为[2.12692809, 0.04858733],并验证了sample_weight=[[0.5],[1.0]]加权后的结果;test_ce_loss 还将其接入mx.mod.Module完成了一个 10 类问题的端到端训练验证。
SigmoidBinaryCrossEntropyLoss(Sigmoid 二分类交叉熵,别名 SigmoidBCELoss)
适用于二分类与多标签分类,label取值应在[0, 1](loss.py)。
关键参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
from_sigmoid | False | 为 False 时损失内部将 sigmoid 与 BCE 合并计算,通过 log-sum-exp 技巧数值更稳定;为 True 时假定pred已是 sigmoid 输出,直接计算-Σ [label*log(pred) + (1-label)*log(1-pred)] |
额外输入pos_weight:一个长度等于类别数的正样本加权向量(如pred形状(64,10)时取(1,10))。pos_weight > 1会降低假阴性数量、提升召回率;pos_weight < 1则降低假阳性数量、提升精确率,可用于类别不平衡场景。
从源码看(loss.py),非 sigmoid 模式使用稳定性公式:
max(x, 0) - x*z + log(1 + exp(-|x|))其中 softrelu 项由Activation(act_type='softrelu')计算,等价于数值稳定的log(1+exp(x))。测试 test_bce_loss 同时对照了 NumPy 手写公式与test_bce_loss_with_pos_weight(test_loss.py)对pos_weight路径的验证。
LogisticLoss(逻辑损失)
二分类的另一种形式(loss.py):
L = Σ log(1 + exp(-pred_i * label_i))label_format(默认'signed'):为'signed'时label取值{-1, 1};为'binary'时取值{0, 1}(内部会把(label+1)/2变换后再计算)。传入其他值会抛出ValueError。
实现同样采用稳定性公式relu(pred) - pred*label + softrelu(-|pred|)。测试 test_logistic_loss_equal_bce 证明了binary格式的LogisticLoss与SigmoidBCELoss(from_sigmoid=False)数值完全一致,两者只是标签表达方式的差异。
大间隔与度量学习损失:HingeLoss、SquaredHingeLoss、TripletLoss、CosineEmbeddingLoss
HingeLoss 与 SquaredHingeLoss
经典的 SVM 大间隔损失(loss.py):
Hinge: L = Σ max(0, margin - pred_i * label_i) SquaredHinge: L = Σ max(0, margin - pred_i * label_i)²两者label均需取{-1, 1},margin默认1.0。SquaredHinge 对误分类施加二次惩罚,惩罚更强,是 soft-margin SVM 的常用变体。实现上 Hinge 用F.relu(margin - pred*label),SquaredHinge 在其外层再套一个F.square。
TripletLoss(三元组损失)
度量学习/人脸识别等任务的核心损失(loss.py):
L = Σ max(‖positive_i - pred_i‖₂² - ‖negative_i - pred_i‖₂² + margin, 0)输入为三个张量:pred(锚点)、positive(正样本)、negative(负样本),三者元素数需相同。损失在批轴内先求和,再施加margin(默认1)的 relu 截断,目标是让锚点与正样本的距离至少比与负样本的距离小margin。
CosineEmbeddingLoss(余弦嵌入损失)
衡量两个输入向量间的余弦相似度(loss.py):
L = Σ { 1 - cos_sim(input1_i, input2_i) 若 label_i = 1 max(0, cos_sim(input1_i, input2_i) - margin) 若 label_i = -1 } cos_sim(a, b) = a·b / (‖a‖ · ‖b‖)label为长度等于批大小的一维张量,取值{1, -1}表示两个输入是相似(1)还是不相似(-1);margin默认0,控制不相似对的间隔。
内部_cosine_similarity(loss.py)用F.norm归一化后做点积,并加入1e-12的 epsilon 避免除零。测试 test_cosine_loss 将该损失与 NumPy 逐行手写的余弦损失做了数值比对。
分布与序列损失:KLDivLoss、CTCLoss
KLDivLoss(KL 散度损失)
用于度量两个分布之间的距离,常用于变分自编码器、知识蒸馏等场景(loss.py):
- 当
from_logits=True(默认):pred应为 log 概率(通常来自log_softmax),
L = Σ label_i * [log(label_i) - pred_i]- 当
from_logits=False:pred为未归一化分数(如 Dense 层输出),内部先做log_softmax再按上式计算,axis(默认-1)指定 softmax 维度。
label取值范围应为(0, 1)。实现中在log(label)内加入1e-12防止对 0 取对数。测试 test_kl_loss 用mx.sym.log_softmax(get_net(2))构造 logits 输入完成端到端训练验证。
CTCLoss(连接时序分类损失)
面向语音识别、OCR 等「未分段序列标注」任务的经典损失(loss.py),其理论出自 Graves 的论文《Connectionist Temporal Classification: Labelling Unsegmented Sequence Data with Recurrent Neural Networks》。
参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
layout | 'NTC' | pred的布局,N=批大小、T=序列长度、C=字母表大小;仅支持'NTC'与'TNC',传入其他值会触发断言 |
label_layout | 'NT' | 标签布局,仅支持'NT'与'TN';batch_axis会根据label_layout中N的位置自动推导 |
weight | None | 全局标量权重 |
输入约定:
- pred:softmax 之前的未归一化预测张量,形状随
layout变化(如'TNC'时为(序列长度, 批大小, 字母表大小))。最后一个维度索引alphabet_size - 1保留给内部空白标签(blank),因此alphabet_size应为实际字母表大小加一; - label:从 0 开始编号的标签张量,形状随
label_layout变化;不定长序列需用-1填充成矩形; - pred_lengths / label_lengths(可选,默认
None):形状(batch_size,)的序列长度向量,用于批内各序列长度不一致的场景。传入后use_data_lengths/use_label_lengths会被置为 True。
文档给出一个具体示例(loss.py):词表为[a, b, c],一批含三个序列'ba'、'cbb'、'abac',标签索引为{'a':0, 'b':1, 'c':2, blank:3},则alphabet_size=4,填充后的label张量为:
[[1, 0, -1, -1], [2, 1, 1, -1], [0, 1, 0, 2]]测试 test_ctc_loss 覆盖了NTC/TNC、NT/TN四种布局组合以及传入pred_lengths/label_lengths的变长序列场景;test_ctc_loss_train 则完成了端到端训练。
实战:在 Gluon 训练循环中使用损失函数
Gluon 的损失对象是HybridBlock,调用方式与网络层一致:先实例化,再在训练循环中以loss(pred, label)调用(可选传入sample_weight与部分损失特有的额外输入)。
以 example/gluon/mnist/mnist.py 为代表的典型训练流程如下:
from mxnet import gluon, autograd from mxnet.gluon import nn from mxnet.gluon import loss as gloss net = nn.Sequential() with net.name_scope(): net.add(nn.Dense(128, activation='relu')) net.add(nn.Dense(64, activation='relu')) net.add(nn.Dense(10)) # 输出层不加 softmax loss_fn = gloss.SoftmaxCrossEntropyLoss() # 默认 from_logits=False,内部融合 softmax trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.1, 'momentum': 0.9}) for epoch in range(10): for data, label in train_data: with autograd.record(): output = net(data) loss = loss_fn(output, label) # 返回形状 (batch_size,) 的损失 loss.backward() trainer.step(batch_size)实践要点:
- 不要在输出层手动加 softmax:
SoftmaxCrossEntropyLoss默认from_logits=False,会内部完成log_softmax,与F.softmax_cross_entropy等底层算子同理,数值更稳定; - 多分类用
SoftmaxCrossEntropyLoss,多标签用SigmoidBinaryCrossEntropyLoss:前者基于 softmax 的类别互斥假设,后者每个类别独立做 sigmoid; - 类别不平衡:二分类场景可用
pos_weight,多分类场景可用sample_weight对少数类样本加权,sample_weight形状取(batch_size, 1)即可按样本加权(loss.py); - 符号图训练:所有损失均可在
mx.mod.Module中以loss = Loss(output, l); loss = mx.sym.make_loss(loss)形式接入,test_loss.py 中每个损失都提供了对应的 Module 训练验证; - 输出形状统一为
(batch_size,):除PoissonNLLLoss输出标量均值外,其余损失均沿非批轴聚合,便于直接loss.backward()或作为mx.metric.Loss的输入。
源码结构速查
想深入研读实现细节,可按以下路径对照阅读:
- 模块入口与完整实现:python/mxnet/gluon/loss.py(932 行,含全部 16 个类与两个辅助函数);
- API 索引页:docs/python_docs/python/api/gluon/loss/index.rst(通过
automodule自动从 docstring 生成 API 文档); - 数值与训练验证:tests/python/unittest/test_loss.py(覆盖每个损失的 NumPy 对照与 Module 端到端训练);
- NumPy 兼容模式下的行为验证:tests/python/unittest/test_numpy_gluon.py;
- 典型应用示例:example/gluon/mnist/mnist.py、example/gluon/image_classification.py、example/gluon/embedding_learning/model.py(TripletLoss 的度量学习用法)。
结语
mxnet.gluon.loss以统一的Loss基类收敛了权重与批轴等通用语义,用_apply_weighting统一了样本加权路径,并以HybridBlock保证了命令式与符号式两种执行模式的一致性。无论是快速搭建 MNIST 分类器,还是实现语音识别中的 CTC 对齐、度量学习中的三元组约束,这套预定义损失库都能以最小样板代码直接复用,且每个损失都配有可对照的单元测试,是理解 Gluon 训练管线与数值稳定性设计的上佳入口。
【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考